at 23.05-pre 20 kB view raw
1{ config, lib, utils, pkgs, ... }: 2 3with utils; 4with lib; 5 6let 7 8 cfg = config.networking; 9 interfaces = attrValues cfg.interfaces; 10 11 interfaceIps = i: 12 i.ipv4.addresses 13 ++ optionals cfg.enableIPv6 i.ipv6.addresses; 14 15 interfaceRoutes = i: 16 i.ipv4.routes 17 ++ optionals cfg.enableIPv6 i.ipv6.routes; 18 19 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no"; 20 21 slaves = 22 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) 23 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) 24 ++ map (sit: sit.dev) (attrValues cfg.sits) 25 ++ map (gre: gre.dev) (attrValues cfg.greTunnels) 26 ++ map (vlan: vlan.interface) (attrValues cfg.vlans) 27 # add dependency to physical or independently created vswitch member interface 28 # TODO: warn the user that any address configured on those interfaces will be useless 29 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches); 30 31in 32 33{ 34 35 config = mkIf cfg.useNetworkd { 36 37 assertions = [ { 38 assertion = cfg.defaultGatewayWindowSize == null; 39 message = "networking.defaultGatewayWindowSize is not supported by networkd."; 40 } { 41 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null; 42 message = "networking.defaultGateway.interface is not supported by networkd."; 43 } { 44 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null; 45 message = "networking.defaultGateway6.interface is not supported by networkd."; 46 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: { 47 assertion = !rstp; 48 message = "networking.bridges.${n}.rstp is not supported by networkd."; 49 }) ++ flip mapAttrsToList cfg.fooOverUDP (n: { local, ... }: { 50 assertion = local == null; 51 message = "networking.fooOverUDP.${n}.local is not supported by networkd."; 52 }); 53 54 networking.dhcpcd.enable = mkDefault false; 55 56 systemd.network = 57 let 58 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain); 59 genericNetwork = override: 60 let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address 61 ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address; 62 makeGateway = gateway: { 63 routeConfig = { 64 Gateway = gateway; 65 GatewayOnLink = false; 66 }; 67 }; 68 in optionalAttrs (gateway != [ ]) { 69 routes = override (map makeGateway gateway); 70 } // optionalAttrs (domains != [ ]) { 71 domains = override domains; 72 }; 73 in mkMerge [ { 74 enable = true; 75 } 76 (mkIf cfg.useDHCP { 77 networks."99-ethernet-default-dhcp" = lib.mkIf cfg.useDHCP { 78 # We want to match physical ethernet interfaces as commonly 79 # found on laptops, desktops and servers, to provide an 80 # "out-of-the-box" setup that works for common cases. This 81 # heuristic isn't perfect (it could match interfaces with 82 # custom names that _happen_ to start with en or eth), but 83 # should be good enough to make the common case easy and can 84 # be overridden on a case-by-case basis using 85 # higher-priority networks or by disabling useDHCP. 86 87 # Type=ether matches veth interfaces as well, and this is 88 # more likely to result in interfaces being configured to 89 # use DHCP when they shouldn't. 90 91 # When wait-online.anyInterface is enabled, RequiredForOnline really 92 # means "sufficient for online", so we can enable it. 93 # Otherwise, don't block the network coming online because of default networks. 94 matchConfig.Name = ["en*" "eth*"]; 95 DHCP = "yes"; 96 linkConfig.RequiredForOnline = 97 lib.mkDefault config.systemd.network.wait-online.anyInterface; 98 networkConfig.IPv6PrivacyExtensions = "kernel"; 99 }; 100 networks."99-wireless-client-dhcp" = lib.mkIf cfg.useDHCP { 101 # Like above, but this is much more likely to be correct. 102 matchConfig.WLANInterfaceType = "station"; 103 DHCP = "yes"; 104 linkConfig.RequiredForOnline = 105 lib.mkDefault config.systemd.network.wait-online.anyInterface; 106 networkConfig.IPv6PrivacyExtensions = "kernel"; 107 # We also set the route metric to one more than the default 108 # of 1024, so that Ethernet is preferred if both are 109 # available. 110 dhcpV4Config.RouteMetric = 1025; 111 ipv6AcceptRAConfig.RouteMetric = 1025; 112 }; 113 }) 114 (mkMerge (forEach interfaces (i: { 115 netdevs = mkIf i.virtual ({ 116 "40-${i.name}" = { 117 netdevConfig = { 118 Name = i.name; 119 Kind = i.virtualType; 120 }; 121 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) { 122 User = i.virtualOwner; 123 }; 124 }; 125 }); 126 networks."40-${i.name}" = mkMerge [ (genericNetwork id) { 127 name = mkDefault i.name; 128 DHCP = mkForce (dhcpStr 129 (if i.useDHCP != null then i.useDHCP else false)); 130 address = forEach (interfaceIps i) 131 (ip: "${ip.address}/${toString ip.prefixLength}"); 132 routes = forEach (interfaceRoutes i) 133 (route: { 134 # Most of these route options have not been tested. 135 # Please fix or report any mistakes you may find. 136 routeConfig = 137 optionalAttrs (route.address != null && route.prefixLength != null) { 138 Destination = "${route.address}/${toString route.prefixLength}"; 139 } // 140 optionalAttrs (route.options ? fastopen_no_cookie) { 141 FastOpenNoCookie = route.options.fastopen_no_cookie; 142 } // 143 optionalAttrs (route.via != null) { 144 Gateway = route.via; 145 } // 146 optionalAttrs (route.type != null) { 147 Type = route.type; 148 } // 149 optionalAttrs (route.options ? onlink) { 150 GatewayOnLink = true; 151 } // 152 optionalAttrs (route.options ? initrwnd) { 153 InitialAdvertisedReceiveWindow = route.options.initrwnd; 154 } // 155 optionalAttrs (route.options ? initcwnd) { 156 InitialCongestionWindow = route.options.initcwnd; 157 } // 158 optionalAttrs (route.options ? pref) { 159 IPv6Preference = route.options.pref; 160 } // 161 optionalAttrs (route.options ? mtu) { 162 MTUBytes = route.options.mtu; 163 } // 164 optionalAttrs (route.options ? metric) { 165 Metric = route.options.metric; 166 } // 167 optionalAttrs (route.options ? src) { 168 PreferredSource = route.options.src; 169 } // 170 optionalAttrs (route.options ? protocol) { 171 Protocol = route.options.protocol; 172 } // 173 optionalAttrs (route.options ? quickack) { 174 QuickAck = route.options.quickack; 175 } // 176 optionalAttrs (route.options ? scope) { 177 Scope = route.options.scope; 178 } // 179 optionalAttrs (route.options ? from) { 180 Source = route.options.from; 181 } // 182 optionalAttrs (route.options ? table) { 183 Table = route.options.table; 184 } // 185 optionalAttrs (route.options ? advmss) { 186 TCPAdvertisedMaximumSegmentSize = route.options.advmss; 187 } // 188 optionalAttrs (route.options ? ttl-propagate) { 189 TTLPropagate = route.options.ttl-propagate == "enabled"; 190 }; 191 }); 192 networkConfig.IPv6PrivacyExtensions = "kernel"; 193 linkConfig = optionalAttrs (i.macAddress != null) { 194 MACAddress = i.macAddress; 195 } // optionalAttrs (i.mtu != null) { 196 MTUBytes = toString i.mtu; 197 }; 198 }]; 199 }))) 200 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { 201 netdevs."40-${name}" = { 202 netdevConfig = { 203 Name = name; 204 Kind = "bridge"; 205 }; 206 }; 207 networks = listToAttrs (forEach bridge.interfaces (bi: 208 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { 209 DHCP = mkOverride 0 (dhcpStr false); 210 networkConfig.Bridge = name; 211 } ]))); 212 }))) 213 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: { 214 netdevs."40-${name}" = { 215 netdevConfig = { 216 Name = name; 217 Kind = "bond"; 218 }; 219 bondConfig = let 220 # manual mapping as of 2017-02-03 221 # man 5 systemd.netdev [BOND] 222 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt 223 # driver options. 224 driverOptionMapping = let 225 trans = f: optName: { valTransform = f; optNames = [optName]; }; 226 simp = trans id; 227 ms = trans (v: v + "ms"); 228 in { 229 Mode = simp "mode"; 230 TransmitHashPolicy = simp "xmit_hash_policy"; 231 LACPTransmitRate = simp "lacp_rate"; 232 MIIMonitorSec = ms "miimon"; 233 UpDelaySec = ms "updelay"; 234 DownDelaySec = ms "downdelay"; 235 LearnPacketIntervalSec = simp "lp_interval"; 236 AdSelect = simp "ad_select"; 237 FailOverMACPolicy = simp "fail_over_mac"; 238 ARPValidate = simp "arp_validate"; 239 # apparently in ms for this value?! Upstream bug? 240 ARPIntervalSec = simp "arp_interval"; 241 ARPIPTargets = simp "arp_ip_target"; 242 ARPAllTargets = simp "arp_all_targets"; 243 PrimaryReselectPolicy = simp "primary_reselect"; 244 ResendIGMP = simp "resend_igmp"; 245 PacketsPerSlave = simp "packets_per_slave"; 246 GratuitousARP = { valTransform = id; 247 optNames = [ "num_grat_arp" "num_unsol_na" ]; }; 248 AllSlavesActive = simp "all_slaves_active"; 249 MinLinks = simp "min_links"; 250 }; 251 252 do = bond.driverOptions; 253 assertNoUnknownOption = let 254 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames) 255 driverOptionMapping); 256 # options that apparently don’t exist in the networkd config 257 unknownOptions = [ "primary" ]; 258 assertTrace = bool: msg: if bool then true else builtins.trace msg false; 259 in assert all (driverOpt: assertTrace 260 (elem driverOpt (knownOptions ++ unknownOptions)) 261 "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.") 262 (mapAttrsToList (k: _: k) do); ""; 263 # get those driverOptions that have been set 264 filterSystemdOptions = filterAttrs (sysDOpt: kOpts: 265 any (kOpt: do ? ${kOpt}) kOpts.optNames); 266 # build final set of systemd options to bond values 267 buildOptionSet = mapAttrs (_: kOpts: with kOpts; 268 # we simply take the first set kernel bond option 269 # (one option has multiple names, which is silly) 270 head (map (optN: valTransform (do.${optN})) 271 # only map those that exist 272 (filter (o: do ? ${o}) optNames))); 273 in seq assertNoUnknownOption 274 (buildOptionSet (filterSystemdOptions driverOptionMapping)); 275 276 }; 277 278 networks = listToAttrs (forEach bond.interfaces (bi: 279 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { 280 DHCP = mkOverride 0 (dhcpStr false); 281 networkConfig.Bond = name; 282 } ]))); 283 }))) 284 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: { 285 netdevs."40-${name}" = { 286 netdevConfig = { 287 Name = name; 288 Kind = "macvlan"; 289 }; 290 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; }; 291 }; 292 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 293 macvlan = [ name ]; 294 } ]); 295 }))) 296 (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: { 297 netdevs."40-${name}" = { 298 netdevConfig = { 299 Name = name; 300 Kind = "fou"; 301 }; 302 # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes, 303 # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature 304 # in networkd. 305 fooOverUDPConfig = { 306 Port = fou.port; 307 Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation"; 308 } // (optionalAttrs (fou.protocol != null) { 309 Protocol = fou.protocol; 310 }); 311 }; 312 }))) 313 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: { 314 netdevs."40-${name}" = { 315 netdevConfig = { 316 Name = name; 317 Kind = "sit"; 318 }; 319 tunnelConfig = 320 (optionalAttrs (sit.remote != null) { 321 Remote = sit.remote; 322 }) // (optionalAttrs (sit.local != null) { 323 Local = sit.local; 324 }) // (optionalAttrs (sit.ttl != null) { 325 TTL = sit.ttl; 326 }) // (optionalAttrs (sit.encapsulation != null) ( 327 { 328 FooOverUDP = true; 329 Encapsulation = 330 if sit.encapsulation.type == "fou" 331 then "FooOverUDP" 332 else "GenericUDPEncapsulation"; 333 FOUDestinationPort = sit.encapsulation.port; 334 } // (optionalAttrs (sit.encapsulation.sourcePort != null) { 335 FOUSourcePort = sit.encapsulation.sourcePort; 336 }))); 337 }; 338 networks = mkIf (sit.dev != null) { 339 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 340 tunnel = [ name ]; 341 } ]); 342 }; 343 }))) 344 (mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: { 345 netdevs."40-${name}" = { 346 netdevConfig = { 347 Name = name; 348 Kind = gre.type; 349 }; 350 tunnelConfig = 351 (optionalAttrs (gre.remote != null) { 352 Remote = gre.remote; 353 }) // (optionalAttrs (gre.local != null) { 354 Local = gre.local; 355 }) // (optionalAttrs (gre.ttl != null) { 356 TTL = gre.ttl; 357 }); 358 }; 359 networks = mkIf (gre.dev != null) { 360 "40-${gre.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 361 tunnel = [ name ]; 362 } ]); 363 }; 364 }))) 365 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: { 366 netdevs."40-${name}" = { 367 netdevConfig = { 368 Name = name; 369 Kind = "vlan"; 370 }; 371 vlanConfig.Id = vlan.id; 372 }; 373 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 374 vlan = [ name ]; 375 } ]); 376 }))) 377 ]; 378 379 # We need to prefill the slaved devices with networking options 380 # This forces the network interface creator to initialize slaves. 381 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves); 382 383 systemd.services = let 384 # We must escape interfaces due to the systemd interpretation 385 subsystemDevice = interface: 386 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; 387 # support for creating openvswitch switches 388 createVswitchDevice = n: v: nameValuePair "${n}-netdev" 389 (let 390 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces)); 391 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; 392 in 393 { description = "Open vSwitch Interface ${n}"; 394 wantedBy = [ "network.target" (subsystemDevice n) ]; 395 # and create bridge before systemd-networkd starts because it might create internal interfaces 396 before = [ "systemd-networkd.service" ]; 397 # shutdown the bridge when network is shutdown 398 partOf = [ "network.target" ]; 399 # requires ovs-vswitchd to be alive at all times 400 bindsTo = [ "ovs-vswitchd.service" ]; 401 # start switch after physical interfaces and vswitch daemon 402 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps; 403 wants = deps; # if one or more interface fails, the switch should continue to run 404 serviceConfig.Type = "oneshot"; 405 serviceConfig.RemainAfterExit = true; 406 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ]; 407 preStart = '' 408 echo "Resetting Open vSwitch ${n}..." 409 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ 410 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} 411 ''; 412 script = '' 413 echo "Configuring Open vSwitch ${n}..." 414 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \ 415 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \ 416 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ 417 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} 418 419 420 echo "Adding OpenFlow rules for Open vSwitch ${n}..." 421 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} 422 ''; 423 postStop = '' 424 echo "Cleaning Open vSwitch ${n}" 425 echo "Shuting down internal ${n} interface" 426 ip link set ${n} down || true 427 echo "Deleting flows for ${n}" 428 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true 429 echo "Deleting Open vSwitch ${n}" 430 ovs-vsctl --if-exists del-br ${n} || true 431 ''; 432 }); 433 in mapAttrs' createVswitchDevice cfg.vswitches 434 // { 435 "network-local-commands" = { 436 after = [ "systemd-networkd.service" ]; 437 bindsTo = [ "systemd-networkd.service" ]; 438 }; 439 }; 440 }; 441 442}