1{ config, lib, pkgs, utils, ... }:
2
3with utils;
4with lib;
5
6let
7
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
10 hasVirtuals = any (i: i.virtual) interfaces;
11
12 # We must escape interfaces due to the systemd interpretation
13 subsystemDevice = interface:
14 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
15
16 interfaceIps = i:
17 i.ip4 ++ optionals cfg.enableIPv6 i.ip6
18 ++ optional (i.ipAddress != null) {
19 address = i.ipAddress;
20 prefixLength = i.prefixLength;
21 } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) {
22 address = i.ipv6Address;
23 prefixLength = i.ipv6PrefixLength;
24 };
25
26 destroyBond = i: ''
27 while true; do
28 UPDATED=1
29 SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
30 for I in $SLAVES; do
31 UPDATED=0
32 ip link set "$I" nomaster
33 done
34 [ "$UPDATED" -eq "1" ] && break
35 done
36 ip link set "${i}" down 2>/dev/null || true
37 ip link del "${i}" 2>/dev/null || true
38 '';
39
40in
41
42{
43
44 config = mkIf (!cfg.useNetworkd) {
45
46 systemd.services =
47 let
48
49 networkLocalCommands = {
50 after = [ "network-setup.service" ];
51 bindsTo = [ "network-setup.service" ];
52 };
53
54 networkSetup =
55 { description = "Networking Setup";
56
57 after = [ "network-interfaces.target" "network-pre.target" ];
58 before = [ "network.target" ];
59 wantedBy = [ "network.target" ];
60
61 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
62
63 path = [ pkgs.iproute ];
64
65 serviceConfig.Type = "oneshot";
66 serviceConfig.RemainAfterExit = true;
67
68 script =
69 ''
70 # Set the static DNS configuration, if given.
71 ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
72 ${optionalString (cfg.nameservers != [] && cfg.domain != null) ''
73 domain ${cfg.domain}
74 ''}
75 ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
76 ${flip concatMapStrings cfg.nameservers (ns: ''
77 nameserver ${ns}
78 '')}
79 EOF
80
81 # Set the default gateway.
82 ${optionalString (cfg.defaultGateway != null && cfg.defaultGateway != "") ''
83 # FIXME: get rid of "|| true" (necessary to make it idempotent).
84 ip route add default via "${cfg.defaultGateway}" ${
85 optionalString (cfg.defaultGatewayWindowSize != null)
86 "window ${cfg.defaultGatewayWindowSize}"} || true
87 ''}
88 ${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6 != "") ''
89 # FIXME: get rid of "|| true" (necessary to make it idempotent).
90 ip -6 route add ::/0 via "${cfg.defaultGateway6}" ${
91 optionalString (cfg.defaultGatewayWindowSize != null)
92 "window ${cfg.defaultGatewayWindowSize}"} || true
93 ''}
94 '';
95 };
96
97 # For each interface <foo>, create a job ‘network-addresses-<foo>.service"
98 # that performs static address configuration. It has a "wants"
99 # dependency on ‘<foo>.service’, which is supposed to create
100 # the interface and need not exist (i.e. for hardware
101 # interfaces). It has a binds-to dependency on the actual
102 # network device, so it only gets started after the interface
103 # has appeared, and it's stopped when the interface
104 # disappears.
105 configureAddrs = i:
106 let
107 ips = interfaceIps i;
108 in
109 nameValuePair "network-addresses-${i.name}"
110 { description = "Address configuration of ${i.name}";
111 wantedBy = [ "network-interfaces.target" ];
112 before = [ "network-interfaces.target" ];
113 bindsTo = [ (subsystemDevice i.name) ];
114 after = [ (subsystemDevice i.name) "network-pre.target" ];
115 serviceConfig.Type = "oneshot";
116 serviceConfig.RemainAfterExit = true;
117 path = [ pkgs.iproute ];
118 script =
119 ''
120 echo "bringing up interface..."
121 ip link set "${i.name}" up
122
123 restart_network_interfaces=false
124 '' + flip concatMapStrings (ips) (ip:
125 let
126 address = "${ip.address}/${toString ip.prefixLength}";
127 in
128 ''
129 echo "checking ip ${address}..."
130 if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then
131 echo "added ip ${address}..."
132 restart_network_setup=true
133 elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
134 echo "failed to add ${address}"
135 exit 1
136 fi
137 '')
138 + optionalString (ips != [ ])
139 ''
140 if [ "$restart_network_setup" = "true" ]; then
141 # Ensure that the default gateway remains set.
142 # (Flushing this interface may have removed it.)
143 ${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service
144 fi
145 ${config.systemd.package}/bin/systemctl start ip-up.target
146 '';
147 preStop =
148 ''
149 echo "releasing configured ip's..."
150 '' + flip concatMapStrings (ips) (ip:
151 let
152 address = "${ip.address}/${toString ip.prefixLength}";
153 in
154 ''
155 echo -n "Deleting ${address}..."
156 ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed"
157 echo ""
158 '');
159 };
160
161 createTunDevice = i: nameValuePair "${i.name}-netdev"
162 { description = "Virtual Network Interface ${i.name}";
163 requires = [ "dev-net-tun.device" ];
164 after = [ "dev-net-tun.device" "network-pre.target" ];
165 wantedBy = [ "network.target" (subsystemDevice i.name) ];
166 before = [ "network-interfaces.target" (subsystemDevice i.name) ];
167 path = [ pkgs.iproute ];
168 serviceConfig = {
169 Type = "oneshot";
170 RemainAfterExit = true;
171 };
172 script = ''
173 ip tuntap add dev "${i.name}" \
174 ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \
175 user "${i.virtualOwner}"
176 '';
177 postStop = ''
178 ip link del ${i.name}
179 '';
180 };
181
182 createBridgeDevice = n: v: nameValuePair "${n}-netdev"
183 (let
184 deps = map subsystemDevice v.interfaces;
185 in
186 { description = "Bridge Interface ${n}";
187 wantedBy = [ "network.target" (subsystemDevice n) ];
188 bindsTo = deps ++ optional v.rstp "mstpd.service";
189 partOf = optional v.rstp "mstpd.service";
190 after = [ "network-pre.target" "mstpd.service" ] ++ deps
191 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
192 before = [ "network-interfaces.target" (subsystemDevice n) ];
193 serviceConfig.Type = "oneshot";
194 serviceConfig.RemainAfterExit = true;
195 path = [ pkgs.iproute ];
196 script = ''
197 # Remove Dead Interfaces
198 echo "Removing old bridge ${n}..."
199 ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}"
200
201 echo "Adding bridge ${n}..."
202 ip link add name "${n}" type bridge
203
204 # Enslave child interfaces
205 ${flip concatMapStrings v.interfaces (i: ''
206 ip link set "${i}" master "${n}"
207 ip link set "${i}" up
208 '')}
209
210 # Enable stp on the interface
211 ${optionalString v.rstp ''
212 echo 2 >/sys/class/net/${n}/bridge/stp_state
213 ''}
214
215 ip link set "${n}" up
216 '';
217 postStop = ''
218 ip link set "${n}" down || true
219 ip link del "${n}" || true
220 '';
221 });
222
223 createVswitchDevice = n: v: nameValuePair "${n}-netdev"
224 (let
225 deps = map subsystemDevice v.interfaces;
226 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
227 in
228 { description = "Open vSwitch Interface ${n}";
229 wantedBy = [ "network.target" "vswitchd.service" ] ++ deps;
230 bindsTo = [ "vswitchd.service" (subsystemDevice n) ] ++ deps;
231 partOf = [ "vswitchd.service" ];
232 after = [ "network-pre.target" "vswitchd.service" ] ++ deps;
233 before = [ "network-interfaces.target" ];
234 serviceConfig.Type = "oneshot";
235 serviceConfig.RemainAfterExit = true;
236 path = [ pkgs.iproute config.virtualisation.vswitch.package ];
237 script = ''
238 echo "Removing old Open vSwitch ${n}..."
239 ovs-vsctl --if-exists del-br ${n}
240
241 echo "Adding Open vSwitch ${n}..."
242 ovs-vsctl -- add-br ${n} ${concatMapStrings (i: " -- add-port ${n} ${i}") v.interfaces} \
243 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
244 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
245
246 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
247 ovs-ofctl add-flows ${n} ${ofRules}
248 '';
249 postStop = ''
250 ip link set ${n} down || true
251 ovs-ofctl del-flows ${n} || true
252 ovs-vsctl --if-exists del-br ${n}
253 '';
254 });
255
256 createBondDevice = n: v: nameValuePair "${n}-netdev"
257 (let
258 deps = map subsystemDevice v.interfaces;
259 in
260 { description = "Bond Interface ${n}";
261 wantedBy = [ "network.target" (subsystemDevice n) ];
262 bindsTo = deps;
263 after = [ "network-pre.target" ] ++ deps
264 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
265 before = [ "network-interfaces.target" (subsystemDevice n) ];
266 serviceConfig.Type = "oneshot";
267 serviceConfig.RemainAfterExit = true;
268 path = [ pkgs.iproute pkgs.gawk ];
269 script = ''
270 echo "Destroying old bond ${n}..."
271 ${destroyBond n}
272
273 echo "Creating new bond ${n}..."
274 ip link add name "${n}" type bond \
275 ${optionalString (v.mode != null) "mode ${toString v.mode}"} \
276 ${optionalString (v.miimon != null) "miimon ${toString v.miimon}"} \
277 ${optionalString (v.xmit_hash_policy != null) "xmit_hash_policy ${toString v.xmit_hash_policy}"} \
278 ${optionalString (v.lacp_rate != null) "lacp_rate ${toString v.lacp_rate}"}
279
280 # !!! There must be a better way to wait for the interface
281 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
282
283 # Bring up the bond and enslave the specified interfaces
284 ip link set "${n}" up
285 ${flip concatMapStrings v.interfaces (i: ''
286 ip link set "${i}" down
287 ip link set "${i}" master "${n}"
288 '')}
289 '';
290 postStop = destroyBond n;
291 });
292
293 createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
294 (let
295 deps = [ (subsystemDevice v.interface) ];
296 in
297 { description = "Vlan Interface ${n}";
298 wantedBy = [ "network.target" (subsystemDevice n) ];
299 bindsTo = deps;
300 after = [ "network-pre.target" ] ++ deps;
301 before = [ "network-interfaces.target" (subsystemDevice n) ];
302 serviceConfig.Type = "oneshot";
303 serviceConfig.RemainAfterExit = true;
304 path = [ pkgs.iproute ];
305 script = ''
306 # Remove Dead Interfaces
307 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
308 ip link add link "${v.interface}" name "${n}" type macvlan \
309 ${optionalString (v.mode != null) "mode ${v.mode}"}
310 ip link set "${n}" up
311 '';
312 postStop = ''
313 ip link delete "${n}"
314 '';
315 });
316
317 createSitDevice = n: v: nameValuePair "${n}-netdev"
318 (let
319 deps = optional (v.dev != null) (subsystemDevice v.dev);
320 in
321 { description = "6-to-4 Tunnel Interface ${n}";
322 wantedBy = [ "network.target" (subsystemDevice n) ];
323 bindsTo = deps;
324 after = [ "network-pre.target" ] ++ deps;
325 before = [ "network-interfaces.target" (subsystemDevice n) ];
326 serviceConfig.Type = "oneshot";
327 serviceConfig.RemainAfterExit = true;
328 path = [ pkgs.iproute ];
329 script = ''
330 # Remove Dead Interfaces
331 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
332 ip link add name "${n}" type sit \
333 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
334 ${optionalString (v.local != null) "local \"${v.local}\""} \
335 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
336 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
337 ip link set "${n}" up
338 '';
339 postStop = ''
340 ip link delete "${n}"
341 '';
342 });
343
344 createVlanDevice = n: v: nameValuePair "${n}-netdev"
345 (let
346 deps = [ (subsystemDevice v.interface) ];
347 in
348 { description = "Vlan Interface ${n}";
349 wantedBy = [ "network.target" (subsystemDevice n) ];
350 bindsTo = deps;
351 after = [ "network-pre.target" ] ++ deps;
352 before = [ "network-interfaces.target" (subsystemDevice n) ];
353 serviceConfig.Type = "oneshot";
354 serviceConfig.RemainAfterExit = true;
355 path = [ pkgs.iproute ];
356 script = ''
357 # Remove Dead Interfaces
358 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
359 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
360 ip link set "${n}" up
361 '';
362 postStop = ''
363 ip link delete "${n}"
364 '';
365 });
366
367 in listToAttrs (
368 map configureAddrs interfaces ++
369 map createTunDevice (filter (i: i.virtual) interfaces))
370 // mapAttrs' createBridgeDevice cfg.bridges
371 // mapAttrs' createVswitchDevice cfg.vswitches
372 // mapAttrs' createBondDevice cfg.bonds
373 // mapAttrs' createMacvlanDevice cfg.macvlans
374 // mapAttrs' createSitDevice cfg.sits
375 // mapAttrs' createVlanDevice cfg.vlans
376 // {
377 "network-setup" = networkSetup;
378 "network-local-commands" = networkLocalCommands;
379 };
380
381 services.udev.extraRules =
382 ''
383 KERNEL=="tun", TAG+="systemd"
384 '';
385
386 };
387
388}