1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 pkg = pkgs.cjdns;
10
11 cfg = config.services.cjdns;
12
13 connectToSubmodule =
14 { ... }:
15 {
16 options = {
17 password = lib.mkOption {
18 type = lib.types.str;
19 description = "Authorized password to the opposite end of the tunnel.";
20 };
21 login = lib.mkOption {
22 default = "";
23 type = lib.types.str;
24 description = "(optional) name your peer has for you";
25 };
26 peerName = lib.mkOption {
27 default = "";
28 type = lib.types.str;
29 description = "(optional) human-readable name for peer";
30 };
31 publicKey = lib.mkOption {
32 type = lib.types.str;
33 description = "Public key at the opposite end of the tunnel.";
34 };
35 hostname = lib.mkOption {
36 default = "";
37 example = "foobar.hype";
38 type = lib.types.str;
39 description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
40 };
41 };
42 };
43
44 # Additional /etc/hosts entries for peers with an associated hostname
45 cjdnsExtraHosts = pkgs.runCommand "cjdns-hosts" { } ''
46 exec >$out
47 ${lib.concatStringsSep "\n" (
48 lib.mapAttrsToList (
49 k: v:
50 lib.optionalString (
51 v.hostname != ""
52 ) "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}"
53 ) (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo)
54 )}
55 '';
56
57 parseModules =
58 x:
59 x
60 // {
61 connectTo = lib.mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo;
62 };
63
64 cjdrouteConf = builtins.toJSON (
65 lib.recursiveUpdate {
66 admin = {
67 bind = cfg.admin.bind;
68 password = "@CJDNS_ADMIN_PASSWORD@";
69 };
70 authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords;
71 interfaces = {
72 ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ];
73 UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ];
74 };
75
76 privateKey = "@CJDNS_PRIVATE_KEY@";
77
78 resetAfterInactivitySeconds = 100;
79
80 router = {
81 interface = {
82 type = "TUNInterface";
83 };
84 ipTunnel = {
85 allowedConnections = [ ];
86 outgoingConnections = [ ];
87 };
88 };
89
90 security = [
91 {
92 exemptAngel = 1;
93 setuser = "nobody";
94 }
95 ];
96
97 } cfg.extraConfig
98 );
99
100in
101
102{
103 options = {
104
105 services.cjdns = {
106
107 enable = lib.mkOption {
108 type = lib.types.bool;
109 default = false;
110 description = ''
111 Whether to enable the cjdns network encryption
112 and routing engine. A file at /etc/cjdns.keys will
113 be created if it does not exist to contain a random
114 secret key that your IPv6 address will be derived from.
115 '';
116 };
117
118 extraConfig = lib.mkOption {
119 type = lib.types.attrs;
120 default = { };
121 example = {
122 router.interface.tunDevice = "tun10";
123 };
124 description = ''
125 Extra configuration, given as attrs, that will be merged recursively
126 with the rest of the JSON generated by this module, at the root node.
127 '';
128 };
129
130 confFile = lib.mkOption {
131 type = lib.types.nullOr lib.types.path;
132 default = null;
133 example = "/etc/cjdroute.conf";
134 description = ''
135 Ignore all other cjdns options and load configuration from this file.
136 '';
137 };
138
139 authorizedPasswords = lib.mkOption {
140 type = lib.types.listOf lib.types.str;
141 default = [ ];
142 example = [
143 "snyrfgkqsc98qh1y4s5hbu0j57xw5s0"
144 "z9md3t4p45mfrjzdjurxn4wuj0d8swv"
145 "49275fut6tmzu354pq70sr5b95qq0vj"
146 ];
147 description = ''
148 Any remote cjdns nodes that offer these passwords on
149 connection will be allowed to route through this node.
150 '';
151 };
152
153 admin = {
154 bind = lib.mkOption {
155 type = lib.types.str;
156 default = "127.0.0.1:11234";
157 description = ''
158 Bind the administration port to this address and port.
159 '';
160 };
161 };
162
163 UDPInterface = {
164 bind = lib.mkOption {
165 type = lib.types.str;
166 default = "";
167 example = "192.168.1.32:43211";
168 description = ''
169 Address and port to bind UDP tunnels to.
170 '';
171 };
172 connectTo = lib.mkOption {
173 type = lib.types.attrsOf (lib.types.submodule (connectToSubmodule));
174 default = { };
175 example = lib.literalExpression ''
176 {
177 "192.168.1.1:27313" = {
178 hostname = "homer.hype";
179 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
180 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
181 };
182 }
183 '';
184 description = ''
185 Credentials for making UDP tunnels.
186 '';
187 };
188 };
189
190 ETHInterface = {
191 bind = lib.mkOption {
192 type = lib.types.str;
193 default = "";
194 example = "eth0";
195 description = ''
196 Bind to this device for native ethernet operation.
197 `all` is a pseudo-name which will try to connect to all devices.
198 '';
199 };
200
201 beacon = lib.mkOption {
202 type = lib.types.int;
203 default = 2;
204 description = ''
205 Auto-connect to other cjdns nodes on the same network.
206 Options:
207 0: Disabled.
208 1: Accept beacons, this will cause cjdns to accept incoming
209 beacon messages and try connecting to the sender.
210 2: Accept and send beacons, this will cause cjdns to broadcast
211 messages on the local network which contain a randomly
212 generated per-session password, other nodes which have this
213 set to 1 or 2 will hear the beacon messages and connect
214 automatically.
215 '';
216 };
217
218 connectTo = lib.mkOption {
219 type = lib.types.attrsOf (lib.types.submodule (connectToSubmodule));
220 default = { };
221 example = lib.literalExpression ''
222 {
223 "01:02:03:04:05:06" = {
224 hostname = "homer.hype";
225 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
226 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
227 };
228 }
229 '';
230 description = ''
231 Credentials for connecting look similar to UDP credientials
232 except they begin with the mac address.
233 '';
234 };
235 };
236
237 addExtraHosts = lib.mkOption {
238 type = lib.types.bool;
239 default = false;
240 description = ''
241 Whether to add cjdns peers with an associated hostname to
242 {file}`/etc/hosts`. Beware that enabling this
243 incurs heavy eval-time costs.
244 '';
245 };
246
247 };
248
249 };
250
251 config = lib.mkIf cfg.enable {
252
253 boot.kernelModules = [ "tun" ];
254
255 # networking.firewall.allowedUDPPorts = ...
256
257 systemd.services.cjdns = {
258 description = "cjdns: routing engine designed for security, scalability, speed and ease of use";
259 wantedBy = [
260 "multi-user.target"
261 "sleep.target"
262 ];
263 after = [ "network-online.target" ];
264 bindsTo = [ "network-online.target" ];
265
266 preStart = lib.optionalString (cfg.confFile == null) ''
267 [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys
268
269 if [ -z "$CJDNS_PRIVATE_KEY" ]; then
270 shopt -s lastpipe
271 ${pkg}/bin/makekeys | { read private ipv6 public; }
272
273 install -m 600 <(echo "CJDNS_PRIVATE_KEY=$private") /etc/cjdns.keys
274 install -m 444 <(echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public") /etc/cjdns.public
275 fi
276
277 if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then
278 echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" \
279 >> /etc/cjdns.keys
280 fi
281 '';
282
283 script = (
284 if cfg.confFile != null then
285 "${pkg}/bin/cjdroute < ${cfg.confFile}"
286 else
287 ''
288 source /etc/cjdns.keys
289 (cat <<'EOF'
290 ${cjdrouteConf}
291 EOF
292 ) | sed \
293 -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \
294 -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \
295 | ${pkg}/bin/cjdroute
296 ''
297 );
298
299 startLimitIntervalSec = 0;
300 serviceConfig = {
301 Type = "forking";
302 Restart = "always";
303 RestartSec = 1;
304 CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID";
305 ProtectSystem = true;
306 # Doesn't work on i686, causing service to fail
307 MemoryDenyWriteExecute = !pkgs.stdenv.hostPlatform.isi686;
308 ProtectHome = true;
309 PrivateTmp = true;
310 };
311 };
312
313 networking.hostFiles = lib.mkIf cfg.addExtraHosts [ cjdnsExtraHosts ];
314
315 assertions = [
316 {
317 assertion = (cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null);
318 message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
319 }
320 {
321 assertion = config.networking.enableIPv6;
322 message = "networking.enableIPv6 must be enabled for CJDNS to work";
323 }
324 ];
325
326 };
327
328}