mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-22 15:03:28 +00:00
Merge pull request #5043 from wkennington/master.networkd
nixos/networking: Revamp networking configuration and add an experimental networkd option.
This commit is contained in:
commit
bcfe7b2200
@ -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
|
||||
''}
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 ];
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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 = ''
|
||||
|
@ -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" ];
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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
|
||||
'';
|
||||
|
||||
|
@ -77,8 +77,7 @@ in
|
||||
jobs.ntpd =
|
||||
{ description = "NTP Daemon";
|
||||
|
||||
wantedBy = [ "ip-up.target" ];
|
||||
partOf = [ "ip-up.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
path = [ ntp ];
|
||||
|
||||
|
@ -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}";
|
||||
};
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
<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.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
})
|
||||
];
|
||||
}
|
||||
|
@ -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" ];
|
||||
|
||||
|
340
nixos/modules/tasks/network-interfaces-scripted.nix
Normal file
340
nixos/modules/tasks/network-interfaces-scripted.nix
Normal file
@ -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 <<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
Normal file
174
nixos/modules/tasks/network-interfaces-systemd.nix
Normal file
@ -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);
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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 <<EOF
|
||||
${optionalString (cfg.nameservers != [] && cfg.domain != "") ''
|
||||
domain ${cfg.domain}
|
||||
''}
|
||||
${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
|
||||
${flip concatMapStrings cfg.nameservers (ns: ''
|
||||
nameserver ${ns}
|
||||
'')}
|
||||
EOF
|
||||
|
||||
# Disable or enable IPv6.
|
||||
${optionalString (!config.boot.isContainer) ''
|
||||
if [ -e /proc/sys/net/ipv6/conf/all/disable_ipv6 ]; then
|
||||
echo ${if cfg.enableIPv6 then "0" else "1"} > /proc/sys/net/ipv6/conf/all/disable_ipv6
|
||||
fi
|
||||
''}
|
||||
|
||||
# Set the default gateway.
|
||||
${optionalString (cfg.defaultGateway != "") ''
|
||||
# FIXME: get rid of "|| true" (necessary to make it idempotent).
|
||||
ip route add default via "${cfg.defaultGateway}" ${
|
||||
optionalString (cfg.defaultGatewayWindowSize != null)
|
||||
"window ${cfg.defaultGatewayWindowSize}"} || true
|
||||
''}
|
||||
|
||||
# Turn on forwarding if any interface has enabled proxy_arp.
|
||||
${optionalString (any (i: i.proxyARP) interfaces) ''
|
||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
''}
|
||||
|
||||
# Run any user-specified commands.
|
||||
${cfg.localCommands}
|
||||
'';
|
||||
};
|
||||
|
||||
# For each interface <foo>, create a job ‘<foo>-cfg.service"
|
||||
# that performs static configuration. It has a "wants"
|
||||
# dependency on ‘<foo>.service’, which is supposed to create
|
||||
# the interface and need not exist (i.e. for hardware
|
||||
# interfaces). It has a binds-to dependency on the actual
|
||||
# network device, so it only gets started after the interface
|
||||
# has appeared, and it's stopped when the interface
|
||||
# disappears.
|
||||
configureInterface = i:
|
||||
let
|
||||
ips = i.ip4 ++ optionals cfg.enableIPv6 i.ip6
|
||||
++ optional (i.ipAddress != null) {
|
||||
address = i.ipAddress;
|
||||
prefixLength = i.prefixLength;
|
||||
} ++ optional (cfg.enableIPv6 && i.ipv6Address != null) {
|
||||
address = i.ipv6Address;
|
||||
prefixLength = i.ipv6PrefixLength;
|
||||
};
|
||||
in
|
||||
nameValuePair "${i.name}-cfg"
|
||||
{ description = "Configuration of ${i.name}";
|
||||
wantedBy = [ "network-interfaces.target" ];
|
||||
bindsTo = [ (subsystemDevice i.name) ];
|
||||
after = [ (subsystemDevice i.name) ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
path = [ pkgs.iproute pkgs.gawk ];
|
||||
script =
|
||||
''
|
||||
echo "bringing up interface..."
|
||||
ip link set "${i.name}" up
|
||||
''
|
||||
+ optionalString (i.macAddress != null)
|
||||
''
|
||||
echo "setting MAC address to ${i.macAddress}..."
|
||||
ip link set "${i.name}" address "${i.macAddress}"
|
||||
''
|
||||
+ optionalString (i.mtu != null)
|
||||
''
|
||||
echo "setting MTU to ${toString i.mtu}..."
|
||||
ip link set "${i.name}" mtu "${toString i.mtu}"
|
||||
''
|
||||
|
||||
# Ip Setup
|
||||
+
|
||||
''
|
||||
curIps=$(ip -o a show dev "${i.name}" | awk '{print $4}')
|
||||
# Only do an add if it's necessary. This is
|
||||
# useful when the Nix store is accessed via this
|
||||
# interface (e.g. in a QEMU VM test).
|
||||
''
|
||||
+ flip concatMapStrings (ips) (ip:
|
||||
let
|
||||
address = "${ip.address}/${toString ip.prefixLength}";
|
||||
in
|
||||
''
|
||||
echo "checking ip ${address}..."
|
||||
if ! echo "$curIps" | grep "${address}" >/dev/null 2>&1; then
|
||||
if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then
|
||||
echo "added ip ${address}..."
|
||||
restart_network_setup=true
|
||||
elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
|
||||
echo "failed to add ${address}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
'')
|
||||
+ optionalString (ips != [ ])
|
||||
''
|
||||
if [ restart_network_setup = true ]; then
|
||||
# Ensure that the default gateway remains set.
|
||||
# (Flushing this interface may have removed it.)
|
||||
${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service
|
||||
fi
|
||||
${config.systemd.package}/bin/systemctl start ip-up.target
|
||||
''
|
||||
+ optionalString i.proxyARP
|
||||
''
|
||||
echo 1 > /proc/sys/net/ipv4/conf/${i.name}/proxy_arp
|
||||
''
|
||||
+ optionalString (i.proxyARP && cfg.enableIPv6)
|
||||
''
|
||||
echo 1 > /proc/sys/net/ipv6/conf/${i.name}/proxy_ndp
|
||||
'';
|
||||
preStop =
|
||||
''
|
||||
echo "releasing configured ip's..."
|
||||
''
|
||||
+ flip concatMapStrings (ips) (ip:
|
||||
let
|
||||
address = "${ip.address}/${toString ip.prefixLength}";
|
||||
in
|
||||
''
|
||||
echo -n "Deleting ${address}..."
|
||||
ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed"
|
||||
echo ""
|
||||
'');
|
||||
};
|
||||
|
||||
createTunDevice = i: nameValuePair "${i.name}-netdev"
|
||||
{ description = "Virtual Network Interface ${i.name}";
|
||||
requires = [ "dev-net-tun.device" ];
|
||||
after = [ "dev-net-tun.device" ];
|
||||
wantedBy = [ "network.target" (subsystemDevice i.name) ];
|
||||
path = [ pkgs.iproute ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
ip tuntap add dev "${i.name}" \
|
||||
${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \
|
||||
user "${i.virtualOwner}"
|
||||
'';
|
||||
postStop = ''
|
||||
ip link del ${i.name}
|
||||
'';
|
||||
};
|
||||
|
||||
createBridgeDevice = n: v: nameValuePair "${n}-netdev"
|
||||
(let
|
||||
deps = map subsystemDevice v.interfaces;
|
||||
in
|
||||
{ description = "Bridge Interface ${n}";
|
||||
wantedBy = [ "network.target" (subsystemDevice n) ];
|
||||
bindsTo = deps;
|
||||
after = deps;
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
path = [ pkgs.bridge_utils pkgs.iproute ];
|
||||
script =
|
||||
''
|
||||
# Remove Dead Interfaces
|
||||
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
|
||||
|
||||
brctl addbr "${n}"
|
||||
|
||||
# Set bridge's hello time to 0 to avoid startup delays.
|
||||
brctl setfd "${n}" 0
|
||||
|
||||
${flip concatMapStrings v.interfaces (i: ''
|
||||
brctl addif "${n}" "${i}"
|
||||
ip link set "${i}" up
|
||||
ip addr flush dev "${i}"
|
||||
|
||||
echo "bringing up network device ${n}..."
|
||||
ip link set "${n}" up
|
||||
'')}
|
||||
|
||||
# !!! Should delete (brctl delif) any interfaces that
|
||||
# no longer belong to the bridge.
|
||||
'';
|
||||
postStop =
|
||||
''
|
||||
ip link set "${n}" down
|
||||
brctl delbr "${n}"
|
||||
'';
|
||||
});
|
||||
|
||||
createBondDevice = n: v: nameValuePair "${n}-netdev"
|
||||
(let
|
||||
deps = map subsystemDevice v.interfaces;
|
||||
in
|
||||
{ description = "Bond Interface ${n}";
|
||||
wantedBy = [ "network.target" (subsystemDevice n) ];
|
||||
bindsTo = deps;
|
||||
after = deps;
|
||||
before = [ "${n}-cfg.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
path = [ pkgs.ifenslave pkgs.iproute ];
|
||||
script = ''
|
||||
ip link add name "${n}" type bond
|
||||
|
||||
# !!! There must be a better way to wait for the interface
|
||||
while [ ! -d /sys/class/net/${n} ]; do sleep 0.1; done;
|
||||
|
||||
# Ensure the link is down so that we can set options
|
||||
ip link set "${n}" down
|
||||
|
||||
# Set the miimon and mode options
|
||||
${optionalString (v.miimon != null)
|
||||
"echo \"${toString v.miimon}\" >/sys/class/net/${n}/bonding/miimon"}
|
||||
${optionalString (v.mode != null)
|
||||
"echo \"${v.mode}\" >/sys/class/net/${n}/bonding/mode"}
|
||||
${optionalString (v.lacp_rate != null)
|
||||
"echo \"${v.lacp_rate}\" >/sys/class/net/${n}/bonding/lacp_rate"}
|
||||
${optionalString (v.xmit_hash_policy != null)
|
||||
"echo \"${v.xmit_hash_policy}\" >/sys/class/net/${n}/bonding/xmit_hash_policy"}
|
||||
|
||||
# Bring up the bond and enslave the specified interfaces
|
||||
ip link set "${n}" up
|
||||
${flip concatMapStrings v.interfaces (i: ''
|
||||
ifenslave "${n}" "${i}"
|
||||
'')}
|
||||
'';
|
||||
postStop = ''
|
||||
${flip concatMapStrings v.interfaces (i: ''
|
||||
ifenslave -d "${n}" "${i}" >/dev/null 2>&1 || true
|
||||
'')}
|
||||
ip link set "${n}" down >/dev/null 2>&1 || true
|
||||
ip link del "${n}" >/dev/null 2>&1 || true
|
||||
'';
|
||||
});
|
||||
|
||||
createSitDevice = n: v: nameValuePair "${n}-netdev"
|
||||
(let
|
||||
deps = optional (v.dev != null) (subsystemDevice v.dev);
|
||||
in
|
||||
{ description = "6-to-4 Tunnel Interface ${n}";
|
||||
wantedBy = [ "network.target" (subsystemDevice n) ];
|
||||
bindsTo = deps;
|
||||
after = deps;
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
path = [ pkgs.iproute ];
|
||||
script = ''
|
||||
# Remove Dead Interfaces
|
||||
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
|
||||
ip link add name "${n}" type sit \
|
||||
${optionalString (v.remote != null) "remote \"${v.remote}\""} \
|
||||
${optionalString (v.local != null) "local \"${v.local}\""} \
|
||||
${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
|
||||
${optionalString (v.dev != null) "dev \"${v.dev}\""}
|
||||
ip link set "${n}" up
|
||||
'';
|
||||
postStop = ''
|
||||
ip link delete "${n}"
|
||||
'';
|
||||
});
|
||||
|
||||
createVlanDevice = n: v: nameValuePair "${n}-netdev"
|
||||
(let
|
||||
deps = [ (subsystemDevice v.interface) ];
|
||||
in
|
||||
{ description = "Vlan Interface ${n}";
|
||||
wantedBy = [ "network.target" (subsystemDevice n) ];
|
||||
bindsTo = deps;
|
||||
after = deps;
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
path = [ pkgs.iproute ];
|
||||
script = ''
|
||||
# Remove Dead Interfaces
|
||||
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
|
||||
ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
|
||||
ip link set "${n}" up
|
||||
'';
|
||||
postStop = ''
|
||||
ip link delete "${n}"
|
||||
'';
|
||||
});
|
||||
|
||||
in listToAttrs (
|
||||
map configureInterface interfaces ++
|
||||
map createTunDevice (filter (i: i.virtual) interfaces))
|
||||
// mapAttrs' createBridgeDevice cfg.bridges
|
||||
// mapAttrs' createBondDevice cfg.bonds
|
||||
// mapAttrs' createSitDevice cfg.sits
|
||||
// mapAttrs' createVlanDevice cfg.vlans
|
||||
// { "network-setup" = networkSetup; };
|
||||
|
||||
# Set the host and domain names in the activation script. Don't
|
||||
# clear it if it's not configured in the NixOS configuration,
|
||||
# since it may have been set by dhcpcd in the meantime.
|
||||
@ -899,7 +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}"
|
||||
'';
|
||||
})));
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {};
|
||||
|
365
nixos/tests/networking.nix
Normal file
365
nixos/tests/networking.nix
Normal file
@ -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"}";
|
||||
})
|
Loading…
Reference in New Issue
Block a user