diff --git a/globals.nix b/globals.nix index 668470f..96060b3 100644 --- a/globals.nix +++ b/globals.nix @@ -28,15 +28,63 @@ in }; home-lan = { - cidrv4 = "192.168.1.0/24"; - cidrv6 = "fd10::/64"; - hosts.ward.id = 1; - hosts.sire.id = 2; - hosts.ward-adguardhome.id = 3; - hosts.ward-web-proxy.id = 4; - hosts.sire-samba.id = 10; - hosts.wallbox.id = 40; - hosts.home-assistant-temp.id = 85; + vlans = { + personal = { + id = 10; + cidrv4 = "192.168.10.0/24"; + cidrv6 = "fd10::/64"; + hosts.ward.id = 1; + hosts.ward-adguardhome.id = 3; + }; + services = { + id = 20; + cidrv4 = "192.168.20.0/24"; + cidrv6 = "fd20::/64"; + hosts.ward.id = 1; + hosts.sire.id = 2; + hosts.ward-adguardhome = { + id = 3; + inherit (nodes.ward-adguardhome.config.lib.microvm.interfaces.vlan-services) mac; + }; + hosts.ward-web-proxy = { + id = 4; + inherit (nodes.ward-web-proxy.config.lib.microvm.interfaces.vlan-services) mac; + }; + hosts.sire-samba = { + id = 10; + inherit (nodes.sire-samba.config.lib.microvm.interfaces.vlan-services) mac; + }; + }; + devices = { + id = 30; + cidrv4 = "192.168.30.0/24"; + cidrv6 = "fd30::/64"; + hosts.ward.id = 1; + hosts.ward-adguardhome.id = 3; + hosts.wallbox = { + id = 40; + mac = globals.macs.wallbox; + }; + hosts.home-assistant-temp = { + id = 85; + mac = globals.macs.home-assistant; + }; + }; + iot = { + id = 40; + cidrv4 = "192.168.40.0/24"; + cidrv6 = "fd40::/64"; + hosts.ward.id = 1; + hosts.ward-adguardhome.id = 3; + }; + guests = { + id = 50; + cidrv4 = "192.168.50.0/24"; + cidrv6 = "fd50::/64"; + hosts.ward.id = 1; + hosts.ward-adguardhome.id = 3; + }; + }; }; proxy-home = { diff --git a/hosts/ward/kea.nix b/hosts/ward/kea.nix index f8a31c7..df354de 100644 --- a/hosts/ward/kea.nix +++ b/hosts/ward/kea.nix @@ -2,7 +2,6 @@ lib, globals, utils, - nodes, ... }: let @@ -30,58 +29,42 @@ in renew-timer = 3600; interfaces-config = { # XXX: BUG: why does this bind other macvtaps? - interfaces = [ "lan-self" ]; + interfaces = map (name: "me-${name}") (builtins.attrNames globals.net.home-lan.vlans); service-sockets-max-retries = -1; }; - option-data = [ - { - name = "domain-name-servers"; - data = globals.net.home-lan.hosts.ward-adguardhome.ipv4; - } - ]; - subnet4 = [ - { - id = 1; - interface = "lan-self"; - subnet = globals.net.home-lan.cidrv4; - pools = [ - { - pool = "${net.cidr.host 20 globals.net.home-lan.cidrv4} - ${ - net.cidr.host (-6) globals.net.home-lan.cidrv4 - }"; - } - ]; - option-data = [ - { - name = "routers"; - data = globals.net.home-lan.hosts.ward.ipv4; # FIXME: how to advertise v6 address also? - } - ]; - # FIXME: map this over globals.guests or smth. marker tag for finding: ipv4 192.168.1.1 - reservations = [ - { - hw-address = nodes.ward-adguardhome.config.lib.microvm.mac; - ip-address = globals.net.home-lan.hosts.ward-adguardhome.ipv4; - } - { - hw-address = nodes.ward-web-proxy.config.lib.microvm.mac; - ip-address = globals.net.home-lan.hosts.ward-web-proxy.ipv4; - } - { - hw-address = nodes.sire-samba.config.lib.microvm.mac; - ip-address = globals.net.home-lan.hosts.sire-samba.ipv4; - } - { - hw-address = globals.macs.wallbox; - ip-address = globals.net.home-lan.hosts.wallbox.ipv4; - } - { - hw-address = globals.macs.home-assistant; - ip-address = globals.net.home-lan.hosts.home-assistant-temp.ipv4; - } - ]; - } - ]; + subnet4 = lib.mapAttrsToList globals.net.home-lan.vlans ( + vlanName: vlanCfg: [ + { + inherit (vlanCfg) id; + interface = "me-${vlanName}"; + subnet = vlanCfg.cidrv4; + pools = [ + { + pool = "${net.cidr.host 20 vlanCfg.cidrv4} - ${net.cidr.host (-6) vlanCfg.cidrv4}"; + } + ]; + option-data = [ + { + name = "routers"; + data = vlanCfg.hosts.ward.ipv4; # FIXME: how to advertise v6 address also? + } + { + name = "domain-name-servers"; + data = vlanCfg.hosts.ward-adguardhome.ipv4; + } + ]; + reservations = lib.concatLists ( + lib.forEach (builtins.attrValues vlanCfg.hosts) ( + hostCfg: + lib.optional (hostCfg.mac != null) { + hw-address = hostCfg.mac; + ip-address = hostCfg.ipv4; + } + ) + ); + } + ] + ); }; }; diff --git a/hosts/ward/net.nix b/hosts/ward/net.nix index 4512838..8fc78e7 100644 --- a/hosts/ward/net.nix +++ b/hosts/ward/net.nix @@ -4,12 +4,6 @@ lib, ... }: -let - vlans.personal = 10; - vlans.devices = 20; - vlans.iot = 30; - vlans.guest = 40; -in { boot.kernel.sysctl."net.ipv4.ip_forward" = 1; networking.hostId = config.repo.secrets.local.networking.hostId; @@ -17,11 +11,19 @@ in globals.monitoring.ping.ward = { hostv4 = lib.net.cidr.ip globals.net.home-lan.hosts.ward.cidrv4; hostv6 = lib.net.cidr.ip globals.net.home-lan.hosts.ward.cidrv6; - network = "home-lan"; + network = "home-lan.vlans.devices"; }; + boot.initrd.availableKernelModules = [ "8021q" ]; boot.initrd.systemd.network = { enable = true; + netdevs."30-vlan-home" = { + netdevConfig = { + Kind = "vlan"; + Name = "vlan-home"; + }; + vlanConfig.Id = globals.net.home-lan.vlans.home.id; + }; networks = { "10-wan" = { address = [ globals.net.home-wan.hosts.ward.cidrv4 ]; @@ -30,12 +32,21 @@ in networkConfig.IPv6PrivacyExtensions = "yes"; linkConfig.RequiredForOnline = "routable"; }; - "20-lan" = { + "10-lan" = { + matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.lan.mac; + # This interface should only be used from attached vlans. + # So don't acquire a link local address and only wait for + # this interface to gain a carrier. + networkConfig.LinkLocalAddressing = "no"; + linkConfig.RequiredForOnline = "carrier"; + vlan = [ "vlan-home" ]; + }; + "30-vlan-home" = { address = [ globals.net.home-lan.hosts.ward.cidrv4 globals.net.home-lan.hosts.ward.cidrv6 ]; - matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.lan.mac; + matchConfig.Name = "vlan-home"; networkConfig = { IPv4Forwarding = "yes"; IPv6PrivacyExtensions = "yes"; @@ -48,11 +59,18 @@ in # Create a MACVTAP for ourselves too, so that we can communicate with # our guests on the same interface. - systemd.network.netdevs = - { - "10-lan-self" = { + systemd.network.netdevs = lib.flip lib.concatMapAttrs globals.net.home-lan.vlans ( + vlanName: vlanCfg: { + "30-vlan-${vlanName}" = { netdevConfig = { - Name = "lan-self"; + Kind = "vlan"; + Name = "vlan-${vlanName}"; + }; + vlanConfig.Id = vlanCfg.id; + }; + "40-me-${vlanName}" = { + netdevConfig = { + Name = "me-${vlanName}"; Kind = "macvlan"; }; extraConfig = '' @@ -61,30 +79,18 @@ in ''; }; } - // lib.flip lib.mapAttrs' vlans ( - vlanName: vlanId: - lib.nameValuePair "40-vlan-${vlanName}" { - netdevConfig = { - Kind = "vlan"; - Name = "vlan-${vlanName}"; - }; - vlanConfig.Id = vlanId; - } - ); + ); systemd.network.networks = { "10-lan" = { matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.lan.mac; - # This interface should only be used from attached macvtaps. + # This interface should only be used from attached vlans. # So don't acquire a link local address and only wait for # this interface to gain a carrier. networkConfig.LinkLocalAddressing = "no"; linkConfig.RequiredForOnline = "carrier"; - extraConfig = '' - [Network] - MACVLAN=lan-self - ''; + vlan = map (name: "vlan-${name}") (builtins.attrNames globals.net.home-lan.vlans); }; "10-wan" = { #DHCP = "yes"; @@ -95,46 +101,13 @@ in gateway = [ globals.net.home-wan.hosts.fritzbox.ipv4 ]; matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.wan.mac; networkConfig.IPv6PrivacyExtensions = "yes"; - dhcpV6Config.PrefixDelegationHint = "::/64"; + # dhcpV6Config.PrefixDelegationHint = "::/64"; # FIXME: This should not be needed, but for some reason part of networkd # isn't seeing the RAs and not triggering DHCPv6. Even though some other # part of networkd is properly seeing them and logging accordingly. dhcpV6Config.WithoutRA = "solicit"; linkConfig.RequiredForOnline = "routable"; }; - "20-lan-self" = { - address = [ - globals.net.home-lan.hosts.ward.cidrv4 - globals.net.home-lan.hosts.ward.cidrv6 - ]; - matchConfig.Name = "lan-self"; - networkConfig = { - IPv4Forwarding = "yes"; - IPv6PrivacyExtensions = "yes"; - IPv6SendRA = true; - IPv6AcceptRA = false; - DHCPPrefixDelegation = true; - MulticastDNS = true; - }; - dhcpPrefixDelegationConfig.UplinkInterface = "wan"; - dhcpPrefixDelegationConfig.Token = "::ff"; - # Announce a static prefix - ipv6Prefixes = [ - { Prefix = globals.net.home-lan.cidrv6; } - ]; - # Delegate prefix - dhcpPrefixDelegationConfig = { - SubnetId = "22"; - }; - # Provide a DNS resolver - # ipv6SendRAConfig = { - # Managed = true; - # EmitDNS = true; - # FIXME: this is not the true ipv6 of adguardhome DNS = globals.net.home-lan.hosts.ward-adguardhome.ipv6; - # FIXME: todo assign static additional to reservation in kea - # }; - linkConfig.RequiredForOnline = "routable"; - }; # Remaining macvtap interfaces should not be touched. "90-macvtap-ignore" = { matchConfig.Kind = "macvtap"; @@ -142,10 +115,53 @@ in linkConfig.Unmanaged = "yes"; }; } - // lib.flip lib.mapAttrs' vlans ( - vlanName: _: - lib.nameValuePair "40-vlan-${vlanName}" { - matchConfig.Name = "vlan-${vlanName}"; + // lib.flip lib.concatMapAttrs globals.net.home-lan.vlans ( + vlanName: vlanCfg: { + "30-vlan-${vlanName}" = { + matchConfig.Name = "vlan-${vlanName}"; + # This interface should only be used from attached macvlans. + # So don't acquire a link local address and only wait for + # this interface to gain a carrier. + networkConfig.LinkLocalAddressing = "no"; + linkConfig.RequiredForOnline = "carrier"; + extraConfig = '' + [Network] + MACVLAN=me-${vlanName} + ''; + }; + "40-me-${vlanName}" = { + address = [ + vlanCfg.hosts.ward.cidrv4 + vlanCfg.hosts.ward.cidrv6 + ]; + matchConfig.Name = "me-${vlanName}"; + networkConfig = { + IPv4Forwarding = "yes"; + IPv6PrivacyExtensions = "yes"; + IPv6SendRA = true; + IPv6AcceptRA = false; + # DHCPPrefixDelegation = true; + MulticastDNS = true; + }; + # dhcpPrefixDelegationConfig.UplinkInterface = "wan"; + # dhcpPrefixDelegationConfig.Token = "::ff"; + # Announce a static prefix + ipv6Prefixes = [ + { Prefix = vlanCfg.cidrv6; } + ]; + # Delegate prefix + # dhcpPrefixDelegationConfig = { + # SubnetId = vlanCfg.id; + # }; + # Provide a DNS resolver + # ipv6SendRAConfig = { + # Managed = true; + # EmitDNS = true; + # FIXME: this is not the true ipv6 of adguardhome DNS = globals.net.home-lan.hosts.ward-adguardhome.ipv6; + # FIXME: todo assign static additional to reservation in kea + # }; + linkConfig.RequiredForOnline = "routable"; + }; } ); @@ -155,31 +171,35 @@ in "nd-router-solicit" ]; - zones = { - untrusted.interfaces = [ "wan" ]; - lan.interfaces = [ "lan-self" ]; - proxy-home.interfaces = [ "proxy-home" ]; - }; + zones = + { + untrusted.interfaces = [ "wan" ]; + proxy-home.interfaces = [ "proxy-home" ]; + } + // lib.flip lib.concatMapAttrs globals.net.home-lan.vlans ( + vlanName: _: { + "me-${vlanName}".interfaces = [ "me-${vlanName}" ]; + } + ); rules = { - masquerade = { - from = [ "lan" ]; - to = [ "untrusted" ]; - masquerade = true; - }; - - outbound = { - from = [ "lan" ]; + masquerade-internet = { + from = [ + "vlan-home" + "vlan-services" + "vlan-devices" + "vlan-guests" + ]; to = [ - "lan" "untrusted" ]; + masquerade = true; late = true; # Only accept after any rejects have been processed verdict = "accept"; }; - lan-to-local = { - from = [ "lan" ]; + services-to-local = { + from = [ "vlan-services" ]; to = [ "local" ]; allowedUDPPorts = [ config.wireguard.proxy-home.server.port ]; @@ -191,19 +211,6 @@ in to = [ "proxy-home" ]; verdict = "accept"; }; - - #masquerade-vpn = { - # from = ["wg-home"]; - # to = ["lan"]; - # masquerade = true; - #}; - - #outbound-vpn = { - # from = ["wg-home"]; - # to = ["lan"]; - # late = true; # Only accept after any rejects have been processed - # verdict = "accept"; - #}; }; }; diff --git a/modules/globals.nix b/modules/globals.nix index c3b24d0..0e4fd06 100644 --- a/modules/globals.nix +++ b/modules/globals.nix @@ -15,6 +15,84 @@ let description = "The network to which this endpoint is associated."; }; }; + + networkOptions = netSubmod: { + cidrv4 = mkOption { + type = types.nullOr types.net.cidrv4; + description = "The CIDRv4 of this network"; + default = null; + }; + + cidrv6 = mkOption { + type = types.nullOr types.net.cidrv6; + description = "The CIDRv6 of this network"; + default = null; + }; + + hosts = mkOption { + default = { }; + type = types.attrsOf ( + types.submodule (hostSubmod: { + options = { + id = mkOption { + type = types.int; + description = "The id of this host in the network"; + }; + + mac = mkOption { + type = types.nullOr types.net.mac; + description = "The MAC of this host, if known. May be used to reserve an address in DHCP resolution."; + default = null; + }; + + ipv4 = mkOption { + type = types.nullOr types.net.ipv4; + description = "The IPv4 of this host"; + readOnly = true; + default = + if netSubmod.config.cidrv4 == null then + null + else + lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv4; + }; + + ipv6 = mkOption { + type = types.nullOr types.net.ipv6; + description = "The IPv6 of this host"; + readOnly = true; + default = + if netSubmod.config.cidrv6 == null then + null + else + lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv6; + }; + + cidrv4 = mkOption { + type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part + description = "The IPv4 of this host including CIDR mask"; + readOnly = true; + default = + if netSubmod.config.cidrv4 == null then + null + else + lib.net.cidr.hostCidr hostSubmod.config.id netSubmod.config.cidrv4; + }; + + cidrv6 = mkOption { + type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part + description = "The IPv6 of this host including CIDR mask"; + readOnly = true; + default = + if netSubmod.config.cidrv6 == null then + null + else + lib.net.cidr.hostCidr hostSubmod.config.id netSubmod.config.cidrv6; + }; + }; + }) + ); + }; + }; in { options = { @@ -41,72 +119,24 @@ in }; net = mkOption { + default = { }; type = types.attrsOf ( types.submodule (netSubmod: { - options = { - cidrv4 = mkOption { - type = types.nullOr types.net.cidrv4; - description = "The CIDRv4 of this network"; - default = null; - }; - - cidrv6 = mkOption { - type = types.nullOr types.net.cidrv6; - description = "The CIDRv6 of this network"; - default = null; - }; - - hosts = mkOption { + options = networkOptions netSubmod // { + vlans = mkOption { + default = { }; type = types.attrsOf ( - types.submodule (hostSubmod: { - options = { + types.submodule (vlanNetSubmod: { + options = networkOptions vlanNetSubmod // { id = mkOption { - type = types.int; - description = "The id of this host in the network"; + type = types.ints.between 1 4094; + description = "The VLAN id"; }; - ipv4 = mkOption { - type = types.nullOr types.net.ipv4; - description = "The IPv4 of this host"; - readOnly = true; - default = - if netSubmod.config.cidrv4 == null then - null - else - lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv4; - }; - - ipv6 = mkOption { - type = types.nullOr types.net.ipv6; - description = "The IPv6 of this host"; - readOnly = true; - default = - if netSubmod.config.cidrv6 == null then - null - else - lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv6; - }; - - cidrv4 = mkOption { - type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part - description = "The IPv4 of this host including CIDR mask"; - readOnly = true; - default = - if netSubmod.config.cidrv4 == null then - null - else - lib.net.cidr.hostCidr hostSubmod.config.id netSubmod.config.cidrv4; - }; - - cidrv6 = mkOption { - type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part - description = "The IPv6 of this host including CIDR mask"; - readOnly = true; - default = - if netSubmod.config.cidrv6 == null then - null - else - lib.net.cidr.hostCidr hostSubmod.config.id netSubmod.config.cidrv6; + name = mkOption { + description = "The name of this VLAN"; + default = vlanNetSubmod.config._module.args.name; + type = types.str; }; }; }) diff --git a/topology/default.nix b/topology/default.nix index 87dc17d..79cb118 100644 --- a/topology/default.nix +++ b/topology/default.nix @@ -57,8 +57,8 @@ in "eth7" ] ]; - connections.eth1 = mkConnection "ward" "lan-self"; - connections.eth2 = mkConnection "sire" "lan-self"; + connections.eth1 = mkConnection "ward" "lan"; + connections.eth2 = mkConnection "sire" "lan"; connections.eth7 = mkConnection "zackbiene" "lan1"; };