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