shinobu/router: Add network segmentation

23.11
Simon Bruder 2023-10-18 20:12:41 +02:00
parent 1740570d00
commit 4611e12772
Signed by: simon
GPG Key ID: 8D3C82F9F309F8EC
5 changed files with 179 additions and 79 deletions

View File

@ -1,6 +1,55 @@
{ ... }:
{ lib, ... }:
let
mkSubnet = v4: v6:
let
splitCidr = lib.splitString "/";
fst = lib.flip lib.elemAt 0;
snd = lib.flip lib.elemAt 1;
v4Split = splitCidr v4;
v6Split = splitCidr v6;
in
{
v4 = rec {
cidr = v4;
net = fst v4Split;
suffix = snd v4Split;
withoutLastComponent = lib.substring 0 ((lib.stringLength net) - 1) net;
gateway = "${withoutLastComponent}1";
gatewayCidr = "${gateway}/${suffix}";
};
v6 = rec {
cidr = v6;
net = fst v6Split;
suffix = snd v6Split;
gateway = "${net}1";
gatewayCidr = "${gateway}/${suffix}";
};
};
in
{
domain = "home.sbruder.de";
vlan = {
lan = {
id = 10;
subnet = mkSubnet "10.80.1.0/24" "fd00:80:1::/64";
domain = "home.sbruder.de";
};
management = {
id = 20;
subnet = mkSubnet "10.80.2.0/24" "fd00:80:2::/64";
domain = "management.sbruder.de";
};
guest = {
id = 30;
subnet = mkSubnet "10.80.3.0/24" "fd00:80:3::/64";
domain = "guest.sbruder.de";
};
iot = {
id = 40;
subnet = mkSubnet "10.80.4.0/24" "fd00:80:4::/64";
domain = "iot.sbruder.de";
};
};
tc = {
interface = "enp1s0";
# 4160kbit is slightly smaller than the average upload

View File

@ -1,26 +1,22 @@
# Home network configuration
# (2.5GbE clients)
# | |
# +----------+ +----------+
# | | | | | | (1GbE clients)
# | | | | | +|-|-|-|-|+
# +---+----+ +-+-+-+-+-+ |5 4 3 2 1|
# |upstream| | 1 2 3 4 | |TL-SG105 |
# +--------+ | shinobu | +---------+
#
# +----------+ +------------+
# | | | | (clients)# (guests)
# | | | +--|--|--|--|-#+-|--|--|-|-unused|
# +---+----+ +-+-+-+-+-+ | 01 02 …… 12 # 13 …… 24 | 25 26 |
# |upstream| | 1 2 3 4 | | aruba Instant On 1830 | (SFP) |
# +--------+ | shinobu | +------------------------+-------+
# +---------+
#
# It consists of shinobu as a router (this configuration),
# connected to a TP-LINK TL-SG105E “smart managed” (i.e., it can do VLANs) 5-port switch.
# connected to a aruba (HPE) Instant ON 1830 24-port 1GbE switch.
# The upstream comes (for now) from a PŸUR “WLAN-Kabelbox” (Compal CH7467CE).
# Sadly, I could not enable bridge mode on it, so the packets now go through (at least) three layers of NAT:
# device → NAT on shinobu → NAT on plastic router → PŸUR CGNAT
#
# Because the switch only supports GbE,
# the two clients I currently have with support for 2.5GbE are connected
# directly to the two remaining network interfaces on shinobu.
# Once I have more devices with support for 2.5GbE
# or I find a good deal on a matching switch,
# I will change this.
# Because of issues with the NICs operating at 2.5GbE,
# all clients are connected to the switch,
# even if they have a 2.5GbE NIC.
#
# Wireless is configured by providing the whole hostapd configuration file as a secret.
# Once nixpkgs PR 222536 is merged, I will migrate to using the NixOS module.
@ -52,48 +48,85 @@ in
enable = true;
# not all interfaces need to be up
wait-online.extraArgs = [ "--any" ];
netdevs = {
br-lan = {
netdevConfig = {
Name = "br-lan";
Kind = "bridge";
netdevs = lib.mkMerge [
(lib.mapAttrs
(name: config: {
netdevConfig = {
Kind = "vlan";
Name = name;
};
vlanConfig = {
Id = config.id;
};
})
cfg.vlan)
(lib.mapAttrs'
(name: config: lib.nameValuePair "br-${name}" {
netdevConfig = {
Name = "br-${name}";
Kind = "bridge";
};
})
cfg.vlan)
];
networks = lib.mkMerge [
(lib.mapAttrs
(name: config: {
inherit name;
matchConfig = {
Type = "vlan";
};
bridge = [ "br-${name}" ];
})
cfg.vlan)
(lib.mapAttrs'
(name: config: lib.nameValuePair "br-${name}" {
name = "br-${name}";
domains = [ config.domain ];
address = lib.mapAttrsToList (family: familyConfig: familyConfig.gatewayCidr) config.subnet;
networkConfig = {
IPv6AcceptRA = false;
};
})
cfg.vlan)
{
wan = {
name = "enp1s0";
DHCP = "ipv4";
networkConfig = {
IPv6AcceptRA = "yes";
};
dhcpV4Config = {
UseDNS = "no";
};
ipv6AcceptRAConfig = {
# Only use RA
DHCPv6Client = false;
UseDNS = "no";
};
};
};
};
networks = {
wan = {
name = "enp1s0";
DHCP = "ipv4";
networkConfig = {
IPv6AcceptRA = "yes";
physical-lan = {
name = "enp2s0";
vlan = lib.attrNames cfg.vlan;
# no autoconfiguration needed, only tagged VLAN
networkConfig = {
LinkLocalAddressing = "no";
LLDP = "no";
EmitLLDP = "no";
IPv6AcceptRA = "no";
IPv6SendRA = "no";
};
};
dhcpV4Config = {
UseDNS = "no";
lan2 = {
name = "enp3s0";
bridge = [ "br-lan" ];
};
ipv6AcceptRAConfig = {
# Only use RA
DHCPv6Client = false;
UseDNS = "no";
lan3 = {
name = "enp4s0";
bridge = [ "br-lan" ];
};
};
lan1 = {
name = "enp2s0";
bridge = [ "br-lan" ];
};
lan2 = {
name = "enp3s0";
bridge = [ "br-lan" ];
};
lan3 = {
name = "enp4s0";
bridge = [ "br-lan" ];
};
br-lan = {
name = "br-lan";
domains = [ cfg.domain ];
address = [ "10.80.1.1/24" "fd00:80:1::1/64" ];
};
};
}
];
};
services.resolved.enable = false;
}

View File

@ -1,4 +1,4 @@
{ config, pkgs, ... }:
{ config, lib, pkgs, ... }:
let
cfg = pkgs.callPackage ./common.nix { };
in
@ -9,35 +9,39 @@ in
settings = {
bogus-priv = true; # do not forward revese lookups of internal addresses
domain-needed = true; # do not forward names without domain
interface = "br-lan"; # only respond to queries from lan
interface = lib.mapAttrsToList (name: config: "br-${name}") cfg.vlan; # only respond to queries from own interfaces
no-hosts = true; # do not resolve hosts from /etc/hosts
no-resolv = true; # only use explicitly configured resolvers
dhcp-fqdn = true; # only insert qualified names of DHCP clients into DNS
cache-size = 10000;
inherit (cfg) domain;
domain = [
"invalid.sbruder.de" # used when no rule below matches
] ++ (lib.flatten (lib.mapAttrsToList
(name: { domain, subnet, ... }: [
"${domain},br-${name}" # only this is not enough
"${domain},${subnet.v4.cidr}"
"${domain},${subnet.v6.cidr}"
])
cfg.vlan));
# Allow resolving the router
interface-name = [
"${config.networking.hostName}.${cfg.domain},br-lan"
"${config.networking.hostName},br-lan"
];
interface-name = lib.mapAttrsToList (name: { domain, ... }: "${config.networking.hostName}.${domain},br-${name}") cfg.vlan;
# DHCPv4
dhcp-range = [
"10.80.1.20,10.80.1.150,12h" # DHCPv4
"fd00:80:1::,ra-stateless,ra-names" # SLAAC (for addresses) / DHCPv6 (for DNS)
];
dhcp-option = [
"option:router,10.80.1.1"
"option6:dns-server,fd00:80:1::1"
];
dhcp-range = lib.flatten (lib.mapAttrsToList
(name: { subnet, ... }: [
"tag:br-${name},${subnet.v4.withoutLastComponent}2,${subnet.v4.withoutLastComponent}254,12h" # DHCPv4
"tag:br-${name},${subnet.v6.net},ra-stateless,ra-names" # SLAAC (for addresses) / DHCPv6 (for DNS)
])
cfg.vlan);
dhcp-option = lib.flatten (lib.mapAttrsToList
(name: { subnet, ... }: [
"tag:br-${name},option:router,${subnet.v4.gateway}"
"tag:br-${name},option6:dns-server,${subnet.v6.gateway}"
])
cfg.vlan);
# Despite its name, the switch does not have a “smart” configuration,
# that would allow me to tell it not to get DHCP from wan,
# but from lan instead.
# So it has to use static configuration.
host-record = "switchviech,switchviech.${cfg.domain},10.80.1.19";
server = [
"127.0.0.1#5053"
];

View File

@ -10,7 +10,10 @@ let
else lib.generators.mkValueStringDefault { } v;
} " = ";
passthru = { };
passthru = {
VLANS = lib.attrNames cfg.vlan;
VLAN_BRIDGES = map (name: "br-${name}") (lib.attrNames cfg.vlan);
};
defines = lib.concatStringsSep
"\n"

View File

@ -1,4 +1,4 @@
define NAT_LAN_IFACES = { "br-lan" }
define NAT_LAN_IFACES = { "br-lan", "br-guest" }
define PHYSICAL_WAN = "enp1s0"
define NAT_WAN_IFACES = { $PHYSICAL_WAN }
@ -6,9 +6,20 @@ table inet filter {
chain forward {
type filter hook forward priority filter; policy drop
# allow traffic between lan and wan
# plastic router, might be vulnerable (FIXME v6 is still reachable)
iifname "br-guest" ip daddr "192.168.0.1" drop
# allow traffic between selected VLANs and wan
iifname $NAT_LAN_IFACES oifname $NAT_WAN_IFACES counter accept
iifname $NAT_WAN_IFACES oifname $NAT_LAN_IFACES ct state established,related counter accept
# traffic from lan to all other vlans is allowed
iifname "br-lan" oifname $VLAN_BRIDGES counter accept;
iifname $VLAN_BRIDGES oifname "br-lan" ct state established,related counter accept
iifname "br-iot" ip daddr 167.235.30.249 tcp dport 1883 counter accept # FIXME migrate service to shinobu
iifname "br-iot" udp dport 123 counter accept # FIXME too generic
iifname $NAT_WAN_IFACES oifname "br-iot" ct state established,related counter accept
}
}