1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 pkg = pkgs.cjdns;
8
9 cfg = config.services.cjdns;
10
11 connectToSubmodule =
12 { options, ... }:
13 { options =
14 { password = mkOption {
15 type = types.str;
16 description = "Authorized password to the opposite end of the tunnel.";
17 };
18 publicKey = mkOption {
19 type = types.str;
20 description = "Public key at the opposite end of the tunnel.";
21 };
22 hostname = mkOption {
23 default = "";
24 example = "foobar.hype";
25 type = types.str;
26 description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
27 };
28 };
29 };
30
31 peers = mapAttrsToList (n: v: v) (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo);
32
33 pubs = toString (map (p: if p.hostname == "" then "" else p.publicKey) peers);
34 hosts = toString (map (p: if p.hostname == "" then "" else p.hostname) peers);
35
36 cjdnsHosts =
37 if hosts != "" then
38 import (pkgs.stdenv.mkDerivation {
39 name = "cjdns-hosts";
40 builder = ./cjdns-hosts.sh;
41
42 inherit (pkgs) cjdns;
43 inherit pubs hosts;
44 })
45 else "";
46
47 parseModules = x:
48 x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; };
49
50 # would be nice to merge 'cfg' with a //,
51 # but the json nesting is wacky.
52 cjdrouteConf = builtins.toJSON ( {
53 admin = {
54 bind = cfg.admin.bind;
55 password = "@CJDNS_ADMIN_PASSWORD@";
56 };
57 authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords;
58 interfaces = {
59 ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ];
60 UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ];
61 };
62
63 privateKey = "@CJDNS_PRIVATE_KEY@";
64
65 resetAfterInactivitySeconds = 100;
66
67 router = {
68 interface = { type = "TUNInterface"; };
69 ipTunnel = {
70 allowedConnections = [];
71 outgoingConnections = [];
72 };
73 };
74
75 security = [ { exemptAngel = 1; setuser = "nobody"; } ];
76
77 });
78
79in
80
81{
82 options = {
83
84 services.cjdns = {
85
86 enable = mkOption {
87 type = types.bool;
88 default = false;
89 description = ''
90 Whether to enable the cjdns network encryption
91 and routing engine. A file at /etc/cjdns.keys will
92 be created if it does not exist to contain a random
93 secret key that your IPv6 address will be derived from.
94 '';
95 };
96
97 confFile = mkOption {
98 type = types.str;
99 default = "";
100 example = "/etc/cjdroute.conf";
101 description = ''
102 Ignore all other cjdns options and load configuration from this file.
103 '';
104 };
105
106 authorizedPasswords = mkOption {
107 type = types.listOf types.str;
108 default = [ ];
109 example = [
110 "snyrfgkqsc98qh1y4s5hbu0j57xw5s0"
111 "z9md3t4p45mfrjzdjurxn4wuj0d8swv"
112 "49275fut6tmzu354pq70sr5b95qq0vj"
113 ];
114 description = ''
115 Any remote cjdns nodes that offer these passwords on
116 connection will be allowed to route through this node.
117 '';
118 };
119
120 admin = {
121 bind = mkOption {
122 type = types.string;
123 default = "127.0.0.1:11234";
124 description = ''
125 Bind the administration port to this address and port.
126 '';
127 };
128 };
129
130 UDPInterface = {
131 bind = mkOption {
132 type = types.string;
133 default = "";
134 example = "192.168.1.32:43211";
135 description = ''
136 Address and port to bind UDP tunnels to.
137 '';
138 };
139 connectTo = mkOption {
140 type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
141 default = { };
142 example = {
143 "192.168.1.1:27313" = {
144 hostname = "homer.hype";
145 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
146 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
147 };
148 };
149 description = ''
150 Credentials for making UDP tunnels.
151 '';
152 };
153 };
154
155 ETHInterface = {
156 bind = mkOption {
157 default = "";
158 example = "eth0";
159 description =
160 ''
161 Bind to this device for native ethernet operation.
162 <literal>all</literal> is a pseudo-name which will try to connect to all devices.
163 '';
164 };
165
166 beacon = mkOption {
167 type = types.int;
168 default = 2;
169 description = ''
170 Auto-connect to other cjdns nodes on the same network.
171 Options:
172 0: Disabled.
173 1: Accept beacons, this will cause cjdns to accept incoming
174 beacon messages and try connecting to the sender.
175 2: Accept and send beacons, this will cause cjdns to broadcast
176 messages on the local network which contain a randomly
177 generated per-session password, other nodes which have this
178 set to 1 or 2 will hear the beacon messages and connect
179 automatically.
180 '';
181 };
182
183 connectTo = mkOption {
184 type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
185 default = { };
186 example = {
187 "01:02:03:04:05:06" = {
188 hostname = "homer.hype";
189 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
190 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
191 };
192 };
193 description = ''
194 Credentials for connecting look similar to UDP credientials
195 except they begin with the mac address.
196 '';
197 };
198 };
199
200 };
201
202 };
203
204 config = mkIf config.services.cjdns.enable {
205
206 boot.kernelModules = [ "tun" ];
207
208 # networking.firewall.allowedUDPPorts = ...
209
210 systemd.services.cjdns = {
211 description = "encrypted networking for everybody";
212 wantedBy = [ "network.target" ];
213 after = [ "networkSetup.service" "network-interfaces.target" ];
214
215 preStart = if cfg.confFile != "" then "" else ''
216 [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys
217
218 if [ -z "$CJDNS_PRIVATE_KEY" ]; then
219 shopt -s lastpipe
220 ${pkg}/bin/makekeys | { read private ipv6 public; }
221
222 umask 0077
223 echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys
224 echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public
225
226 chmod 600 /etc/cjdns.keys
227 chmod 444 /etc/cjdns.public
228 fi
229
230 if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then
231 echo "CJDNS_ADMIN_PASSWORD=$(${pkgs.coreutils}/bin/head -c 96 /dev/urandom | ${pkgs.coreutils}/bin/tr -dc A-Za-z0-9)" \
232 >> /etc/cjdns.keys
233 fi
234 '';
235
236 script = (
237 if cfg.confFile != "" then "${pkg}/bin/cjdroute < ${cfg.confFile}" else
238 ''
239 source /etc/cjdns.keys
240 echo '${cjdrouteConf}' | sed \
241 -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \
242 -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \
243 | ${pkg}/bin/cjdroute
244 ''
245 );
246
247 serviceConfig = {
248 Type = "forking";
249 Restart = "on-failure";
250 };
251 };
252
253 networking.extraHosts = "${cjdnsHosts}";
254
255 assertions = [
256 { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile == "" );
257 message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
258 }
259 { assertion = config.networking.enableIPv6;
260 message = "networking.enableIPv6 must be enabled for CJDNS to work";
261 }
262 ];
263
264 };
265
266}