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 ${toString 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 ${toString 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 = flip concatMapStrings (ips) (ip:
148 let
149 address = "${ip.address}/${toString ip.prefixLength}";
150 in
151 ''
152 echo -n "deleting ${address}..."
153 ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed"
154 echo ""
155 '');
156 };
157
158 createTunDevice = i: nameValuePair "${i.name}-netdev"
159 { description = "Virtual Network Interface ${i.name}";
160 requires = [ "dev-net-tun.device" ];
161 after = [ "dev-net-tun.device" "network-pre.target" ];
162 wantedBy = [ "network.target" (subsystemDevice i.name) ];
163 before = [ "network-interfaces.target" (subsystemDevice i.name) ];
164 path = [ pkgs.iproute ];
165 serviceConfig = {
166 Type = "oneshot";
167 RemainAfterExit = true;
168 };
169 script = ''
170 ip tuntap add dev "${i.name}" \
171 ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \
172 user "${i.virtualOwner}"
173 '';
174 postStop = ''
175 ip link del ${i.name}
176 '';
177 };
178
179 createBridgeDevice = n: v: nameValuePair "${n}-netdev"
180 (let
181 deps = map subsystemDevice v.interfaces;
182 in
183 { description = "Bridge Interface ${n}";
184 wantedBy = [ "network.target" (subsystemDevice n) ];
185 bindsTo = deps ++ optional v.rstp "mstpd.service";
186 partOf = optional v.rstp "mstpd.service";
187 after = [ "network-pre.target" "mstpd.service" ] ++ deps
188 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
189 before = [ "network-interfaces.target" (subsystemDevice n) ];
190 serviceConfig.Type = "oneshot";
191 serviceConfig.RemainAfterExit = true;
192 path = [ pkgs.iproute ];
193 script = ''
194 # Remove Dead Interfaces
195 echo "Removing old bridge ${n}..."
196 ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}"
197
198 echo "Adding bridge ${n}..."
199 ip link add name "${n}" type bridge
200
201 # Enslave child interfaces
202 ${flip concatMapStrings v.interfaces (i: ''
203 ip link set "${i}" master "${n}"
204 ip link set "${i}" up
205 '')}
206
207 # Enable stp on the interface
208 ${optionalString v.rstp ''
209 echo 2 >/sys/class/net/${n}/bridge/stp_state
210 ''}
211
212 ip link set "${n}" up
213 '';
214 postStop = ''
215 ip link set "${n}" down || true
216 ip link del "${n}" || true
217 '';
218 });
219
220 createVswitchDevice = n: v: nameValuePair "${n}-netdev"
221 (let
222 deps = map subsystemDevice v.interfaces;
223 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
224 in
225 { description = "Open vSwitch Interface ${n}";
226 wantedBy = [ "network.target" "vswitchd.service" ] ++ deps;
227 bindsTo = [ "vswitchd.service" (subsystemDevice n) ] ++ deps;
228 partOf = [ "vswitchd.service" ];
229 after = [ "network-pre.target" "vswitchd.service" ] ++ deps;
230 before = [ "network-interfaces.target" ];
231 serviceConfig.Type = "oneshot";
232 serviceConfig.RemainAfterExit = true;
233 path = [ pkgs.iproute config.virtualisation.vswitch.package ];
234 script = ''
235 echo "Removing old Open vSwitch ${n}..."
236 ovs-vsctl --if-exists del-br ${n}
237
238 echo "Adding Open vSwitch ${n}..."
239 ovs-vsctl -- add-br ${n} ${concatMapStrings (i: " -- add-port ${n} ${i}") v.interfaces} \
240 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
241 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
242
243 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
244 ovs-ofctl add-flows ${n} ${ofRules}
245 '';
246 postStop = ''
247 ip link set ${n} down || true
248 ovs-ofctl del-flows ${n} || true
249 ovs-vsctl --if-exists del-br ${n}
250 '';
251 });
252
253 createBondDevice = n: v: nameValuePair "${n}-netdev"
254 (let
255 deps = map subsystemDevice v.interfaces;
256 in
257 { description = "Bond Interface ${n}";
258 wantedBy = [ "network.target" (subsystemDevice n) ];
259 bindsTo = deps;
260 after = [ "network-pre.target" ] ++ deps
261 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
262 before = [ "network-interfaces.target" (subsystemDevice n) ];
263 serviceConfig.Type = "oneshot";
264 serviceConfig.RemainAfterExit = true;
265 path = [ pkgs.iproute pkgs.gawk ];
266 script = ''
267 echo "Destroying old bond ${n}..."
268 ${destroyBond n}
269
270 echo "Creating new bond ${n}..."
271 ip link add name "${n}" type bond \
272 ${optionalString (v.mode != null) "mode ${toString v.mode}"} \
273 ${optionalString (v.miimon != null) "miimon ${toString v.miimon}"} \
274 ${optionalString (v.xmit_hash_policy != null) "xmit_hash_policy ${toString v.xmit_hash_policy}"} \
275 ${optionalString (v.lacp_rate != null) "lacp_rate ${toString v.lacp_rate}"}
276
277 # !!! There must be a better way to wait for the interface
278 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
279
280 # Bring up the bond and enslave the specified interfaces
281 ip link set "${n}" up
282 ${flip concatMapStrings v.interfaces (i: ''
283 ip link set "${i}" down
284 ip link set "${i}" master "${n}"
285 '')}
286 '';
287 postStop = destroyBond n;
288 });
289
290 createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
291 (let
292 deps = [ (subsystemDevice v.interface) ];
293 in
294 { description = "Vlan Interface ${n}";
295 wantedBy = [ "network.target" (subsystemDevice n) ];
296 bindsTo = deps;
297 after = [ "network-pre.target" ] ++ deps;
298 before = [ "network-interfaces.target" (subsystemDevice n) ];
299 serviceConfig.Type = "oneshot";
300 serviceConfig.RemainAfterExit = true;
301 path = [ pkgs.iproute ];
302 script = ''
303 # Remove Dead Interfaces
304 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
305 ip link add link "${v.interface}" name "${n}" type macvlan \
306 ${optionalString (v.mode != null) "mode ${v.mode}"}
307 ip link set "${n}" up
308 '';
309 postStop = ''
310 ip link delete "${n}"
311 '';
312 });
313
314 createSitDevice = n: v: nameValuePair "${n}-netdev"
315 (let
316 deps = optional (v.dev != null) (subsystemDevice v.dev);
317 in
318 { description = "6-to-4 Tunnel Interface ${n}";
319 wantedBy = [ "network.target" (subsystemDevice n) ];
320 bindsTo = deps;
321 after = [ "network-pre.target" ] ++ deps;
322 before = [ "network-interfaces.target" (subsystemDevice n) ];
323 serviceConfig.Type = "oneshot";
324 serviceConfig.RemainAfterExit = true;
325 path = [ pkgs.iproute ];
326 script = ''
327 # Remove Dead Interfaces
328 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
329 ip link add name "${n}" type sit \
330 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
331 ${optionalString (v.local != null) "local \"${v.local}\""} \
332 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
333 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
334 ip link set "${n}" up
335 '';
336 postStop = ''
337 ip link delete "${n}"
338 '';
339 });
340
341 createVlanDevice = n: v: nameValuePair "${n}-netdev"
342 (let
343 deps = [ (subsystemDevice v.interface) ];
344 in
345 { description = "Vlan Interface ${n}";
346 wantedBy = [ "network.target" (subsystemDevice n) ];
347 bindsTo = deps;
348 after = [ "network-pre.target" ] ++ deps;
349 before = [ "network-interfaces.target" (subsystemDevice n) ];
350 serviceConfig.Type = "oneshot";
351 serviceConfig.RemainAfterExit = true;
352 path = [ pkgs.iproute ];
353 script = ''
354 # Remove Dead Interfaces
355 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
356 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
357 ip link set "${n}" up
358 '';
359 postStop = ''
360 ip link delete "${n}"
361 '';
362 });
363
364 in listToAttrs (
365 map configureAddrs interfaces ++
366 map createTunDevice (filter (i: i.virtual) interfaces))
367 // mapAttrs' createBridgeDevice cfg.bridges
368 // mapAttrs' createVswitchDevice cfg.vswitches
369 // mapAttrs' createBondDevice cfg.bonds
370 // mapAttrs' createMacvlanDevice cfg.macvlans
371 // mapAttrs' createSitDevice cfg.sits
372 // mapAttrs' createVlanDevice cfg.vlans
373 // {
374 "network-setup" = networkSetup;
375 "network-local-commands" = networkLocalCommands;
376 };
377
378 services.udev.extraRules =
379 ''
380 KERNEL=="tun", TAG+="systemd"
381 '';
382
383 };
384
385}