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 createBondDevice = n: v: nameValuePair "${n}-netdev"
224 (let
225 deps = map subsystemDevice v.interfaces;
226 in
227 { description = "Bond Interface ${n}";
228 wantedBy = [ "network.target" (subsystemDevice n) ];
229 bindsTo = deps;
230 after = [ "network-pre.target" ] ++ deps
231 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
232 before = [ "network-interfaces.target" (subsystemDevice n) ];
233 serviceConfig.Type = "oneshot";
234 serviceConfig.RemainAfterExit = true;
235 path = [ pkgs.iproute pkgs.gawk ];
236 script = ''
237 echo "Destroying old bond ${n}..."
238 ${destroyBond n}
239
240 echo "Creating new bond ${n}..."
241 ip link add name "${n}" type bond \
242 ${optionalString (v.mode != null) "mode ${toString v.mode}"} \
243 ${optionalString (v.miimon != null) "miimon ${toString v.miimon}"} \
244 ${optionalString (v.xmit_hash_policy != null) "xmit_hash_policy ${toString v.xmit_hash_policy}"} \
245 ${optionalString (v.lacp_rate != null) "lacp_rate ${toString v.lacp_rate}"}
246
247 # !!! There must be a better way to wait for the interface
248 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
249
250 # Bring up the bond and enslave the specified interfaces
251 ip link set "${n}" up
252 ${flip concatMapStrings v.interfaces (i: ''
253 ip link set "${i}" down
254 ip link set "${i}" master "${n}"
255 '')}
256 '';
257 postStop = destroyBond n;
258 });
259
260 createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
261 (let
262 deps = [ (subsystemDevice v.interface) ];
263 in
264 { description = "Vlan Interface ${n}";
265 wantedBy = [ "network.target" (subsystemDevice n) ];
266 bindsTo = deps;
267 after = [ "network-pre.target" ] ++ deps;
268 before = [ "network-interfaces.target" (subsystemDevice n) ];
269 serviceConfig.Type = "oneshot";
270 serviceConfig.RemainAfterExit = true;
271 path = [ pkgs.iproute ];
272 script = ''
273 # Remove Dead Interfaces
274 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
275 ip link add link "${v.interface}" name "${n}" type macvlan \
276 ${optionalString (v.mode != null) "mode ${v.mode}"}
277 ip link set "${n}" up
278 '';
279 postStop = ''
280 ip link delete "${n}"
281 '';
282 });
283
284 createSitDevice = n: v: nameValuePair "${n}-netdev"
285 (let
286 deps = optional (v.dev != null) (subsystemDevice v.dev);
287 in
288 { description = "6-to-4 Tunnel Interface ${n}";
289 wantedBy = [ "network.target" (subsystemDevice n) ];
290 bindsTo = deps;
291 after = [ "network-pre.target" ] ++ deps;
292 before = [ "network-interfaces.target" (subsystemDevice n) ];
293 serviceConfig.Type = "oneshot";
294 serviceConfig.RemainAfterExit = true;
295 path = [ pkgs.iproute ];
296 script = ''
297 # Remove Dead Interfaces
298 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
299 ip link add name "${n}" type sit \
300 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
301 ${optionalString (v.local != null) "local \"${v.local}\""} \
302 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
303 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
304 ip link set "${n}" up
305 '';
306 postStop = ''
307 ip link delete "${n}"
308 '';
309 });
310
311 createVlanDevice = n: v: nameValuePair "${n}-netdev"
312 (let
313 deps = [ (subsystemDevice v.interface) ];
314 in
315 { description = "Vlan Interface ${n}";
316 wantedBy = [ "network.target" (subsystemDevice n) ];
317 bindsTo = deps;
318 after = [ "network-pre.target" ] ++ deps;
319 before = [ "network-interfaces.target" (subsystemDevice n) ];
320 serviceConfig.Type = "oneshot";
321 serviceConfig.RemainAfterExit = true;
322 path = [ pkgs.iproute ];
323 script = ''
324 # Remove Dead Interfaces
325 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
326 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
327 ip link set "${n}" up
328 '';
329 postStop = ''
330 ip link delete "${n}"
331 '';
332 });
333
334 in listToAttrs (
335 map configureAddrs interfaces ++
336 map createTunDevice (filter (i: i.virtual) interfaces))
337 // mapAttrs' createBridgeDevice cfg.bridges
338 // mapAttrs' createBondDevice cfg.bonds
339 // mapAttrs' createMacvlanDevice cfg.macvlans
340 // mapAttrs' createSitDevice cfg.sits
341 // mapAttrs' createVlanDevice cfg.vlans
342 // {
343 "network-setup" = networkSetup;
344 "network-local-commands" = networkLocalCommands;
345 };
346
347 services.udev.extraRules =
348 ''
349 KERNEL=="tun", TAG+="systemd"
350 '';
351
352 };
353
354}