at master 22 kB view raw
1{ 2 config, 3 lib, 4 utils, 5 pkgs, 6 ... 7}: 8 9with utils; 10with lib; 11 12let 13 14 cfg = config.networking; 15 interfaces = attrValues cfg.interfaces; 16 17 interfaceIps = i: i.ipv4.addresses ++ optionals cfg.enableIPv6 i.ipv6.addresses; 18 19 interfaceRoutes = i: i.ipv4.routes ++ optionals cfg.enableIPv6 i.ipv6.routes; 20 21 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no"; 22 23 slaves = 24 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) 25 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) 26 ++ map (sit: sit.dev) (attrValues cfg.sits) 27 ++ map (ipip: ipip.dev) (attrValues cfg.ipips) 28 ++ map (gre: gre.dev) (attrValues cfg.greTunnels) 29 ++ map (vlan: vlan.interface) (attrValues cfg.vlans) 30 # add dependency to physical or independently created vswitch member interface 31 # TODO: warn the user that any address configured on those interfaces will be useless 32 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) ( 33 attrValues cfg.vswitches 34 ); 35 36 defaultGateways = mkMerge ( 37 forEach [ cfg.defaultGateway cfg.defaultGateway6 ] ( 38 gateway: 39 optionalAttrs (gateway != null && gateway.interface != null) { 40 networks."40-${gateway.interface}" = { 41 matchConfig.Name = gateway.interface; 42 routes = [ 43 ( 44 { 45 Gateway = gateway.address; 46 } 47 // optionalAttrs (gateway.metric != null) { 48 Metric = gateway.metric; 49 } 50 // optionalAttrs (gateway.source != null) { 51 PreferredSource = gateway.source; 52 } 53 ) 54 ]; 55 }; 56 } 57 ) 58 ); 59 60 genericDhcpNetworks = mkIf cfg.useDHCP { 61 networks."99-ethernet-default-dhcp" = { 62 matchConfig = { 63 Type = "ether"; 64 Kind = "!*"; # physical interfaces have no kind 65 }; 66 DHCP = "yes"; 67 networkConfig.IPv6PrivacyExtensions = "kernel"; 68 }; 69 networks."99-wireless-client-dhcp" = { 70 matchConfig.WLANInterfaceType = "station"; 71 DHCP = "yes"; 72 networkConfig.IPv6PrivacyExtensions = "kernel"; 73 # We also set the route metric to one more than the default 74 # of 1024, so that Ethernet is preferred if both are 75 # available. 76 dhcpV4Config.RouteMetric = 1025; 77 ipv6AcceptRAConfig.RouteMetric = 1025; 78 }; 79 }; 80 81 interfaceNetworks = mkMerge ( 82 forEach interfaces (i: { 83 links = mkIf i.wakeOnLan.enable { 84 "40-${i.name}" = { 85 matchConfig.name = i.name; 86 linkConfig.WakeOnLan = concatStringsSep " " i.wakeOnLan.policy; 87 }; 88 }; 89 netdevs = mkIf i.virtual { 90 "40-${i.name}" = { 91 netdevConfig = { 92 Name = i.name; 93 Kind = i.virtualType; 94 }; 95 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) { 96 User = i.virtualOwner; 97 }; 98 }; 99 }; 100 networks."40-${i.name}" = { 101 name = mkDefault i.name; 102 DHCP = mkForce ( 103 dhcpStr ( 104 if i.useDHCP != null then i.useDHCP else (config.networking.useDHCP && i.ipv4.addresses == [ ]) 105 ) 106 ); 107 address = forEach (interfaceIps i) (ip: "${ip.address}/${toString ip.prefixLength}"); 108 routes = forEach (interfaceRoutes i) ( 109 route: 110 mkMerge [ 111 # Most of these route options have not been tested. 112 # Please fix or report any mistakes you may find. 113 (mkIf (route.address != null && route.prefixLength != null) { 114 Destination = "${route.address}/${toString route.prefixLength}"; 115 }) 116 (mkIf (route.options ? fastopen_no_cookie) { 117 FastOpenNoCookie = route.options.fastopen_no_cookie; 118 }) 119 (mkIf (route.via != null) { 120 Gateway = route.via; 121 }) 122 (mkIf (route.type != null) { 123 Type = route.type; 124 }) 125 (mkIf (route.options ? onlink) { 126 GatewayOnLink = true; 127 }) 128 (mkIf (route.options ? initrwnd) { 129 InitialAdvertisedReceiveWindow = route.options.initrwnd; 130 }) 131 (mkIf (route.options ? initcwnd) { 132 InitialCongestionWindow = route.options.initcwnd; 133 }) 134 (mkIf (route.options ? pref) { 135 IPv6Preference = route.options.pref; 136 }) 137 (mkIf (route.options ? mtu) { 138 MTUBytes = route.options.mtu; 139 }) 140 (mkIf (route.options ? metric) { 141 Metric = route.options.metric; 142 }) 143 (mkIf (route.options ? src) { 144 PreferredSource = route.options.src; 145 }) 146 (mkIf (route.options ? protocol) { 147 Protocol = route.options.protocol; 148 }) 149 (mkIf (route.options ? quickack) { 150 QuickAck = route.options.quickack; 151 }) 152 (mkIf (route.options ? scope) { 153 Scope = route.options.scope; 154 }) 155 (mkIf (route.options ? from) { 156 Source = route.options.from; 157 }) 158 (mkIf (route.options ? table) { 159 Table = route.options.table; 160 }) 161 (mkIf (route.options ? advmss) { 162 TCPAdvertisedMaximumSegmentSize = route.options.advmss; 163 }) 164 (mkIf (route.options ? ttl-propagate) { 165 TTLPropagate = route.options.ttl-propagate == "enabled"; 166 }) 167 ] 168 ); 169 networkConfig.IPv6PrivacyExtensions = "kernel"; 170 linkConfig = 171 optionalAttrs (i.macAddress != null) { 172 MACAddress = i.macAddress; 173 } 174 // optionalAttrs (i.mtu != null) { 175 MTUBytes = toString i.mtu; 176 }; 177 bridgeConfig = optionalAttrs i.proxyARP { 178 ProxyARP = i.proxyARP; 179 }; 180 }; 181 }) 182 ); 183 184 bridgeNetworks = mkMerge ( 185 flip mapAttrsToList cfg.bridges ( 186 name: bridge: { 187 netdevs."40-${name}" = { 188 netdevConfig = { 189 Name = name; 190 Kind = "bridge"; 191 }; 192 }; 193 networks = listToAttrs ( 194 forEach bridge.interfaces ( 195 bi: 196 nameValuePair "40-${bi}" { 197 DHCP = mkOverride 0 (dhcpStr false); 198 networkConfig.Bridge = name; 199 } 200 ) 201 ); 202 } 203 ) 204 ); 205 206 vlanNetworks = mkMerge ( 207 flip mapAttrsToList cfg.vlans ( 208 name: vlan: { 209 netdevs."40-${name}" = { 210 netdevConfig = { 211 Name = name; 212 Kind = "vlan"; 213 }; 214 vlanConfig.Id = vlan.id; 215 }; 216 networks."40-${vlan.interface}" = { 217 vlan = [ name ]; 218 }; 219 } 220 ) 221 ); 222 223in 224 225{ 226 config = mkMerge [ 227 228 (mkIf config.boot.initrd.network.enable { 229 # Note this is if initrd.network.enable, not if 230 # initrd.systemd.network.enable. By setting the latter and not the 231 # former, the user retains full control over the configuration. 232 boot.initrd.systemd.network = mkMerge [ 233 defaultGateways 234 genericDhcpNetworks 235 interfaceNetworks 236 bridgeNetworks 237 vlanNetworks 238 ]; 239 boot.initrd.availableKernelModules = 240 optional (cfg.bridges != { }) "bridge" ++ optional (cfg.vlans != { }) "8021q"; 241 }) 242 243 (mkIf cfg.useNetworkd { 244 245 assertions = [ 246 { 247 assertion = cfg.defaultGatewayWindowSize == null; 248 message = "networking.defaultGatewayWindowSize is not supported by networkd."; 249 } 250 { 251 assertion = cfg.defaultGateway != null -> cfg.defaultGateway.interface != null; 252 message = "networking.defaultGateway.interface is not optional when using networkd."; 253 } 254 { 255 assertion = cfg.defaultGateway6 != null -> cfg.defaultGateway6.interface != null; 256 message = "networking.defaultGateway6.interface is not optional when using networkd."; 257 } 258 ] 259 ++ flip mapAttrsToList cfg.bridges ( 260 n: 261 { rstp, ... }: 262 { 263 assertion = !rstp; 264 message = "networking.bridges.${n}.rstp is not supported by networkd."; 265 } 266 ) 267 ++ flip mapAttrsToList cfg.fooOverUDP ( 268 n: 269 { local, ... }: 270 { 271 assertion = local == null; 272 message = "networking.fooOverUDP.${n}.local is not supported by networkd."; 273 } 274 ); 275 276 networking.dhcpcd.enable = mkDefault false; 277 278 systemd.network = mkMerge [ 279 { 280 enable = true; 281 } 282 defaultGateways 283 genericDhcpNetworks 284 interfaceNetworks 285 bridgeNetworks 286 (mkMerge ( 287 flip mapAttrsToList cfg.bonds ( 288 name: bond: { 289 netdevs."40-${name}" = { 290 netdevConfig = { 291 Name = name; 292 Kind = "bond"; 293 }; 294 bondConfig = 295 let 296 # manual mapping as of 2017-02-03 297 # man 5 systemd.netdev [BOND] 298 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt 299 # driver options. 300 driverOptionMapping = 301 let 302 trans = f: optName: { 303 valTransform = f; 304 optNames = [ optName ]; 305 }; 306 simp = trans id; 307 ms = trans (v: v + "ms"); 308 in 309 { 310 Mode = simp "mode"; 311 TransmitHashPolicy = simp "xmit_hash_policy"; 312 LACPTransmitRate = simp "lacp_rate"; 313 MIIMonitorSec = ms "miimon"; 314 UpDelaySec = ms "updelay"; 315 DownDelaySec = ms "downdelay"; 316 LearnPacketIntervalSec = simp "lp_interval"; 317 AdSelect = simp "ad_select"; 318 FailOverMACPolicy = simp "fail_over_mac"; 319 ARPValidate = simp "arp_validate"; 320 # apparently in ms for this value?! Upstream bug? 321 ARPIntervalSec = simp "arp_interval"; 322 ARPIPTargets = simp "arp_ip_target"; 323 ARPAllTargets = simp "arp_all_targets"; 324 PrimaryReselectPolicy = simp "primary_reselect"; 325 ResendIGMP = simp "resend_igmp"; 326 PacketsPerSlave = simp "packets_per_slave"; 327 GratuitousARP = { 328 valTransform = id; 329 optNames = [ 330 "num_grat_arp" 331 "num_unsol_na" 332 ]; 333 }; 334 AllSlavesActive = simp "all_slaves_active"; 335 MinLinks = simp "min_links"; 336 }; 337 338 do = bond.driverOptions; 339 assertNoUnknownOption = 340 let 341 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames) driverOptionMapping); 342 # options that apparently don’t exist in the networkd config 343 unknownOptions = [ "primary" ]; 344 assertTrace = bool: msg: if bool then true else builtins.trace msg false; 345 in 346 assert all ( 347 driverOpt: 348 assertTrace (elem driverOpt (knownOptions ++ unknownOptions)) 349 "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd." 350 ) (mapAttrsToList (k: _: k) do); 351 ""; 352 # get those driverOptions that have been set 353 filterSystemdOptions = filterAttrs (sysDOpt: kOpts: any (kOpt: do ? ${kOpt}) kOpts.optNames); 354 # build final set of systemd options to bond values 355 buildOptionSet = mapAttrs ( 356 _: kOpts: 357 with kOpts; 358 # we simply take the first set kernel bond option 359 # (one option has multiple names, which is silly) 360 head ( 361 map (optN: valTransform (do.${optN})) 362 # only map those that exist 363 (filter (o: do ? ${o}) optNames) 364 ) 365 ); 366 in 367 seq assertNoUnknownOption (buildOptionSet (filterSystemdOptions driverOptionMapping)); 368 369 }; 370 371 networks = listToAttrs ( 372 forEach bond.interfaces ( 373 bi: 374 nameValuePair "40-${bi}" { 375 DHCP = mkOverride 0 (dhcpStr false); 376 networkConfig.Bond = name; 377 } 378 ) 379 ); 380 } 381 ) 382 )) 383 (mkMerge ( 384 flip mapAttrsToList cfg.macvlans ( 385 name: macvlan: { 386 netdevs."40-${name}" = { 387 netdevConfig = { 388 Name = name; 389 Kind = "macvlan"; 390 }; 391 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; }; 392 }; 393 networks."40-${macvlan.interface}" = { 394 macvlan = [ name ]; 395 }; 396 } 397 ) 398 )) 399 (mkMerge ( 400 flip mapAttrsToList cfg.fooOverUDP ( 401 name: fou: { 402 netdevs."40-${name}" = { 403 netdevConfig = { 404 Name = name; 405 Kind = "fou"; 406 }; 407 # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes, 408 # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature 409 # in networkd. 410 fooOverUDPConfig = { 411 Port = fou.port; 412 Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation"; 413 } 414 // (optionalAttrs (fou.protocol != null) { 415 Protocol = fou.protocol; 416 }); 417 }; 418 } 419 ) 420 )) 421 (mkMerge ( 422 flip mapAttrsToList cfg.sits ( 423 name: sit: { 424 netdevs."40-${name}" = { 425 netdevConfig = { 426 Name = name; 427 Kind = "sit"; 428 }; 429 tunnelConfig = 430 (optionalAttrs (sit.remote != null) { 431 Remote = sit.remote; 432 }) 433 // (optionalAttrs (sit.local != null) { 434 Local = sit.local; 435 }) 436 // (optionalAttrs (sit.ttl != null) { 437 TTL = sit.ttl; 438 }) 439 // (optionalAttrs (sit.encapsulation.type != "6in4") ( 440 { 441 FooOverUDP = true; 442 Encapsulation = if sit.encapsulation.type == "fou" then "FooOverUDP" else "GenericUDPEncapsulation"; 443 FOUDestinationPort = sit.encapsulation.port; 444 } 445 // (optionalAttrs (sit.encapsulation.sourcePort != null) { 446 FOUSourcePort = sit.encapsulation.sourcePort; 447 }) 448 )); 449 }; 450 networks = mkIf (sit.dev != null) { 451 "40-${sit.dev}" = { 452 tunnel = [ name ]; 453 }; 454 }; 455 } 456 ) 457 )) 458 (mkMerge ( 459 flip mapAttrsToList cfg.ipips ( 460 name: ipip: { 461 netdevs."40-${name}" = { 462 netdevConfig = { 463 Name = name; 464 Kind = if ipip.encapsulation.type == "ipip" then "ipip" else "ip6tnl"; 465 }; 466 tunnelConfig = 467 (optionalAttrs (ipip.remote != null) { 468 Remote = ipip.remote; 469 }) 470 // (optionalAttrs (ipip.local != null) { 471 Local = ipip.local; 472 }) 473 // (optionalAttrs (ipip.ttl != null) { 474 TTL = ipip.ttl; 475 }) 476 // (optionalAttrs (ipip.encapsulation.type != "ipip") { 477 # IPv6 tunnel options 478 Mode = if ipip.encapsulation.type == "4in6" then "ipip6" else "ip6ip6"; 479 EncapsulationLimit = ipip.encapsulation.type; 480 }); 481 }; 482 networks = mkIf (ipip.dev != null) { 483 "40-${ipip.dev}" = { 484 tunnel = [ name ]; 485 }; 486 }; 487 } 488 ) 489 )) 490 (mkMerge ( 491 flip mapAttrsToList cfg.greTunnels ( 492 name: gre: { 493 netdevs."40-${name}" = { 494 netdevConfig = { 495 Name = name; 496 Kind = gre.type; 497 }; 498 tunnelConfig = 499 (optionalAttrs (gre.remote != null) { 500 Remote = gre.remote; 501 }) 502 // (optionalAttrs (gre.local != null) { 503 Local = gre.local; 504 }) 505 // (optionalAttrs (gre.ttl != null) { 506 TTL = gre.ttl; 507 }); 508 }; 509 networks = mkIf (gre.dev != null) { 510 "40-${gre.dev}" = { 511 tunnel = [ name ]; 512 }; 513 }; 514 } 515 ) 516 )) 517 vlanNetworks 518 ]; 519 520 # We need to prefill the slaved devices with networking options 521 # This forces the network interface creator to initialize slaves. 522 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves); 523 524 systemd.services = 525 let 526 # We must escape interfaces due to the systemd interpretation 527 subsystemDevice = interface: "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; 528 # support for creating openvswitch switches 529 createVswitchDevice = 530 n: v: 531 nameValuePair "${n}-netdev" ( 532 let 533 deps = map subsystemDevice ( 534 attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces) 535 ); 536 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; 537 in 538 { 539 description = "Open vSwitch Interface ${n}"; 540 wantedBy = [ 541 "network.target" 542 (subsystemDevice n) 543 ]; 544 # and create bridge before systemd-networkd starts because it might create internal interfaces 545 before = [ "systemd-networkd.service" ]; 546 # shutdown the bridge when network is shutdown 547 partOf = [ "network.target" ]; 548 # requires ovs-vswitchd to be alive at all times 549 bindsTo = [ "ovs-vswitchd.service" ]; 550 # start switch after physical interfaces and vswitch daemon 551 after = [ 552 "network-pre.target" 553 "ovs-vswitchd.service" 554 ] 555 ++ deps; 556 wants = deps; # if one or more interface fails, the switch should continue to run 557 serviceConfig.Type = "oneshot"; 558 serviceConfig.RemainAfterExit = true; 559 path = [ 560 pkgs.iproute2 561 config.virtualisation.vswitch.package 562 ]; 563 preStart = '' 564 echo "Resetting Open vSwitch ${n}..." 565 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ 566 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} 567 ''; 568 script = '' 569 echo "Configuring Open vSwitch ${n}..." 570 ovs-vsctl ${ 571 concatStrings ( 572 mapAttrsToList ( 573 name: config: 574 " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}" 575 ) v.interfaces 576 ) 577 } \ 578 ${ 579 concatStrings ( 580 mapAttrsToList ( 581 name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}" 582 ) v.interfaces 583 ) 584 } \ 585 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ 586 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} 587 588 589 echo "Adding OpenFlow rules for Open vSwitch ${n}..." 590 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} 591 ''; 592 postStop = '' 593 echo "Cleaning Open vSwitch ${n}" 594 echo "Shutting down internal ${n} interface" 595 ip link set dev ${n} down || true 596 echo "Deleting flows for ${n}" 597 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true 598 echo "Deleting Open vSwitch ${n}" 599 ovs-vsctl --if-exists del-br ${n} || true 600 ''; 601 } 602 ); 603 in 604 mapAttrs' createVswitchDevice cfg.vswitches 605 // { 606 "network-local-commands" = { 607 after = [ "systemd-networkd.service" ]; 608 bindsTo = [ "systemd-networkd.service" ]; 609 }; 610 }; 611 }) 612 613 ]; 614}