diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 136a5bda7459..773d0b1f1a7d 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -84,13 +84,40 @@ in
dnsmasq_conf=/etc/dnsmasq-conf.conf
dnsmasq_resolv=/etc/dnsmasq-resolv.conf
'';
- };
+
+ } // (optionalAttrs config.services.resolved.enable (
+ if dnsmasqResolve then {
+ "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+ } else {
+ "resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+ }
+ ));
# The ‘ip-up’ target is started when we have IP connectivity. So
# services that depend on IP connectivity (like ntpd) should be
# pulled in by this target.
systemd.targets.ip-up.description = "Services Requiring IP Connectivity";
+ # This is needed when /etc/resolv.conf is being overriden by networkd
+ # and other configurations. If the file is destroyed by an environment
+ # activation then it must be rebuilt so that applications which interface
+ # with /etc/resolv.conf directly don't break.
+ system.activationScripts.resolvconf = stringAfter [ "etc" "tmpfs" "var" ]
+ ''
+ # Systemd resolved controls its own resolv.conf
+ rm -f /run/resolvconf/interfaces/systemd
+ ${optionalString config.services.resolved.enable ''
+ rm -rf /run/resolvconf/interfaces
+ mkdir -p /run/resolvconf/interfaces
+ ln -s /run/systemd/resolve/resolv.conf /run/resolvconf/interfaces/systemd
+ ''}
+
+ # Make sure resolv.conf is up to date if not managed by systemd
+ ${optionalString (!config.services.resolved.enable) ''
+ ${pkgs.openresolv}/bin/resolvconf -u
+ ''}
+ '';
+
};
}
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 8cdc9d2dd4c1..ecf68136f97b 100755
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -388,6 +388,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/chrony.nix b/nixos/modules/services/networking/chrony.nix
index 58b30269ca7a..fe062b30e4b7 100644
--- a/nixos/modules/services/networking/chrony.nix
+++ b/nixos/modules/services/networking/chrony.nix
@@ -100,8 +100,8 @@ in
jobs.chronyd =
{ description = "chrony daemon";
- wantedBy = [ "ip-up.target" ];
- partOf = [ "ip-up.target" ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
path = [ chrony ];
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index 15dbf80a987e..1ad8cbae15cf 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -8,15 +8,29 @@ let
cfg = config.networking.dhcpcd;
+ interfaces = attrValues config.networking.interfaces;
+
+ enableDHCP = config.networking.useDHCP || any (i: i.useDHCP == true) interfaces;
+
# 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) 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))
++ config.networking.dhcpcd.denyInterfaces;
+ arrayAppendOrNull = a1: a2: if a1 == null && a2 == null then null
+ else if a1 == null then a2 else if a2 == null then a1
+ else a1 ++ a2;
+
+ # If dhcp is disabled but explicit interfaces are enabled,
+ # we need to provide dhcp just for those interfaces.
+ allowInterfaces = arrayAppendOrNull cfg.allowInterfaces
+ (if !config.networking.useDHCP && enableDHCP then
+ map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
+
# Config file adapted from the one that ships with dhcpcd.
dhcpcdConf = pkgs.writeText "dhcpcd.conf"
''
@@ -41,7 +55,7 @@ let
denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
# Use the list of allowed interfaces if specified
- ${optionalString (cfg.allowInterfaces != null) "allowinterfaces ${toString cfg.allowInterfaces}"}
+ ${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"}
${cfg.extraConfig}
'';
@@ -132,7 +146,7 @@ in
###### implementation
- config = mkIf config.networking.useDHCP {
+ config = mkIf enableDHCP {
systemd.services.dhcpcd =
{ description = "DHCP Client";
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 5c68dd89fb12..fbb211911f1c 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -82,7 +82,7 @@ in
systemd.services.dnsmasq = {
description = "dnsmasq daemon";
- after = [ "network.target" ];
+ after = [ "network.target" "systemd-resolved.conf" ];
wantedBy = [ "multi-user.target" ];
path = [ dnsmasq ];
preStart = ''
diff --git a/nixos/modules/services/networking/gogoclient.nix b/nixos/modules/services/networking/gogoclient.nix
index 416007941976..9d16f0efb435 100644
--- a/nixos/modules/services/networking/gogoclient.nix
+++ b/nixos/modules/services/networking/gogoclient.nix
@@ -76,8 +76,7 @@ in
exec ${pkgs.gogoclient}/bin/gogoc -y -f /var/lib/gogoc/gogoc.conf
'';
} // optionalAttrs cfg.autorun {
- wantedBy = [ "ip-up.target" ];
- partOf = [ "ip-up.target" ];
+ wantedBy = [ "multi-user.target" ];
};
};
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 39e83e7b4272..37bc1df2361b 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -52,6 +52,7 @@ let
#!/bin/sh
if test "$2" = "up"; then
${config.systemd.package}/bin/systemctl start ip-up.target
+ ${config.systemd.package}/bin/systemctl start network-online.target
fi
'';
diff --git a/nixos/modules/services/networking/ntpd.nix b/nixos/modules/services/networking/ntpd.nix
index 527882aad288..8f4bf26d411d 100644
--- a/nixos/modules/services/networking/ntpd.nix
+++ b/nixos/modules/services/networking/ntpd.nix
@@ -77,8 +77,7 @@ in
jobs.ntpd =
{ description = "NTP Daemon";
- wantedBy = [ "ip-up.target" ];
- partOf = [ "ip-up.target" ];
+ wantedBy = [ "multi-user.target" ];
path = [ ntp ];
diff --git a/nixos/modules/services/networking/openntpd.nix b/nixos/modules/services/networking/openntpd.nix
index bd8a7a04a2af..2f9031481d1d 100644
--- a/nixos/modules/services/networking/openntpd.nix
+++ b/nixos/modules/services/networking/openntpd.nix
@@ -41,8 +41,7 @@ in
systemd.services.openntpd = {
description = "OpenNTP Server";
- wantedBy = [ "ip-up.target" ];
- partOf = [ "ip-up.target" ];
+ wantedBy = [ "multi-user.target" ];
serviceConfig.ExecStart = "${package}/sbin/ntpd -d -f ${cfgFile}";
};
};
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 6fff776f8581..3762bda94a5c 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -141,8 +141,6 @@ fi
# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
if [ -n "@useHostResolvConf@" -a -e /etc/resolv.conf ]; then
cat /etc/resolv.conf | resolvconf -m 1000 -a host
-else
- touch /etc/resolv.conf
fi
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 07f3cb9e952c..20851c626d75 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -4,15 +4,184 @@ with lib;
let
- checkService = v:
- let assertValueOneOf = name: values: attr:
- let val = attr.${name};
- in optional (attr ? ${name} && !elem val values) "Systemd service field `${name}' cannot have value `${val}'.";
- checkType = assertValueOneOf "Type" ["simple" "forking" "oneshot" "dbus" "notify" "idle"];
- checkRestart = assertValueOneOf "Restart" ["no" "on-success" "on-failure" "on-abort" "always"];
- errors = concatMap (c: c v) [checkType checkRestart];
- in if errors == [] then true
- else builtins.trace (concatStringsSep "\n" errors) false;
+ boolValues = [true false "yes" "no"];
+
+ assertValueOneOf = name: values: group: attr:
+ optional (attr ? ${name} && !elem attr.${name} values)
+ "Systemd ${group} field `${name}' cannot have value `${attr.${name}}'.";
+
+ assertHasField = name: group: attr:
+ optional (!(attr ? ${name}))
+ "Systemd ${group} field `${name}' must exist.";
+
+ assertOnlyFields = fields: group: attr:
+ let badFields = filter (name: ! elem name fields) (attrNames attr); in
+ optional (badFields != [ ])
+ "Systemd ${group} has extra fields [${concatStringsSep " " badFields}].";
+
+ assertRange = name: min: max: group: attr:
+ optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
+ "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
+
+ digits = map toString (range 0 9);
+
+ isByteFormat = s:
+ let
+ l = reverseList (stringToCharacters s);
+ suffix = head l;
+ nums = tail l;
+ in elem suffix (["K" "M" "G" "T"] ++ digits)
+ && all (num: elem num digits) nums;
+
+ assertByteFormat = name: group: attr:
+ optional (attr ? ${name} && ! isByteFormat attr.${name})
+ "Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT].";
+
+ hexChars = stringToCharacters "0123456789abcdefABCDEF";
+
+ isMacAddress = s: stringLength s == 17
+ && flip all (splitString ":" s) (bytes:
+ all (byte: elem byte hexChars) (stringToCharacters bytes)
+ );
+
+ assertMacAddress = name: group: attr:
+ optional (attr ? ${name} && ! isMacAddress attr.${name})
+ "Systemd ${group} field `${name}' must be a valid mac address.";
+
+ checkUnitConfig = group: checks: v:
+ let errors = concatMap (c: c group v) checks; in
+ if errors == [] then true
+ else builtins.trace (concatStringsSep "\n" errors) false;
+
+ checkService = checkUnitConfig "Service" [
+ (assertValueOneOf "Type" [
+ "simple" "forking" "oneshot" "dbus" "notify" "idle"
+ ])
+ (assertValueOneOf "Restart" [
+ "no" "on-success" "on-failure" "on-abort" "always"
+ ])
+ ];
+
+ checkLink = checkUnitConfig "Link" [
+ (assertOnlyFields [
+ "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name"
+ "MTUBytes" "BitsPerSecond" "Duplex" "WakeOnLan"
+ ])
+ (assertValueOneOf "MACAddressPolicy" ["persistent" "random"])
+ (assertMacAddress "MACAddress")
+ (assertValueOneOf "NamePolicy" [
+ "kernel" "database" "onboard" "slot" "path" "mac"
+ ])
+ (assertByteFormat "MTUBytes")
+ (assertByteFormat "BitsPerSecond")
+ (assertValueOneOf "Duplex" ["half" "full"])
+ (assertValueOneOf "WakeOnLan" ["phy" "magic" "off"])
+ ];
+
+ checkNetdev = checkUnitConfig "Netdev" [
+ (assertOnlyFields [
+ "Description" "Name" "Kind" "MTUBytes" "MACAddress"
+ ])
+ (assertHasField "Name")
+ (assertHasField "Kind")
+ (assertValueOneOf "Kind" [
+ "bridge" "bond" "vlan" "macvlan" "vxlan" "ipip"
+ "gre" "sit" "vti" "veth" "tun" "tap" "dummy"
+ ])
+ (assertByteFormat "MTUBytes")
+ (assertMacAddress "MACAddress")
+ ];
+
+ checkVlan = checkUnitConfig "VLAN" [
+ (assertOnlyFields ["Id"])
+ (assertRange "Id" 0 4094)
+ ];
+
+ checkMacvlan = checkUnitConfig "MACVLAN" [
+ (assertOnlyFields ["Mode"])
+ (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"])
+ ];
+
+ checkVxlan = checkUnitConfig "VXLAN" [
+ (assertOnlyFields ["Id" "Group" "TOS" "TTL" "MacLearning"])
+ (assertRange "TTL" 0 255)
+ (assertValueOneOf "MacLearning" boolValues)
+ ];
+
+ checkTunnel = checkUnitConfig "Tunnel" [
+ (assertOnlyFields ["Local" "Remote" "TOS" "TTL" "DiscoverPathMTU"])
+ (assertRange "TTL" 0 255)
+ (assertValueOneOf "DiscoverPathMTU" boolValues)
+ ];
+
+ checkPeer = checkUnitConfig "Peer" [
+ (assertOnlyFields ["Name" "MACAddress"])
+ (assertMacAddress "MACAddress")
+ ];
+
+ tunTapChecks = [
+ (assertOnlyFields ["OneQueue" "MultiQueue" "PacketInfo" "User" "Group"])
+ (assertValueOneOf "OneQueue" boolValues)
+ (assertValueOneOf "MultiQueue" boolValues)
+ (assertValueOneOf "PacketInfo" boolValues)
+ ];
+
+ checkTun = checkUnitConfig "Tun" tunTapChecks;
+
+ checkTap = checkUnitConfig "Tap" tunTapChecks;
+
+ checkBond = checkUnitConfig "Bond" [
+ (assertOnlyFields [
+ "Mode" "TransmitHashPolicy" "LACPTransmitRate" "MIIMonitorSec"
+ "UpDelaySec" "DownDelaySec"
+ ])
+ (assertValueOneOf "Mode" [
+ "balance-rr" "active-backup" "balance-xor"
+ "broadcast" "802.3ad" "balance-tlb" "balance-alb"
+ ])
+ (assertValueOneOf "TransmitHashPolicy" [
+ "layer2" "layer3+4" "layer2+3" "encap2+3" "802.3ad" "encap3+4"
+ ])
+ (assertValueOneOf "LACPTransmitRate" ["slow" "fast"])
+ ];
+
+ checkNetwork = checkUnitConfig "Network" [
+ (assertOnlyFields [
+ "Description" "DHCP" "DHCPServer" "IPv4LL" "IPv4LLRoute"
+ "LLMNR" "Domains" "Bridge" "Bond"
+ ])
+ (assertValueOneOf "DHCP" ["both" "none" "v4" "v6"])
+ (assertValueOneOf "DHCPServer" boolValues)
+ (assertValueOneOf "IPv4LL" boolValues)
+ (assertValueOneOf "IPv4LLRoute" boolValues)
+ (assertValueOneOf "LLMNR" boolValues)
+ ];
+
+ checkAddress = checkUnitConfig "Address" [
+ (assertOnlyFields ["Address" "Peer" "Broadcast" "Label"])
+ (assertHasField "Address")
+ ];
+
+ checkRoute = checkUnitConfig "Route" [
+ (assertOnlyFields ["Gateway" "Destination" "Metric"])
+ (assertHasField "Gateway")
+ ];
+
+ checkDhcp = checkUnitConfig "DHCP" [
+ (assertOnlyFields [
+ "UseDNS" "UseMTU" "SendHostname" "UseHostname" "UseDomains" "UseRoutes"
+ "CriticalConnections" "VendorClassIdentifier" "RequestBroadcast"
+ "RouteMetric"
+ ])
+ (assertValueOneOf "UseDNS" boolValues)
+ (assertValueOneOf "UseMTU" boolValues)
+ (assertValueOneOf "SendHostname" boolValues)
+ (assertValueOneOf "UseHostname" boolValues)
+ (assertValueOneOf "UseDomains" boolValues)
+ (assertValueOneOf "UseRoutes" boolValues)
+ (assertValueOneOf "CriticalConnections" boolValues)
+ (assertValueOneOf "RequestBroadcast" boolValues)
+ ];
unitOption = mkOptionType {
name = "systemd option";
@@ -140,6 +309,15 @@ in rec {
'';
};
+ requisite = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Similar to requires. However if the units listed are not started,
+ they will not be started and the transaction will fail.
+ '';
+ };
+
unitConfig = mkOption {
default = {};
example = { RequiresMountsFor = "/data"; };
@@ -441,4 +619,345 @@ in rec {
targetOptions = commonUnitOptions;
+ commonNetworkOptions = {
+
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ If set to false, this unit will be a symlink to
+ /dev/null.
+ '';
+ };
+
+ matchConfig = mkOption {
+ default = {};
+ example = { Name = "eth0"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Match] section of the unit. See
+ systemd.link5
+ systemd.netdev5
+ systemd.network5
+ for details.
+ '';
+ };
+
+ };
+
+ linkOptions = commonNetworkOptions // {
+
+ linkConfig = mkOption {
+ default = {};
+ example = { MACAddress = "00:ff:ee:aa:cc:dd"; };
+ type = types.addCheck (types.attrsOf unitOption) checkLink;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Link] section of the unit. See
+ systemd.link
+ 5 for details.
+ '';
+ };
+
+ };
+
+ netdevOptions = commonNetworkOptions // {
+
+ netdevConfig = mkOption {
+ default = {};
+ example = { Name = "mybridge"; Kind = "bridge"; };
+ type = types.addCheck (types.attrsOf unitOption) checkNetdev;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Netdev] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ vlanConfig = mkOption {
+ default = {};
+ example = { Id = "4"; };
+ type = types.addCheck (types.attrsOf unitOption) checkVlan;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [VLAN] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ macvlanConfig = mkOption {
+ default = {};
+ example = { Mode = "private"; };
+ type = types.addCheck (types.attrsOf unitOption) checkMacvlan;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [MACVLAN] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ vxlanConfig = mkOption {
+ default = {};
+ example = { Id = "4"; };
+ type = types.addCheck (types.attrsOf unitOption) checkVxlan;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [VXLAN] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ tunnelConfig = mkOption {
+ default = {};
+ example = { Remote = "192.168.1.1"; };
+ type = types.addCheck (types.attrsOf unitOption) checkTunnel;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Tunnel] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ peerConfig = mkOption {
+ default = {};
+ example = { Name = "veth2"; };
+ type = types.addCheck (types.attrsOf unitOption) checkPeer;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Peer] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ tunConfig = mkOption {
+ default = {};
+ example = { User = "openvpn"; };
+ type = types.addCheck (types.attrsOf unitOption) checkTun;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Tun] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ tapConfig = mkOption {
+ default = {};
+ example = { User = "openvpn"; };
+ type = types.addCheck (types.attrsOf unitOption) checkTap;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Tap] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ bondConfig = mkOption {
+ default = {};
+ example = { Mode = "802.3ad"; };
+ type = types.addCheck (types.attrsOf unitOption) checkBond;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Bond] section of the unit. See
+ systemd.netdev
+ 5 for details.
+ '';
+ };
+
+ };
+
+ addressOptions = {
+
+ addressConfig = mkOption {
+ default = {};
+ example = { Address = "192.168.0.100/24"; };
+ type = types.addCheck (types.attrsOf unitOption) checkAddress;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Address] section of the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
+ };
+
+ routeOptions = {
+
+ routeConfig = mkOption {
+ default = {};
+ example = { Gateway = "192.168.0.1"; };
+ type = types.addCheck (types.attrsOf unitOption) checkRoute;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Route] section of the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
+ };
+
+ networkOptions = commonNetworkOptions // {
+
+ networkConfig = mkOption {
+ default = {};
+ example = { Description = "My Network"; };
+ type = types.addCheck (types.attrsOf unitOption) checkNetwork;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Network] section of the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
+ dhcpConfig = mkOption {
+ default = {};
+ example = { UseDNS = true; UseRoutes = true; };
+ type = types.addCheck (types.attrsOf unitOption) checkDhcp;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [DHCP] section of the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
+ 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;
+ description = ''
+ A list of addresses to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ gateway = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of gateways to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ dns = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of dns servers to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ ntp = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of ntp servers to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ vlan = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of vlan interfaces to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ macvlan = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of macvlan interfaces to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ vxlan = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of vxlan interfaces to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ tunnel = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of tunnel interfaces to be added to the network section of the
+ unit. See systemd.network
+ 5 for details.
+ '';
+ };
+
+ addresses = mkOption {
+ default = [ ];
+ type = types.listOf types.optionSet;
+ options = [ addressOptions ];
+ description = ''
+ A list of address sections to be added to the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
+ routes = mkOption {
+ default = [ ];
+ type = types.listOf types.optionSet;
+ options = [ routeOptions ];
+ description = ''
+ A list of route sections to be added to the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
+ };
+
}
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 8a86149a9e12..89029a098e96 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -96,6 +96,12 @@ let
"systemd-modules-load.service"
"kmod-static-nodes.service"
+ # Networking
+ "systemd-networkd.service"
+ "systemd-networkd-wait-online.service"
+ "systemd-resolved.service"
+ "systemd-timesyncd.service"
+
# Filesystems.
"systemd-fsck@.service"
"systemd-fsck-root.service"
@@ -212,6 +218,8 @@ let
{ PartOf = toString config.partOf; }
// optionalAttrs (config.conflicts != [])
{ Conflicts = toString config.conflicts; }
+ // optionalAttrs (config.requisite != [])
+ { Requisite = toString config.requisite; }
// optionalAttrs (config.restartTriggers != [])
{ X-Restart-Triggers = toString config.restartTriggers; }
// optionalAttrs (config.description != "") {
@@ -292,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"
@@ -384,6 +405,103 @@ let
'';
};
+ commonMatchText = def: ''
+ [Match]
+ ${attrsToSection def.matchConfig}
+ '';
+
+ linkToUnit = name: def:
+ { inherit (def) enable;
+ text = commonMatchText def +
+ ''
+ [Link]
+ ${attrsToSection def.linkConfig}
+ '';
+ };
+
+ netdevToUnit = name: def:
+ { inherit (def) enable;
+ text = commonMatchText def +
+ ''
+ [NetDev]
+ ${attrsToSection def.netdevConfig}
+
+ ${optionalString (def.vlanConfig != { }) ''
+ [VLAN]
+ ${attrsToSection def.vlanConfig}
+
+ ''}
+ ${optionalString (def.macvlanConfig != { }) ''
+ [MACVLAN]
+ ${attrsToSection def.macvlanConfig}
+
+ ''}
+ ${optionalString (def.vxlanConfig != { }) ''
+ [VXLAN]
+ ${attrsToSection def.vxlanConfig}
+
+ ''}
+ ${optionalString (def.tunnelConfig != { }) ''
+ [Tunnel]
+ ${attrsToSection def.tunnelConfig}
+
+ ''}
+ ${optionalString (def.peerConfig != { }) ''
+ [Peer]
+ ${attrsToSection def.peerConfig}
+
+ ''}
+ ${optionalString (def.tunConfig != { }) ''
+ [Tun]
+ ${attrsToSection def.tunConfig}
+
+ ''}
+ ${optionalString (def.tapConfig != { }) ''
+ [Tap]
+ ${attrsToSection def.tapConfig}
+
+ ''}
+ ${optionalString (def.bondConfig != { }) ''
+ [Bond]
+ ${attrsToSection def.bondConfig}
+
+ ''}
+ '';
+ };
+
+ networkToUnit = name: def:
+ { inherit (def) enable;
+ text = commonMatchText def +
+ ''
+ [Network]
+ ${attrsToSection def.networkConfig}
+ ${concatStringsSep "\n" (map (s: "Address=${s}") def.address)}
+ ${concatStringsSep "\n" (map (s: "Gateway=${s}") def.gateway)}
+ ${concatStringsSep "\n" (map (s: "DNS=${s}") def.dns)}
+ ${concatStringsSep "\n" (map (s: "NTP=${s}") def.ntp)}
+ ${concatStringsSep "\n" (map (s: "VLAN=${s}") def.vlan)}
+ ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)}
+ ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)}
+ ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
+
+ ${optionalString (def.dhcpConfig != { }) ''
+ [DHCP]
+ ${attrsToSection def.dhcpConfig}
+
+ ''}
+ ${flip concatMapStrings def.addresses (x: ''
+ [Address]
+ ${attrsToSection x.addressConfig}
+
+ '')}
+ ${flip concatMapStrings def.routes (x: ''
+ [Route]
+ ${attrsToSection x.routeConfig}
+
+ '')}
+ '';
+ };
+
generateUnits = type: units: upstreamUnits: upstreamWants:
pkgs.runCommand "${type}-units" { preferLocalBuild = true; } ''
mkdir -p $out
@@ -468,8 +586,9 @@ let
mkdir -p $out/getty.target.wants/
ln -s ../autovt@tty1.service $out/getty.target.wants/
- ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \
- ../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/
+ ln -s ../local-fs.target ../remote-fs.target ../network.target \
+ ../nss-lookup.target ../nss-user-lookup.target ../swap.target \
+ $out/multi-user.target.wants/
''}
''; # */
@@ -562,6 +681,47 @@ in
'';
};
+ systemd.network.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable networkd or not.
+ '';
+ };
+
+ systemd.network.links = mkOption {
+ default = {};
+ type = types.attrsOf types.optionSet;
+ options = [ linkOptions ];
+ description = "Definiton of systemd network links.";
+ };
+
+ systemd.network.netdevs = mkOption {
+ default = {};
+ type = types.attrsOf types.optionSet;
+ options = [ netdevOptions ];
+ description = "Definiton of systemd network devices.";
+ };
+
+ systemd.network.networks = mkOption {
+ default = {};
+ type = types.attrsOf types.optionSet;
+ options = [ networkOptions networkConfig ];
+ description = "Definiton of systemd networks.";
+ };
+
+ systemd.network.units = mkOption {
+ description = "Definition of networkd units.";
+ default = {};
+ type = types.attrsOf types.optionSet;
+ options = { name, config, ... }:
+ { options = concreteUnitOptions;
+ config = {
+ unit = mkDefault (makeUnit name config);
+ };
+ };
+ };
+
systemd.defaultUnit = mkOption {
default = "multi-user.target";
type = types.str;
@@ -645,6 +805,22 @@ in
'';
};
+ services.resolved.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enables the systemd dns resolver daemon.
+ '';
+ };
+
+ services.timesyncd.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enables the systemd ntp client daemon.
+ '';
+ };
+
systemd.tmpfiles.rules = mkOption {
type = types.listOf types.str;
default = [];
@@ -701,7 +877,7 @@ in
###### implementation
- config = {
+ config = mkMerge [ {
warnings = concatLists (mapAttrsToList (name: service:
optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no")
@@ -714,6 +890,9 @@ in
environment.etc."systemd/system".source =
generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants;
+ environment.etc."systemd/network".source =
+ generateUnits "network" cfg.network.units [] [];
+
environment.etc."systemd/user".source =
generateUnits "user" cfg.user.units upstreamUserUnits [];
@@ -766,6 +945,8 @@ in
unitConfig.X-StopOnReconfiguration = true;
};
+ systemd.targets.network-online.after = [ "ip-up.target" ];
+
systemd.units =
mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
// mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
@@ -779,6 +960,11 @@ in
(v: let n = escapeSystemdPath v.where;
in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
+ systemd.network.units =
+ mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.network.links
+ // mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.network.netdevs
+ // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.network.networks;
+
systemd.user.units =
mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
// mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.user.sockets;
@@ -833,5 +1019,65 @@ in
systemd.services.systemd-remount-fs.restartIfChanged = false;
systemd.services.systemd-journal-flush.restartIfChanged = false;
- };
+ }
+ (mkIf config.systemd.network.enable {
+ users.extraUsers.systemd-network.uid = config.ids.uids.systemd-network;
+ users.extraGroups.systemd-network.gid = config.ids.gids.systemd-network;
+
+ systemd.services.systemd-networkd = {
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."systemd/network".source ];
+ };
+
+ systemd.services.systemd-networkd-wait-online = {
+ before = [ "network-online.target" "ip-up.target" ];
+ wantedBy = [ "network-online.target" "ip-up.target" ];
+ };
+
+ systemd.services."systemd-network-wait-online@" = {
+ description = "Wait for Network Interface %I to be Configured";
+ conflicts = [ "shutdown.target" ];
+ requisite = [ "systemd-networkd.service" ];
+ after = [ "systemd-networkd.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I";
+ };
+ };
+
+ services.resolved.enable = mkDefault true;
+ services.timesyncd.enable = mkDefault config.services.ntp.enable;
+ })
+ (mkIf config.services.resolved.enable {
+ users.extraUsers.systemd-resolve.uid = config.ids.uids.systemd-resolve;
+ users.extraGroups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
+
+ systemd.services.systemd-resolved = {
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
+ };
+
+ environment.etc."systemd/resolved.conf".text = ''
+ [Resolve]
+ DNS=${concatStringsSep " " config.networking.nameservers}
+ '';
+ })
+ (mkIf config.services.timesyncd.enable {
+ users.extraUsers.systemd-timesync.uid = config.ids.uids.systemd-timesync;
+ users.extraGroups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
+
+ systemd.services.systemd-timesyncd = {
+ wantedBy = [ "sysinit.target" ];
+ restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
+ };
+
+ environment.etc."systemd/timesyncd.conf".text = ''
+ [Time]
+ NTP=${concatStringsSep " " config.services.ntp.servers}
+ '';
+
+ systemd.services.ntpd.enable = false;
+ })
+ ];
}
diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix
index 16752ce7e1b5..f7f2d9658069 100644
--- a/nixos/modules/tasks/filesystems/nfs.nix
+++ b/nixos/modules/tasks/filesystems/nfs.nix
@@ -73,8 +73,7 @@ in
path = [ pkgs.nfsUtils pkgs.sysvtools pkgs.utillinux ];
- wantedBy = [ "network-online.target" "multi-user.target" ];
- before = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
requires = [ "basic.target" "rpcbind.service" ];
after = [ "basic.target" "rpcbind.service" "network.target" ];
@@ -100,8 +99,7 @@ in
path = [ pkgs.sysvtools pkgs.utillinux ];
- wantedBy = [ "network-online.target" "multi-user.target" ];
- before = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
requires = [ "rpcbind.service" ];
after = [ "rpcbind.service" ];
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
new file mode 100644
index 000000000000..30fcb3a80104
--- /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;
+ };
+
+ destroyBond = i: ''
+ while true; do
+ UPDATED=1
+ SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
+ for I in $SLAVES; do
+ UPDATED=0
+ ip link set "$I" nomaster
+ done
+ [ "$UPDATED" -eq "1" ] && break
+ done
+ ip link set "${i}" down || true
+ ip link del "${i}" || true
+ '';
+
+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" ];
+ wantedBy = [ "network.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 <, create a job ‘network-addresses-.service"
+ # that performs static address configuration. It has a "wants"
+ # dependency on ‘.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.
+ configureAddrs = i:
+ let
+ ips = interfaceIps i;
+ in
+ nameValuePair "network-addresses-${i.name}"
+ { description = "Addresss configuration of ${i.name}";
+ wantedBy = [ "network-interfaces.target" ];
+ before = [ "network-interfaces.target" ];
+ bindsTo = [ (subsystemDevice i.name) ];
+ after = [ (subsystemDevice i.name) ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute ];
+ script =
+ ''
+ echo "bringing up interface..."
+ ip link set "${i.name}" up
+
+ restart_network_interfaces=false
+ '' + flip concatMapStrings (ips) (ip:
+ let
+ address = "${ip.address}/${toString ip.prefixLength}";
+ in
+ ''
+ echo "checking ip ${address}..."
+ 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
+ '')
+ + 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.iproute ];
+ script = ''
+ # Remove Dead Interfaces
+ echo "Removing old bridge ${n}..."
+ ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}"
+
+ echo "Adding bridge ${n}..."
+ ip link add name "${n}" type bridge
+
+ # Enslave child interfaces
+ ${flip concatMapStrings v.interfaces (i: ''
+ ip link set "${i}" master "${n}"
+ ip link set "${i}" up
+ '')}
+
+ ip link set "${n}" up
+ '';
+ postStop = ''
+ ip link set "${n}" down || true
+ ip link del "${n}" || true
+ '';
+ });
+
+ 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.iproute pkgs.gawk ];
+ script = ''
+ echo "Destroying old bond ${n}..."
+ ${destroyBond n}
+
+ echo "Creating new bond ${n}..."
+ ip link add name "${n}" type bond \
+ ${optionalString (v.mode != null) "mode ${toString v.mode}"} \
+ ${optionalString (v.miimon != null) "miimon ${toString v.miimon}"} \
+ ${optionalString (v.xmit_hash_policy != null) "xmit_hash_policy ${toString v.xmit_hash_policy}"} \
+ ${optionalString (v.lacp_rate != null) "lacp_rate ${toString v.lacp_rate}"}
+
+ # !!! There must be a better way to wait for the interface
+ while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
+
+ # Bring up the bond and enslave the specified interfaces
+ ip link set "${n}" up
+ ${flip concatMapStrings v.interfaces (i: ''
+ ip link set "${i}" master "${n}"
+ '')}
+ '';
+ postStop = destroyBond n;
+ });
+
+ createMacvlanDevice = 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 macvlan \
+ ${optionalString (v.mode != null) "mode ${v.mode}"}
+ ip link set "${n}" up
+ '';
+ postStop = ''
+ ip link delete "${n}"
+ '';
+ });
+
+ 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 configureAddrs interfaces ++
+ map createTunDevice (filter (i: i.virtual) interfaces))
+ // mapAttrs' createBridgeDevice cfg.bridges
+ // mapAttrs' createBondDevice cfg.bonds
+ // mapAttrs' createMacvlanDevice cfg.macvlans
+ // 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 000000000000..334b24b5ad38
--- /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.";
+ } ];
+
+ 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: {
+ 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.macvlans (name: macvlan: {
+ netdevs."40-${name}" = {
+ netdevConfig = {
+ Name = name;
+ Kind = "macvlan";
+ };
+ macvlanConfig.Mode = macvlan.mode;
+ };
+ networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+ macvlan = [ 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 9579eaa77d06..0ee2c9d2d00d 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -45,6 +45,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 +213,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 +236,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 +279,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.
'';
@@ -414,6 +428,37 @@ in
};
};
+ networking.macvlans = mkOption {
+ type = types.attrsOf types.optionSet;
+ default = { };
+ example = {
+ wan = {
+ interface = "enp2s0";
+ mode = "vepa";
+ };
+ };
+ description = ''
+ This option allows you to define macvlan interfaces which should
+ be automatically created.
+ '';
+ options = {
+
+ interface = mkOption {
+ example = "enp4s0";
+ type = types.string;
+ description = "The interface the macvlan will transmit packets through.";
+ };
+
+ mode = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "vepa";
+ description = "The mode of the macvlan device.";
+ };
+
+ };
+ };
+
networking.sits = mkOption {
type = types.attrsOf types.optionSet;
default = { };
@@ -523,6 +568,16 @@ 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. Note that this option is experimental,
+ enable at your own risk.
+ '';
+ };
+
};
@@ -552,345 +607,18 @@ 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.net.ipv4.conf.all.promote_secondaries" = true;
+ "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" ];
- wantedBy = [ "network.target" ];
-
- unitConfig.ConditionCapability = "CAP_NET_ADMIN";
-
- 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 < /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 , create a job ‘-cfg.service"
- # that performs static configuration. It has a "wants"
- # dependency on ‘.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 +627,7 @@ in
hostname "${cfg.hostName}"
'';
system.activationScripts.domain =
- optionalString (cfg.domain != "") ''
+ optionalString (cfg.domain != null) ''
domainname "${cfg.domain}"
'';
@@ -918,11 +646,54 @@ 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
+ ];
+ systemd.services = {
+ network-local-commands = {
+ description = "Extra networking commands.";
+ before = [ "network.target" ];
+ wantedBy = [ "network.target" ];
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+ path = [ pkgs.iproute ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ script = ''
+ # Run any user-specified commands.
+ ${cfg.localCommands}
+ '';
+ };
+ } // (listToAttrs (flip map interfaces (i:
+ nameValuePair "network-link-${i.name}"
+ { description = "Link configuration of ${i.name}";
+ wantedBy = [ "network-interfaces.target" ];
+ before = [ "network-interfaces.target" ];
+ bindsTo = [ (subsystemDevice i.name) ];
+ after = [ (subsystemDevice i.name) ];
+ path = [ pkgs.iproute ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script =
+ ''
+ echo "Configuring link..."
+ '' + 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}"
+ '';
+ })));
};
}
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 5173c33cab71..e850c1f6cddb 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -66,6 +66,14 @@ in rec {
(all nixos.tests.misc)
(all nixos.tests.nat.firewall)
(all nixos.tests.nat.standalone)
+ (all nixos.tests.networking.scripted.static)
+ (all nixos.tests.networking.scripted.dhcpSimple)
+ (all nixos.tests.networking.scripted.dhcpOneIf)
+ (all nixos.tests.networking.scripted.bond)
+ (all nixos.tests.networking.scripted.bridge)
+ (all nixos.tests.networking.scripted.macvlan)
+ (all nixos.tests.networking.scripted.sit)
+ (all nixos.tests.networking.scripted.vlan)
(all nixos.tests.nfs3)
(all nixos.tests.openssh)
(all nixos.tests.printing)
diff --git a/nixos/release.nix b/nixos/release.nix
index 7e2bfdb850b4..f3fe109b58ee 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -269,6 +269,22 @@ in rec {
tests.mysqlReplication = callTest tests/mysql-replication.nix {};
tests.nat.firewall = callTest tests/nat.nix { withFirewall = true; };
tests.nat.standalone = callTest tests/nat.nix { withFirewall = false; };
+ tests.networking.networkd.static = callTest tests/networking.nix { networkd = true; test = "static"; };
+ tests.networking.networkd.dhcpSimple = callTest tests/networking.nix { networkd = true; test = "dhcpSimple"; };
+ tests.networking.networkd.dhcpOneIf = callTest tests/networking.nix { networkd = true; test = "dhcpOneIf"; };
+ tests.networking.networkd.bond = callTest tests/networking.nix { networkd = true; test = "bond"; };
+ tests.networking.networkd.bridge = callTest tests/networking.nix { networkd = true; test = "bridge"; };
+ tests.networking.networkd.macvlan = callTest tests/networking.nix { networkd = true; test = "macvlan"; };
+ tests.networking.networkd.sit = callTest tests/networking.nix { networkd = true; test = "sit"; };
+ tests.networking.networkd.vlan = callTest tests/networking.nix { networkd = true; test = "vlan"; };
+ tests.networking.scripted.static = callTest tests/networking.nix { networkd = false; test = "static"; };
+ tests.networking.scripted.dhcpSimple = callTest tests/networking.nix { networkd = false; test = "dhcpSimple"; };
+ tests.networking.scripted.dhcpOneIf = callTest tests/networking.nix { networkd = false; test = "dhcpOneIf"; };
+ tests.networking.scripted.bond = callTest tests/networking.nix { networkd = false; test = "bond"; };
+ tests.networking.scripted.bridge = callTest tests/networking.nix { networkd = false; test = "bridge"; };
+ tests.networking.scripted.macvlan = callTest tests/networking.nix { networkd = false; test = "macvlan"; };
+ tests.networking.scripted.sit = callTest tests/networking.nix { networkd = false; test = "sit"; };
+ tests.networking.scripted.vlan = callTest tests/networking.nix { networkd = false; test = "vlan"; };
tests.nfs3 = callTest tests/nfs.nix { version = 3; };
tests.nsd = callTest tests/nsd.nix {};
tests.openssh = callTest tests/openssh.nix {};
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
new file mode 100644
index 000000000000..b5e09cc3950b
--- /dev/null
+++ b/nixos/tests/networking.nix
@@ -0,0 +1,365 @@
+import ./make-test.nix ({ networkd, test, ... }:
+ let
+ router = { config, pkgs, ... }:
+ with pkgs.lib;
+ let
+ vlanIfs = range 1 (length config.virtualisation.vlans);
+ in {
+ virtualisation.vlans = [ 1 2 3 ];
+ networking = {
+ useDHCP = false;
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
+ nameValuePair "eth${toString n}" {
+ ipAddress = "192.168.${toString n}.1";
+ prefixLength = 24;
+ })));
+ };
+ services.dhcpd = {
+ enable = true;
+ interfaces = map (n: "eth${toString n}") vlanIfs;
+ extraConfig = ''
+ option subnet-mask 255.255.255.0;
+ '' + flip concatMapStrings vlanIfs (n: ''
+ subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
+ option broadcast-address 192.168.${toString n}.255;
+ option routers 192.168.${toString n}.1;
+ range 192.168.${toString n}.2 192.168.${toString n}.254;
+ }
+ '');
+ };
+ };
+ testCases = {
+ static = {
+ name = "Static";
+ nodes.router = router;
+ nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 2 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = false;
+ defaultGateway = "192.168.1.1";
+ interfaces.eth1.ip4 = mkOverride 0 [
+ { address = "192.168.1.2"; prefixLength = 24; }
+ { address = "192.168.1.3"; prefixLength = 32; }
+ { address = "192.168.1.10"; prefixLength = 32; }
+ ];
+ interfaces.eth2.ip4 = mkOverride 0 [
+ { address = "192.168.2.2"; prefixLength = 24; }
+ ];
+ };
+ };
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client->waitForUnit("network.target");
+ $router->waitForUnit("network.target");
+
+ # Make sure dhcpcd is not started
+ $client->fail("systemctl status dhcpcd.service");
+
+ # Test vlan 1
+ $client->succeed("ping -c 1 192.168.1.1");
+ $client->succeed("ping -c 1 192.168.1.2");
+ $client->succeed("ping -c 1 192.168.1.3");
+ $client->succeed("ping -c 1 192.168.1.10");
+
+ $router->succeed("ping -c 1 192.168.1.1");
+ $router->succeed("ping -c 1 192.168.1.2");
+ $router->succeed("ping -c 1 192.168.1.3");
+ $router->succeed("ping -c 1 192.168.1.10");
+
+ # Test vlan 2
+ $client->succeed("ping -c 1 192.168.2.1");
+ $client->succeed("ping -c 1 192.168.2.2");
+
+ $router->succeed("ping -c 1 192.168.2.1");
+ $router->succeed("ping -c 1 192.168.2.2");
+
+ # Test default gateway
+ $router->succeed("ping -c 1 192.168.3.1");
+ $client->succeed("ping -c 1 192.168.3.1");
+ '';
+ };
+ dhcpSimple = {
+ name = "SimpleDHCP";
+ nodes.router = router;
+ nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 2 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = true;
+ interfaces.eth1.ip4 = mkOverride 0 [ ];
+ interfaces.eth2.ip4 = mkOverride 0 [ ];
+ };
+ };
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client->waitForUnit("network.target");
+ $router->waitForUnit("network.target");
+ $client->waitForUnit("dhcpcd.service");
+
+ # Wait until we have an ip address on each interface
+ $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done");
+ $client->succeed("while ! ip addr show dev eth2 | grep '192.168.2'; do true; done");
+
+ # Test vlan 1
+ $client->succeed("ping -c 1 192.168.1.1");
+ $client->succeed("ping -c 1 192.168.1.2");
+
+ $router->succeed("ping -c 1 192.168.1.1");
+ $router->succeed("ping -c 1 192.168.1.2");
+
+ # Test vlan 2
+ $client->succeed("ping -c 1 192.168.2.1");
+ $client->succeed("ping -c 1 192.168.2.2");
+
+ $router->succeed("ping -c 1 192.168.2.1");
+ $router->succeed("ping -c 1 192.168.2.2");
+ '';
+ };
+ dhcpOneIf = {
+ name = "OneInterfaceDHCP";
+ nodes.router = router;
+ nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 2 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = false;
+ interfaces.eth1 = {
+ ip4 = mkOverride 0 [ ];
+ useDHCP = true;
+ };
+ interfaces.eth2.ip4 = mkOverride 0 [ ];
+ };
+ };
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client->waitForUnit("network.target");
+ $router->waitForUnit("network.target");
+ $client->waitForUnit("dhcpcd.service");
+
+ # Wait until we have an ip address on each interface
+ $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done");
+
+ # Test vlan 1
+ $client->succeed("ping -c 1 192.168.1.1");
+ $client->succeed("ping -c 1 192.168.1.2");
+
+ $router->succeed("ping -c 1 192.168.1.1");
+ $router->succeed("ping -c 1 192.168.1.2");
+
+ # Test vlan 2
+ $client->succeed("ping -c 1 192.168.2.1");
+ $client->fail("ping -c 1 192.168.2.2");
+
+ $router->succeed("ping -c 1 192.168.2.1");
+ $router->fail("ping -c 1 192.168.2.2");
+ '';
+ };
+ bond = let
+ node = address: { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 2 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = false;
+ bonds.bond = {
+ mode = "balance-rr";
+ interfaces = [ "eth1" "eth2" ];
+ };
+ interfaces.bond.ip4 = mkOverride 0
+ [ { inherit address; prefixLength = 30; } ];
+ };
+ };
+ in {
+ name = "Bond";
+ nodes.client1 = node "192.168.1.1";
+ nodes.client2 = node "192.168.1.2";
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client1->waitForUnit("network.target");
+ $client2->waitForUnit("network.target");
+
+ # Test bonding
+ $client1->succeed("ping -c 2 192.168.1.1");
+ $client1->succeed("ping -c 2 192.168.1.2");
+
+ $client2->succeed("ping -c 2 192.168.1.1");
+ $client2->succeed("ping -c 2 192.168.1.2");
+ '';
+ };
+ bridge = let
+ node = { address, vlan }: { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ vlan ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = false;
+ interfaces.eth1.ip4 = mkOverride 0
+ [ { inherit address; prefixLength = 24; } ];
+ };
+ };
+ in {
+ name = "Bridge";
+ nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
+ nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
+ nodes.router = { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 2 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = false;
+ bridges.bridge.interfaces = [ "eth1" "eth2" ];
+ interfaces.eth1.ip4 = mkOverride 0 [ ];
+ interfaces.eth2.ip4 = mkOverride 0 [ ];
+ interfaces.bridge.ip4 = mkOverride 0
+ [ { address = "192.168.1.1"; prefixLength = 24; } ];
+ };
+ };
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client1->waitForUnit("network.target");
+ $client2->waitForUnit("network.target");
+ $router->waitForUnit("network.target");
+
+ # Test bridging
+ $client1->succeed("ping -c 1 192.168.1.1");
+ $client1->succeed("ping -c 1 192.168.1.2");
+ $client1->succeed("ping -c 1 192.168.1.3");
+
+ $client2->succeed("ping -c 1 192.168.1.1");
+ $client2->succeed("ping -c 1 192.168.1.2");
+ $client2->succeed("ping -c 1 192.168.1.3");
+
+ $router->succeed("ping -c 1 192.168.1.1");
+ $router->succeed("ping -c 1 192.168.1.2");
+ $router->succeed("ping -c 1 192.168.1.3");
+ '';
+ };
+ macvlan = {
+ name = "MACVLAN";
+ nodes.router = router;
+ nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = true;
+ macvlans.macvlan.interface = "eth1";
+ interfaces.eth1.ip4 = mkOverride 0 [ ];
+ };
+ };
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client->waitForUnit("network.target");
+ $router->waitForUnit("network.target");
+ $client->waitForUnit("dhcpcd.service");
+
+ # Wait until we have an ip address on each interface
+ $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done");
+ $client->succeed("while ! ip addr show dev macvlan | grep '192.168.1'; do true; done");
+
+ # Test macvlan
+ $client->succeed("ping -c 1 192.168.1.1");
+ $client->succeed("ping -c 1 192.168.1.2");
+ $client->succeed("ping -c 1 192.168.1.3");
+
+ $router->succeed("ping -c 1 192.168.1.1");
+ $router->succeed("ping -c 1 192.168.1.2");
+ $router->succeed("ping -c 1 192.168.1.3");
+ '';
+ };
+ sit = let
+ node = { address4, remote, address6 }: { config, pkgs, ... }: with pkgs.lib; {
+ virtualisation.vlans = [ 1 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.enable = false;
+ useDHCP = false;
+ sits.sit = {
+ inherit remote;
+ local = address4;
+ dev = "eth1";
+ };
+ interfaces.eth1.ip4 = mkOverride 0
+ [ { address = address4; prefixLength = 24; } ];
+ interfaces.sit.ip6 = mkOverride 0
+ [ { address = address6; prefixLength = 64; } ];
+ };
+ };
+ in {
+ name = "Sit";
+ nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
+ nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client1->waitForUnit("network.target");
+ $client2->waitForUnit("network.target");
+
+ $client1->succeed("ip addr >&2");
+ $client2->succeed("ip addr >&2");
+
+ # Test ipv6
+ $client1->succeed("ping6 -c 1 fc00::1");
+ $client1->succeed("ping6 -c 1 fc00::2");
+
+ $client2->succeed("ping6 -c 1 fc00::1");
+ $client2->succeed("ping6 -c 1 fc00::2");
+ '';
+ };
+ vlan = let
+ node = address: { config, pkgs, ... }: with pkgs.lib; {
+ #virtualisation.vlans = [ 1 ];
+ networking = {
+ useNetworkd = networkd;
+ firewall.allowPing = true;
+ useDHCP = false;
+ vlans.vlan = {
+ id = 1;
+ interface = "eth0";
+ };
+ interfaces.eth0.ip4 = mkOverride 0 [ ];
+ interfaces.eth1.ip4 = mkOverride 0 [ ];
+ interfaces.vlan.ip4 = mkOverride 0
+ [ { inherit address; prefixLength = 24; } ];
+ };
+ };
+ in {
+ name = "vlan";
+ nodes.client1 = node "192.168.1.1";
+ nodes.client2 = node "192.168.1.2";
+ testScript = { nodes, ... }:
+ ''
+ startAll;
+
+ $client1->waitForUnit("network.target");
+ $client2->waitForUnit("network.target");
+
+ # Test vlan is setup
+ $client1->succeed("ip addr show dev vlan >&2");
+ $client2->succeed("ip addr show dev vlan >&2");
+ '';
+ };
+ };
+ case = testCases.${test};
+ in case // {
+ name = "${case.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
+ })