summaryrefslogtreecommitdiff
path: root/modules/yggdrasil-wg
diff options
context:
space:
mode:
Diffstat (limited to 'modules/yggdrasil-wg')
-rw-r--r--modules/yggdrasil-wg/default.nix234
1 files changed, 168 insertions, 66 deletions
diff --git a/modules/yggdrasil-wg/default.nix b/modules/yggdrasil-wg/default.nix
index 8e2ba7a4..80443644 100644
--- a/modules/yggdrasil-wg/default.nix
+++ b/modules/yggdrasil-wg/default.nix
@@ -5,86 +5,183 @@ with lib;
5let 5let
6 listenPort = 51820; 6 listenPort = 51820;
7 udp2rawPort = 51821; 7 udp2rawPort = 51821;
8 subnet = "2a03:4000:52:ada:1"; 8 wgSubnet = "2a03:4000:52:ada:1";
9 subnetLength = 80; 9 wgSubnetLength = 80;
10 hostLength = subnetLength + 16; 10 wgHostLength = wgSubnetLength + 16;
11 batSubnet = "2a03:4000:52:ada:2";
12 batSubnetLength = 80;
13 batHostLength = batSubnetLength + 16;
11 14
12 links = [ 15 links = mkLinks [
13 { from = "vidhar"; 16 { from = "vidhar";
14 to = "surtr"; 17 to = "surtr";
15 endpointHost = "202.61.241.61"; 18 endpointHost = "202.61.241.61";
16 persistentKeepalive = 25; 19 udp2raw = true;
17 dynamicEndpointRefreshSeconds = 86400; 20 PersistentKeepalive = 25;
18 } 21 }
19 { from = "sif"; 22 { from = "sif";
20 to = "surtr"; 23 to = "surtr";
21 endpointHost = "202.61.241.61"; 24 endpointHost = "202.61.241.61";
22 persistentKeepalive = 25; 25 udp2raw = true;
23 dynamicEndpointRefreshSeconds = 86400; 26 PersistentKeepalive = 25;
24 } 27 }
25 ];
26 routes = [
27 { from = "sif"; 28 { from = "sif";
28 to = "vidhar"; 29 to = "vidhar";
29 via = "surtr"; 30 endpointHost = "192.168.2.168";
30 } 31 PersistentKeepalive = 25;
31 { from = "vidhar";
32 to = "sif";
33 via = "surtr";
34 } 32 }
35 ]; 33 ];
36 hostIPs = { 34 wgHostIPs = {
37 surtr = ["${subnet}::/${toString hostLength}"]; 35 surtr = "${wgSubnet}::/${toString wgHostLength}";
38 vidhar = ["${subnet}:1::/${toString hostLength}"]; 36 vidhar = "${wgSubnet}:1::/${toString wgHostLength}";
39 sif = ["${subnet}:2::/${toString hostLength}"]; 37 sif = "${wgSubnet}:2::/${toString wgHostLength}";
38 };
39 greHostMACPrefixes = {
40 surtr = "02:00:00:00:00";
41 vidhar = "02:00:00:00:01";
42 sif = "02:00:00:00:02";
43 };
44 batHostIPs = {
45 surtr = ["${batSubnet}::/${toString batHostLength}"];
46 vidhar = ["${batSubnet}:1::/${toString batHostLength}"];
47 sif = ["${batSubnet}:2::/${toString batHostLength}"];
40 }; 48 };
41 49
42 mkPublicKeyPath = host: ./hosts + "/${host}.pub"; 50 mkPublicKeyPath = host: ./hosts + "/${host}.pub";
43 mkPrivateKeyPath = host: ./hosts + "/${host}.priv"; 51 mkPrivateKeyPath = host: ./hosts + "/${host}.priv";
52
53 kernel = config.boot.kernelPackages;
44 54
45 publicKeyPath = mkPublicKeyPath hostName; 55 publicKeyPath = mkPublicKeyPath hostName;
46 privateKeyPath = mkPrivateKeyPath hostName; 56 privateKeyPath = mkPrivateKeyPath hostName;
47 inNetwork = pathExists privateKeyPath && pathExists publicKeyPath; 57 inNetwork = pathExists privateKeyPath && pathExists publicKeyPath;
48 hostLinks = filter ({ from, to, ... }: from == hostName || to == hostName) links; 58 hostLinks = filter ({ from, to, ... }: from == hostName || to == hostName) links;
49 hostRoutes = filter ({ from, to, ... }: from == hostName || to == hostName) routes; 59 # hostRoutes = filter ({ from, to, ... }: from == hostName || to == hostName) routes;
50 isRouter = inNetwork && any ({via, ...}: via == hostName) routes; 60 # isRouter = inNetwork && any ({via, ...}: via == hostName) routes;
51 linkToPeer = ix: opts@{from, to, ...}: 61 linkToPeer = opts@{from, to, ...}:
52 let 62 let
53 other = if from == hostName then to else from; 63 other = if from == hostName then to else from;
54 in { 64 in {
55 allowedIPs = hostIPs.${other} ++ concatMap (rArgs: if rArgs.from != hostName || rArgs.via != to then [] else hostIPs.${rArgs.to}) routes; 65 AllowedIPs = wgHostIPs.${other}; # ++ concatMap (rArgs: if rArgs.from != hostName || rArgs.via != to then [] else wgHostIPs.${rArgs.to}) routes;
56 publicKey = trim (readFile (mkPublicKeyPath other)); 66 PublicKey = trim (readFile (mkPublicKeyPath other));
57 } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"])) opts // optionalAttrs (opts ? "endpointHost") { endpoint = "127.0.0.1:${toString (udp2rawPort + ix)}"; })); 67 } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost" "udp2raw"])) opts // optionalAttrs (opts ? "endpointHost") (if opts ? "udp2raw" then { Endpoint = "127.0.0.1:${toString (udp2rawPort + opts.udp2raw)}"; } else { Endpoint = "${opts.endpointHost}:${toString listenPort}"; })));
58 68 linkToGreDev = opts@{from, to, ...}:
69 let
70 other = if from == hostName then to else from;
71 in nameValuePair "yggdrasil-gre-${other}" {
72 netdevConfig = {
73 Name = "yggdrasil-gre-${other}";
74 Kind = "ip6gretap";
75 MTUBytes = toString 1280;
76 };
77 tunnelConfig = {
78 Local = stripSubnet wgHostIPs.${hostName};
79 Remote = stripSubnet wgHostIPs.${other};
80 };
81 };
82 linkToGreNetwork = ix: opts@{from, to, ...}:
83 let
84 other = if from == hostName then to else from;
85 hexIx = let
86 hexIx' = toHexString ix;
87 in if (stringLength hexIx' < 2) then "0${hexIx'}" else hexIx';
88 in nameValuePair "yggdrasil-gre-${other}" {
89 matchConfig = {
90 Name = "yggdrasil-gre-${other}";
91 };
92 linkConfig = {
93 MACAddress = "${greHostMACPrefixes.${hostName}}:${hexIx}";
94 };
95 networkConfig = {
96 Tunnel = "yggdrasil-gre-${other}";
97 BatmanAdvanced = "yggdrasil";
98 };
99 linkConfig = {
100 RequiredForOnline = false;
101 };
102 };
103
59 trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str; 104 trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str;
60 stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0; 105 stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0;
106 optIx = optName: xs: let
107 withOpts = listToAttrs (imap0 (ix: x: nameValuePair x.name (x.value // { ${optName} = ix; })) (filter (x: x.value.${optName} or false) (imap0 (ix: nameValuePair (toString ix)) xs)));
108 withoutOpts = listToAttrs (map (nv: nameValuePair nv.name (removeAttrs nv.value [optName])) (filter (x: !(x.value.${optName} or false)) (imap0 (ix: nameValuePair (toString ix)) xs)));
109 in genList (ix: withOpts.${toString ix} or withoutOpts.${toString ix}) (length xs);
110 mkLinks = optIx "udp2raw";
61in { 111in {
62 config = { 112 config = {
63 assertions = [ 113 assertions = [
64 { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath); 114 { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath);
65 message = "yggdrasil-wg: Either both public and private keys must exist or neither."; 115 message = "yggdrasil-wg: Either both public and private keys must exist or neither.";
66 } 116 }
67 { assertion = !inNetwork || (hostIPs ? "${hostName}"); 117 { assertion = !inNetwork || (wgHostIPs ? "${hostName}");
68 message = "yggdrasil-wg: Entry in hostIPs must exist."; 118 message = "yggdrasil-wg: Entry in wgHostIPs must exist.";
69 } 119 }
70 ] ++ map ({from, to, ...}: let other = if from == hostName then to else from; in { assertion = pathExists (mkPublicKeyPath other); message = "yggdrasil-wg: This host (${hostName}) has a link with ‘${other}’, but no public key is available for ‘${other}’."; }) hostLinks; 120 ] ++ map ({from, to, ...}: let other = if from == hostName then to else from; in { assertion = pathExists (mkPublicKeyPath other); message = "yggdrasil-wg: This host (${hostName}) has a link with ‘${other}’, but no public key is available for ‘${other}’."; }) hostLinks;
71 121
72 networking.wireguard.interfaces = mkIf inNetwork { 122 systemd.network = mkIf inNetwork {
73 yggdrasil = { 123 enable = true;
74 allowedIPsAsRoutes = false; 124 netdevs = {
75 inherit listenPort; 125 yggdrasil-wg = {
76 ips = hostIPs.${hostName}; 126 netdevConfig = {
77 peers = filter (value: value != null) (imap0 (ix: opts@{to, from, ...}: if from == hostName || to == hostName then linkToPeer ix opts else null) links); 127 Name = "yggdrasil-wg";
78 privateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; 128 Kind = "wireguard";
79 postSetup = '' 129 MTUBytes = toString (1280 + 70);
80 ip li set mtu 1280 dev yggdrasil 130 };
81 ${concatMapStringsSep "\n" (linkArgs: let other = if linkArgs.from == hostName then linkArgs.to else linkArgs.from; in concatMapStringsSep "\n" (otherIP: "ip route replace \"${otherIP}\" dev \"yggdrasil\" table \"main\"") hostIPs.${other}) hostLinks} 131 wireguardConfig = {
82 ${concatMapStringsSep "\n" (routeArgs: let other = if routeArgs.from == hostName then routeArgs.to else routeArgs.from; in concatMapStringsSep "\n" (otherIP: concatMapStringsSep "\n" (viaIP: "ip route replace \"${otherIP}\" via \"${viaIP}\" dev \"yggdrasil\" table \"main\"") (map stripSubnet hostIPs.${routeArgs.via})) hostIPs.${other}) hostRoutes} 132 PrivateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path;
83 ''; 133 ListenPort = listenPort;
84 }; 134 };
135 wireguardPeers = map (opts@{to, from, ...}: { wireguardPeerConfig = linkToPeer opts; }) hostLinks;
136 };
137 yggdrasil = {
138 netdevConfig = {
139 Name = "yggdrasil";
140 Kind = "batadv";
141 };
142 };
143 } // listToAttrs (map linkToGreDev hostLinks);
144
145 networks = {
146 yggdrasil-wg = {
147 name = "yggdrasil-wg";
148 matchConfig = {
149 Name = "yggdrasil-wg";
150 };
151 address = [wgHostIPs.${hostName}];
152 linkConfig = {
153 RequiredForOnline = false;
154 };
155 };
156 yggdrasil = {
157 name = "yggdrasil";
158 matchConfig = {
159 Name = "yggdrasil";
160 };
161 address = batHostIPs.${hostName};
162 linkConfig = {
163 RequiredForOnline = false;
164 };
165 };
166 } // listToAttrs (imap0 linkToGreNetwork hostLinks);
85 }; 167 };
86 168
87 systemd.services = listToAttrs (filter ({ value, ...}: value != null) (imap0 (ix: opts@{to, from, ...}: let other = if from == hostName then to else from; in nameValuePair "yggdrasil-udp2raw@${other}" (if opts ? "endpointHost" && (from == hostName || to == hostName) then { 169 # networking.wireguard.interfaces = mkIf inNetwork {
170 # yggdrasil = {
171 # allowedIPsAsRoutes = false;
172 # inherit listenPort;
173 # ips = wgHostIPs.${hostName};
174 # peers = filter (value: value != null) (map (opts@{to, from, ...}: if from == hostName || to == hostName then linkToPeer opts else null) links);
175 # privateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path;
176 # postSetup = ''
177 # ip li set mtu 1280 dev yggdrasil
178 # ${concatMapStringsSep "\n" (linkArgs: let other = if linkArgs.from == hostName then linkArgs.to else linkArgs.from; in concatMapStringsSep "\n" (otherIP: "ip route replace \"${otherIP}\" dev \"yggdrasil\" table \"main\"") wgHostIPs.${other}) hostLinks}
179 # ${concatMapStringsSep "\n" (routeArgs: let other = if routeArgs.from == hostName then routeArgs.to else routeArgs.from; in concatMapStringsSep "\n" (otherIP: concatMapStringsSep "\n" (viaIP: "ip route replace \"${otherIP}\" via \"${viaIP}\" dev \"yggdrasil\" table \"main\"") (map stripSubnet wgHostIPs.${routeArgs.via})) wgHostIPs.${other}) hostRoutes}
180 # '';
181 # };
182 # };
183
184 systemd.services = listToAttrs (filter ({ value, ...}: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in nameValuePair "yggdrasil-udp2raw@${other}" (if opts ? "endpointHost" && opts ? "udp2raw" then {
88 path = with pkgs; [iptables]; 185 path = with pkgs; [iptables];
89 serviceConfig = { 186 serviceConfig = {
90 RuntimeDirectory = ["udp2raw-config-${other}"]; 187 RuntimeDirectory = ["udp2raw-config-${other}"];
@@ -95,16 +192,17 @@ in {
95 cat >''${RUNTIME_DIRECTORY}/udp2raw.conf <<EOF 192 cat >''${RUNTIME_DIRECTORY}/udp2raw.conf <<EOF
96 ${if from == hostName then '' 193 ${if from == hostName then ''
97 -c 194 -c
98 -l 127.0.0.1:${toString (udp2rawPort + ix)} 195 -l 127.0.0.1:${toString (udp2rawPort + opts.udp2raw)}
99 -r ${opts.endpointHost}:${toString (udp2rawPort + ix)} 196 -r ${opts.endpointHost}:${toString (udp2rawPort + opts.udp2raw)}
100 '' else '' 197 '' else ''
101 -s 198 -s
102 -l 0.0.0.0:${toString (udp2rawPort + ix)} 199 -l 0.0.0.0:${toString (udp2rawPort + opts.udp2raw)}
103 -r 127.0.0.1:${toString listenPort} 200 -r 127.0.0.1:${toString listenPort}
104 ''} 201 ''}
105 -k $secret 202 -k $secret
106 --auth-mode hmac_sha1 203 --auth-mode hmac_sha1
107 --raw-mode faketcp 204 --raw-mode faketcp
205 --seq-mode 4
108 -a 206 -a
109 --retry-on-error 207 --retry-on-error
110 EOF 208 EOF
@@ -112,38 +210,42 @@ in {
112 ExecStart = "${pkgs.udp2raw}/bin/udp2raw --conf-file \${RUNTIME_DIRECTORY}/udp2raw.conf"; 210 ExecStart = "${pkgs.udp2raw}/bin/udp2raw --conf-file \${RUNTIME_DIRECTORY}/udp2raw.conf";
113 Restart = "always"; 211 Restart = "always";
114 }; 212 };
115 } else null)) links)) // { 213 } else null)) hostLinks));
116 "wireguard-yggdrasil" = { 214 # // {
117 bindsTo = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks); 215 # "wireguard-yggdrasil" = {
118 after = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks); 216 # bindsTo = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" && opts ? "udp2raw" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks);
119 }; 217 # after = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" && opts ? "udp2raw" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks);
120 firewall.path = optionals isRouter [pkgs.procps]; 218 # };
121 }; 219 # firewall.path = optionals isRouter [pkgs.procps];
220 # };
122 221
123 sops.secrets = { 222 sops.secrets = {
124 "yggdrasil-wg.priv" = mkIf (pathExists privateKeyPath) { 223 "yggdrasil-wg.priv" = mkIf (pathExists privateKeyPath) {
125 format = "binary"; 224 format = "binary";
126 sopsFile = privateKeyPath; 225 sopsFile = privateKeyPath;
127 }; 226 };
128 "yggdrasil-udp2raw-secret" = mkIf (any (opts@{to, from, ...}: (to == hostName || from == hostName) && opts ? "endpointHost") links) { 227 "yggdrasil-udp2raw-secret" = mkIf (any (opts@{to, from, ...}: opts ? "endpointHost" && opts ? "udp2raw") hostLinks) {
129 format = "binary"; 228 format = "binary";
130 sopsFile = ./udp2raw-secret; 229 sopsFile = ./udp2raw-secret;
131 }; 230 };
132 }; 231 };
133 232
134 networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair hostIPs))); 233 networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair batHostIPs)));
135 234
136 networking.firewall = mkIf isRouter { 235 # networking.firewall = mkIf isRouter {
137 extraCommands = '' 236 # extraCommands = ''
138 ip6tables -A FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept 237 # ip6tables -A FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept
139 ip46tables -A FORWARD -j nixos-fw-log-refuse 238 # ip46tables -A FORWARD -j nixos-fw-log-refuse
140 sysctl net.ipv6.conf.all.forwarding=1 239 # sysctl net.ipv6.conf.all.forwarding=1
141 ''; 240 # '';
142 extraStopCommands = '' 241 # extraStopCommands = ''
143 sysctl net.ipv6.conf.all.forwarding=0 242 # sysctl net.ipv6.conf.all.forwarding=0
144 ip46tables -D FORWARD -j nixos-fw-log-refuse || true 243 # ip46tables -D FORWARD -j nixos-fw-log-refuse || true
145 ip6tables -D FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept || true 244 # ip6tables -D FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept || true
146 ''; 245 # '';
147 }; 246 # };
247
248 boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard ++ [kernel.batman_adv];
249 environment.systemPackages = with pkgs; [ wireguard-tools batctl ];
148 }; 250 };
149} 251}