at 22.05-pre 27 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3with utils; 4with lib; 5 6let 7 8 cfg = config.networking; 9 interfaces = attrValues cfg.interfaces; 10 11 slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds) 12 ++ concatMap (i: i.interfaces) (attrValues cfg.bridges) 13 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches) 14 ++ concatMap (i: [i.interface]) (attrValues cfg.macvlans) 15 ++ concatMap (i: [i.interface]) (attrValues cfg.vlans); 16 17 # We must escape interfaces due to the systemd interpretation 18 subsystemDevice = interface: 19 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; 20 21 interfaceIps = i: 22 i.ipv4.addresses 23 ++ optionals cfg.enableIPv6 i.ipv6.addresses; 24 25 destroyBond = i: '' 26 while true; do 27 UPDATED=1 28 SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}') 29 for I in $SLAVES; do 30 UPDATED=0 31 ip link set "$I" nomaster 32 done 33 [ "$UPDATED" -eq "1" ] && break 34 done 35 ip link set "${i}" down 2>/dev/null || true 36 ip link del "${i}" 2>/dev/null || true 37 ''; 38 39 # warn that these attributes are deprecated (2017-2-2) 40 # Should be removed in the release after next 41 bondDeprecation = rec { 42 deprecated = [ "lacp_rate" "miimon" "mode" "xmit_hash_policy" ]; 43 filterDeprecated = bond: (filterAttrs (attrName: attr: 44 elem attrName deprecated && attr != null) bond); 45 }; 46 47 bondWarnings = 48 let oneBondWarnings = bondName: bond: 49 mapAttrsToList (bondText bondName) (bondDeprecation.filterDeprecated bond); 50 bondText = bondName: optName: _: 51 "${bondName}.${optName} is deprecated, use ${bondName}.driverOptions"; 52 in { 53 warnings = flatten (mapAttrsToList oneBondWarnings cfg.bonds); 54 }; 55 56 normalConfig = { 57 systemd.network.links = let 58 createNetworkLink = i: nameValuePair "40-${i.name}" { 59 matchConfig.OriginalName = i.name; 60 linkConfig = optionalAttrs (i.macAddress != null) { 61 MACAddress = i.macAddress; 62 } // optionalAttrs (i.mtu != null) { 63 MTUBytes = toString i.mtu; 64 } // optionalAttrs (i.wakeOnLan.enable == true) { 65 WakeOnLan = "magic"; 66 }; 67 }; 68 in listToAttrs (map createNetworkLink interfaces); 69 systemd.services = 70 let 71 72 deviceDependency = dev: 73 # Use systemd service if we manage device creation, else 74 # trust udev when not in a container 75 if (hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces)) || 76 (hasAttr dev cfg.bridges) || 77 (hasAttr dev cfg.bonds) || 78 (hasAttr dev cfg.macvlans) || 79 (hasAttr dev cfg.sits) || 80 (hasAttr dev cfg.vlans) || 81 (hasAttr dev cfg.vswitches) 82 then [ "${dev}-netdev.service" ] 83 else optional (dev != null && dev != "lo" && !config.boot.isContainer) (subsystemDevice dev); 84 85 hasDefaultGatewaySet = (cfg.defaultGateway != null && cfg.defaultGateway.address != "") 86 || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != ""); 87 88 networkLocalCommands = { 89 after = [ "network-setup.service" ]; 90 bindsTo = [ "network-setup.service" ]; 91 }; 92 93 networkSetup = 94 { description = "Networking Setup"; 95 96 after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ]; 97 before = [ "network.target" "shutdown.target" ]; 98 wants = [ "network.target" ]; 99 # exclude bridges from the partOf relationship to fix container networking bug #47210 100 partOf = map (i: "network-addresses-${i.name}.service") (filter (i: !(hasAttr i.name cfg.bridges)) interfaces); 101 conflicts = [ "shutdown.target" ]; 102 wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target"; 103 104 unitConfig.ConditionCapability = "CAP_NET_ADMIN"; 105 106 path = [ pkgs.iproute2 ]; 107 108 serviceConfig = { 109 Type = "oneshot"; 110 RemainAfterExit = true; 111 }; 112 113 unitConfig.DefaultDependencies = false; 114 115 script = 116 '' 117 ${optionalString config.networking.resolvconf.enable '' 118 # Set the static DNS configuration, if given. 119 ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF 120 ${optionalString (cfg.nameservers != [] && cfg.domain != null) '' 121 domain ${cfg.domain} 122 ''} 123 ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)} 124 ${flip concatMapStrings cfg.nameservers (ns: '' 125 nameserver ${ns} 126 '')} 127 EOF 128 ''} 129 130 # Set the default gateway. 131 ${optionalString (cfg.defaultGateway != null && cfg.defaultGateway.address != "") '' 132 ${optionalString (cfg.defaultGateway.interface != null) '' 133 ip route replace ${cfg.defaultGateway.address} dev ${cfg.defaultGateway.interface} ${optionalString (cfg.defaultGateway.metric != null) 134 "metric ${toString cfg.defaultGateway.metric}" 135 } proto static 136 ''} 137 ip route replace default ${optionalString (cfg.defaultGateway.metric != null) 138 "metric ${toString cfg.defaultGateway.metric}" 139 } via "${cfg.defaultGateway.address}" ${ 140 optionalString (cfg.defaultGatewayWindowSize != null) 141 "window ${toString cfg.defaultGatewayWindowSize}"} ${ 142 optionalString (cfg.defaultGateway.interface != null) 143 "dev ${cfg.defaultGateway.interface}"} proto static 144 ''} 145 ${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "") '' 146 ${optionalString (cfg.defaultGateway6.interface != null) '' 147 ip -6 route replace ${cfg.defaultGateway6.address} dev ${cfg.defaultGateway6.interface} ${optionalString (cfg.defaultGateway6.metric != null) 148 "metric ${toString cfg.defaultGateway6.metric}" 149 } proto static 150 ''} 151 ip -6 route replace default ${optionalString (cfg.defaultGateway6.metric != null) 152 "metric ${toString cfg.defaultGateway6.metric}" 153 } via "${cfg.defaultGateway6.address}" ${ 154 optionalString (cfg.defaultGatewayWindowSize != null) 155 "window ${toString cfg.defaultGatewayWindowSize}"} ${ 156 optionalString (cfg.defaultGateway6.interface != null) 157 "dev ${cfg.defaultGateway6.interface}"} proto static 158 ''} 159 ''; 160 }; 161 162 # For each interface <foo>, create a job ‘network-addresses-<foo>.service" 163 # that performs static address configuration. It has a "wants" 164 # dependency on ‘<foo>.service’, which is supposed to create 165 # the interface and need not exist (i.e. for hardware 166 # interfaces). It has a binds-to dependency on the actual 167 # network device, so it only gets started after the interface 168 # has appeared, and it's stopped when the interface 169 # disappears. 170 configureAddrs = i: 171 let 172 ips = interfaceIps i; 173 in 174 nameValuePair "network-addresses-${i.name}" 175 { description = "Address configuration of ${i.name}"; 176 wantedBy = [ 177 "network-setup.service" 178 "network.target" 179 ]; 180 # order before network-setup because the routes that are configured 181 # there may need ip addresses configured 182 before = [ "network-setup.service" ]; 183 bindsTo = deviceDependency i.name; 184 after = [ "network-pre.target" ] ++ (deviceDependency i.name); 185 serviceConfig.Type = "oneshot"; 186 serviceConfig.RemainAfterExit = true; 187 # Restart rather than stop+start this unit to prevent the 188 # network from dying during switch-to-configuration. 189 stopIfChanged = false; 190 path = [ pkgs.iproute2 ]; 191 script = 192 '' 193 state="/run/nixos/network/addresses/${i.name}" 194 mkdir -p $(dirname "$state") 195 196 ip link set "${i.name}" up 197 198 ${flip concatMapStrings ips (ip: 199 let 200 cidr = "${ip.address}/${toString ip.prefixLength}"; 201 in 202 '' 203 echo "${cidr}" >> $state 204 echo -n "adding address ${cidr}... " 205 if out=$(ip addr add "${cidr}" dev "${i.name}" 2>&1); then 206 echo "done" 207 elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then 208 echo "'ip addr add "${cidr}" dev "${i.name}"' failed: $out" 209 exit 1 210 fi 211 '' 212 )} 213 214 state="/run/nixos/network/routes/${i.name}" 215 mkdir -p $(dirname "$state") 216 217 ${flip concatMapStrings (i.ipv4.routes ++ i.ipv6.routes) (route: 218 let 219 cidr = "${route.address}/${toString route.prefixLength}"; 220 via = optionalString (route.via != null) ''via "${route.via}"''; 221 options = concatStrings (mapAttrsToList (name: val: "${name} ${val} ") route.options); 222 in 223 '' 224 echo "${cidr}" >> $state 225 echo -n "adding route ${cidr}... " 226 if out=$(ip route add "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then 227 echo "done" 228 elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then 229 echo "'ip route add "${cidr}" ${options} ${via} dev "${i.name}"' failed: $out" 230 exit 1 231 fi 232 '' 233 )} 234 ''; 235 preStop = '' 236 state="/run/nixos/network/routes/${i.name}" 237 if [ -e "$state" ]; then 238 while read cidr; do 239 echo -n "deleting route $cidr... " 240 ip route del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed" 241 done < "$state" 242 rm -f "$state" 243 fi 244 245 state="/run/nixos/network/addresses/${i.name}" 246 if [ -e "$state" ]; then 247 while read cidr; do 248 echo -n "deleting address $cidr... " 249 ip addr del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed" 250 done < "$state" 251 rm -f "$state" 252 fi 253 ''; 254 }; 255 256 createTunDevice = i: nameValuePair "${i.name}-netdev" 257 { description = "Virtual Network Interface ${i.name}"; 258 bindsTo = optional (!config.boot.isContainer) "dev-net-tun.device"; 259 after = optional (!config.boot.isContainer) "dev-net-tun.device" ++ [ "network-pre.target" ]; 260 wantedBy = [ "network-setup.service" (subsystemDevice i.name) ]; 261 partOf = [ "network-setup.service" ]; 262 before = [ "network-setup.service" ]; 263 path = [ pkgs.iproute2 ]; 264 serviceConfig = { 265 Type = "oneshot"; 266 RemainAfterExit = true; 267 }; 268 script = '' 269 ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}" 270 ''; 271 postStop = '' 272 ip link del ${i.name} || true 273 ''; 274 }; 275 276 createBridgeDevice = n: v: nameValuePair "${n}-netdev" 277 (let 278 deps = concatLists (map deviceDependency v.interfaces); 279 in 280 { description = "Bridge Interface ${n}"; 281 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 282 bindsTo = deps ++ optional v.rstp "mstpd.service"; 283 partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service"; 284 after = [ "network-pre.target" ] ++ deps ++ optional v.rstp "mstpd.service" 285 ++ map (i: "network-addresses-${i}.service") v.interfaces; 286 before = [ "network-setup.service" ]; 287 serviceConfig.Type = "oneshot"; 288 serviceConfig.RemainAfterExit = true; 289 path = [ pkgs.iproute2 ]; 290 script = '' 291 # Remove Dead Interfaces 292 echo "Removing old bridge ${n}..." 293 ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}" 294 295 echo "Adding bridge ${n}..." 296 ip link add name "${n}" type bridge 297 298 # Enslave child interfaces 299 ${flip concatMapStrings v.interfaces (i: '' 300 ip link set "${i}" master "${n}" 301 ip link set "${i}" up 302 '')} 303 # Save list of enslaved interfaces 304 echo "${flip concatMapStrings v.interfaces (i: '' 305 ${i} 306 '')}" > /run/${n}.interfaces 307 308 ${optionalString config.virtualisation.libvirtd.enable '' 309 # Enslave dynamically added interfaces which may be lost on nixos-rebuild 310 # 311 # if `libvirtd.service` is not running, do not use `virsh` which would try activate it via 'libvirtd.socket' and thus start it out-of-order. 312 # `libvirtd.service` will set up bridge interfaces when it will start normally. 313 # 314 if /run/current-system/systemd/bin/systemctl --quiet is-active 'libvirtd.service'; then 315 for uri in qemu:///system lxc:///; do 316 for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do 317 ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \ 318 ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \ 319 ${pkgs.bash}/bin/bash 320 done 321 done 322 fi 323 ''} 324 325 # Enable stp on the interface 326 ${optionalString v.rstp '' 327 echo 2 >/sys/class/net/${n}/bridge/stp_state 328 ''} 329 330 ip link set "${n}" up 331 ''; 332 postStop = '' 333 ip link set "${n}" down || true 334 ip link del "${n}" || true 335 rm -f /run/${n}.interfaces 336 ''; 337 reload = '' 338 # Un-enslave child interfaces (old list of interfaces) 339 for interface in `cat /run/${n}.interfaces`; do 340 ip link set "$interface" nomaster up 341 done 342 343 # Enslave child interfaces (new list of interfaces) 344 ${flip concatMapStrings v.interfaces (i: '' 345 ip link set "${i}" master "${n}" 346 ip link set "${i}" up 347 '')} 348 # Save list of enslaved interfaces 349 echo "${flip concatMapStrings v.interfaces (i: '' 350 ${i} 351 '')}" > /run/${n}.interfaces 352 353 # (Un-)set stp on the bridge 354 echo ${if v.rstp then "2" else "0"} > /sys/class/net/${n}/bridge/stp_state 355 ''; 356 reloadIfChanged = true; 357 }); 358 359 createVswitchDevice = n: v: nameValuePair "${n}-netdev" 360 (let 361 deps = concatLists (map deviceDependency (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces))); 362 internalConfigs = map (i: "network-addresses-${i}.service") (attrNames (filterAttrs (_: config: config.type == "internal") v.interfaces)); 363 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; 364 in 365 { description = "Open vSwitch Interface ${n}"; 366 wantedBy = [ "network-setup.service" (subsystemDevice n) ] ++ internalConfigs; 367 # before = [ "network-setup.service" ]; 368 # should work without internalConfigs dependencies because address/link configuration depends 369 # on the device, which is created by ovs-vswitchd with type=internal, but it does not... 370 before = [ "network-setup.service" ] ++ internalConfigs; 371 partOf = [ "network-setup.service" ]; # shutdown the bridge when network is shutdown 372 bindsTo = [ "ovs-vswitchd.service" ]; # requires ovs-vswitchd to be alive at all times 373 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps; # start switch after physical interfaces and vswitch daemon 374 wants = deps; # if one or more interface fails, the switch should continue to run 375 serviceConfig.Type = "oneshot"; 376 serviceConfig.RemainAfterExit = true; 377 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ]; 378 preStart = '' 379 echo "Resetting Open vSwitch ${n}..." 380 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ 381 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} 382 ''; 383 script = '' 384 echo "Configuring Open vSwitch ${n}..." 385 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \ 386 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \ 387 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ 388 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} 389 390 391 echo "Adding OpenFlow rules for Open vSwitch ${n}..." 392 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} 393 ''; 394 postStop = '' 395 echo "Cleaning Open vSwitch ${n}" 396 echo "Shuting down internal ${n} interface" 397 ip link set ${n} down || true 398 echo "Deleting flows for ${n}" 399 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true 400 echo "Deleting Open vSwitch ${n}" 401 ovs-vsctl --if-exists del-br ${n} || true 402 ''; 403 }); 404 405 createBondDevice = n: v: nameValuePair "${n}-netdev" 406 (let 407 deps = concatLists (map deviceDependency v.interfaces); 408 in 409 { description = "Bond Interface ${n}"; 410 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 411 bindsTo = deps; 412 partOf = [ "network-setup.service" ]; 413 after = [ "network-pre.target" ] ++ deps 414 ++ map (i: "network-addresses-${i}.service") v.interfaces; 415 before = [ "network-setup.service" ]; 416 serviceConfig.Type = "oneshot"; 417 serviceConfig.RemainAfterExit = true; 418 path = [ pkgs.iproute2 pkgs.gawk ]; 419 script = '' 420 echo "Destroying old bond ${n}..." 421 ${destroyBond n} 422 423 echo "Creating new bond ${n}..." 424 ip link add name "${n}" type bond \ 425 ${let opts = (mapAttrs (const toString) 426 (bondDeprecation.filterDeprecated v)) 427 // v.driverOptions; 428 in concatStringsSep "\n" 429 (mapAttrsToList (set: val: " ${set} ${val} \\") opts)} 430 431 # !!! There must be a better way to wait for the interface 432 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done; 433 434 # Bring up the bond and enslave the specified interfaces 435 ip link set "${n}" up 436 ${flip concatMapStrings v.interfaces (i: '' 437 ip link set "${i}" down 438 ip link set "${i}" master "${n}" 439 '')} 440 ''; 441 postStop = destroyBond n; 442 }); 443 444 createMacvlanDevice = n: v: nameValuePair "${n}-netdev" 445 (let 446 deps = deviceDependency v.interface; 447 in 448 { description = "Vlan Interface ${n}"; 449 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 450 bindsTo = deps; 451 partOf = [ "network-setup.service" ]; 452 after = [ "network-pre.target" ] ++ deps; 453 before = [ "network-setup.service" ]; 454 serviceConfig.Type = "oneshot"; 455 serviceConfig.RemainAfterExit = true; 456 path = [ pkgs.iproute2 ]; 457 script = '' 458 # Remove Dead Interfaces 459 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 460 ip link add link "${v.interface}" name "${n}" type macvlan \ 461 ${optionalString (v.mode != null) "mode ${v.mode}"} 462 ip link set "${n}" up 463 ''; 464 postStop = '' 465 ip link delete "${n}" || true 466 ''; 467 }); 468 469 createFouEncapsulation = n: v: nameValuePair "${n}-fou-encap" 470 (let 471 # if we have a device to bind to we can wait for its addresses to be 472 # configured, otherwise external sequencing is required. 473 deps = optionals (v.local != null && v.local.dev != null) 474 (deviceDependency v.local.dev ++ [ "network-addresses-${v.local.dev}.service" ]); 475 fouSpec = "port ${toString v.port} ${ 476 if v.protocol != null then "ipproto ${toString v.protocol}" else "gue" 477 } ${ 478 optionalString (v.local != null) "local ${escapeShellArg v.local.address} ${ 479 optionalString (v.local.dev != null) "dev ${escapeShellArg v.local.dev}" 480 }" 481 }"; 482 in 483 { description = "FOU endpoint ${n}"; 484 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 485 bindsTo = deps; 486 partOf = [ "network-setup.service" ]; 487 after = [ "network-pre.target" ] ++ deps; 488 before = [ "network-setup.service" ]; 489 serviceConfig.Type = "oneshot"; 490 serviceConfig.RemainAfterExit = true; 491 path = [ pkgs.iproute2 ]; 492 script = '' 493 # always remove previous incarnation since show can't filter 494 ip fou del ${fouSpec} >/dev/null 2>&1 || true 495 ip fou add ${fouSpec} 496 ''; 497 postStop = '' 498 ip fou del ${fouSpec} || true 499 ''; 500 }); 501 502 createSitDevice = n: v: nameValuePair "${n}-netdev" 503 (let 504 deps = deviceDependency v.dev; 505 in 506 { description = "6-to-4 Tunnel Interface ${n}"; 507 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 508 bindsTo = deps; 509 partOf = [ "network-setup.service" ]; 510 after = [ "network-pre.target" ] ++ deps; 511 before = [ "network-setup.service" ]; 512 serviceConfig.Type = "oneshot"; 513 serviceConfig.RemainAfterExit = true; 514 path = [ pkgs.iproute2 ]; 515 script = '' 516 # Remove Dead Interfaces 517 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 518 ip link add name "${n}" type sit \ 519 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ 520 ${optionalString (v.local != null) "local \"${v.local}\""} \ 521 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \ 522 ${optionalString (v.dev != null) "dev \"${v.dev}\""} \ 523 ${optionalString (v.encapsulation != null) 524 "encap ${v.encapsulation.type} encap-dport ${toString v.encapsulation.port} ${ 525 optionalString (v.encapsulation.sourcePort != null) 526 "encap-sport ${toString v.encapsulation.sourcePort}" 527 }"} 528 ip link set "${n}" up 529 ''; 530 postStop = '' 531 ip link delete "${n}" || true 532 ''; 533 }); 534 535 createVlanDevice = n: v: nameValuePair "${n}-netdev" 536 (let 537 deps = deviceDependency v.interface; 538 in 539 { description = "Vlan Interface ${n}"; 540 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 541 bindsTo = deps; 542 partOf = [ "network-setup.service" ]; 543 after = [ "network-pre.target" ] ++ deps; 544 before = [ "network-setup.service" ]; 545 serviceConfig.Type = "oneshot"; 546 serviceConfig.RemainAfterExit = true; 547 path = [ pkgs.iproute2 ]; 548 script = '' 549 # Remove Dead Interfaces 550 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 551 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" 552 553 # We try to bring up the logical VLAN interface. If the master 554 # interface the logical interface is dependent upon is not up yet we will 555 # fail to immediately bring up the logical interface. The resulting logical 556 # interface will brought up later when the master interface is up. 557 ip link set "${n}" up || true 558 ''; 559 postStop = '' 560 ip link delete "${n}" || true 561 ''; 562 }); 563 564 in listToAttrs ( 565 map configureAddrs interfaces ++ 566 map createTunDevice (filter (i: i.virtual) interfaces)) 567 // mapAttrs' createBridgeDevice cfg.bridges 568 // mapAttrs' createVswitchDevice cfg.vswitches 569 // mapAttrs' createBondDevice cfg.bonds 570 // mapAttrs' createMacvlanDevice cfg.macvlans 571 // mapAttrs' createFouEncapsulation cfg.fooOverUDP 572 // mapAttrs' createSitDevice cfg.sits 573 // mapAttrs' createVlanDevice cfg.vlans 574 // { 575 network-setup = networkSetup; 576 network-local-commands = networkLocalCommands; 577 }; 578 579 services.udev.extraRules = 580 '' 581 KERNEL=="tun", TAG+="systemd" 582 ''; 583 584 585 }; 586 587in 588 589{ 590 config = mkMerge [ 591 bondWarnings 592 (mkIf (!cfg.useNetworkd) normalConfig) 593 { # Ensure slave interfaces are brought up 594 networking.interfaces = genAttrs slaves (i: {}); 595 } 596 ]; 597}