nix-config/hosts/containers/freifunk/default.nix

432 lines
12 KiB
Nix

{ 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";
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" "upstream1" ];
upstreamMark = 3;
rt_table_upstream = 100;
vpn6AddrPart = "200.2";
in {
imports = [
"${modulesPath}/profiles/minimal.nix"
../../../config/lxc-container.nix
../../../config/shared.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;
};
services.collectd.plugins.protocols = "";
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.iproute}/bin/ip rule del priority 300 || true
${pkgs.iproute}/bin/ip rule add to 10.200.0.0/16 table bmx_hosts priority 300
${pkgs.iproute}/bin/ip rule del priority 33000 || true
${pkgs.iproute}/bin/ip rule add table bmx_tuns priority 33000
${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 -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 ];
sops.secrets."wireguard/vpn6/privateKey" = {
group = "systemd-network";
mode = "0440";
};
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
systemd.network = {
netdevs = {
# Dummy interface for primary (10.200) address
bmx-prime = {
enable = true;
netdevConfig = {
Kind = "bridge";
Name = meshLoopback;
};
};
# Freifunk Dresden Backbone
wg-vpn6 = {
enable = true;
netdevConfig = {
Name = "wg-vpn6";
Kind = "wireguard";
};
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.169:5006";
PublicKey = "CIJa7xiRRIrLtEB7uyzwoyaQcpe0b8F2d16+3hk8KjU=";
AllowedIPs = "10.203.${vpn6AddrPart}/32";
};
} ];
};
ipip-vpn6 = {
enable = true;
netdevConfig = {
Name = "ipip-vpn6";
Kind = "ipip";
};
tunnelConfig = {
Local = "10.203.${ddmeshAddrPart}";
Remote = "10.203.${vpn6AddrPart}";
};
};
};
networks = {
# Wired mesh interface
"10-bmx" = {
enable = true;
matchConfig = { Name = meshInterface; };
addresses = [{
addressConfig.Address = "10.201.${ddmeshAddrPart}/16";
}];
};
# Dummy interface for primary (10.200) address
"11-bmx-loopback" = {
enable = true;
matchConfig = { Name = meshLoopback; };
addresses = [{
addressConfig.Address = "10.200.${ddmeshAddrPart}/32";
}];
};
"31-wg-vpn6" = {
enable = true;
matchConfig.Name = "wg-vpn6";
addresses = [{
addressConfig.Address = "10.203.${ddmeshAddrPart}/32";
}];
routes = [ {
routeConfig.Destination = "10.203.${vpn6AddrPart}/32";
} ];
};
"32-ipip-vpn6" = {
enable = true;
matchConfig.Name = "ipip-vpn6";
addresses = [{
addressConfig.Address = "10.201.${ddmeshAddrPart}/16";
}];
};
# ZW
"20-core" = {
enable = true;
matchConfig = { Name = "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;
};
} ];
};
};
};
# 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-vpn6 /linklayer 1
'';
Restart = "always";
};
};
# 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;
recommendedOptimisation = true;
recommendedGzipSettings = 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?
}