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}