1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
7
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
10
11 interfaceIps = i:
12 i.ipv4.addresses
13 ++ optionals cfg.enableIPv6 i.ipv6.addresses;
14
15 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
16
17 slaves =
18 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
19 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
20 ++ map (sit: sit.dev) (attrValues cfg.sits)
21 ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
22 # add dependency to physical or independently created vswitch member interface
23 # TODO: warn the user that any address configured on those interfaces will be useless
24 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
25
26in
27
28{
29
30 config = mkIf cfg.useNetworkd {
31
32 assertions = [ {
33 assertion = cfg.defaultGatewayWindowSize == null;
34 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
35 } {
36 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
37 message = "networking.defaultGateway.interface is not supported by networkd.";
38 } {
39 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
40 message = "networking.defaultGateway6.interface is not supported by networkd.";
41 } {
42 assertion = cfg.useDHCP == false;
43 message = ''
44 networking.useDHCP is not supported by networkd.
45 Please use per interface configuration and set the global option to false.
46 '';
47 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
48 assertion = !rstp;
49 message = "networking.bridges.${n}.rstp is not supported by networkd.";
50 }) ++ flip mapAttrsToList cfg.fooOverUDP (n: { local, ... }: {
51 assertion = local == null;
52 message = "networking.fooOverUDP.${n}.local is not supported by networkd.";
53 });
54
55 networking.dhcpcd.enable = mkDefault false;
56
57 systemd.network =
58 let
59 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
60 genericNetwork = override:
61 let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
62 ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
63 in optionalAttrs (gateway != [ ]) {
64 routes = override [
65 {
66 routeConfig = {
67 Gateway = gateway;
68 GatewayOnLink = false;
69 };
70 }
71 ];
72 } // optionalAttrs (domains != [ ]) {
73 domains = override domains;
74 };
75 in mkMerge [ {
76 enable = true;
77 }
78 (mkMerge (forEach interfaces (i: {
79 netdevs = mkIf i.virtual ({
80 "40-${i.name}" = {
81 netdevConfig = {
82 Name = i.name;
83 Kind = i.virtualType;
84 };
85 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
86 User = i.virtualOwner;
87 };
88 };
89 });
90 networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
91 name = mkDefault i.name;
92 DHCP = mkForce (dhcpStr
93 (if i.useDHCP != null then i.useDHCP else false));
94 address = forEach (interfaceIps i)
95 (ip: "${ip.address}/${toString ip.prefixLength}");
96 networkConfig.IPv6PrivacyExtensions = "kernel";
97 linkConfig = optionalAttrs (i.macAddress != null) {
98 MACAddress = i.macAddress;
99 } // optionalAttrs (i.mtu != null) {
100 MTUBytes = toString i.mtu;
101 };
102 }];
103 })))
104 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
105 netdevs."40-${name}" = {
106 netdevConfig = {
107 Name = name;
108 Kind = "bridge";
109 };
110 };
111 networks = listToAttrs (forEach bridge.interfaces (bi:
112 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
113 DHCP = mkOverride 0 (dhcpStr false);
114 networkConfig.Bridge = name;
115 } ])));
116 })))
117 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
118 netdevs."40-${name}" = {
119 netdevConfig = {
120 Name = name;
121 Kind = "bond";
122 };
123 bondConfig = let
124 # manual mapping as of 2017-02-03
125 # man 5 systemd.netdev [BOND]
126 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
127 # driver options.
128 driverOptionMapping = let
129 trans = f: optName: { valTransform = f; optNames = [optName]; };
130 simp = trans id;
131 ms = trans (v: v + "ms");
132 in {
133 Mode = simp "mode";
134 TransmitHashPolicy = simp "xmit_hash_policy";
135 LACPTransmitRate = simp "lacp_rate";
136 MIIMonitorSec = ms "miimon";
137 UpDelaySec = ms "updelay";
138 DownDelaySec = ms "downdelay";
139 LearnPacketIntervalSec = simp "lp_interval";
140 AdSelect = simp "ad_select";
141 FailOverMACPolicy = simp "fail_over_mac";
142 ARPValidate = simp "arp_validate";
143 # apparently in ms for this value?! Upstream bug?
144 ARPIntervalSec = simp "arp_interval";
145 ARPIPTargets = simp "arp_ip_target";
146 ARPAllTargets = simp "arp_all_targets";
147 PrimaryReselectPolicy = simp "primary_reselect";
148 ResendIGMP = simp "resend_igmp";
149 PacketsPerSlave = simp "packets_per_slave";
150 GratuitousARP = { valTransform = id;
151 optNames = [ "num_grat_arp" "num_unsol_na" ]; };
152 AllSlavesActive = simp "all_slaves_active";
153 MinLinks = simp "min_links";
154 };
155
156 do = bond.driverOptions;
157 assertNoUnknownOption = let
158 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames)
159 driverOptionMapping);
160 # options that apparently don’t exist in the networkd config
161 unknownOptions = [ "primary" ];
162 assertTrace = bool: msg: if bool then true else builtins.trace msg false;
163 in assert all (driverOpt: assertTrace
164 (elem driverOpt (knownOptions ++ unknownOptions))
165 "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd.")
166 (mapAttrsToList (k: _: k) do); "";
167 # get those driverOptions that have been set
168 filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
169 any (kOpt: do ? ${kOpt}) kOpts.optNames);
170 # build final set of systemd options to bond values
171 buildOptionSet = mapAttrs (_: kOpts: with kOpts;
172 # we simply take the first set kernel bond option
173 # (one option has multiple names, which is silly)
174 head (map (optN: valTransform (do.${optN}))
175 # only map those that exist
176 (filter (o: do ? ${o}) optNames)));
177 in seq assertNoUnknownOption
178 (buildOptionSet (filterSystemdOptions driverOptionMapping));
179
180 };
181
182 networks = listToAttrs (forEach bond.interfaces (bi:
183 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
184 DHCP = mkOverride 0 (dhcpStr false);
185 networkConfig.Bond = name;
186 } ])));
187 })))
188 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
189 netdevs."40-${name}" = {
190 netdevConfig = {
191 Name = name;
192 Kind = "macvlan";
193 };
194 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
195 };
196 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
197 macvlan = [ name ];
198 } ]);
199 })))
200 (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: {
201 netdevs."40-${name}" = {
202 netdevConfig = {
203 Name = name;
204 Kind = "fou";
205 };
206 # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes,
207 # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature
208 # in networkd.
209 fooOverUDPConfig = {
210 Port = fou.port;
211 Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation";
212 } // (optionalAttrs (fou.protocol != null) {
213 Protocol = fou.protocol;
214 });
215 };
216 })))
217 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
218 netdevs."40-${name}" = {
219 netdevConfig = {
220 Name = name;
221 Kind = "sit";
222 };
223 tunnelConfig =
224 (optionalAttrs (sit.remote != null) {
225 Remote = sit.remote;
226 }) // (optionalAttrs (sit.local != null) {
227 Local = sit.local;
228 }) // (optionalAttrs (sit.ttl != null) {
229 TTL = sit.ttl;
230 }) // (optionalAttrs (sit.encapsulation != null) (
231 {
232 FooOverUDP = true;
233 Encapsulation =
234 if sit.encapsulation.type == "fou"
235 then "FooOverUDP"
236 else "GenericUDPEncapsulation";
237 FOUDestinationPort = sit.encapsulation.port;
238 } // (optionalAttrs (sit.encapsulation.sourcePort != null) {
239 FOUSourcePort = sit.encapsulation.sourcePort;
240 })));
241 };
242 networks = mkIf (sit.dev != null) {
243 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
244 tunnel = [ name ];
245 } ]);
246 };
247 })))
248 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
249 netdevs."40-${name}" = {
250 netdevConfig = {
251 Name = name;
252 Kind = "vlan";
253 };
254 vlanConfig.Id = vlan.id;
255 };
256 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
257 vlan = [ name ];
258 } ]);
259 })))
260 ];
261
262 # We need to prefill the slaved devices with networking options
263 # This forces the network interface creator to initialize slaves.
264 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
265
266 systemd.services = let
267 # We must escape interfaces due to the systemd interpretation
268 subsystemDevice = interface:
269 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
270 # support for creating openvswitch switches
271 createVswitchDevice = n: v: nameValuePair "${n}-netdev"
272 (let
273 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces));
274 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
275 in
276 { description = "Open vSwitch Interface ${n}";
277 wantedBy = [ "network.target" (subsystemDevice n) ];
278 # and create bridge before systemd-networkd starts because it might create internal interfaces
279 before = [ "systemd-networkd.service" ];
280 # shutdown the bridge when network is shutdown
281 partOf = [ "network.target" ];
282 # requires ovs-vswitchd to be alive at all times
283 bindsTo = [ "ovs-vswitchd.service" ];
284 # start switch after physical interfaces and vswitch daemon
285 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps;
286 wants = deps; # if one or more interface fails, the switch should continue to run
287 serviceConfig.Type = "oneshot";
288 serviceConfig.RemainAfterExit = true;
289 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ];
290 preStart = ''
291 echo "Resetting Open vSwitch ${n}..."
292 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
293 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
294 '';
295 script = ''
296 echo "Configuring Open vSwitch ${n}..."
297 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \
298 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \
299 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
300 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
301
302
303 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
304 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
305 '';
306 postStop = ''
307 echo "Cleaning Open vSwitch ${n}"
308 echo "Shuting down internal ${n} interface"
309 ip link set ${n} down || true
310 echo "Deleting flows for ${n}"
311 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
312 echo "Deleting Open vSwitch ${n}"
313 ovs-vsctl --if-exists del-br ${n} || true
314 '';
315 });
316 in mapAttrs' createVswitchDevice cfg.vswitches
317 // {
318 "network-local-commands" = {
319 after = [ "systemd-networkd.service" ];
320 bindsTo = [ "systemd-networkd.service" ];
321 };
322 };
323 };
324
325}