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