Merge pull request #5043 from wkennington/master.networkd

nixos/networking: Revamp networking configuration and add an experimental networkd option.

+28 -1
nixos/modules/config/networking.nix
···
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
+
''}
+
'';
};
+2
nixos/modules/module-list.nix
···
./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
+2 -2
nixos/modules/services/networking/chrony.nix
···
jobs.chronyd =
{ description = "chrony daemon";
-
wantedBy = [ "ip-up.target" ];
-
partOf = [ "ip-up.target" ];
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
path = [ chrony ];
+17 -3
nixos/modules/services/networking/dhcpcd.nix
···
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"
···
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}
'';
···
###### implementation
-
config = mkIf config.networking.useDHCP {
+
config = mkIf enableDHCP {
systemd.services.dhcpcd =
{ description = "DHCP Client";
+1 -1
nixos/modules/services/networking/dnsmasq.nix
···
systemd.services.dnsmasq = {
description = "dnsmasq daemon";
-
after = [ "network.target" ];
+
after = [ "network.target" "systemd-resolved.conf" ];
wantedBy = [ "multi-user.target" ];
path = [ dnsmasq ];
preStart = ''
+1 -2
nixos/modules/services/networking/gogoclient.nix
···
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" ];
};
};
+1
nixos/modules/services/networking/networkmanager.nix
···
#!/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
'';
+1 -2
nixos/modules/services/networking/ntpd.nix
···
jobs.ntpd =
{ description = "NTP Daemon";
-
wantedBy = [ "ip-up.target" ];
-
partOf = [ "ip-up.target" ];
+
wantedBy = [ "multi-user.target" ];
path = [ ntp ];
+1 -2
nixos/modules/services/networking/openntpd.nix
···
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}";
};
};
-2
nixos/modules/system/boot/stage-2-init.sh
···
# 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
+528 -9
nixos/modules/system/boot/systemd-unit-options.nix
···
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";
···
description = ''
If the specified units are started, then this unit is stopped
and vice versa.
+
'';
+
};
+
+
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.
'';
};
···
};
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
+
<literal>[Match]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+
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
+
<literal>[Link]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.link</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Netdev]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[VLAN]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[MACVLAN]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[VXLAN]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Tunnel]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Peer]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Tun]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Tap]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Bond]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Address]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Route]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[Network]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<literal>[DHCP]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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 <citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> 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
+
<citerefentry><refentrytitle>systemd.network</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> for details.
+
'';
+
};
+
+
};
}
+250 -4
nixos/modules/system/boot/systemd.nix
···
"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"
···
{ 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 != "") {
···
};
};
+
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"
···
'';
};
+
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
···
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/
''}
''; # */
···
'';
};
+
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;
···
'';
};
+
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 = [];
···
###### implementation
-
config = {
+
config = mkMerge [ {
warnings = concatLists (mapAttrsToList (name: service:
optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no")
···
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 [];
···
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
···
(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;
···
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;
+
})
+
];
}
+2 -4
nixos/modules/tasks/filesystems/nfs.nix
···
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" ];
···
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" ];
+340
nixos/modules/tasks/network-interfaces-scripted.nix
···
+
{ 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 <<EOF
+
${optionalString (cfg.nameservers != [] && cfg.domain != null) ''
+
domain ${cfg.domain}
+
''}
+
${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
+
${flip concatMapStrings cfg.nameservers (ns: ''
+
nameserver ${ns}
+
'')}
+
EOF
+
'') + ''
+
# Set the default gateway.
+
${optionalString (cfg.defaultGateway != null) ''
+
# FIXME: get rid of "|| true" (necessary to make it idempotent).
+
ip route add default via "${cfg.defaultGateway}" ${
+
optionalString (cfg.defaultGatewayWindowSize != null)
+
"window ${cfg.defaultGatewayWindowSize}"} || true
+
''}
+
'';
+
};
+
+
# For each interface <foo>, create a job ‘network-addresses-<foo>.service"
+
# that performs static address 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.
+
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"
+
'';
+
+
};
+
+
}
+174
nixos/modules/tasks/network-interfaces-systemd.nix
···
+
{ 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);
+
+
};
+
+
}
+114 -343
nixos/modules/tasks/network-interfaces.nix
···
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 = [
···
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).
···
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.
'';
···
};
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.
'';
···
};
};
+
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 = { };
···
'';
};
+
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.
+
'';
+
};
+
};
···
# 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 <<EOF
-
${optionalString (cfg.nameservers != [] && cfg.domain != "") ''
-
domain ${cfg.domain}
-
''}
-
${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
-
${flip concatMapStrings cfg.nameservers (ns: ''
-
nameserver ${ns}
-
'')}
-
EOF
-
-
# Disable or enable IPv6.
-
${optionalString (!config.boot.isContainer) ''
-
if [ -e /proc/sys/net/ipv6/conf/all/disable_ipv6 ]; then
-
echo ${if cfg.enableIPv6 then "0" else "1"} > /proc/sys/net/ipv6/conf/all/disable_ipv6
-
fi
-
''}
-
-
# Set the default gateway.
-
${optionalString (cfg.defaultGateway != "") ''
-
# FIXME: get rid of "|| true" (necessary to make it idempotent).
-
ip route add default via "${cfg.defaultGateway}" ${
-
optionalString (cfg.defaultGatewayWindowSize != null)
-
"window ${cfg.defaultGatewayWindowSize}"} || true
-
''}
-
-
# Turn on forwarding if any interface has enabled proxy_arp.
-
${optionalString (any (i: i.proxyARP) interfaces) ''
-
echo 1 > /proc/sys/net/ipv4/ip_forward
-
''}
-
-
# Run any user-specified commands.
-
${cfg.localCommands}
-
'';
-
};
-
-
# For each interface <foo>, create a job ‘<foo>-cfg.service"
-
# that performs static configuration. It has a "wants"
-
# dependency on ‘<foo>.service’, which is supposed to create
-
# the interface and need not exist (i.e. for hardware
-
# interfaces). It has a binds-to dependency on the actual
-
# network device, so it only gets started after the interface
-
# has appeared, and it's stopped when the interface
-
# disappears.
-
configureInterface = i:
-
let
-
ips = i.ip4 ++ optionals cfg.enableIPv6 i.ip6
-
++ optional (i.ipAddress != null) {
-
address = i.ipAddress;
-
prefixLength = i.prefixLength;
-
} ++ optional (cfg.enableIPv6 && i.ipv6Address != null) {
-
address = i.ipv6Address;
-
prefixLength = i.ipv6PrefixLength;
-
};
-
in
-
nameValuePair "${i.name}-cfg"
-
{ description = "Configuration of ${i.name}";
-
wantedBy = [ "network-interfaces.target" ];
-
bindsTo = [ (subsystemDevice i.name) ];
-
after = [ (subsystemDevice i.name) ];
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
path = [ pkgs.iproute pkgs.gawk ];
-
script =
-
''
-
echo "bringing up interface..."
-
ip link set "${i.name}" up
-
''
-
+ optionalString (i.macAddress != null)
-
''
-
echo "setting MAC address to ${i.macAddress}..."
-
ip link set "${i.name}" address "${i.macAddress}"
-
''
-
+ optionalString (i.mtu != null)
-
''
-
echo "setting MTU to ${toString i.mtu}..."
-
ip link set "${i.name}" mtu "${toString i.mtu}"
-
''
-
-
# Ip Setup
-
+
-
''
-
curIps=$(ip -o a show dev "${i.name}" | awk '{print $4}')
-
# Only do an add if it's necessary. This is
-
# useful when the Nix store is accessed via this
-
# interface (e.g. in a QEMU VM test).
-
''
-
+ flip concatMapStrings (ips) (ip:
-
let
-
address = "${ip.address}/${toString ip.prefixLength}";
-
in
-
''
-
echo "checking ip ${address}..."
-
if ! echo "$curIps" | grep "${address}" >/dev/null 2>&1; then
-
if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then
-
echo "added ip ${address}..."
-
restart_network_setup=true
-
elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
-
echo "failed to add ${address}"
-
exit 1
-
fi
-
fi
-
'')
-
+ optionalString (ips != [ ])
-
''
-
if [ restart_network_setup = true ]; then
-
# Ensure that the default gateway remains set.
-
# (Flushing this interface may have removed it.)
-
${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service
-
fi
-
${config.systemd.package}/bin/systemctl start ip-up.target
-
''
-
+ optionalString i.proxyARP
-
''
-
echo 1 > /proc/sys/net/ipv4/conf/${i.name}/proxy_arp
-
''
-
+ optionalString (i.proxyARP && cfg.enableIPv6)
-
''
-
echo 1 > /proc/sys/net/ipv6/conf/${i.name}/proxy_ndp
-
'';
-
preStop =
-
''
-
echo "releasing configured ip's..."
-
''
-
+ flip concatMapStrings (ips) (ip:
-
let
-
address = "${ip.address}/${toString ip.prefixLength}";
-
in
-
''
-
echo -n "Deleting ${address}..."
-
ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed"
-
echo ""
-
'');
-
};
-
-
createTunDevice = i: nameValuePair "${i.name}-netdev"
-
{ description = "Virtual Network Interface ${i.name}";
-
requires = [ "dev-net-tun.device" ];
-
after = [ "dev-net-tun.device" ];
-
wantedBy = [ "network.target" (subsystemDevice i.name) ];
-
path = [ pkgs.iproute ];
-
serviceConfig = {
-
Type = "oneshot";
-
RemainAfterExit = true;
-
};
-
script = ''
-
ip tuntap add dev "${i.name}" \
-
${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \
-
user "${i.virtualOwner}"
-
'';
-
postStop = ''
-
ip link del ${i.name}
-
'';
-
};
-
-
createBridgeDevice = n: v: nameValuePair "${n}-netdev"
-
(let
-
deps = map subsystemDevice v.interfaces;
-
in
-
{ description = "Bridge Interface ${n}";
-
wantedBy = [ "network.target" (subsystemDevice n) ];
-
bindsTo = deps;
-
after = deps;
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
path = [ pkgs.bridge_utils pkgs.iproute ];
-
script =
-
''
-
# Remove Dead Interfaces
-
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
-
-
brctl addbr "${n}"
-
-
# Set bridge's hello time to 0 to avoid startup delays.
-
brctl setfd "${n}" 0
-
-
${flip concatMapStrings v.interfaces (i: ''
-
brctl addif "${n}" "${i}"
-
ip link set "${i}" up
-
ip addr flush dev "${i}"
-
-
echo "bringing up network device ${n}..."
-
ip link set "${n}" up
-
'')}
-
-
# !!! Should delete (brctl delif) any interfaces that
-
# no longer belong to the bridge.
-
'';
-
postStop =
-
''
-
ip link set "${n}" down
-
brctl delbr "${n}"
-
'';
-
});
-
-
createBondDevice = n: v: nameValuePair "${n}-netdev"
-
(let
-
deps = map subsystemDevice v.interfaces;
-
in
-
{ description = "Bond Interface ${n}";
-
wantedBy = [ "network.target" (subsystemDevice n) ];
-
bindsTo = deps;
-
after = deps;
-
before = [ "${n}-cfg.service" ];
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
path = [ pkgs.ifenslave pkgs.iproute ];
-
script = ''
-
ip link add name "${n}" type bond
-
-
# !!! There must be a better way to wait for the interface
-
while [ ! -d /sys/class/net/${n} ]; do sleep 0.1; done;
-
-
# Ensure the link is down so that we can set options
-
ip link set "${n}" down
-
-
# Set the miimon and mode options
-
${optionalString (v.miimon != null)
-
"echo \"${toString v.miimon}\" >/sys/class/net/${n}/bonding/miimon"}
-
${optionalString (v.mode != null)
-
"echo \"${v.mode}\" >/sys/class/net/${n}/bonding/mode"}
-
${optionalString (v.lacp_rate != null)
-
"echo \"${v.lacp_rate}\" >/sys/class/net/${n}/bonding/lacp_rate"}
-
${optionalString (v.xmit_hash_policy != null)
-
"echo \"${v.xmit_hash_policy}\" >/sys/class/net/${n}/bonding/xmit_hash_policy"}
-
-
# Bring up the bond and enslave the specified interfaces
-
ip link set "${n}" up
-
${flip concatMapStrings v.interfaces (i: ''
-
ifenslave "${n}" "${i}"
-
'')}
-
'';
-
postStop = ''
-
${flip concatMapStrings v.interfaces (i: ''
-
ifenslave -d "${n}" "${i}" >/dev/null 2>&1 || true
-
'')}
-
ip link set "${n}" down >/dev/null 2>&1 || true
-
ip link del "${n}" >/dev/null 2>&1 || true
-
'';
-
});
-
-
createSitDevice = n: v: nameValuePair "${n}-netdev"
-
(let
-
deps = optional (v.dev != null) (subsystemDevice v.dev);
-
in
-
{ description = "6-to-4 Tunnel Interface ${n}";
-
wantedBy = [ "network.target" (subsystemDevice n) ];
-
bindsTo = deps;
-
after = deps;
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
path = [ pkgs.iproute ];
-
script = ''
-
# Remove Dead Interfaces
-
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
-
ip link add name "${n}" type sit \
-
${optionalString (v.remote != null) "remote \"${v.remote}\""} \
-
${optionalString (v.local != null) "local \"${v.local}\""} \
-
${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
-
${optionalString (v.dev != null) "dev \"${v.dev}\""}
-
ip link set "${n}" up
-
'';
-
postStop = ''
-
ip link delete "${n}"
-
'';
-
});
-
-
createVlanDevice = n: v: nameValuePair "${n}-netdev"
-
(let
-
deps = [ (subsystemDevice v.interface) ];
-
in
-
{ description = "Vlan Interface ${n}";
-
wantedBy = [ "network.target" (subsystemDevice n) ];
-
bindsTo = deps;
-
after = deps;
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
path = [ pkgs.iproute ];
-
script = ''
-
# Remove Dead Interfaces
-
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
-
ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
-
ip link set "${n}" up
-
'';
-
postStop = ''
-
ip link delete "${n}"
-
'';
-
});
-
-
in listToAttrs (
-
map configureInterface interfaces ++
-
map createTunDevice (filter (i: i.virtual) interfaces))
-
// mapAttrs' createBridgeDevice cfg.bridges
-
// mapAttrs' createBondDevice cfg.bonds
-
// mapAttrs' createSitDevice cfg.sits
-
// mapAttrs' createVlanDevice cfg.vlans
-
// { "network-setup" = networkSetup; };
-
# Set the host and domain names in the activation script. Don't
# clear it if it's not configured in the NixOS configuration,
# since it may have been set by dhcpcd in the meantime.
···
hostname "${cfg.hostName}"
'';
system.activationScripts.domain =
-
optionalString (cfg.domain != "") ''
+
optionalString (cfg.domain != null) ''
domainname "${cfg.domain}"
'';
···
}
];
-
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}"
+
'';
+
})));
};
}
+8
nixos/release-combined.nix
···
(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)
+16
nixos/release.nix
···
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 {};
+365
nixos/tests/networking.nix
···
+
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"}";
+
})