{ config, pkgs, lib, modulesPath, zentralwerk, ... }: let inherit (zentralwerk.lib.config.site.net) core; inherit (config.networking) hostName; coreAddress = core.hosts4.${hostName}; meshInterface = "bmx"; meshLoopback = "bmx_prime"; ddmeshRegisterUrl = "https://register.freifunk-dresden.de/bot.php"; ddmeshBroadcast = "10.255.255.255"; inherit (pkgs.c3d2-freifunk) ddmeshRegisterKey; ddmeshNode = 51073; ddmeshAddrPart = "200.74"; rt_table_hosts = 7; rt_table_nets = rt_table_hosts + 1; rt_table_tuns = rt_table_hosts + 2; sysinfo-json = import ./sysinfo-json.nix { inherit pkgs ddmeshNode; }; upstreams = [ "upstream4" "upstream3" ]; upstreamMark = 3; rt_table_upstream = 100; node51001AddrPart = "200.2"; mac = { core = "00:de:13:cb:9a:7b"; bmx = "00:de:13:cb:9a:7c"; }; in { imports = [ "${modulesPath}/profiles/minimal.nix" ]; boot.tmpOnTmpfs = true; boot.postBootCommands = '' if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net mknod -m 666 /dev/net/tun c 10 200 fi ''; c3d2 = { isInHq = false; hq.statistics.enable = true; deployment = { server = "server10"; autoNetSetup = false; }; }; services.collectd.plugins.protocols = ""; microvm.interfaces = [ { type = "tap"; id = "core-freifunk"; mac = mac.core; } { type = "tap"; id = "bmx-freifunk"; mac = mac.bmx; } ]; networking.hostName = "freifunk"; networking.useNetworkd = true; networking.nameservers = [ "172.20.73.8" "9.9.9.9" ]; networking.firewall.enable = false; networking.nat = { enable = true; # This doesn't really work, hence the `extraCommands` externalInterface = meshInterface; #internalInterfaces = [ "core" ]; # Setup routing into Freifunk, # masquerading anything that isn't already their IP range extraCommands = '' ${pkgs.iptables}/bin/iptables -t nat -F POSTROUTING ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING \ \! --source 10.200.0.0/15 -o ${meshInterface} -j SNAT --to 10.200.${ddmeshAddrPart} ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING \ \! --source 10.200.0.0/15 -o ipip-node51001 -j SNAT --to 10.200.${ddmeshAddrPart} ${pkgs.iptables}/bin/iptables -t nat -o bat0 -A POSTROUTING -j MASQUERADE set -e ''; }; # Configure rt_table name networking.iproute2 = { enable = true; rttablesExtraConfig = '' ${toString rt_table_upstream} upstream ${toString rt_table_hosts} bmx_hosts ${toString rt_table_nets} bmx_nets ${toString rt_table_tuns} bmx_tuns ''; }; environment.systemPackages = with pkgs; [ tcpdump bmon wireguard-tools iperf bmxd ]; sops = { age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; defaultSopsFile = ./secrets.yaml; secrets."wireguard/vpn6/privateKey" = { group = "systemd-network"; mode = "0440"; }; }; # unbreak wg-vpn6 ingress path boot.kernel.sysctl."net.ipv4.conf.core.rp_filter" = 0; systemd.network = { netdevs = { # Dummy interface for primary (10.200) address "10-bmx-prime" = { enable = true; netdevConfig = { Kind = "dummy"; Name = meshLoopback; }; }; # Freifunk Dresden Backbone "31-wg-vpn6" = { enable = true; netdevConfig = { Name = "wg-vpn6"; Kind = "wireguard"; MTUBytes = "1320"; }; wireguardConfig = { PrivateKeyFile = config.sops.secrets."wireguard/vpn6/privateKey".path; ListenPort = 5006; # Mark for routing with the upstream routing table FirewallMark = upstreamMark; }; wireguardPeers = [ { wireguardPeerConfig = { # vpn6.freifunk-dresden.de Endpoint = "85.195.253.5:5006"; PublicKey = "CIJa7xiRRIrLtEB7uyzwoyaQcpe0b8F2d16+3hk8KjU="; AllowedIPs = "10.203.0.0/16"; }; } ]; }; "32-ipip-node51001" = { enable = true; netdevConfig = { Name = "ipip-node51001"; Kind = "ipip"; }; tunnelConfig = { Local = "10.203.${ddmeshAddrPart}"; Remote = "10.203.${node51001AddrPart}"; }; }; }; links = { # Wired mesh interface "10-bmx" = { enable = true; matchConfig = { MACAddress = mac.bmx; }; linkConfig.Name = meshInterface; }; # Wired core interface "10-core" = { enable = true; matchConfig = { MACAddress = mac.core; }; linkConfig.Name = "core"; }; }; networks = { # Wired mesh interface "10-bmx" = { enable = true; matchConfig = { MACAddress = mac.bmx; }; addresses = [{ addressConfig = { Address = "10.201.${ddmeshAddrPart}/16"; Broadcast = ddmeshBroadcast; }; }]; routingPolicyRules = [ { routingPolicyRuleConfig = { Priority = 300; To = "10.200.0.0/16"; Table = rt_table_hosts; }; } ]; }; # Dummy interface for primary (10.200) address "11-bmx-prime" = { enable = true; matchConfig = { Name = meshLoopback; }; addresses = [{ addressConfig.Address = "10.200.${ddmeshAddrPart}/32"; }]; routingPolicyRules = [ { routingPolicyRuleConfig = { Priority = 33000; Table = rt_table_tuns; }; } ]; }; "31-wg-vpn6" = { enable = true; matchConfig.Name = "wg-vpn6"; addresses = [{ addressConfig.Address = "10.203.${ddmeshAddrPart}/16"; }]; # reverse dependency networkConfig.Tunnel = [ "ipip-node51001" ]; }; "32-ipip-node51001" = { enable = true; matchConfig.Name = "ipip-node51001"; addresses = [{ addressConfig = { Address = "10.201.${ddmeshAddrPart}/16"; Broadcast = ddmeshBroadcast; }; }]; }; # ZW "20-core" = { enable = true; matchConfig = { MACAddress = mac.core; }; addresses = map (Address: { addressConfig = { inherit Address; }; }) ( [ "${coreAddress}/${toString core.subnet4Len}" ] ++ map (hosts6: "${hosts6.${hostName}}/64") ( builtins.attrValues core.hosts6 ) ); routingPolicyRules = [ { # Marked wireguard packets take the upstream routing table routingPolicyRuleConfig = { Table = rt_table_upstream; FirewallMark = upstreamMark; }; } ]; # reverse dependency networkConfig.Tunnel = [ "wg-vpn6" ]; }; }; }; # Freifunk Dresden routing daemon systemd.services.bmxd = { after = [ "systemd-networkd.service" ]; wantedBy = [ "network.target" ]; serviceConfig = { ExecStart = '' ${pkgs.bmxd}/sbin/bmxd \ --rt_table_offset=${toString rt_table_hosts} \ --no_fork 1 \ --throw-rules 0 \ --prio-rules 0 \ --gateway_tunnel_network 10.200.0.0/16 \ --purge_timeout 20 \ --one_way_tunnel 1 \ -r 3 --gateway_hysteresis 20 \ dev=${meshLoopback} /linklayer 0 \ dev=${meshInterface} /linklayer 1 \ dev=ipip-node51001 /linklayer 1 ''; Restart = "always"; RestartSec = "60"; }; }; # Re-register periodically systemd.services.ddmesh-register-node = { script = '' ${pkgs.curl}/bin/curl -k \ -o /tmp/ddmesh-registration.json \ '${ddmeshRegisterUrl}?registerkey=${ddmeshRegisterKey}&node=${ toString ddmeshNode }' ''; serviceConfig = { User = "nobody"; Group = "nogroup"; }; }; systemd.timers.ddmesh-register-node = { partOf = [ "ddmesh-register-node.service" ]; wantedBy = [ "timers.target" ]; timerConfig.OnCalendar = "daily"; }; # Refresh sysinfo.json systemd.services.sysinfo-json = { script = '' ${sysinfo-json}/bin/bmxddump.sh ${sysinfo-json}/bin/sysinfo-json.cgi > /run/nginx/sysinfo.json ''; }; systemd.timers.sysinfo-json = { partOf = [ "sysinfo-json.service" ]; wantedBy = [ "timers.target" ]; timerConfig.OnCalendar = "minutely"; }; # Advertise Freifunk routes to ZW core services.bird2 = { enable = true; config = '' protocol kernel K4 { ipv4 { export all; }; } # BIRD routing table for Wireguard transport ipv4 table upstream4_table; # Kernel routing table for Wireguard transport protocol kernel upstream4 { kernel table ${toString rt_table_upstream}; ipv4 { export all; table upstream4_table; }; } protocol kernel K6 { ipv6 { export all; }; } protocol device { scan time 10; } ipv4 table bmx_gw; protocol kernel BMX_GW { learn; kernel table ${toString rt_table_tuns}; ipv4 { table bmx_gw; import filter { if net ~ [ 0.0.0.0/0 ] then { # Learn Freifunk default route accept; } reject; }; }; } protocol pipe import_bmx_gw { table master4; peer table bmx_gw; import all; } protocol ospf v2 ZW4 { ipv4 { export where net != 0.0.0.0/0; import where net != 0.0.0.0/0; }; area 0 { stubnet 10.200.0.0/15; interface "core" { hello 10; wait 20; authentication cryptographic; password "${pkgs.zentralwerk-ospf-message-digest-key}"; }; }; } protocol ospf v2 ZW4_freifunk { ipv4 { export where net = 0.0.0.0/0; }; area 0 { interface "core" instance ${toString zentralwerk.lib.config.site.hosts.freifunk.ospf.upstreamInstance} { hello 10; wait 20; authentication cryptographic; password "${pkgs.zentralwerk-ospf-message-digest-key}"; }; }; } protocol ospf v3 ZW6 { ipv6 { import all; }; area 0 { interface "core" { hello 10; wait 20; authentication cryptographic; password "${pkgs.zentralwerk-ospf-message-digest-key}"; }; }; } ${lib.concatStrings (lib.imap0 (i: upstream: '' # OSPFv2 to receive a default route from ${upstream} protocol ospf v2 ZW4_${upstream} { ipv4 { import filter { preference = preference + ${toString (200 - i)}; accept; }; table upstream4_table; }; area 0 { interface "core" instance ${toString zentralwerk.lib.config.site.hosts.${upstream}.ospf.upstreamInstance} { hello 10; wait 20; authentication cryptographic; password "${pkgs.zentralwerk-ospf-message-digest-key}"; }; }; }; '') upstreams)} ${lib.concatStrings (lib.imap0 (i: upstream: '' # OSPFv3 to receive a default route from ${upstream} protocol ospf v3 ZW6_${upstream} { ipv6 { import filter { preference = preference + ${toString (200 - i)}; accept; }; }; area 0 { interface "core" instance ${toString zentralwerk.lib.config.site.hosts.${upstream}.ospf.upstreamInstance} { hello 10; wait 20; authentication cryptographic; password "${pkgs.zentralwerk-ospf-message-digest-key}"; }; }; }; '') upstreams)} router id ${coreAddress}; ''; }; # HTTP Reverse Proxy to provide services into Freifunk services.nginx = { enable = true; appendHttpConfig = '' proxy_buffering off; ''; virtualHosts = { "c3d2.ffdd" = { default = true; root = ./assets; locations = let sysinfo-json = { alias = "/run/nginx/sysinfo.json"; extraConfig = '' add_header Content-Type "application/json;charset=UTF-8"; ''; }; in { "/" = { index = "index.html"; extraConfig = '' etag off; add_header etag "\"${builtins.substring 11 32 (./assets)}\""; ''; }; "=/sysinfo-json.cgi" = sysinfo-json; "=/sysinfo.json" = sysinfo-json; }; }; "storage.hq.c3d2.ffdd".locations."/".proxyPass = "http://storage.hq.c3d2.de/"; "grafana.hq.c3d2.ffdd".locations."/" = { proxyPass = "https://grafana.hq.c3d2.de/"; extraConfig = '' proxy_ssl_server_name on; ''; }; "influxdb.hq.c3d2.ffdd".locations."/".proxyPass = "http://grafana.hq.c3d2.de:8086/"; }; }; # This value determines the NixOS release with which your system is to be # compatible, in order to avoid breaking some software such as database # servers. You should change this only after NixOS release notes say you # should. system.stateVersion = "20.03"; # Did you read the comment? }