diff options
-rwxr-xr-x | nixos/modules/module-list.nix | 2 | ||||
-rw-r--r-- | nixos/modules/services/networking/dhcpcd.nix | 2 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd-unit-options.nix | 24 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd.nix | 15 | ||||
-rw-r--r-- | nixos/modules/tasks/network-interfaces-scripted.nix | 340 | ||||
-rw-r--r-- | nixos/modules/tasks/network-interfaces-systemd.nix | 174 | ||||
-rw-r--r-- | nixos/modules/tasks/network-interfaces.nix | 407 |
7 files changed, 615 insertions, 349 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 85afcb824fb..9420ca4af40 100755 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -387,6 +387,8 @@ ./tasks/kbd.nix ./tasks/lvm.nix ./tasks/network-interfaces.nix + ./tasks/network-interfaces-systemd.nix + ./tasks/network-interfaces-scripted.nix ./tasks/scsi-link-power-management.nix ./tasks/swraid.nix ./tasks/trackpoint.nix diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 15dbf80a987..d70008e7c23 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -11,7 +11,7 @@ let # Don't start dhcpcd on explicitly configured interfaces or on # interfaces that are part of a bridge, bond or sit device. ignoredInterfaces = - map (i: i.name) (filter (i: i.ip4 != [ ] || i.ipAddress != null) (attrValues config.networking.interfaces)) + map (i: i.name) (filter (i: if i.useDHCP != null then i.useDHCP else i.ip4 != [ ] || i.ipAddress != null) (attrValues config.networking.interfaces)) ++ mapAttrsToList (i: _: i) config.networking.sits ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges)) ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds)) diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix index 52ad71a66c7..20851c626d7 100644 --- a/nixos/modules/system/boot/systemd-unit-options.nix +++ b/nixos/modules/system/boot/systemd-unit-options.nix @@ -832,6 +832,30 @@ in rec { ''; }; + name = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The name of the network interface to match against. + ''; + }; + + DHCP = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Whether to enable DHCP on the interfaces matched. + ''; + }; + + domains = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = '' + A list of domains to pass to the network config. + ''; + }; + address = mkOption { default = [ ]; type = types.listOf types.str; diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 8c53d342b36..f34c16b9ac0 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -300,6 +300,19 @@ let }; }; + networkConfig = { name, config, ... }: { + config = { + matchConfig = optionalAttrs (config.name != null) { + Name = config.name; + }; + networkConfig = optionalAttrs (config.DHCP != null) { + DHCP = config.DHCP; + } // optionalAttrs (config.domains != null) { + Domains = concatStringsSep " " config.domains; + }; + }; + }; + toOption = x: if x == true then "true" else if x == false then "false" @@ -693,7 +706,7 @@ in systemd.network.networks = mkOption { default = {}; type = types.attrsOf types.optionSet; - options = [ networkOptions ]; + options = [ networkOptions networkConfig ]; description = "Definiton of systemd networks."; }; diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix new file mode 100644 index 00000000000..a39f1a09a34 --- /dev/null +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -0,0 +1,340 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; +with utils; + +let + + cfg = config.networking; + interfaces = attrValues cfg.interfaces; + hasVirtuals = any (i: i.virtual) interfaces; + + # We must escape interfaces due to the systemd interpretation + subsystemDevice = interface: + "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; + + interfaceIps = i: + i.ip4 ++ optionals cfg.enableIPv6 i.ip6 + ++ optional (i.ipAddress != null) { + address = i.ipAddress; + prefixLength = i.prefixLength; + } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) { + address = i.ipv6Address; + prefixLength = i.ipv6PrefixLength; + }; + +in + +{ + + config = mkIf (!cfg.useNetworkd) { + + systemd.targets."network-interfaces" = + { description = "All Network Interfaces"; + wantedBy = [ "network.target" ]; + unitConfig.X-StopOnReconfiguration = true; + }; + + systemd.services = + let + + networkLocalCommands = { + after = [ "network-setup.service" ]; + bindsTo = [ "network-setup.service" ]; + }; + + networkSetup = + { description = "Networking Setup"; + + after = [ "network-interfaces.target" ]; + before = [ "network.target" "network-online.target" ]; + wantedBy = [ "network.target" "network-online.target" ]; + + unitConfig.ConditionCapability = "CAP_NET_ADMIN"; + + path = [ pkgs.iproute ]; + + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + + script = + (optionalString (!config.services.resolved.enable) '' + # Set the static DNS configuration, if given. + ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF + ${optionalString (cfg.nameservers != [] && cfg.domain != null) '' + domain ${cfg.domain} + ''} + ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)} + ${flip concatMapStrings cfg.nameservers (ns: '' + nameserver ${ns} + '')} + EOF + '') + '' + # Set the default gateway. + ${optionalString (cfg.defaultGateway != null) '' + # FIXME: get rid of "|| true" (necessary to make it idempotent). + ip route add default via "${cfg.defaultGateway}" ${ + optionalString (cfg.defaultGatewayWindowSize != null) + "window ${cfg.defaultGatewayWindowSize}"} || true + ''} + ''; + }; + + # For each interface <foo>, create a job ‘<foo>-cfg.service" + # that performs static configuration. It has a "wants" + # dependency on ‘<foo>.service’, which is supposed to create + # the interface and need not exist (i.e. for hardware + # interfaces). It has a binds-to dependency on the actual + # network device, so it only gets started after the interface + # has appeared, and it's stopped when the interface + # disappears. + configureInterface = i: + let + ips = interfaceIps i; + in + nameValuePair "${i.name}-cfg" + { description = "Configuration of ${i.name}"; + wantedBy = [ "network-interfaces.target" ]; + bindsTo = [ (subsystemDevice i.name) ]; + after = [ (subsystemDevice i.name) ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.iproute pkgs.gawk ]; + script = + '' + echo "bringing up interface..." + ip link set "${i.name}" up + '' + + optionalString (i.macAddress != null) + '' + echo "setting MAC address to ${i.macAddress}..." + ip link set "${i.name}" address "${i.macAddress}" + '' + + optionalString (i.mtu != null) + '' + echo "setting MTU to ${toString i.mtu}..." + ip link set "${i.name}" mtu "${toString i.mtu}" + '' + + # Ip Setup + + + '' + curIps=$(ip -o a show dev "${i.name}" | awk '{print $4}') + # Only do an add if it's necessary. This is + # useful when the Nix store is accessed via this + # interface (e.g. in a QEMU VM test). + '' + + flip concatMapStrings (ips) (ip: + let + address = "${ip.address}/${toString ip.prefixLength}"; + in + '' + echo "checking ip ${address}..." + if ! echo "$curIps" | grep "${address}" >/dev/null 2>&1; then + if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then + echo "added ip ${address}..." + restart_network_setup=true + elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then + echo "failed to add ${address}" + exit 1 + fi + fi + '') + + optionalString (ips != [ ]) + '' + if [ restart_network_setup = true ]; then + # Ensure that the default gateway remains set. + # (Flushing this interface may have removed it.) + ${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service + fi + ${config.systemd.package}/bin/systemctl start ip-up.target + ''; + preStop = + '' + echo "releasing configured ip's..." + '' + + flip concatMapStrings (ips) (ip: + let + address = "${ip.address}/${toString ip.prefixLength}"; + in + '' + echo -n "Deleting ${address}..." + ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed" + echo "" + ''); + }; + + createTunDevice = i: nameValuePair "${i.name}-netdev" + { description = "Virtual Network Interface ${i.name}"; + requires = [ "dev-net-tun.device" ]; + after = [ "dev-net-tun.device" ]; + wantedBy = [ "network.target" (subsystemDevice i.name) ]; + path = [ pkgs.iproute ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + ip tuntap add dev "${i.name}" \ + ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \ + user "${i.virtualOwner}" + ''; + postStop = '' + ip link del ${i.name} + ''; + }; + + createBridgeDevice = n: v: nameValuePair "${n}-netdev" + (let + deps = map subsystemDevice v.interfaces; + in + { description = "Bridge Interface ${n}"; + wantedBy = [ "network.target" (subsystemDevice n) ]; + bindsTo = deps; + after = deps; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.bridge_utils pkgs.iproute ]; + script = + '' + # Remove Dead Interfaces + ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" + + brctl addbr "${n}" + + # Set bridge's hello time to 0 to avoid startup delays. + brctl setfd "${n}" 0 + + ${flip concatMapStrings v.interfaces (i: '' + brctl addif "${n}" "${i}" + ip link set "${i}" up + ip addr flush dev "${i}" + + echo "bringing up network device ${n}..." + ip link set "${n}" up + '')} + + # !!! Should delete (brctl delif) any interfaces that + # no longer belong to the bridge. + ''; + postStop = + '' + ip link set "${n}" down + brctl delbr "${n}" + ''; + }); + + createBondDevice = n: v: nameValuePair "${n}-netdev" + (let + deps = map subsystemDevice v.interfaces; + in + { description = "Bond Interface ${n}"; + wantedBy = [ "network.target" (subsystemDevice n) ]; + bindsTo = deps; + after = deps; + before = [ "${n}-cfg.service" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.ifenslave pkgs.iproute ]; + script = '' + ip link add name "${n}" type bond + + # !!! There must be a better way to wait for the interface + while [ ! -d /sys/class/net/${n} ]; do sleep 0.1; done; + + # Ensure the link is down so that we can set options + ip link set "${n}" down + + # Set the miimon and mode options + ${optionalString (v.miimon != null) + "echo \"${toString v.miimon}\" >/sys/class/net/${n}/bonding/miimon"} + ${optionalString (v.mode != null) + "echo \"${v.mode}\" >/sys/class/net/${n}/bonding/mode"} + ${optionalString (v.lacp_rate != null) + "echo \"${v.lacp_rate}\" >/sys/class/net/${n}/bonding/lacp_rate"} + ${optionalString (v.xmit_hash_policy != null) + "echo \"${v.xmit_hash_policy}\" >/sys/class/net/${n}/bonding/xmit_hash_policy"} + + # Bring up the bond and enslave the specified interfaces + ip link set "${n}" up + ${flip concatMapStrings v.interfaces (i: '' + ifenslave "${n}" "${i}" + '')} + ''; + postStop = '' + ${flip concatMapStrings v.interfaces (i: '' + ifenslave -d "${n}" "${i}" >/dev/null 2>&1 || true + '')} + ip link set "${n}" down >/dev/null 2>&1 || true + ip link del "${n}" >/dev/null 2>&1 || true + ''; + }); + + createSitDevice = n: v: nameValuePair "${n}-netdev" + (let + deps = optional (v.dev != null) (subsystemDevice v.dev); + in + { description = "6-to-4 Tunnel Interface ${n}"; + wantedBy = [ "network.target" (subsystemDevice n) ]; + bindsTo = deps; + after = deps; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.iproute ]; + script = '' + # Remove Dead Interfaces + ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" + ip link add name "${n}" type sit \ + ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ + ${optionalString (v.local != null) "local \"${v.local}\""} \ + ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \ + ${optionalString (v.dev != null) "dev \"${v.dev}\""} + ip link set "${n}" up + ''; + postStop = '' + ip link delete "${n}" + ''; + }); + + createVlanDevice = n: v: nameValuePair "${n}-netdev" + (let + deps = [ (subsystemDevice v.interface) ]; + in + { description = "Vlan Interface ${n}"; + wantedBy = [ "network.target" (subsystemDevice n) ]; + bindsTo = deps; + after = deps; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.iproute ]; + script = '' + # Remove Dead Interfaces + ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" + ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" + ip link set "${n}" up + ''; + postStop = '' + ip link delete "${n}" + ''; + }); + + in listToAttrs ( + map configureInterface interfaces ++ + map createTunDevice (filter (i: i.virtual) interfaces)) + // mapAttrs' createBridgeDevice cfg.bridges + // mapAttrs' createBondDevice cfg.bonds + // mapAttrs' createSitDevice cfg.sits + // mapAttrs' createVlanDevice cfg.vlans + // { + "network-setup" = networkSetup; + "network-local-commands" = networkLocalCommands; + }; + + services.udev.extraRules = + '' + KERNEL=="tun", TAG+="systemd" + ''; + + }; + +} diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix new file mode 100644 index 00000000000..2ff4793acef --- /dev/null +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -0,0 +1,174 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; +with utils; + +let + + cfg = config.networking; + interfaces = attrValues cfg.interfaces; + + interfaceIps = i: + i.ip4 ++ optionals cfg.enableIPv6 i.ip6 + ++ optional (i.ipAddress != null) { + address = i.ipAddress; + prefixLength = i.prefixLength; + } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) { + address = i.ipv6Address; + prefixLength = i.ipv6PrefixLength; + }; + + dhcpStr = useDHCP: if useDHCP then "both" else "none"; + + slaves = + concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) + ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) + ++ map (sit: sit.dev) (attrValues cfg.sits) + ++ map (vlan: vlan.interface) (attrValues cfg.vlans); + +in + +{ + + config = mkIf cfg.useNetworkd { + + assertions = [ { + assertion = cfg.defaultGatewayWindowSize == null; + message = "networking.defaultGatewayWindowSize is not supported by networkd."; + } { + assertion = ! cfg.useHostResolvConf; + message = "networking.useHostResolvConf is not supported by networkd."; + } ]; + + systemd.services.dhcpcd.enable = mkDefault false; + + systemd.services.network-local-commands = { + after = [ "systemd-networkd.service" ]; + bindsTo = [ "systemd-networkd.service" ]; + }; + + systemd.network = + let + domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain); + genericNetwork = override: { + DHCP = override (dhcpStr cfg.useDHCP); + } // optionalAttrs (cfg.defaultGateway != null) { + gateway = override [ cfg.defaultGateway ]; + } // optionalAttrs (domains != [ ]) { + domains = override domains; + }; + in mkMerge [ { + enable = true; + networks."99-main" = genericNetwork mkDefault; + } + (mkMerge (flip map interfaces (i: { + links."40-${i.name}" = { + matchConfig.Name = i.name; + linkConfig = + (optionalAttrs (i.macAddress != null) { + MACAddress = i.macAddress; + }) // (optionalAttrs (i.mtu != null) { + MTUBytes = toString i.mtu; + }); + }; + netdevs = mkIf i.virtual ( + let + devType = if i.virtualType != null then i.virtualType + else (if hasPrefix "tun" i.name then "tun" else "tap"); + in { + "40-${i.name}" = { + netdevConfig = { + Name = i.name; + Kind = devType; + }; + "${devType}Config" = optionalAttrs (i.virtualOwner != null) { + User = i.virtualOwner; + }; + }; + }); + networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) { + name = mkDefault i.name; + DHCP = mkForce (dhcpStr + (if i.useDHCP != null then i.useDHCP else interfaceIps i == [ ])); + address = flip map (interfaceIps i) + (ip: "${ip.address}/${toString ip.prefixLength}"); + } ]; + }))) + (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "bridge"; + }; + }; + networks = listToAttrs (flip map bridge.interfaces (bi: + nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { + DHCP = mkOverride 0 (dhcpStr false); + networkConfig.Bridge = name; + } ]))); + }))) + (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "bond"; + }; + bondConfig = + (optionalAttrs (bond.lacp_rate != null) { + LACPTransmitRate = bond.lacp_rate; + }) // (optionalAttrs (bond.miimon != null) { + MIIMonitorSec = bond.miimon; + }) // (optionalAttrs (bond.mode != null) { + Mode = bond.mode; + }) // (optionalAttrs (bond.xmit_hash_policy != null) { + TransmitHashPolicy = bond.xmit_hash_policy; + }); + }; + networks = listToAttrs (flip map bond.interfaces (bi: + nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { + DHCP = mkOverride 0 (dhcpStr false); + networkConfig.Bond = name; + } ]))); + }))) + (mkMerge (flip mapAttrsToList cfg.sits (name: sit: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "sit"; + }; + tunnelConfig = + (optionalAttrs (sit.remote != null) { + Remote = sit.remote; + }) // (optionalAttrs (sit.local != null) { + Local = sit.local; + }) // (optionalAttrs (sit.ttl != null) { + TTL = sit.ttl; + }); + }; + networks = mkIf (sit.dev != null) { + "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) { + tunnel = [ name ]; + } ]); + }; + }))) + (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "vlan"; + }; + vlanConfig.Id = vlan.id; + }; + networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { + vlan = [ name ]; + } ]); + }))) + ]; + + # We need to prefill the slaved devices with networking options + # This forces the network interface creator to initialize slaves. + networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves); + + }; + +} diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 6b22cf96e1d..11b641d40eb 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -11,10 +11,6 @@ let hasSits = cfg.sits != { }; hasBonds = cfg.bonds != { }; - # We must escape interfaces due to the systemd interpretation - subsystemDevice = interface: - "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; - addrOpts = v: assert v == 4 || v == 6; { @@ -45,6 +41,16 @@ let description = "Name of the interface."; }; + useDHCP = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Whether this interface should be configured with dhcp. + Null implies the old behavior which depends on whether ip addresses + are specified or not. + ''; + }; + ip4 = mkOption { default = [ ]; example = [ @@ -203,6 +209,7 @@ in networking.hostName = mkOption { default = "nixos"; + type = types.str; description = '' The name of the machine. Leave it empty if you want to obtain it from a DHCP server (if using DHCP). @@ -225,14 +232,16 @@ in networking.enableIPv6 = mkOption { default = true; + type = types.bool; description = '' Whether to enable support for IPv6. ''; }; networking.defaultGateway = mkOption { - default = ""; + default = null; example = "131.211.84.1"; + type = types.nullOr types.str; description = '' The default gateway. It can be left empty if it is auto-detected through DHCP. ''; @@ -266,8 +275,9 @@ in }; networking.domain = mkOption { - default = ""; + default = null; example = "home"; + type = types.nullOr types.str; description = '' The domain. It can be left empty if it is auto-detected through DHCP. ''; @@ -523,6 +533,15 @@ in ''; }; + networking.useNetworkd = mkOption { + default = false; + type = types.bool; + description = '' + Whether we should use networkd as the network configuration backend or + the legacy script based system. + ''; + }; + }; @@ -552,345 +571,17 @@ in # from being created. optionalString hasBonds "options bonding max_bonds=0"; - environment.systemPackages = - [ pkgs.host - pkgs.iproute - pkgs.iputils - pkgs.nettools - pkgs.wirelesstools - pkgs.iw - pkgs.rfkill - pkgs.openresolv - ] - ++ optional (cfg.bridges != {}) pkgs.bridge_utils - ++ optional hasVirtuals pkgs.tunctl - ++ optional cfg.enableIPv6 pkgs.ndisc6; + boot.kernel.sysctl = { + "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6); + "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6); + "net.ipv4.conf.all_forwarding" = mkDefault (any (i: i.proxyARP) interfaces); + "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces); + } // listToAttrs (concatLists (flip map (filter (i: i.proxyARP) interfaces) + (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)) + )); security.setuidPrograms = [ "ping" "ping6" ]; - systemd.targets."network-interfaces" = - { description = "All Network Interfaces"; - wantedBy = [ "network.target" ]; - unitConfig.X-StopOnReconfiguration = true; - }; - - systemd.services = - let - - networkSetup = - { description = "Networking Setup"; - - after = [ "network-interfaces.target" ]; - before = [ "network.target" "network-online.target" ]; - wantedBy = [ "network.target" "network-online.target" ]; - - unitConfig.ConditionCapability = "CAP_NET_ADMIN"; - - path = [ pkgs.iproute ]; - - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - - script = - (optionalString (!config.services.resolved.enable) '' - # Set the static DNS configuration, if given. - ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF - ${optionalString (cfg.nameservers != [] && cfg.domain != "") '' - domain ${cfg.domain} - ''} - ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)} - ${flip concatMapStrings cfg.nameservers (ns: '' - nameserver ${ns} - '')} - EOF - '') + '' - # Disable or enable IPv6. - ${optionalString (!config.boot.isContainer) '' - if [ -e /proc/sys/net/ipv6/conf/all/disable_ipv6 ]; then - echo ${if cfg.enableIPv6 then "0" else "1"} > /proc/sys/net/ipv6/conf/all/disable_ipv6 - fi - ''} - - # Set the default gateway. - ${optionalString (cfg.defaultGateway != "") '' - # FIXME: get rid of "|| true" (necessary to make it idempotent). - ip route add default via "${cfg.defaultGateway}" ${ - optionalString (cfg.defaultGatewayWindowSize != null) - "window ${cfg.defaultGatewayWindowSize}"} || true - ''} - - # Turn on forwarding if any interface has enabled proxy_arp. - ${optionalString (any (i: i.proxyARP) interfaces) '' - echo 1 > /proc/sys/net/ipv4/ip_forward - ''} - - # Run any user-specified commands. - ${cfg.localCommands} - ''; - }; - - # For each interface <foo>, create a job ‘<foo>-cfg.service" - # that performs static configuration. It has a "wants" - # dependency on ‘<foo>.service’, which is supposed to create - # the interface and need not exist (i.e. for hardware - # interfaces). It has a binds-to dependency on the actual - # network device, so it only gets started after the interface - # has appeared, and it's stopped when the interface - # disappears. - configureInterface = i: - let - ips = i.ip4 ++ optionals cfg.enableIPv6 i.ip6 - ++ optional (i.ipAddress != null) { - address = i.ipAddress; - prefixLength = i.prefixLength; - } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) { - address = i.ipv6Address; - prefixLength = i.ipv6PrefixLength; - }; - in - nameValuePair "${i.name}-cfg" - { description = "Configuration of ${i.name}"; - wantedBy = [ "network-interfaces.target" ]; - bindsTo = [ (subsystemDevice i.name) ]; - after = [ (subsystemDevice i.name) ]; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - path = [ pkgs.iproute pkgs.gawk ]; - script = - '' - echo "bringing up interface..." - ip link set "${i.name}" up - '' - + optionalString (i.macAddress != null) - '' - echo "setting MAC address to ${i.macAddress}..." - ip link set "${i.name}" address "${i.macAddress}" - '' - + optionalString (i.mtu != null) - '' - echo "setting MTU to ${toString i.mtu}..." - ip link set "${i.name}" mtu "${toString i.mtu}" - '' - - # Ip Setup - + - '' - curIps=$(ip -o a show dev "${i.name}" | awk '{print $4}') - # Only do an add if it's necessary. This is - # useful when the Nix store is accessed via this - # interface (e.g. in a QEMU VM test). - '' - + flip concatMapStrings (ips) (ip: - let - address = "${ip.address}/${toString ip.prefixLength}"; - in - '' - echo "checking ip ${address}..." - if ! echo "$curIps" | grep "${address}" >/dev/null 2>&1; then - if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then - echo "added ip ${address}..." - restart_network_setup=true - elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then - echo "failed to add ${address}" - exit 1 - fi - fi - '') - + optionalString (ips != [ ]) - '' - if [ restart_network_setup = true ]; then - # Ensure that the default gateway remains set. - # (Flushing this interface may have removed it.) - ${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service - fi - ${config.systemd.package}/bin/systemctl start ip-up.target - '' - + optionalString i.proxyARP - '' - echo 1 > /proc/sys/net/ipv4/conf/${i.name}/proxy_arp - '' - + optionalString (i.proxyARP && cfg.enableIPv6) - '' - echo 1 > /proc/sys/net/ipv6/conf/${i.name}/proxy_ndp - ''; - preStop = - '' - echo "releasing configured ip's..." - '' - + flip concatMapStrings (ips) (ip: - let - address = "${ip.address}/${toString ip.prefixLength}"; - in - '' - echo -n "Deleting ${address}..." - ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed" - echo "" - ''); - }; - - createTunDevice = i: nameValuePair "${i.name}-netdev" - { description = "Virtual Network Interface ${i.name}"; - requires = [ "dev-net-tun.device" ]; - after = [ "dev-net-tun.device" ]; - wantedBy = [ "network.target" (subsystemDevice i.name) ]; - path = [ pkgs.iproute ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = '' - ip tuntap add dev "${i.name}" \ - ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \ - user "${i.virtualOwner}" - ''; - postStop = '' - ip link del ${i.name} - ''; - }; - - createBridgeDevice = n: v: nameValuePair "${n}-netdev" - (let - deps = map subsystemDevice v.interfaces; - in - { description = "Bridge Interface ${n}"; - wantedBy = [ "network.target" (subsystemDevice n) ]; - bindsTo = deps; - after = deps; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - path = [ pkgs.bridge_utils pkgs.iproute ]; - script = - '' - # Remove Dead Interfaces - ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" - - brctl addbr "${n}" - - # Set bridge's hello time to 0 to avoid startup delays. - brctl setfd "${n}" 0 - - ${flip concatMapStrings v.interfaces (i: '' - brctl addif "${n}" "${i}" - ip link set "${i}" up - ip addr flush dev "${i}" - - echo "bringing up network device ${n}..." - ip link set "${n}" up - '')} - - # !!! Should delete (brctl delif) any interfaces that - # no longer belong to the bridge. - ''; - postStop = - '' - ip link set "${n}" down - brctl delbr "${n}" - ''; - }); - - createBondDevice = n: v: nameValuePair "${n}-netdev" - (let - deps = map subsystemDevice v.interfaces; - in - { description = "Bond Interface ${n}"; - wantedBy = [ "network.target" (subsystemDevice n) ]; - bindsTo = deps; - after = deps; - before = [ "${n}-cfg.service" ]; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - path = [ pkgs.ifenslave pkgs.iproute ]; - script = '' - ip link add name "${n}" type bond - - # !!! There must be a better way to wait for the interface - while [ ! -d /sys/class/net/${n} ]; do sleep 0.1; done; - - # Ensure the link is down so that we can set options - ip link set "${n}" down - - # Set the miimon and mode options - ${optionalString (v.miimon != null) - "echo \"${toString v.miimon}\" >/sys/class/net/${n}/bonding/miimon"} - ${optionalString (v.mode != null) - "echo \"${v.mode}\" >/sys/class/net/${n}/bonding/mode"} - ${optionalString (v.lacp_rate != null) - "echo \"${v.lacp_rate}\" >/sys/class/net/${n}/bonding/lacp_rate"} - ${optionalString (v.xmit_hash_policy != null) - "echo \"${v.xmit_hash_policy}\" >/sys/class/net/${n}/bonding/xmit_hash_policy"} - - # Bring up the bond and enslave the specified interfaces - ip link set "${n}" up - ${flip concatMapStrings v.interfaces (i: '' - ifenslave "${n}" "${i}" - '')} - ''; - postStop = '' - ${flip concatMapStrings v.interfaces (i: '' - ifenslave -d "${n}" "${i}" >/dev/null 2>&1 || true - '')} - ip link set "${n}" down >/dev/null 2>&1 || true - ip link del "${n}" >/dev/null 2>&1 || true - ''; - }); - - createSitDevice = n: v: nameValuePair "${n}-netdev" - (let - deps = optional (v.dev != null) (subsystemDevice v.dev); - in - { description = "6-to-4 Tunnel Interface ${n}"; - wantedBy = [ "network.target" (subsystemDevice n) ]; - bindsTo = deps; - after = deps; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - path = [ pkgs.iproute ]; - script = '' - # Remove Dead Interfaces - ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" - ip link add name "${n}" type sit \ - ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ - ${optionalString (v.local != null) "local \"${v.local}\""} \ - ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \ - ${optionalString (v.dev != null) "dev \"${v.dev}\""} - ip link set "${n}" up - ''; - postStop = '' - ip link delete "${n}" - ''; - }); - - createVlanDevice = n: v: nameValuePair "${n}-netdev" - (let - deps = [ (subsystemDevice v.interface) ]; - in - { description = "Vlan Interface ${n}"; - wantedBy = [ "network.target" (subsystemDevice n) ]; - bindsTo = deps; - after = deps; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - path = [ pkgs.iproute ]; - script = '' - # Remove Dead Interfaces - ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" - ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" - ip link set "${n}" up - ''; - postStop = '' - ip link delete "${n}" - ''; - }); - - in listToAttrs ( - map configureInterface interfaces ++ - map createTunDevice (filter (i: i.virtual) interfaces)) - // mapAttrs' createBridgeDevice cfg.bridges - // mapAttrs' createBondDevice cfg.bonds - // mapAttrs' createSitDevice cfg.sits - // mapAttrs' createVlanDevice cfg.vlans - // { "network-setup" = networkSetup; }; - # Set the host and domain names in the activation script. Don't # clear it if it's not configured in the NixOS configuration, # since it may have been set by dhcpcd in the meantime. @@ -899,7 +590,7 @@ in hostname "${cfg.hostName}" ''; system.activationScripts.domain = - optionalString (cfg.domain != "") '' + optionalString (cfg.domain != null) '' domainname "${cfg.domain}" ''; @@ -918,11 +609,33 @@ in } ]; - services.udev.extraRules = - '' - KERNEL=="tun", TAG+="systemd" - ''; + environment.systemPackages = + [ pkgs.host + pkgs.iproute + pkgs.iputils + pkgs.nettools + pkgs.wirelesstools + pkgs.iw + pkgs.rfkill + pkgs.openresolv + ] + ++ optional (cfg.bridges != {}) pkgs.bridge_utils + ++ optional hasVirtuals pkgs.tunctl + ++ optional cfg.enableIPv6 pkgs.ndisc6; + systemd.services.network-local-commands = { + description = "Extra networking commands."; + before = [ "network.target" "network-online.target" ]; + wantedBy = [ "network.target" "network-online.target" ]; + unitConfig.ConditionCapability = "CAP_NET_ADMIN"; + path = [ pkgs.iproute ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + script = '' + # Run any user-specified commands. + ${cfg.localCommands} + ''; + }; }; } |