diff options
Diffstat (limited to 'nixos/modules/tasks/network-interfaces.nix')
-rw-r--r-- | nixos/modules/tasks/network-interfaces.nix | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix new file mode 100644 index 00000000000..0177d6396df --- /dev/null +++ b/nixos/modules/tasks/network-interfaces.nix @@ -0,0 +1,447 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.networking; + interfaces = attrValues cfg.interfaces; + hasVirtuals = any (i: i.virtual) interfaces; + + interfaceOpts = { name, ... }: { + + options = { + + name = mkOption { + example = "eth0"; + type = types.string; + description = "Name of the interface."; + }; + + ipAddress = mkOption { + default = null; + example = "10.0.0.1"; + type = types.nullOr types.string; + description = '' + IP address of the interface. Leave empty to configure the + interface using DHCP. + ''; + }; + + prefixLength = mkOption { + default = null; + example = 24; + type = types.nullOr types.int; + description = '' + Subnet mask of the interface, specified as the number of + bits in the prefix (<literal>24</literal>). + ''; + }; + + subnetMask = mkOption { + default = ""; + example = "255.255.255.0"; + type = types.string; + description = '' + Subnet mask of the interface, specified as a bitmask. + This is deprecated; use <option>prefixLength</option> + instead. + ''; + }; + + macAddress = mkOption { + default = null; + example = "00:11:22:33:44:55"; + type = types.nullOr types.string; + description = '' + MAC address of the interface. Leave empty to use the default. + ''; + }; + + virtual = mkOption { + default = false; + type = types.bool; + description = '' + Whether this interface is virtual and should be created by tunctl. + This is mainly useful for creating bridges between a host a virtual + network such as VPN or a virtual machine. + + Defaults to tap device, unless interface contains "tun" in its name. + ''; + }; + + virtualOwner = mkOption { + default = "root"; + type = types.uniq types.string; + description = '' + In case of a virtual device, the user who owns it. + ''; + }; + + proxyARP = mkOption { + default = false; + type = types.bool; + description = '' + Turn on proxy_arp for this device (and proxy_ndp for ipv6). + This is mainly useful for creating pseudo-bridges between a real + interface and a virtual network such as VPN or a virtual machine for + interfaces that don't support real bridging (most wlan interfaces). + As ARP proxying acts slightly above the link-layer, below-ip traffic + isn't bridged, so things like DHCP won't work. The advantage above + using NAT lies in the fact that no IP addresses are shared, so all + hosts are reachable/routeable. + + WARNING: turns on ip-routing, so if you have multiple interfaces, you + should think of the consequence and setup firewall rules to limit this. + ''; + }; + + }; + + config = { + name = mkDefault name; + }; + + }; + +in + +{ + + ###### interface + + options = { + + networking.hostName = mkOption { + default = "nixos"; + description = '' + The name of the machine. Leave it empty if you want to obtain + it from a DHCP server (if using DHCP). + ''; + }; + + networking.enableIPv6 = mkOption { + default = true; + description = '' + Whether to enable support for IPv6. + ''; + }; + + networking.defaultGateway = mkOption { + default = ""; + example = "131.211.84.1"; + description = '' + The default gateway. It can be left empty if it is auto-detected through DHCP. + ''; + }; + + networking.defaultGatewayWindowSize = mkOption { + default = null; + example = 524288; + type = types.nullOr types.int; + description = '' + The window size of the default gateway. It limits maximal data bursts that TCP peers + are allowed to send to us. + ''; + }; + + networking.nameservers = mkOption { + default = []; + example = ["130.161.158.4" "130.161.33.17"]; + description = '' + The list of nameservers. It can be left empty if it is auto-detected through DHCP. + ''; + }; + + networking.domain = mkOption { + default = ""; + example = "home"; + description = '' + The domain. It can be left empty if it is auto-detected through DHCP. + ''; + }; + + networking.localCommands = mkOption { + default = ""; + example = "text=anything; echo You can put $text here."; + description = '' + Shell commands to be executed at the end of the + <literal>network-setup</literal> systemd service. Note that if + you are using DHCP to obtain the network configuration, + interfaces may not be fully configured yet. + ''; + }; + + networking.interfaces = mkOption { + default = {}; + example = + { eth0 = { + ipAddress = "131.211.84.78"; + subnetMask = "255.255.255.128"; + }; + }; + description = '' + The configuration for each network interface. If + <option>networking.useDHCP</option> is true, then every + interface not listed here will be configured using DHCP. + ''; + type = types.loaOf types.optionSet; + options = [ interfaceOpts ]; + }; + + networking.bridges = mkOption { + default = { }; + example = + { br0.interfaces = [ "eth0" "eth1" ]; + br1.interfaces = [ "eth2" "wlan0" ]; + }; + description = + '' + This option allows you to define Ethernet bridge devices + that connect physical networks together. The value of this + option is an attribute set. Each attribute specifies a + bridge, with the attribute name specifying the name of the + bridge's network interface. + ''; + + type = types.attrsOf types.optionSet; + + options = { + + interfaces = mkOption { + example = [ "eth0" "eth1" ]; + type = types.listOf types.string; + description = + "The physical network interfaces connected by the bridge."; + }; + + }; + + }; + + networking.useDHCP = mkOption { + default = true; + merge = mergeEnableOption; + description = '' + Whether to use DHCP to obtain an IP address and other + configuration for all network interfaces that are not manually + configured. + ''; + }; + + }; + + + ###### implementation + + config = { + + boot.kernelModules = optional cfg.enableIPv6 "ipv6" ++ optional hasVirtuals "tun"; + + 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; + + 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" ]; + wantedBy = [ "network.target" ]; + + path = [ pkgs.iproute ]; + + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + + script = + '' + # Set the static DNS configuration, if given. + ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF + ${optionalString (cfg.nameservers != [] && cfg.domain != "") '' + domain ${cfg.domain} + ''} + ${flip concatMapStrings cfg.nameservers (ns: '' + nameserver ${ns} + '')} + EOF + + # Disable or enable IPv6. + 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: nameValuePair "${i.name}-cfg" + (let mask = + if i.prefixLength != null then toString i.prefixLength else + if i.subnetMask != "" then i.subnetMask else "32"; + in + { description = "Configuration of ${i.name}"; + wantedBy = [ "network-interfaces.target" ]; + bindsTo = [ "sys-subsystem-net-devices-${i.name}.device" ]; + after = [ "sys-subsystem-net-devices-${i.name}.device" ]; + 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.ipAddress != null) + '' + cur=$(ip -4 -o a show dev "${i.name}" | awk '{print $4}') + # Only do a flush/add if it's necessary. This is + # useful when the Nix store is accessed via this + # interface (e.g. in a QEMU VM test). + if [ "$cur" != "${i.ipAddress}/${mask}" ]; then + echo "configuring interface..." + ip -4 addr flush dev "${i.name}" + ip -4 addr add "${i.ipAddress}/${mask}" dev "${i.name}" + # 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 + else + echo "skipping configuring interface" + 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 + ''; + }); + + createTunDevice = i: nameValuePair "${i.name}" + { description = "Virtual Network Interface ${i.name}"; + requires = [ "dev-net-tun.device" ]; + after = [ "dev-net-tun.device" ]; + wantedBy = [ "network.target" ]; + requiredBy = [ "sys-subsystem-net-devices-${i.name}.device" ]; + serviceConfig = + { Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.tunctl}/bin/tunctl -t '${i.name}' -u '${i.virtualOwner}'"; + ExecStop = "${pkgs.tunctl}/bin/tunctl -d '${i.name}'"; + }; + }; + + createBridgeDevice = n: v: + let + deps = map (i: "sys-subsystem-net-devices-${i}.device") v.interfaces; + in + { description = "Bridge Interface ${n}"; + wantedBy = [ "network.target" "sys-subsystem-net-devices-${n}.device" ]; + bindsTo = deps; + after = deps; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.bridge_utils pkgs.iproute ]; + script = + '' + 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}" + ''; + }; + + in listToAttrs ( + map configureInterface interfaces ++ + map createTunDevice (filter (i: i.virtual) interfaces)) + // mapAttrs createBridgeDevice cfg.bridges + // { "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 dhclient in the meantime. + system.activationScripts.hostname = + optionalString (config.networking.hostName != "") '' + hostname "${config.networking.hostName}" + ''; + system.activationScripts.domain = + optionalString (config.networking.domain != "") '' + domainname "${config.networking.domain}" + ''; + + services.udev.extraRules = + '' + KERNEL=="tun", TAG+="systemd" + ''; + + }; + +} |