{ config, lib, ... }: let cfg = config.router; v6 = cfg.wan.ipv6.enable; pppoe = cfg.wan.ipv4.type == "pppoe"; vlan = cfg.wan.ipv4.pppoe.vlan; vlanDevice = "${cfg.wan.interface}.${toString vlan}"; dhcp = if pppoe then (if v6 then "ipv6" else "no") else (if v6 then "yes" else "ipv4"); in { config = lib.mkIf cfg.enable { networking.useNetworkd = true; networking.useDHCP = false; boot.kernel.sysctl = { "net.ipv4.conf.all.forwarding" = 1; "net.ipv6.conf.all.forwarding" = 1; }; systemd.network.netdevs = lib.optionalAttrs (pppoe && vlan != null) { "05-wan-pppoe" = { netdevConfig = { Kind = "vlan"; Name = vlanDevice; }; vlanConfig.Id = vlan; }; }; systemd.network.networks = { "10-wan" = { matchConfig.Name = cfg.wan.interface; networkConfig = { DHCP = dhcp; } // lib.optionalAttrs v6 { IPv6AcceptRA = true; KeepConfiguration = "yes"; } // lib.optionalAttrs (pppoe && vlan != null) { VLAN = [ vlanDevice ]; }; } // lib.optionalAttrs (!pppoe) { # The router runs its own resolver; never adopt the ISP's DNS for the host. dhcpV4Config.UseDNS = false; } // lib.optionalAttrs v6 { # The ISP sends RAs (default route) without DHCPv6 flags, so solicit anyway. dhcpV6Config = { WithoutRA = "solicit"; UseDelegatedPrefix = true; UseDNS = false; }; }; } // lib.optionalAttrs (pppoe && vlan != null) { # pppd configures the session; networkd only has to bring the device up. "11-wan-pppoe" = { matchConfig.Name = vlanDevice; networkConfig = { LinkLocalAddressing = "no"; IPv6AcceptRA = false; }; }; } // lib.mapAttrs' ( name: lan: lib.nameValuePair "20-lan-${name}" ( { matchConfig.Name = lan.interface; address = [ "${lan.ipv4.address}/${toString lan.ipv4.prefixLength}" ]; networkConfig = { ConfigureWithoutCarrier = true; # Never accept RAs from the inside. IPv6AcceptRA = false; } // lib.optionalAttrs lan.ipv6.pd { DHCPPrefixDelegation = true; IPv6SendRA = true; }; } // lib.optionalAttrs lan.ipv6.pd { dhcpPrefixDelegationConfig = { UplinkInterface = cfg.wan.interface; SubnetId = 0; Announce = true; Assign = true; }; # No RDNSS yet: the global address is unknown at evaluation time. ipv6SendRAConfig.EmitDNS = false; } ) ) cfg.lans; }; }