WIP: router0-dmz0: use bridge vlan filtering for the dynamic wlan interfaces

This commit is contained in:
steveej 2023-12-26 00:37:20 +01:00
parent c365970cdf
commit 8bcb433257
3 changed files with 202 additions and 64 deletions

View file

@ -16,35 +16,46 @@
nixos-nftables-firewall
;
mkVlanIpv4HostAddr = { vlanid, host, ipv4Offset ? 20, cidr ? true }:
builtins.concatStringsSep "."
[ "192" "168" (toString (ipv4Offset + vlanid)) "${toString host}${lib.strings.optionalString cidr "/24"}" ];
# vlanRangeStart = 1;
# vlanRangeEnd = 20;
# vlanRange = (lib.lists.range vlanRangeStart vlanRangeEnd);
vlanRangeStart = builtins.head vlanRange;
vlanRangeEnd = builtins.elemAt vlanRange ((builtins.length vlanRange)-1);
vlanRange = builtins.map (vlanid: (lib.strings.toInt vlanid)) (builtins.attrNames vlans);
vlanRangeWith0 = [ 0 ] ++ vlanRange;
mkVlanIpv4HostAddr = { vlanid, host, thirdIpv4SegmentMin ? 20, cidr ? true }: let
# reserve the first subnet for vlanid == 0
# number the other subnets continously from there
offset =
if vlanid == 0
then thirdIpv4SegmentMin
else thirdIpv4SegmentMin + 1 - vlanRangeStart;
in
builtins.concatStringsSep "."
[ "192" "168" (toString (vlanid + offset)) "${toString host}${lib.strings.optionalString cidr "/24"}" ];
defaultVlan = {
name = "internal";
packet_priority = 0;
};
vlans = {
"1".name = "dmz";
"1".packet_priority = -5;
"10".name = "mgmt";
"10".packet_priority = 0;
"2".name = "iot";
"2".packet_priority = -5;
"11".name = "dmz";
"11".packet_priority = -5;
"3".name = "office";
"3".packet_priority = -10;
"12".name = "iot";
"12".packet_priority = -5;
"4".name = "guests";
"4".packet_priority = 10;
"13".name = "office";
"13".packet_priority = -10;
"5".name = "smarties";
"14".name = "guests";
"14".packet_priority = 10;
"15".name = "iot2";
"15".packet_priority = -10;
};
getVlanDomain = { vlanid }:
if vlanid == 0
@ -72,6 +83,9 @@ in {
"nix-command"
"flakes"
];
nix.settings.max-jobs = lib.mkDefault "auto";
nix.settings.cores = lib.mkDefault 0;
}
# TODO
@ -155,8 +169,8 @@ in {
firewall = {
enable = true;
zones = {
lan.interfaces = ["br-lan"];
vlan.interfaces = builtins.map (vlanid: "br-vlan.${toString vlanid}") vlanRange;
lan.interfaces = [ "br-lan" ];
vlan.interfaces = builtins.map (vlanid: "br-lan.${toString vlanid}") vlanRange;
# lan.ipv4Addresses = ["192.168.0.0/16"];
wan.interfaces = ["wan" "lan0"];
} //
@ -180,9 +194,14 @@ in {
"ip6 nexthdr icmpv6 icmpv6 type { ${builtins.concatStringsSep ", " ipv6IcmpTypes} } accept"
];
in {
fw = {
from = ["fw"];
verdict = "accept";
};
lan-to-fw = {
from = ["lan"];
to = ["fw"];
to = ["fw" "lan"];
verdict = "accept";
};
@ -206,6 +225,7 @@ in {
allowedTCPPortRanges = [
{ from = 22; to = 22; }
{ from = 53; to = 53; }
{ from = 5201; to = 5201; }
];
from = ["vlan"];
to = ["fw"];
@ -247,16 +267,25 @@ in {
netdevConfig = {
Kind = "bridge";
Name = "br-lan";
};
extraConfig = ''
[Bridge]
STP=true
# VLANFiltering=yes
# DefaultPVID=1
STP=yes
VLANFiltering=yes
VLANProtocol=802.1q
DefaultPVID=0
'';
};
# TODO: generate one of these for each vlanid
"20-br-lan.15" = {
netdevConfig = {
Kind = "vlan";
Name = "br-lan.15";
};
vlanConfig.Id = 15;
};
};
networks = {
# use lan0 as secondary WAN interface
@ -308,6 +337,7 @@ in {
};
linkConfig.RequiredForOnline = "enslaved";
};
"30-lan3" = {
matchConfig.Name = "lan3";
networkConfig = {
@ -315,6 +345,15 @@ in {
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "enslaved";
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}";
};
}
];
};
# Configure the bridge for its desired function
"40-br-lan" = {
@ -328,40 +367,112 @@ in {
};
# Don't wait for it as it also would wait for wlan and DFS which takes around 5 min
linkConfig.RequiredForOnline = "no";
linkConfig.ActivationPolicy = "always-up";
# TODO: understand when this would be needed
# bridgeVLANs = [
# {
# bridgeVLANConfig = {
# VLAN = "${vlanRangeStart}-${vlanRangeEnd}";
# };
# }
# ];
};
}
# VLAN interface addresses
//
lib.attrsets.foldlAttrs
(acc: _: value: acc // value)
{}
(lib.attrsets.genAttrs
(builtins.map
builtins.toString
vlanRange
)
(vlanid: {
"50-br-vlan.${vlanid}" = {
matchConfig.Name = "br-vlan.${toString vlanid}";
address = [
(mkVlanIpv4HostAddr { vlanid = (lib.strings.toInt vlanid); host = 1; })
];
networkConfig = {
ConfigureWithoutCarrier = true;
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}";
};
# Don't wait for it as it also would wait for wlan and DFS which takes around 5 min
linkConfig.RequiredForOnline = "no";
}
];
vlan = [
"br-lan.15"
];
};
# TODO: generate one of these for each vlanid
"41-br-lan.15" = let
vlanid = 15;
in {
matchConfig.Name = "br-lan.15";
address = [
(mkVlanIpv4HostAddr { inherit vlanid; host = 1; })
];
networkConfig = {
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "no";
linkConfig.ActivationPolicy = "always-up";
bridgeVLANs = [
{
bridgeVLANConfig = {
# TODO debug this: each vlanid is native to each port
VLAN = vlanid;
};
}
];
};
# TODO: generate one of these for each vlanid
# configure the wlan interface as a bridge member that
# * only gets traffic for vid 15
# * untags traffic after receiving it
# * tags traffic that comes out of it
"41-wlan0.15" = let
vlanid = 15;
in {
matchConfig.Name = "wlan0.15";
networkConfig = {
Bridge = "br-lan";
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "no";
bridgeVLANs = [
{
bridgeVLANConfig = {
# TODO debug this: each vlanid is native to each port
VLAN = vlanid;
PVID = vlanid;
EgressUntagged = vlanid;
};
}
];
};
}
# configuration for the hostapd dynamic interfaces
# TODO: refactor this to configure the following per vlanid
# * netdev type vlan
# * host address for vlan
# * vlan config for wlan interface
//
builtins.foldl'
(acc: cur: acc // cur)
{}
(builtins.map
({ vlanid, vlanid' }: {
"50-br-lan.${vlanid'}" = {
matchConfig.Name = "br-lan.${vlanid'}";
address = [
(mkVlanIpv4HostAddr { inherit vlanid; host = 1; })
];
networkConfig = {
ConfigureWithoutCarrier = true;
};
})
linkConfig.RequiredForOnline = "no";
bridgeVLANs = [
{
bridgeVLANConfig = {
# TODO debug this: each vlanid is native to each port
VLAN = vlanid;
PVID = vlanid;
};
}
];
};
})
(builtins.map
(vlanid: { inherit vlanid; vlanid' = builtins.toString vlanid; })
vlanRange
)
);
};
@ -384,15 +495,17 @@ in {
capabilities = ["HT40+" "LDPC" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1" "MAX-AMSDU-7935"];
};
networks = {
wlan0 = {
wlan0 = let
iface = "wlan0";
in {
ssid = "mlsia";
bssid = mkBssid 0;
# authentication.mode = "wpa3-sae";
authentication.mode = "wpa3-sae-transition";
authentication.wpaPskFile = config.sops.secrets.wlan0_wpaPskFile.path;
authentication.saePasswordsFile = config.sops.secrets.wlan0_saePasswordsFile.path;
authentication.wpaPskFile = config.sops.secrets."${iface}_wpaPskFile".path;
authentication.saePasswordsFile = config.sops.secrets."${iface}_saePasswordsFile".path;
settings = {
# bridge = "br-lan";
@ -410,16 +523,18 @@ in {
vlan_tagged_interface = "br-lan";
vlan_naming = 1;
vlan_bridge = "br-vlan.";
vlan_bridge = "br-${iface}.";
dynamic_vlan = 1;
vlan_file = toString (pkgs.writeText "hostapd.vlan" ''
# Optional wildcard entry matching all VLAN IDs. The first # in the interface
# name will be replaced with the VLAN ID. The network interfaces are created
# (and removed) dynamically based on the use.
# see https://w1.fi/cgit/hostap/tree/hostapd/hostapd.vlan
* wlan0.#
* ${iface}.#
'');
vlan_no_bridge = 1;
wpa_key_mgmt = lib.mkForce (builtins.concatStringsSep " " [
"WPA-PSK"
@ -563,7 +678,7 @@ in {
services.resolved.enable = false;
services.dnsmasq = let
mkIfName = { vlanid }: if vlanid == 0 then "br-lan" else "br-vlan.${toString vlanid}";
mkIfName = { vlanid }: if vlanid == 0 then "br-lan" else "br-lan.${toString vlanid}";
in {
enable = true;
settings = {
@ -695,5 +810,22 @@ in {
environment.systemPackages = [
pkgs.ethtool
pkgs.neovim
(pkgs.writeShellScriptBin "dbg-ip" ''
echo links:
ip -br -c l
echo
echo addresses:
ip -br -c a
echo
echo vlans:
bridge -c vlan
'')
(pkgs.writeShellScriptBin "dbg-dnsmasq" ''
# get the rendered in-use config
pgrep -a dnsmasq | grep -Eo '[^ ]*conf' | xargs cat | grep -Eo '[^=]*conf' | xargs cat
'')
];
}

View file

@ -96,6 +96,12 @@
hostapd_main = pkgs.hostapd.overrideDerivation(attrs: {
src = self.inputs.hostapd;
version = self.inputs.hostapd.rev;
patches = attrs.patches ++ [
(builtins.fetchurl {
url = "https://raw.githubusercontent.com/openwrt/openwrt/main/package/network/services/hostapd/patches/710-vlan_no_bridge.patch";
sha256 = "sha256:1p6fjjdwx5xrxyvllfrmvdkiji7bgx997k1qmp199bbic1fq6ks9";
})
];
});
};
};