summary refs log tree commit diff
path: root/nixos/modules/tasks/network-interfaces.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/tasks/network-interfaces.nix')
-rw-r--r--nixos/modules/tasks/network-interfaces.nix447
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"
+      '';
+
+  };
+
+}