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