├── .gitignore ├── tests ├── .gitignore └── generate-keys.sh ├── contrib ├── micronet │ ├── .gitignore │ ├── .dockerignore │ ├── examples │ │ ├── client.sh │ │ └── conf.lua │ ├── src │ │ ├── config.h │ │ ├── link.c │ │ ├── common.h │ │ ├── peer.c │ │ ├── nat.c │ │ ├── micronet.c │ │ ├── wan.c │ │ ├── conf.h │ │ └── server.h │ ├── Dockerfile │ ├── scripts │ │ └── file2buf.py │ ├── Makefile │ └── README.md └── tools │ └── clear_upnp.lua ├── .dockerignore ├── .gitmodules ├── config └── public ├── src ├── core │ ├── os.c │ ├── os.h │ ├── mem.h │ ├── config.h │ ├── key.h │ ├── common.h │ ├── orchid.c │ ├── packet.h │ ├── packet.c │ ├── serdes.h │ ├── secretdata.c │ ├── net.h │ ├── ipc_event.c │ ├── luawh.h │ ├── pcap.c │ ├── smartptr.c │ ├── workerlib.c │ ├── serdes.c │ ├── key.c │ ├── ipc.c │ ├── whupnplib.c │ └── net.c ├── sink-udp.lua ├── tools │ ├── check-wg.lua │ ├── pubkey.lua │ ├── bid.lua │ ├── workbit.lua │ ├── down.lua │ ├── orchid.lua │ ├── ipc.lua │ ├── forget.lua │ ├── genkey.lua │ ├── find.lua │ ├── inspect.lua │ ├── reload.lua │ ├── authenticate.lua │ ├── show.lua │ ├── resolve.lua │ ├── cli.lua │ ├── search.lua │ ├── completion.lua │ └── up.lua ├── key.lua ├── queue.lua ├── bwlog.lua ├── time.lua ├── ns_keybase.lua ├── kadstore.lua ├── peer.lua ├── getent.lua ├── hosts.lua ├── packet.lua ├── auth.lua ├── ipc.lua ├── nat.lua ├── connectivity.lua ├── lo.lua ├── wh.lua ├── wgsync.lua ├── kad.lua └── handlers.lua ├── docker ├── Dockerfile.root1 ├── spawn.lua ├── Dockerfile.sandbox ├── wh-sandbox-test ├── sandbox.bashrc ├── 0nc.lua └── Dockerfile ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .obj 2 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | keys 2 | -------------------------------------------------------------------------------- /contrib/micronet/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .obj 3 | tags 4 | test 5 | -------------------------------------------------------------------------------- /contrib/micronet/.dockerignore: -------------------------------------------------------------------------------- 1 | bin 2 | examples 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/WireGuard"] 2 | path = deps/WireGuard 3 | url = git://git.zx2c4.com/WireGuard 4 | -------------------------------------------------------------------------------- /config/public: -------------------------------------------------------------------------------- 1 | name public 2 | workbit 8 3 | 4 | boot P17zMwXJFbBdJEn05RFIMADw9TX5_m2xgf31OgNKX3w bootstrap.wirehub.io:62096 5 | -------------------------------------------------------------------------------- /contrib/micronet/examples/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | make /dev/net/tun 3 | make 4 | UNET_SERVERNAME=172.17.0.1 ./bin/micronet client $1 5 | -------------------------------------------------------------------------------- /src/core/os.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | 4 | uint64_t now_seconds(void) { 5 | return time(NULL); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/core/os.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_OS_H 2 | #define WIREHUB_OS_H 3 | 4 | uint64_t now_seconds(void); 5 | 6 | #endif // WIREHUB_OS_H 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/sink-udp.lua: -------------------------------------------------------------------------------- 1 | require'wh' 2 | 3 | local s = wh.socket_udp(wh.address('0.0.0.0', wh.DEFAULT_PORT)) 4 | 5 | while true do 6 | wh.select({s}, {}, {}) 7 | wh.recvfrom(s, 1500) 8 | end 9 | 10 | -------------------------------------------------------------------------------- /src/tools/check-wg.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print("Usage: wh check-wg") 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | if check_wg() then 10 | print("OK! WireGuard is installed.") 11 | else 12 | return -1 13 | end 14 | -------------------------------------------------------------------------------- /contrib/micronet/src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef MICRONET_CONFIG_H 2 | #define MICRONET_CONFIG_H 3 | 4 | #define DEFAULT_SERVER_PORT 4321 5 | 6 | #define UNET_DEFAULT_MTU 1500 7 | #define UNET_ENV_MTU "UNET_MTU" 8 | 9 | 10 | #endif // MICRONET_CONFIG_H 11 | 12 | -------------------------------------------------------------------------------- /contrib/micronet/examples/conf.lua: -------------------------------------------------------------------------------- 1 | W = wan() 2 | 3 | M(W | peer{up_ip=subnet('51.15.227.165', 0)}) -- root 4 | 5 | M(W | peer{up_ip=subnet("1.1.1.1", 0)}) 6 | 7 | HomeLan = W | nat{up_ip=subnet('1.1.1.2', 0)} 8 | M(HomeLan | peer()) 9 | M(HomeLan | peer()) 10 | 11 | -------------------------------------------------------------------------------- /src/core/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_MEM_H 2 | #define WIREHUB_MEM_H 3 | 4 | static inline void* memdup(const void* p, size_t len) { 5 | void* n = malloc(len); 6 | assert(n); 7 | memcpy(n, p, len); 8 | return n; 9 | } 10 | 11 | #endif // WIREHUB_MEM_H 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/tools/pubkey.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh pubkey') 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local b64k = io.stdin:read() 10 | 11 | local ok, value = pcall(wh.fromb64, b64k, 'wg') 12 | 13 | if not ok then 14 | printf("Invalid key: %s", value) 15 | return 16 | end 17 | 18 | local sk = value 19 | 20 | local k = wh.publickey(sk) 21 | 22 | print(wh.tob64(k)) 23 | -------------------------------------------------------------------------------- /src/core/config.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_CONFIG_H 2 | #define WIREHUB_CONFIG_H 3 | 4 | // source: https://www.wireguard.com/install/#kernel-requirements 5 | #define WH_LINUX_MINVERSION { 3, 10 } 6 | 7 | #define WH_ENV_CONFPATH "WH_CONFPATH" 8 | #define WH_DEFAULT_CONFPATH "/etc/wirehub/" 9 | #define WH_DEFAULT_SOCKPATH "/var/run/wirehub/" 10 | #define WH_ENABLE_MINIUPNPC 1 11 | 12 | #define WH_TUN_ICMP 1 13 | 14 | #endif // WIREHUB_CONFIG_H 15 | 16 | -------------------------------------------------------------------------------- /src/core/key.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_KEY_H 2 | #define WIREHUB_KEY_H 3 | 4 | #include "common.h" 5 | 6 | int genkey(uint8_t* ed25519_sk, const char* key, int workbit, int num_threads); 7 | int auth_packet(uint8_t* p, size_t ml, const uint8_t* sk, const uint8_t* pk); 8 | int verify_packet(const uint8_t* p, size_t pl, const uint8_t* sk); 9 | unsigned int workbit(const uint8_t* pk, const void* k, size_t l); 10 | 11 | #endif // WIREHUB_KEY_H 12 | 13 | -------------------------------------------------------------------------------- /contrib/micronet/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest as builder 2 | 3 | RUN apk add --no-cache \ 4 | build-base \ 5 | linux-headers \ 6 | lua5.3-dev \ 7 | python3 8 | 9 | WORKDIR /root 10 | COPY scripts scripts 11 | COPY src src 12 | COPY Makefile Makefile 13 | 14 | RUN make 15 | 16 | FROM alpine:latest as micronet 17 | 18 | RUN apk add --no-cache \ 19 | lua5.3 20 | 21 | COPY --from=builder /root/bin/micronet /usr/local/bin 22 | 23 | -------------------------------------------------------------------------------- /contrib/tools/clear_upnp.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | require('wh') 4 | require('helpers') 5 | 6 | local d, iaddr, url = wh.upnp.discover_igd(1) 7 | 8 | if not d then 9 | print("no UPnP device detected") 10 | return 11 | end 12 | 13 | for _, r in ipairs(wh.upnp.list_redirects(d)) do 14 | if r.iaddr == iaddr then 15 | printf('remove :%d -> %s:%d', r.eport, r.iaddr, r.iport) 16 | wh.upnp.remove_redirect(d, r) 17 | end 18 | end 19 | 20 | -------------------------------------------------------------------------------- /docker/Dockerfile.root1: -------------------------------------------------------------------------------- 1 | FROM wirehub/wh:latest 2 | 3 | RUN apk add --no-cache \ 4 | bash \ 5 | bash-completion 6 | 7 | WORKDIR /opt/wh 8 | 9 | RUN nc 172.17.0.1 1324 > ./sk 10 | 11 | RUN echo "lua src/sink-udp.lua &" >> ./run-root1.sh && \ 12 | echo "FG=y LOG=2 wh up /etc/wirehub/public private-key ./sk mode direct" >> ./run-root1.sh && \ 13 | chmod +x ./run-root1.sh 14 | 15 | RUN wh completion get-bash > /usr/share/bash-completion/completions/wh 16 | 17 | ENTRYPOINT ./run-root1.sh 18 | 19 | -------------------------------------------------------------------------------- /contrib/micronet/src/link.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | static void _link_kernel(struct node* n, struct packet* p) { 4 | // XXX implement 5 | if (p->dir == UP) { 6 | sendto_id(n, n->up, p); 7 | 8 | } else { // p->dir == DOWN 9 | sendto_id(n, n->as.link.down, p); 10 | } 11 | } 12 | 13 | int _link(lua_State* L) { 14 | struct node* n = _init_node(L); 15 | node_id down = luaN_checkid(L, NODE_ARGS(1)); 16 | 17 | n->kernel = _link_kernel; 18 | n->as.link.down = down; 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/generate-keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NAME=test 4 | 5 | rm -f /public.wh 6 | 7 | echo "name $NAME" >> /public.wh 8 | echo "subnet 10.0.42.1/24" >> /public.wh 9 | echo "workbit 8" >> /public.wh 10 | echo "boot P17zMwXJFbBdJEn05RFIMADw9TX5_m2xgf31OgNKX3w bootstrap.wirehub.io" >> /public.wh 11 | 12 | CWD="$(dirname "$0")" 13 | KPATH="$CWD/keys" 14 | 15 | rm -f $KPATH/*.{sk,k} 16 | mkdir -p $KPATH 17 | 18 | for i in {1..9} 19 | do 20 | echo "generating key $i..." 21 | wh genkey /public.wh | tee $KPATH/$i.sk | wh pubkey > $KPATH/$i.k 22 | echo "trust $i.$NAME `cat $KPATH/$i.k`" >> /public.wh 23 | done 24 | 25 | echo "###" 26 | cat /public.wh | tee > $KPATH/config 27 | -------------------------------------------------------------------------------- /src/key.lua: -------------------------------------------------------------------------------- 1 | local dbg_keys = {idx=1} 2 | function wh.key(k_or_p, n) 3 | local k, p 4 | 5 | if type(k_or_p) == 'table' then 6 | p = k_or_p 7 | k = p.k 8 | else 9 | p = nil 10 | k = k_or_p 11 | 12 | if k == nil then 13 | return nil 14 | end 15 | end 16 | 17 | if not p and n then 18 | p = n.kad:get(k) 19 | end 20 | 21 | if p then 22 | if p.hostname then 23 | return p.hostname 24 | elseif p.ip then 25 | return tostring(p.ip:addr()) 26 | end 27 | end 28 | 29 | local b64 = wh.tob64(k) 30 | b64 = string.sub(b64, 1, 10) 31 | return b64 32 | end 33 | 34 | -------------------------------------------------------------------------------- /contrib/micronet/scripts/file2buf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | MAX = 8 7 | 8 | fpath = sys.argv[1] 9 | name = sys.argv[2] 10 | 11 | with open(fpath, "rb") as fh: 12 | sys.stdout.write("char %s[] = {" % (name,) ) 13 | 14 | i = 0 15 | while True: 16 | if i > 0: 17 | sys.stdout.write(", ") 18 | 19 | if i % MAX == 0: 20 | sys.stdout.write("\n\t") 21 | 22 | c = fh.read(1) 23 | 24 | if not c: 25 | sys.stdout.write("\n") 26 | break 27 | 28 | sys.stdout.write("0x%.2x" % (ord(c), )) 29 | 30 | i = i + 1 31 | 32 | print("};") 33 | print("") 34 | print("unsigned int %s_sz = %s;" % (name, i)) 35 | print("") 36 | 37 | -------------------------------------------------------------------------------- /src/core/common.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_COMMON_H 2 | #define WIREHUB_COMMON_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | */ 24 | /*** CONSTANTS ***************************************************************/ 25 | 26 | #define crypto_scalarmult_curve25519_KEYBASE64BYTES 44 27 | 28 | static const uint8_t wh_pkt_hdr[] = {0xff, 0x00, 0x00, 0x00}; 29 | static const int wh_version[3] = {0, 1, 0}; 30 | 31 | #endif // WIREHUB_COMMON_H 32 | 33 | -------------------------------------------------------------------------------- /docker/spawn.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- Spawn X nodes 4 | -- 5 | -- Usage: spawn.lua 100 # start 100 nodes 6 | 7 | require('wh') 8 | 9 | io.popen('mkdir -p /tmp/spawn') 10 | 11 | if arg[1] == nil then 12 | print(string.format("usage: %s ", arg[0])) 13 | print() 14 | print( "Spawn N WireHub peers (without WireGuard interface). This is useful to populate\n" .. 15 | "a network with ephemeron peers." 16 | ) 17 | return 18 | end 19 | 20 | local n = tonumber(arg[1]) 21 | 22 | for i = 1, n do 23 | local b64k = io.popen(string.format("wh genkey public | tee /tmp/spawn/%s.sk | wh pubkey", i)):read() 24 | print(b64k) 25 | local k = wh.fromb64(b64k) 26 | 27 | print("spawn", i, b64k) 28 | os.execute(string.format("WH_LOGPATH=/tmp/spawn/%s.log wh up public private-key /tmp/spawn/%s.sk listen-port 0 &", b64k, i)) 29 | end 30 | -------------------------------------------------------------------------------- /src/tools/bid.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh bid ') 3 | end 4 | 5 | if not arg[2] or arg[2] == 'help' or not arg[3] then 6 | return help() 7 | end 8 | 9 | if arg[2] == '-' and arg[3] == '-' then 10 | print("ERROR: only one argument may read STDIN") 11 | return -1 12 | end 13 | 14 | local function read_key(idx) 15 | local b64k = arg[idx] 16 | if b64k == '-' then b64k = io.stdin:read() end 17 | local ok, value = pcall(wh.fromb64, b64k) 18 | if not ok then return false, string.format("Invalid key: %s", b64k) end 19 | return true, value 20 | end 21 | 22 | local ok, k1 = read_key(2) 23 | if not ok then 24 | print(k1) 25 | return help() 26 | end 27 | 28 | local ok, k2 = read_key(3) 29 | if not ok then 30 | print(k2) 31 | return help() 32 | end 33 | 34 | local bid = wh.bid(k1, k2) 35 | 36 | print(bid) 37 | 38 | -------------------------------------------------------------------------------- /src/tools/workbit.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh workbit { | namespace }') 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local conf 10 | local idx 11 | if arg[2] and arg[2] ~= "namespace" then 12 | local err 13 | conf, err = openconf(arg[2]) 14 | if not conf then 15 | printf("error: %s", err) 16 | return -1 17 | end 18 | 19 | idx = 3 20 | 21 | else 22 | idx = 2 23 | end 24 | 25 | if not conf then 26 | conf = parsearg(idx, { 27 | namespace=tostring, 28 | }) 29 | 30 | if not conf.namespace then 31 | conf.namespace = wh.DEFAULT_NAMESPACE 32 | end 33 | end 34 | 35 | local k = io.stdin:read() 36 | local ok, value = pcall(wh.fromb64, k) 37 | 38 | if not ok then 39 | printf("Invalid name: %s", value) 40 | return 41 | end 42 | 43 | k = value 44 | 45 | local wb = wh.workbit(k, conf.namespace or 'public') 46 | 47 | print(wb) 48 | -------------------------------------------------------------------------------- /src/tools/down.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh down ') 3 | end 4 | 5 | local interface = arg[2] 6 | if not interface or interface == 'help' then 7 | return help() 8 | end 9 | 10 | local ipc=require'ipc' 11 | 12 | local ok, value = pcall(ipc.call, interface, 'down') 13 | 14 | if not ok then 15 | printf("%s\nError when connecting to WireHub daemon.", value) 16 | return 17 | end 18 | 19 | local sock = value 20 | if not sock then 21 | print("Interface not attached to WireHub") 22 | return -1 23 | end 24 | 25 | local ret = -1 26 | 27 | now = wh.now() 28 | while true do 29 | local r = wh.select({sock}, {}, {}, now+30) 30 | now = wh.now() 31 | 32 | if not r[sock] then 33 | printf("timeout") 34 | break 35 | end 36 | 37 | local buf = wh.recv(sock, 65535) 38 | 39 | if not buf or #buf == 0 then 40 | break 41 | end 42 | 43 | if buf == "OK\n" then 44 | ret = 0 45 | break 46 | end 47 | end 48 | wh.close(sock) 49 | 50 | return ret 51 | 52 | -------------------------------------------------------------------------------- /contrib/micronet/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean run-server vgd-server docker 2 | 3 | MICRONET=bin/micronet 4 | SRC=$(wildcard src/*.c) obj/server.lua.c 5 | HDR=$(wildcard src/*.h) 6 | 7 | CFLAGS=-Wall -Wextra -g -llua -I/usr/include/lua5.3 -L/usr/lib/lua5.3 8 | 9 | all: $(MICRONET) 10 | 11 | /dev/net/tun: 12 | mkdir /dev/net 13 | mknod /dev/net/tun c 10 200 14 | 15 | obj/server.lua.c: src/server.lua scripts/file2buf.py 16 | @mkdir -p obj 17 | luac5.3 -o obj/server.luac src/server.lua 18 | scripts/file2buf.py obj/server.luac _luacode_server > obj/server.lua.c 19 | 20 | $(MICRONET): $(SRC) $(HDR) 21 | @mkdir -p bin 22 | $(CC) -o $(MICRONET) $(SRC) $(CFLAGS) 23 | 24 | docker: 25 | docker build -t wirehub/micronet . 26 | 27 | clean: 28 | rm -rf obj bin 29 | 30 | run-server: $(MICRONET) /dev/net/tun 31 | $(MICRONET) server examples/conf.lua 32 | 33 | gdb-server: $(MICRONET) /dev/net/tun 34 | gdb -ex run -args $(MICRONET) server examples/conf.lua 35 | 36 | vgd-server: $(MICRONET) /dev/net/tun 37 | valgrind $(MICRONET) server examples/conf.lua 38 | 39 | -------------------------------------------------------------------------------- /src/tools/orchid.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh orchid { | namespace }') 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local conf 10 | local idx 11 | if arg[2] and arg[2] ~= "namespace" then 12 | local err 13 | conf, err = openconf(arg[2]) 14 | if not conf then 15 | printf("error: %s", err) 16 | return -1 17 | end 18 | 19 | idx = 3 20 | 21 | else 22 | idx = 2 23 | end 24 | 25 | if not conf then 26 | conf = parsearg(idx, { 27 | namespace=tostring, 28 | }) 29 | 30 | if not conf.namespace then 31 | conf.namespace = wh.DEFAULT_NAMESPACE 32 | end 33 | end 34 | 35 | local b64k = io.stdin:read() 36 | local ok, value = pcall(wh.fromb64, b64k) 37 | 38 | if not ok then 39 | printf("invalid key") 40 | return -1 41 | end 42 | 43 | local k = value 44 | 45 | local addr = wh.orchid(conf.namespace, k, 0) 46 | 47 | addr = tostring(addr) 48 | addr = string.sub(addr, 2, string.find(addr, ']')-1) 49 | 50 | print(addr) 51 | -------------------------------------------------------------------------------- /src/core/orchid.c: -------------------------------------------------------------------------------- 1 | #include "net.h" 2 | #include 3 | 4 | void orchid(struct address* a, const void* cid, size_t cid_sz, const void* m, size_t l, uint16_t port) { 5 | unsigned char hash[crypto_generichash_BYTES]; 6 | crypto_generichash_state s; 7 | crypto_generichash_init(&s, NULL, 0, sizeof(hash)); 8 | crypto_generichash_update(&s, (const void*)cid, cid_sz); 9 | crypto_generichash_update(&s, (const void*)m, l); 10 | crypto_generichash_final(&s, hash, sizeof(hash)); 11 | 12 | a->sa_family = a->in6.sin6_family = AF_INET6; 13 | a->in6.sin6_port = htons(port); 14 | 15 | // XXX RFC 4843 states to get the middle 100-bit-long bitstring from the 16 | // hash 17 | 18 | assert(sizeof(a->in6.sin6_addr) <= crypto_generichash_BYTES); 19 | memcpy((uint8_t*)&a->in6.sin6_addr, hash, sizeof(a->in6.sin6_addr)); 20 | ((uint8_t*)&a->in6.sin6_addr)[0] = 0x20; 21 | ((uint8_t*)&a->in6.sin6_addr)[1] = 0x01; 22 | ((uint8_t*)&a->in6.sin6_addr)[2] &= 0x0f; 23 | ((uint8_t*)&a->in6.sin6_addr)[2] |= 0x10; 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/core/packet.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_PACKET_H 2 | #define WIREHUB_PACKET_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #define packet_flags_TIMEMASK (((uint64_t)-1) >> 1) 9 | #define packet_flags_TIMESHIFT 0 10 | #define packet_flags_DIRECTMASK 0x1 11 | #define packet_flags_DIRECTSHIFT 63 12 | 13 | #define packet_hdr(p) (p+0) 14 | #define packet_src(p) (packet_hdr(p)+4) 15 | #define packet_flags_time(p) (packet_src(p)+crypto_scalarmult_curve25519_BYTES) 16 | #define packet_body(p) (packet_flags_time(p)+8) 17 | #define packet_mac(p,l) (packet_body(p)+l) 18 | 19 | static inline size_t packet_size(size_t l) { 20 | return ( 21 | 4 + 22 | crypto_scalarmult_curve25519_BYTES + 23 | //crypto_scalarmult_curve25519_BYTES + 24 | 8 + 25 | l + 26 | crypto_auth_hmacsha512256_BYTES 27 | ); 28 | } 29 | 30 | int auth_packet(uint8_t* p, size_t l, const uint8_t* sk, const uint8_t* pk); 31 | int verify_packet(const uint8_t* p, size_t pl, const uint8_t* sk); 32 | 33 | #endif // PACKET_H 34 | 35 | -------------------------------------------------------------------------------- /docker/Dockerfile.sandbox: -------------------------------------------------------------------------------- 1 | FROM wirehub/builder:latest 2 | 3 | RUN (cd /baseroot && tar cf - .) | (cd / && tar xf -) && \ 4 | rm -r /baseroot /opt /usr/local/lib/lua/5.3/whcore.so /usr/bin/wh && \ 5 | printf "#!/bin/sh\nexport LUA_PATH=/root/wh/src/?.lua\nlua /root/wh/src/tools/cli.lua \$@\n" >> /usr/bin/wh && \ 6 | chmod +x /usr/bin/wh 7 | 8 | RUN apk add --no-cache \ 9 | bash \ 10 | bash-completion \ 11 | bmon \ 12 | build-base \ 13 | curl \ 14 | gdb \ 15 | git \ 16 | iptables \ 17 | linux-headers \ 18 | mtr \ 19 | pv \ 20 | strace \ 21 | tcpdump \ 22 | valgrind \ 23 | vim 24 | 25 | ENV DEBUG y 26 | ENV LUA_PATH "/root/wh/src/?.lua" 27 | ENV LUA_CPATH "/root/wh/.obj/?.so" 28 | ENV PATH "/root/wh/docker:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 29 | 30 | ENV WH_EXPERIMENTAL_MODIFY_HOSTS true 31 | 32 | RUN ln -s /root/wh/docker/sandbox.bashrc /root/.bashrc 33 | RUN wh completion get-bash > /usr/share/bash-completion/completions/wh 34 | 35 | WORKDIR /root/wh 36 | COPY docker docker 37 | 38 | -------------------------------------------------------------------------------- /src/core/packet.c: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | 3 | int auth_packet(uint8_t* p, size_t l, const uint8_t* sk, const uint8_t* pk) { 4 | uint8_t k[crypto_scalarmult_curve25519_SCALARBYTES]; 5 | sodium_mlock(k, sizeof(k)); 6 | 7 | if (crypto_scalarmult_curve25519(k, sk, pk) != 0) { 8 | return -1; 9 | } 10 | 11 | crypto_auth_hmacsha512256(packet_mac(p, l), p, packet_mac(p, l)-p, k); 12 | 13 | sodium_munlock(k, sizeof(k)); 14 | 15 | return 0; 16 | } 17 | 18 | int verify_packet(const uint8_t* p, size_t pl, const uint8_t* sk) { 19 | if (pl 5 | 6 | /** Reads `fd`, deserializes one element and pushes it in the stack. 7 | * 8 | * Returns 1 if an element was read; 0 if none element was read; -1 if 9 | * deserialization failed. 10 | */ 11 | int luaW_read(lua_State* L, int fd); 12 | 13 | /** Reads `fd`, deserializes all elements and pushes them in the stack. 14 | * 15 | * Returns 0 if succeed, else -1. 16 | */ 17 | int luaW_readstack(lua_State* L, int fd); 18 | 19 | /** Serializes element at index `idx` and writes it in `fd`. 20 | * 21 | * Returns 1 if an element was written; 0 if not element was written; raises a 22 | * Lua error if something went wrong. 23 | */ 24 | int luaW_write(lua_State* L, int idx, int fd); 25 | 26 | /** Serializes the stack from index `idx` and writes them in `fd`. 27 | * 28 | * Raises a Lua error if something went wrong. 29 | */ 30 | void luaW_writestack(lua_State* L, int idx, int fd); 31 | 32 | /** Serializes element LUA_TNONE and writes it in `fd`. 33 | * 34 | * Returns 1 if written; 0 if not. 35 | */ 36 | void luaW_writenone(int fd); 37 | 38 | #endif // WH_SERDES_H 39 | 40 | -------------------------------------------------------------------------------- /docker/wh-sandbox-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | if io.open("tests/keys/config") == nil then 4 | print("You first need to generate test keys.") 5 | print("To do so, run on your host machine ...") 6 | print("") 7 | print(" ./tests/generate-keys.sh") 8 | print("") 9 | print("... and retry") 10 | 11 | return 12 | end 13 | 14 | if arg[1] == nil then 15 | print(string.format("usage: %s ", arg[0])) 16 | 17 | return 18 | end 19 | 20 | local id = tonumber(arg[1]) 21 | local mode = arg[2] or 'unknown' 22 | assert(id) 23 | 24 | function execf(...) 25 | local cmd = string.format(...) 26 | print("\x1b[1;30m$ " .. cmd .. "\x1b[0m") 27 | return os.execute(cmd) 28 | end 29 | 30 | execf("rm -f /tmp/log") 31 | execf("make") 32 | print(string.rep('-', 80)) 33 | 34 | --execf("cp tests/keys/config /etc/wirehub/test") 35 | 36 | args = string.format("up ./tests/keys/config private-key tests/keys/%d.sk mode %s", id, mode) 37 | 38 | if os.getenv("VALGRIND") then 39 | execf("WH_LOGPATH=/tmp/log valgrind /usr/local/bin/lua src/tools/cli.lua " .. args) 40 | else 41 | execf("WH_LOGPATH=/tmp/log wh " .. args) 42 | end 43 | 44 | -------------------------------------------------------------------------------- /contrib/micronet/src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef MICRONET_COMMON_H 2 | #define MICRONET_COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "config.h" 17 | 18 | #define ERROR(func) \ 19 | fprintf(stderr, func " error: %s (%s:%d)\n", strerror(errno), __FILE__, __LINE__) 20 | 21 | #define UNET_STR(x) UNET_STR_(x) 22 | #define UNET_STR_(x) #x 23 | 24 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 25 | #define LOG_SOCKADDR(addr) \ 26 | do { \ 27 | char addr_s[INET_ADDRSTRLEN+1]; \ 28 | inet_ntop(AF_INET, &(addr)->sin_addr,addr_s, sizeof(addr_s)-1); \ 29 | LOG("%s:%d", addr_s, ntohs((addr)->sin_port)); \ 30 | } while(0) 31 | 32 | #define LOG_ADDR(addr) \ 33 | do { \ 34 | char addr_s[INET_ADDRSTRLEN+1]; \ 35 | inet_ntop(AF_INET, addr, addr_s, sizeof(addr_s)-1); \ 36 | LOG("%s", addr_s); \ 37 | } while(0) 38 | 39 | 40 | #endif // MICRONET_COMMON_H 41 | 42 | -------------------------------------------------------------------------------- /src/tools/ipc.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh ipc ') 3 | end 4 | 5 | local interface = arg[2] 6 | 7 | if not interface then 8 | return help() 9 | end 10 | 11 | local s = {} 12 | local idx = 3 13 | while arg[idx] do 14 | if arg[idx] == '-' then 15 | s[#s+1] = io.stdin:read() 16 | s[#s+1] = ' ' 17 | break 18 | else 19 | s[#s+1] = arg[idx] 20 | s[#s+1] = ' ' 21 | idx = idx + 1 22 | end 23 | end 24 | 25 | if #s == 0 then 26 | return help() 27 | end 28 | 29 | s[#s] = nil 30 | s = table.concat(s) 31 | 32 | local ipc=require'ipc' 33 | 34 | local ok, value = pcall(ipc.call, interface, s) 35 | 36 | if not ok then 37 | printf("%s\nError when connecting to WireHub daemon.", value) 38 | return 39 | end 40 | 41 | local sock = value 42 | if not sock then 43 | print("Interface not attached to WireHub") 44 | return 45 | end 46 | 47 | while true do 48 | wh.select({sock}, {}, {}, nil) 49 | 50 | local buf = wh.recv(sock, 65535) 51 | 52 | if not buf or #buf == 0 then 53 | break 54 | end 55 | 56 | io.stdout:write(buf) 57 | io.stdout:flush() 58 | end 59 | 60 | wh.close(sock) 61 | -------------------------------------------------------------------------------- /src/tools/forget.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | printf('Usage: wh forget ') 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local interface = arg[2] 10 | local k = arg[3] 11 | 12 | if not interface or not k then 13 | return help() 14 | end 15 | 16 | local ipc_cmd = string.format('forget %s', k) 17 | local ok, value = pcall(require('ipc').call, interface, ipc_cmd) 18 | if not ok then 19 | printf("%s\nError when connecting to WireHub daemon.", value) 20 | return 21 | end 22 | 23 | local sock = value 24 | if not sock then 25 | print("Interface not attached to WireHub") 26 | return 27 | end 28 | 29 | local b64k, via_b64k, addr, mode, is_nated, relay 30 | 31 | now = wh.now() 32 | while true do 33 | local r = wh.select({sock}, {}, {}, now+30) 34 | now = wh.now() 35 | 36 | if not r[sock] then 37 | wh.close(sock) 38 | printf("timeout") 39 | return -1 -- timeout 40 | end 41 | 42 | local buf = wh.recv(sock, 65535) 43 | 44 | if not buf or #buf == 0 then 45 | break 46 | end 47 | 48 | printf("$(red)bad format: %s$(reset)", buf) 49 | break 50 | end 51 | wh.close(sock) 52 | 53 | -------------------------------------------------------------------------------- /contrib/micronet/src/peer.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | static void _peer_kernel(struct node* n, struct packet* p) { 4 | if (p->dir == UP) { 5 | if (n->as.peer.ip.addr.s_addr != p->hdr.ip_src.s_addr) { 6 | DROP(n, p, "bad source address"); 7 | } 8 | 9 | sendto_id(n, n->up, p); 10 | } 11 | 12 | else { // p->dir == DOWN 13 | if (n->as.peer.ip.addr.s_addr != p->hdr.ip_dst.s_addr) { 14 | DROP(n, p, "bad destination address"); 15 | } 16 | 17 | if (n->addr.sin_addr.s_addr == 0) { 18 | DROP(n, p, "unknown micronet client address"); 19 | } 20 | 21 | struct iovec iov[1]; 22 | iov[0].iov_base = p->body; 23 | iov[0].iov_len = p->sz; 24 | _udp_sendto(&n->addr, iov, 1); 25 | 26 | LOG("\n"); 27 | free(p), p=NULL; 28 | } 29 | } 30 | 31 | int _peer(lua_State* L) { 32 | struct node* n = _init_node(L); 33 | struct subnet* up_ip = luaL_checkudata(L, NODE_ARGS(1), "subnet"); 34 | struct subnet* up_gw = luaL_checkudata(L, NODE_ARGS(2), "subnet"); 35 | 36 | n->kernel = _peer_kernel; 37 | n->as.peer.ip = *up_ip; 38 | n->as.peer.gw = *up_gw; 39 | 40 | 41 | return 0; 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /docker/sandbox.bashrc: -------------------------------------------------------------------------------- 1 | export PS1='\u@\h:\W \$ ' 2 | source /etc/profile.d/bash_completion.sh 3 | 4 | alias t="wh-sandbox-test" 5 | 6 | if [ ! -f /dev/net/tun ]; then 7 | mkdir /dev/net 8 | mknod /dev/net/tun c 10 200 9 | fi 10 | 11 | function compile_micronet() { 12 | cp -r contrib/micronet /tmp 13 | (cd /tmp/micronet && make clean && make) 14 | cp /tmp/micronet/bin/micronet /usr/local/bin 15 | } 16 | 17 | clear 18 | echo "#####################" 19 | echo "# wirehub's sandbox #" 20 | echo "#####################" 21 | echo "" 22 | wh check-wg 23 | 24 | 25 | if [ ! -z "$MICRONET" ]; then 26 | if [ -z "$MICRONET_SERVER" ]; then 27 | export MICRONET_SERVER=172.17.0.1 28 | fi 29 | 30 | echo "µnet is enabled, node is $MICRONET, server is $MICRONET_SERVER" 31 | compile_micronet 32 | UNET_SERVERNAME=$MICRONET_SERVER micronet client $MICRONET & 33 | fi 34 | 35 | if [ ! -z "$ROOT" ]; then 36 | echo "start root" 37 | echo $ROOT > /tmp/sk 38 | lua src/sink-udp.lua & 39 | wh up public private-key /tmp/sk mode direct & 40 | 41 | elif [ ! -z "$T" ]; then 42 | if [ ! -f "tests/keys/config" ]; then 43 | wh-sandbox-test -1 44 | exit -1 45 | fi 46 | echo "start test node $T" 47 | wh-sandbox-test $T & 48 | fi 49 | 50 | -------------------------------------------------------------------------------- /src/tools/genkey.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh genkey { | workbit namespace } [threads ]]') 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local args = { 10 | threads=tonumber, 11 | } 12 | 13 | local idx 14 | local conf 15 | if arg[2] and arg[2] ~= 'workbit' and arg[2] ~= 'namespace' and arg[2] ~= 'threads' then 16 | local err 17 | conf, err = openconf(arg[2]) 18 | if not conf then 19 | printf("error: %s", err) 20 | return -1 21 | end 22 | 23 | idx = 3 24 | else 25 | args.namespace = tostring 26 | args.workbit = tonumber 27 | 28 | idx = 2 29 | end 30 | 31 | local opts = parsearg(idx, args) 32 | if not opts then 33 | return help() 34 | end 35 | 36 | if not conf then 37 | if not opts.namespace then 38 | opts.namespace = wh.DEFAULT_NAMESPACE 39 | end 40 | 41 | if not opts.workbit then 42 | opts.workbit = wh.DEFAULT_WORKBIT 43 | end 44 | 45 | conf = opts 46 | end 47 | 48 | if not conf then 49 | printf("Unknown network `%s'", name) 50 | return help() 51 | end 52 | 53 | local sign_sk, sign_k, sk, k = wh.genkey( 54 | conf.namespace, 55 | conf.workbit, 56 | opts.threads or 0 57 | ) 58 | 59 | print(wh.tob64(wh.revealsk(sk), 'wg')) 60 | -------------------------------------------------------------------------------- /src/tools/find.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh find ') 3 | end 4 | 5 | local interface = arg[2] 6 | local b64k = arg[3] 7 | 8 | if not interface or not b64k then 9 | return help() 10 | end 11 | 12 | local wg = wh.wg.get(interface) 13 | 14 | if not wg then 15 | print("Unable to access interface: No such device") 16 | return 17 | end 18 | 19 | local ok, k = pcall(wh.fromb64, b64k) 20 | 21 | if not ok then 22 | print("Invalid key") 23 | return 24 | end 25 | 26 | local ipc=require'ipc' 27 | 28 | local ok, value = pcall(ipc.call, interface, 'search ' .. b64k) 29 | 30 | if not ok then 31 | printf("%s\nError when connecting to WireHub daemon.", value) 32 | return 33 | end 34 | 35 | local sock = value 36 | if not sock then 37 | print("Interface not attached to WireHub") 38 | return 39 | end 40 | 41 | local buf = '' 42 | while true do 43 | local r = wh.select({sock}, {}, {}, 60) 44 | 45 | if #r == 0 then 46 | break 47 | end 48 | 49 | local chunk = wh.recv(sock, 65535) 50 | buf = buf .. chunk 51 | 52 | local newline_idx = string.find(buf, '\n') 53 | if newline_idx then 54 | local line = string.sub(buf, 1, newline_idx-1) 55 | buf = string.sub(buf, newline_idx+1) 56 | 57 | on_new_endpoint(line) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /contrib/micronet/src/nat.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | #define PORT_MAX 65535 4 | #define ICMP_ID_MAX 65535 5 | #define NAT_TIMEOUT 60 6 | 7 | static void _nat_kernel(struct node* n, struct packet* p) { 8 | lua_rawgeti(L, LUA_REGISTRYINDEX, n->as.nat.kernel_ref); 9 | 10 | lua_pushlightuserdata(L, p); 11 | luaL_setmetatable(L, PACKET_MT); 12 | 13 | if (docall(L, 1, 1) != LUA_OK) { 14 | fprintf(stderr, "kernel error: %s\n", 15 | lua_tostring(L, -1) 16 | ); 17 | } 18 | 19 | int isnum; 20 | int id = lua_tointegerx(L, -1, &isnum); 21 | const char* name = lua_tostring(L, -1); 22 | lua_pop(L, 1); 23 | 24 | if (isnum) { 25 | packet_refresh_sum(p); 26 | sendto_id(n, id, p); 27 | } else { 28 | if (!name) name = "unknown reason"; 29 | DROP(n, p, "%s", name); 30 | } 31 | } 32 | 33 | int _nat(lua_State* L) { 34 | struct node* n = _init_node(L); 35 | struct subnet* up_ip = luaL_checkudata(L, NODE_ARGS(1), "subnet"); 36 | struct subnet* up_gw = luaL_checkudata(L, NODE_ARGS(2), "subnet"); 37 | luaL_checktype(L, NODE_ARGS(3), LUA_TFUNCTION); 38 | 39 | n->kernel = _nat_kernel; 40 | n->as.nat.ip = *up_ip; 41 | n->as.nat.gw = *up_gw; 42 | 43 | lua_pushvalue(L, NODE_ARGS(3)); 44 | n->as.nat.kernel_ref = luaL_ref(L, LUA_REGISTRYINDEX); 45 | 46 | return 0; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /contrib/micronet/src/micronet.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main_server(int argc, char* argv[]); 6 | int main_client(int argc, char* argv[]); 7 | int main_read(int argc, char* argv[]); 8 | 9 | static int help(char* arg0) { 10 | fprintf(stderr, 11 | "\n" 12 | "Usage: %s COMMAND\n" 13 | "\n" 14 | "Commands:\n" 15 | " client Run a client\n" 16 | " read Read configuration\n" 17 | " server Run a server\n" 18 | "\n", 19 | arg0 20 | ); 21 | 22 | return EXIT_FAILURE; 23 | } 24 | 25 | int main(int argc, char* argv[]) { 26 | if (argc == 0) { 27 | return EXIT_FAILURE; 28 | } 29 | 30 | char* arg0 = argv[0]; 31 | 32 | int cmd_idx = 0; 33 | char* m = strstr(arg0, "micronet"); 34 | if (m != NULL) { 35 | ++cmd_idx; 36 | } 37 | 38 | if (argc <= cmd_idx) { 39 | return help(arg0); 40 | } 41 | 42 | argc -= cmd_idx; 43 | argv += cmd_idx; 44 | 45 | if (strcmp(argv[0], "client") == 0) { 46 | return main_client(argc, argv); 47 | } else if (strcmp(argv[0], "server") == 0) { 48 | return main_server(argc, argv); 49 | } else if (strcmp(argv[0], "read") == 0) { 50 | return main_read(argc, argv); 51 | } else { 52 | return help(arg0); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/queue.lua: -------------------------------------------------------------------------------- 1 | -- Generic FIFO queue implementation 2 | 3 | local M = {} 4 | 5 | function M.push(q, e) 6 | q = q or {} 7 | q.tail = (q.tail or 0) + 1 8 | q[q.tail] = e 9 | q.heap = q.heap or q.tail 10 | return q 11 | end 12 | 13 | function M.remove(q, c) 14 | if not q then 15 | return 16 | end 17 | 18 | if not q.tail then 19 | assert(not q.heap) 20 | return 21 | end 22 | 23 | local i = q.heap 24 | while i <= q.tail and i-q.heap < c do 25 | q[i] = nil 26 | i = i + 1 27 | end 28 | q.heap = i 29 | 30 | if q.tail < q.heap then 31 | q.heap = nil 32 | q.tail = nil 33 | return nil 34 | end 35 | 36 | return q 37 | end 38 | 39 | function M.pop(q) 40 | if not q.heap then 41 | return 42 | end 43 | 44 | local v = q[q.heap] 45 | 46 | M.remove(q, 1) 47 | 48 | return v 49 | end 50 | 51 | function M.length(q) 52 | if not q or not q.heap then 53 | return 0 54 | end 55 | 56 | assert(q.tail) 57 | 58 | return q.tail - q.heap 59 | end 60 | 61 | local function queue_next(q, k) 62 | if not q or not q.heap then 63 | return 64 | end 65 | assert(q.tail) 66 | 67 | if k == nil then 68 | k = q.heap 69 | else 70 | k = k + 1 71 | end 72 | 73 | if k > q.tail then 74 | return 75 | end 76 | 77 | return k, q[k] 78 | end 79 | 80 | function M.iter(q) 81 | return queue_next, q, nil 82 | end 83 | 84 | return M 85 | 86 | -------------------------------------------------------------------------------- /src/core/secretdata.c: -------------------------------------------------------------------------------- 1 | #include "luawh.h" 2 | #include 3 | 4 | static const char* mt = "secret"; 5 | 6 | void* luaW_newsecret(lua_State* L, size_t len) { 7 | void* p = sodium_malloc(sizeof(size_t) + len); 8 | size_t *plen = p; 9 | void* buf = p + sizeof(size_t); 10 | 11 | luaW_declptr(L, mt, sodium_free); 12 | luaW_pushptr(L, mt, p); 13 | 14 | *plen = len; 15 | return buf; 16 | } 17 | 18 | void* luaW_checksecret(lua_State* L, int idx, size_t len) { 19 | void* p = luaW_checkptr(L, idx, mt); 20 | size_t *plen = (size_t*)p; 21 | 22 | if (*plen != len) { 23 | luaL_error(L, "bad secret size (%d expected, got %d)", 24 | (int)len, 25 | (int)*plen 26 | ); 27 | } 28 | 29 | return p+sizeof(size_t); 30 | } 31 | 32 | void* luaW_tosecret(lua_State* L, int idx, size_t len) { 33 | void* p = luaW_toptr(L, idx, mt); 34 | 35 | if (!p) { 36 | return NULL; 37 | } 38 | 39 | size_t *plen = (size_t*)p; 40 | if (*plen != len) { 41 | return NULL; 42 | } 43 | 44 | return p+sizeof(size_t); 45 | } 46 | 47 | void* luaW_ownsecret(lua_State* L, int idx, size_t len) { 48 | void* p = luaW_ownptr(L, idx, mt); 49 | size_t *plen = (size_t*)p; 50 | 51 | if (*plen != len) { 52 | luaL_error(L, "bad secret size (%zu expected, got %zu)", 53 | len, 54 | *plen 55 | ); 56 | } 57 | 58 | return p+sizeof(size_t); 59 | } 60 | 61 | void luaW_freesecret(void* p) { 62 | sodium_free(p-sizeof(size_t)); 63 | } 64 | -------------------------------------------------------------------------------- /contrib/micronet/src/wan.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | #define PORT_MAX 65535 4 | 5 | static void _wan_kernel(struct node* n, struct packet* p) { 6 | assert(p->dir == UP); 7 | 8 | int i; 9 | for (i=0; ias.wan.count; ++i) { 10 | struct route* r = &n->as.wan.routes[i]; 11 | 12 | if (p->hdr.ip_dst.s_addr == r->subnet.addr.s_addr) { 13 | p->dir = DOWN; 14 | sendto_id(n, r->id, p); 15 | return; 16 | } 17 | } 18 | 19 | char dst[INET_ADDRSTRLEN+1]; 20 | assert(inet_ntop(AF_INET, &p->hdr.ip_dst, dst, sizeof(dst)-1)); 21 | DROP(n, p, "unknown route %s", dst); 22 | } 23 | 24 | static struct route* luaN_checkroutes(lua_State* L, int idx, int* pcount) { 25 | luaL_checktype(L, idx, LUA_TTABLE); 26 | 27 | int i; 28 | *pcount = luaL_len(L, idx); 29 | struct route* r = calloc(*pcount, sizeof(struct route)); 30 | 31 | int l = luaL_len(L, idx); 32 | for (i=0; ikernel = _wan_kernel; 53 | n->as.wan.count = count; 54 | n->as.wan.routes = r; 55 | 56 | return 0; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/tools/inspect.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh inspect {all | }') 3 | end 4 | 5 | local interface = arg[2] 6 | if not interface or interface == 'help' then 7 | return help() 8 | end 9 | 10 | local ipc=require'ipc' 11 | 12 | local function inspect(interface) 13 | local ok, value = pcall(ipc.call, interface, 'inspect') 14 | 15 | if not ok then 16 | printf("%s\nError when connecting to WireHub daemon.", value) 17 | return -1 18 | end 19 | 20 | local sock = value 21 | if not sock then 22 | return -1 23 | end 24 | 25 | local ret = {} 26 | now = wh.now() 27 | while true do 28 | local r = wh.select({sock}, {}, {}, now+30) 29 | now = wh.now() 30 | 31 | if not r[sock] then 32 | printf("timeout") 33 | break 34 | end 35 | 36 | local buf = wh.recv(sock, 65535) 37 | 38 | if not buf or #buf == 0 then 39 | break 40 | end 41 | 42 | ret[#ret+1] = buf 43 | end 44 | 45 | wh.close(sock) 46 | 47 | return table.concat(ret) 48 | end 49 | 50 | if interface == 'all' then 51 | print('[') 52 | 53 | local interfaces = wh.ipc.list() 54 | table.sort(interfaces) 55 | for i, k in ipairs(interfaces) do 56 | if i > 1 then 57 | io.stdout:write(', ') 58 | end 59 | local v = inspect(k) 60 | if v == -1 then return -1 end 61 | io.stdout:write(v) 62 | end 63 | 64 | print(']') 65 | else 66 | local v = inspect(interface) 67 | if v == -1 then return -1 end 68 | print(v) 69 | end 70 | 71 | -------------------------------------------------------------------------------- /src/tools/reload.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print("Usage: wh reload { | all}") 3 | end 4 | 5 | local ipc = require'ipc' 6 | 7 | local all = {} 8 | local interface = arg[2] 9 | 10 | if interface == 'all' then interface = nil end 11 | 12 | local function call(interface, cmd) 13 | local ok, value = pcall(ipc.call, interface, cmd) 14 | 15 | if not ok then 16 | printf("Error when connecting to WireHub daemon: %s\n", value) 17 | return 18 | end 19 | 20 | local sock = value 21 | if not sock then 22 | return 23 | end 24 | 25 | local buf = {} 26 | while true do 27 | local r = wh.select({sock}, {}, {}, 1) 28 | 29 | if not r[sock] then 30 | r[#r+1] = '\n(daemon timeout)' 31 | break 32 | end 33 | 34 | local chunk = wh.recv(sock, 65535) 35 | if not chunk or #chunk == 0 then 36 | break 37 | end 38 | 39 | buf[#buf+1] = chunk 40 | end 41 | 42 | wh.close(sock) 43 | 44 | return table.concat(buf) 45 | end 46 | 47 | local names 48 | local whs = wh.ipc.list() 49 | for _, v in ipairs(whs) do whs[v] = true end 50 | if interface then 51 | if not whs[interface] then 52 | printf('invalid interface: %s', interface) 53 | return help() 54 | end 55 | 56 | names = {interface} 57 | else 58 | names = whs 59 | end 60 | 61 | table.sort(names) 62 | 63 | for _, name in ipairs(names) do 64 | local status = call(name, 'reload') 65 | 66 | if status ~= 'OK\n' then 67 | printf("interface $(bold)%s$(reset): %s", name,status) 68 | end 69 | end 70 | 71 | -------------------------------------------------------------------------------- /src/core/net.h: -------------------------------------------------------------------------------- 1 | #ifndef WIREHUB_NET_H 2 | #define WIREHUB_NET_H 3 | 4 | #include "common.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define IP4_HDRLEN 20 13 | #define UDP_HDRLEN 8 14 | 15 | struct address { 16 | int sa_family; 17 | union { 18 | struct sockaddr in; 19 | struct sockaddr_in in4; 20 | struct sockaddr_in6 in6; 21 | }; 22 | }; 23 | 24 | static inline uint16_t address_port(const struct address* a) { 25 | switch (a->sa_family) { 26 | case AF_INET: return ntohs(a->in4.sin_port); 27 | case AF_INET6: return ntohs(a->in6.sin6_port); 28 | default: return 0; 29 | }; 30 | } 31 | 32 | int parse_address(struct address* a, const char* endpoint, uint16_t port, int numeric); 33 | const char* format_address(const struct address* a, char* s, size_t sl); 34 | int address_from_sockaddr(struct address* out, const struct sockaddr* in); 35 | socklen_t address_len(const struct address* a); 36 | void orchid(struct address* a, const void* cid, size_t cid_sz, const void* m, size_t l, uint16_t port); 37 | 38 | int socket_udp(const struct address* a); 39 | int socket_raw_udp(sa_family_t sa_family, int hdrincl); 40 | int ip4_to_udp(const void* d, const void** pdata, size_t* psize, struct address* src, struct address* dst); 41 | 42 | enum sniff_proto { 43 | SNIFF_PROTO_WG, 44 | SNIFF_PROTO_WH, 45 | }; 46 | 47 | pcap_t* sniff(const char* interface, pcap_direction_t direction, enum sniff_proto proto, const char* expr); 48 | 49 | uint16_t checksum_ip(const void* addr, int len); 50 | 51 | #endif // WIREHUB_NET_H 52 | 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build clean docker docker-sandbox docker-root1 run-docker run-sandbox 2 | 3 | SO = .obj/whcore.so 4 | SRC_C = $(wildcard src/core/*.c) 5 | OBJ_C = $(patsubst src/core/%.c,.obj/%.o,$(SRC_C)) .obj/embeddable-wg.o 6 | 7 | EMBED_WG_PATH = deps/WireGuard/contrib/examples/embeddable-wg-library 8 | 9 | CC=gcc 10 | MINIMAL_CFLAGS=-Wall -fPIC 11 | DEBUG?=n 12 | 13 | ifeq ($(DEBUG), y) 14 | MINIMAL_CFLAGS+=-g 15 | else 16 | MINIMAL_CFLAGS+=-O2 17 | endif 18 | 19 | CFLAGS=$(MINIMAL_CFLAGS) -Wextra -Ideps/WireGuard/contrib/examples/embeddable-wg-library 20 | WG_EMBED_CFLAGS=$(MINIMAL_CFLAGS) 21 | LDFLAGS=-lsodium -lpthread -lpcap -lminiupnpc 22 | 23 | all: build 24 | 25 | build: $(SO) 26 | 27 | $(SO): $(OBJ_C) 28 | $(CC) -shared -o $@ $(OBJ_C) $(LDFLAGS) 29 | ifeq ($(DEBUG), n) 30 | strip $@ 31 | endif 32 | @ls -lh $@ 33 | 34 | .obj/embeddable-wg.o: $(EMBED_WG_PATH)/wireguard.c 35 | $(CC) -c $< -o $@ $(WG_EMBED_CFLAGS) 36 | 37 | .obj/%.o: src/core/%.c 38 | @mkdir -p .obj 39 | $(CC) -c $< -o $@ $(CFLAGS) 40 | 41 | clean: 42 | rm -f $(SO) $(OBJ_C) 43 | 44 | docker: 45 | docker build -t wirehub/wh -f docker/Dockerfile . 46 | 47 | docker-sandbox: 48 | docker build --target builder -t wirehub/builder -f docker/Dockerfile . 49 | docker build -t wirehub/sandbox -f docker/Dockerfile.sandbox . 50 | 51 | docker-root1: docker 52 | docker build --no-cache=true -t wirehub/root1 -f docker/Dockerfile.root1 . 53 | 54 | run-docker: 55 | docker run -it --rm --cap-add NET_ADMIN --cap-add SYS_ADMIN --cap-add SYS_PTRACE wirehub /bin/sh 56 | 57 | run-sandbox: 58 | docker run -it --rm --cap-add NET_ADMIN --cap-add SYS_ADMIN --cap-add SYS_PTRACE -v "$(shell pwd):/root/wh" wirehub/sandbox /bin/bash 59 | 60 | -------------------------------------------------------------------------------- /src/tools/authenticate.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print('Usage: wh authenticate {|} ') 3 | end 4 | 5 | if arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local interface = arg[2] 10 | local k = arg[3] 11 | local alias_sk_path = arg[4] 12 | 13 | if not interface or not k or not alias_sk_path then 14 | return help() 15 | end 16 | 17 | -- XXX 18 | local alias_sk = wh.readsk(alias_sk_path) 19 | if not alias_sk then 20 | printf('cannot load alias private key: %s', alias_sk_path) 21 | return help() 22 | end 23 | 24 | local cmd = string.format('authenticate %s %s', k, alias_sk_path) 25 | local ok, value = pcall(require('ipc').call, interface, cmd) 26 | 27 | if not ok then 28 | printf("error when connecting to WireHub daemon: %s", value) 29 | end 30 | 31 | local sock = value 32 | if not sock then 33 | print("Interface not attached to WireHub") 34 | return 35 | end 36 | 37 | local via_k, addr, mode, is_nated, relay 38 | 39 | local resp = {} 40 | now = wh.now() 41 | while true do 42 | local r = wh.select({sock}, {}, {}, now+30) 43 | --now = wh.now() 44 | 45 | if not r[sock] then 46 | wh.close(sock) 47 | r={'timeout'} 48 | return -1 -- timeout 49 | end 50 | 51 | local buf = wh.recv(sock, 65535) 52 | if not buf or #buf == 0 then 53 | break 54 | end 55 | 56 | resp[#resp+1] = buf 57 | end 58 | wh.close(sock) 59 | 60 | resp = table.concat(resp) 61 | if string.match(resp, 'authenticated!') then 62 | return 0 63 | else 64 | resp = string.match(resp, 'failed: (.*)\n') 65 | if resp then 66 | printf('%s', resp) 67 | end 68 | return -1 69 | end 70 | 71 | -------------------------------------------------------------------------------- /contrib/micronet/src/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef MICRONET_CONF_H 2 | #define MICRONET_CONF_H 3 | 4 | #include "common.h" 5 | #include 6 | #include 7 | #include 8 | 9 | #define PACKET_MT "packet" 10 | 11 | typedef uint32_t node_id; 12 | #define NODEID_NULL ((node_id)0) 13 | 14 | extern lua_State* L; 15 | extern unsigned int nodes_max; 16 | extern struct node* nodes; 17 | 18 | struct node; 19 | struct packet; 20 | 21 | typedef void(*node_kernel_cb)(struct node* n, struct packet* p); 22 | 23 | struct subnet { 24 | struct in_addr addr; 25 | uint8_t cidr; 26 | }; 27 | 28 | struct route { 29 | struct subnet subnet; 30 | node_id id; 31 | }; 32 | 33 | struct node { 34 | node_id id; 35 | struct sockaddr_in addr; 36 | struct packet* pkts_heap,* pkts_tail; 37 | node_kernel_cb kernel; 38 | char* type; 39 | node_id up; 40 | 41 | union { 42 | struct { 43 | struct subnet ip, gw; 44 | } peer; 45 | 46 | struct { 47 | node_id down; 48 | } link; 49 | 50 | struct { 51 | int count; 52 | struct route* routes; 53 | } wan; 54 | 55 | struct { 56 | int kernel_ref; 57 | struct subnet ip, gw; 58 | } nat; 59 | } as; 60 | }; 61 | 62 | int load_config(const char* confpath); 63 | 64 | node_id luaN_checkid(lua_State* L, int idx); 65 | node_id luaN_checkidornil(lua_State* L, int idx); 66 | 67 | struct node* _init_node(lua_State* L); 68 | 69 | static inline struct node* NODE(unsigned int i) { 70 | assert(i > 0 && i <= nodes_max); 71 | return &nodes[i-1]; 72 | } 73 | 74 | int docall (lua_State *L, int narg, int nres); 75 | 76 | #endif // MICRONET_CONF_H 77 | 78 | -------------------------------------------------------------------------------- /contrib/micronet/README.md: -------------------------------------------------------------------------------- 1 | # micronet 2 | 3 | `micronet` is a small software to simulate IP networks. A network topology is 4 | defined in a configuration file. A server is run and relay the traffic between 5 | the peers. Each peer runs a client which initiate a TUN IP tunnel on which all IP 6 | traffic is routed. 7 | 8 | It is used to test WireHub in a simulated Internet on one single machine. 9 | Containers are spawned with WireHub running, and micronet routes the network 10 | traffic between containers. 11 | 12 | Configuration files language is a DSL over Lua. For example, 13 | 14 | ``` 15 | -- Initiate a WAN 16 | W = wan() 17 | 18 | -- Initiate a public peer, with IP 51.15.227.165. It will act as the WireHub's 19 | -- bootstrap node 20 | M(W | peer{up_ip=subnet('51.15.227.165', 0)}) 21 | 22 | -- Initiate another public peer, with IP 1.1.1.1 23 | M(W | peer{up_ip=subnet("1.1.1.1", 0)}) 24 | 25 | -- Initiate a peer behind a full-cone NAT whose IP is 1.1.1.2 26 | M(W | nat{up_ip=subnet('1.1.1.2', 0), mode=NAT_FULL_CONE} | peer()) 27 | ``` 28 | 29 | ## Features 30 | 31 | - **NATs**: symmetric, full-cone, restricted-cone and restricted-port NATs are 32 | supported; 33 | 34 | - **ICMP echo and echo reply**: used for network pings; 35 | 36 | - **UDP**: used by WireGuard and WireHub; 37 | 38 | - **Extensible network componenets with Lua**: network components can be 39 | customized in Lua (see the NAT component). 40 | 41 | ## TODO 42 | 43 | - TCP through NAT: TCP traffic going through NAT is currently not supported 44 | and no effort was done to make it work, as not required by WireHub. 45 | 46 | - Hop simulation: Currently, TTL of IP packets are not decremented. A 47 | traceroute will report always one hop. 48 | 49 | - UPnP support 50 | 51 | - Simulated latency and packet drops 52 | -------------------------------------------------------------------------------- /src/core/ipc_event.c: -------------------------------------------------------------------------------- 1 | #include "luawh.h" 2 | 3 | #define MT "ipc_event" 4 | 5 | struct ipc_event { 6 | int fds[2]; 7 | }; 8 | 9 | static int _set(lua_State* L) { 10 | struct ipc_event* pe = luaW_checkptr(L, 1, MT); 11 | 12 | if (write(pe->fds[1], "\x2a", 1) < 0) { 13 | luaL_error(L, "write() failed: %s", strerror(errno)); 14 | } 15 | 16 | return 0; 17 | } 18 | 19 | static int _clear(lua_State* L) { 20 | struct ipc_event* pe = luaW_checkptr(L, 1, MT); 21 | 22 | char buf[128]; 23 | if (read(pe->fds[0], buf, sizeof(buf)) < 0) { 24 | luaL_error(L, "read() failed: %s", strerror(errno)); 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | static void _delete(void* ud) { 31 | struct ipc_event* pe = ud; 32 | 33 | close(pe->fds[0]); 34 | close(pe->fds[1]); 35 | free(pe); 36 | } 37 | 38 | static int _close(lua_State* L) { 39 | struct ipc_event* pe = luaW_ownptr(L, 1, MT); 40 | 41 | _delete(pe); 42 | return 0; 43 | } 44 | 45 | static int _fd(lua_State* L) { 46 | struct ipc_event* pe = luaW_checkptr(L, 1, MT); 47 | lua_pushinteger(L, pe->fds[0]); 48 | return 1; 49 | } 50 | 51 | static int _new(lua_State* L) { 52 | int fds[2]; 53 | if (pipe(fds)) { 54 | luaL_error(L, "pipe() failed: %s", strerror(errno)); 55 | } 56 | 57 | struct ipc_event* pe = malloc(sizeof(struct ipc_event)); 58 | pe->fds[0] = fds[0]; 59 | pe->fds[1] = fds[1]; 60 | luaW_pushptr(L, MT, pe); 61 | 62 | return 1; 63 | } 64 | 65 | static const luaL_Reg funcs[] = { 66 | {"clear", _clear}, 67 | {"close", _close}, 68 | {"get_fd", _fd}, 69 | {"new", _new}, 70 | {"set", _set}, 71 | {NULL, NULL}, 72 | }; 73 | 74 | LUAMOD_API int luaopen_ipc_event(lua_State* L) { 75 | luaL_checkversion(L); 76 | luaL_newlib(L, funcs); 77 | 78 | luaW_declptr(L, MT, _delete); 79 | 80 | return 1; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/bwlog.lua: -------------------------------------------------------------------------------- 1 | -- Bandwith Logger 2 | -- 3 | -- -- bandwidth logger with a scale of 1 second 4 | -- local bw = require('bwlog'){scale = 1.0} 5 | -- 6 | -- -- packet was received from peer A 7 | -- bw:add_rx(A.k, #me) 8 | -- 9 | -- -- packet is sent to peer A 10 | -- bw:add_tx(A.k, #me) 11 | -- 12 | -- -- Calculate stats 13 | -- bw:avg() 14 | -- 15 | -- -- If method 'avg()' is not called regurarly, remove old history 16 | -- bw:collect() 17 | 18 | local queue = require('queue') 19 | 20 | local MT = { 21 | __index = {}, 22 | } 23 | 24 | function MT.__index.collect(bw) 25 | for i, v in queue.iter(bw) do 26 | local ts = v[1] 27 | 28 | if now - ts <= bw.scale then 29 | break 30 | end 31 | 32 | assert(i == bw.heap) 33 | queue.remove(bw, 1) 34 | end 35 | 36 | bw.last_collect_ts = now 37 | end 38 | 39 | function MT.__index.add(bw, class, dir, sz) 40 | return queue.push(bw, { now, class, dir, sz }) 41 | end 42 | 43 | function MT.__index.add_rx(bw, class, sz) return bw:add(class, 'rx', sz) end 44 | function MT.__index.add_tx(bw, class, sz) return bw:add(class, 'tx', sz) end 45 | 46 | MT.__index.length = queue.length 47 | 48 | function MT.__index.avg(bw) 49 | local acc = {} 50 | for i, v in queue.iter(bw) do 51 | local ts, class, dir, sz = table.unpack(v) 52 | 53 | if now - ts <= bw.scale then 54 | acc[class] = acc[class] or { rx=0, tx=0 } 55 | acc[class][dir] = acc[class][dir] + sz 56 | else 57 | assert(i == bw.heap) 58 | queue.remove(bw, 1) 59 | end 60 | end 61 | 62 | bw.last_collect_ts = now 63 | 64 | for k, v in ipairs(acc) do 65 | v.rx = v.rx / bw.scale 66 | v.tx = v.tx / bw.scale 67 | end 68 | 69 | return acc 70 | end 71 | 72 | return function(bw) 73 | assert(bw and bw.scale) 74 | bw.last_collect_ts = 0 75 | return setmetatable(bw, MT) 76 | end 77 | 78 | -------------------------------------------------------------------------------- /contrib/micronet/src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef MICRONET_SERVER_H 2 | #define MICRONET_SERVER_H 3 | 4 | #include "common.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "conf.h" 11 | 12 | extern struct timespec now; // updated after each epoll_wait() 13 | 14 | #define DROP(n,p,reason, ...) \ 15 | do { \ 16 | LOG("drop! " reason "\n",##__VA_ARGS__); \ 17 | free(p); \ 18 | return; \ 19 | } while(0) 20 | 21 | 22 | enum direction { 23 | UP = 0, 24 | DOWN = 1 25 | }; 26 | 27 | struct packet { 28 | struct packet* next; 29 | size_t sz; 30 | enum direction dir; 31 | node_id from_id; 32 | union { 33 | struct { 34 | uint32_t id; 35 | union { 36 | uint8_t body[UNET_DEFAULT_MTU]; 37 | struct ip hdr; 38 | }; 39 | }; 40 | uint8_t buf[sizeof(uint32_t)+UNET_DEFAULT_MTU]; 41 | }; 42 | }; 43 | 44 | static inline void* packet_ip_payload(struct packet* p, size_t *psize) { 45 | size_t ip_hdr_sz = p->hdr.ip_hl*sizeof(uint32_t); 46 | if (psize) { 47 | *psize = p->sz - ip_hdr_sz; 48 | } 49 | return p->body+ip_hdr_sz; 50 | } 51 | 52 | struct nat_tcpudp { 53 | node_id id; 54 | time_t opened_ts; 55 | struct in_addr saddr; 56 | struct in_addr daddr; 57 | uint16_t sport; 58 | uint16_t dport; 59 | }; 60 | 61 | struct nat_icmp { 62 | node_id id; 63 | time_t opened_ts; 64 | uint16_t siid; 65 | struct in_addr saddr; 66 | }; 67 | 68 | #define NODE_ARGS(i) (3+(i)) 69 | void sendto_id(struct node* from_n, node_id to_id, struct packet* p); 70 | int _udp_sendto(struct sockaddr_in* peer_addr, struct iovec* iov, int iovlen); 71 | 72 | int _peer(lua_State* L); 73 | int _link(lua_State* L); 74 | int _nat(lua_State* L); 75 | int _wan(lua_State* L); 76 | 77 | void packet_refresh_sum(struct packet* p); 78 | void print_packet(FILE* fh, struct packet* p); 79 | 80 | #endif // MICRONET_SERVER_H 81 | 82 | -------------------------------------------------------------------------------- /src/core/luawh.h: -------------------------------------------------------------------------------- 1 | #ifndef LUAWH_H 2 | #define LUAWH_H 3 | 4 | #include "common.h" 5 | #include "net.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | int luaW_version(lua_State *L); 12 | 13 | void* luaW_newsecret(lua_State* L, size_t len); 14 | void* luaW_tosecret(lua_State* L, int idx, size_t len); 15 | void* luaW_checksecret(lua_State* L, int idx, size_t len); 16 | void* luaW_ownsecret(lua_State* L, int idx, size_t len); 17 | void luaW_freesecret(void* p); 18 | 19 | // declare a pointer 20 | // example: luaW_declptr(L, "buffer", free); 21 | void luaW_declptr(lua_State* L, const char* mt, void(*del)(void*)); 22 | // push a pointer. pointer is not owned after the call 23 | // example: luaW_pushptr(L, "buffer", malloc(1024)); 24 | void luaW_pushptr(lua_State* L, const char* mt, void* ptr); 25 | // returns pointer after checking it. raises an error if bad type or pointer is 26 | // dangling 27 | // example: luaW_checkptr(L, -1, "buffer"); 28 | void* luaW_checkptr(lua_State* L, int idx, const char* mt); 29 | // returns pointer after checking it. returns null if pointer is dangling 30 | // example: luaW_toptr(L, -1, "buffer") 31 | void* luaW_toptr(lua_State* L, int idx, const char* mt); 32 | // as luaW_checkptr, but owns the pointer 33 | // example: luaW_ownptr(L, -1, "buffer"); 34 | void* luaW_ownptr(lua_State* L, int idx, const char* mt); 35 | 36 | struct address* luaW_newaddress(lua_State* L); 37 | 38 | static inline uint16_t luaW_checkport(lua_State* L, int idx) { 39 | lua_Number port_n = luaL_checkinteger(L, idx); 40 | 41 | if (port_n < 0 || UINT16_MAX < port_n) { 42 | luaL_error(L, "bad port: %d", port_n); 43 | } 44 | 45 | return (uint16_t)port_n; 46 | } 47 | 48 | void luaW_pushfd(lua_State* L, int fd); 49 | int luaW_getfd(lua_State* L, int idx); 50 | 51 | LUAMOD_API int luaopen_ipc(lua_State* L); 52 | LUAMOD_API int luaopen_ipc_event(lua_State* L); 53 | LUAMOD_API int luaopen_wg(lua_State* L); 54 | LUAMOD_API int luaopen_whcore(lua_State* L); 55 | LUAMOD_API int luaopen_worker(lua_State* L); 56 | 57 | #if WH_ENABLE_MINIUPNPC 58 | LUAMOD_API int luaopen_upnp(lua_State* L); 59 | #endif 60 | 61 | #endif // LUAWH_H 62 | 63 | -------------------------------------------------------------------------------- /src/tools/show.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | print("Usage: wh show { | all | interfaces } [light | all]") 3 | end 4 | 5 | local ipc = require'ipc' 6 | 7 | local all = {} 8 | local interface = arg[2] 9 | 10 | local mode = 'light' 11 | if interface == 'interfaces' then 12 | local interfaces = wh.ipc.list() 13 | table.sort(interfaces) 14 | for _, i in ipairs(interfaces) do 15 | print(i) 16 | end 17 | 18 | return 0 19 | 20 | elseif interface then 21 | mode = arg[3] 22 | if mode == nil then mode = 'light' end 23 | local mode_ok = ({ 24 | light=true, 25 | all=true, 26 | })[mode] 27 | 28 | if not mode_ok then 29 | printf('invalid mode: %s', mode) 30 | return help() 31 | end 32 | end 33 | 34 | if interface == 'all' then interface = nil end 35 | 36 | local function call(interface, cmd) 37 | local ok, value = pcall(ipc.call, interface, cmd) 38 | 39 | if not ok then 40 | printf("Error when connecting to WireHub daemon: %s\n", value) 41 | return 42 | end 43 | 44 | local sock = value 45 | if not sock then 46 | return 47 | end 48 | 49 | local buf = {} 50 | while true do 51 | local r = wh.select({sock}, {}, {}, 1) 52 | 53 | if not r[sock] then 54 | r[#r+1] = '\n(daemon timeout)' 55 | break 56 | end 57 | 58 | local chunk = wh.recv(sock, 65535) 59 | if not chunk or #chunk == 0 then 60 | break 61 | end 62 | 63 | buf[#buf+1] = chunk 64 | end 65 | 66 | wh.close(sock) 67 | 68 | return table.concat(buf) 69 | end 70 | 71 | local names 72 | local whs = wh.ipc.list() 73 | for _, v in ipairs(whs) do whs[v] = true end 74 | if interface then 75 | if not whs[interface] then 76 | printf('invalid interface: %s', interface) 77 | return help() 78 | end 79 | 80 | names = {interface} 81 | else 82 | names = whs 83 | end 84 | 85 | table.sort(names) 86 | 87 | for _, name in ipairs(names) do 88 | local cmd = string.format('describe %s', mode) 89 | local info = call(name, cmd) 90 | 91 | if info then 92 | printf("interface $(bold)%s$(reset) %s", name, info) 93 | end 94 | end 95 | 96 | -------------------------------------------------------------------------------- /src/tools/resolve.lua: -------------------------------------------------------------------------------- 1 | function help() 2 | printf('Usage: wh resolve ') 3 | end 4 | 5 | if not arg[2] or arg[2] == 'help' then 6 | return help() 7 | end 8 | 9 | local name = arg[2] 10 | 11 | local function resolve(cmd, name) 12 | local resolv = {} 13 | 14 | for _, interface in ipairs(wh.ipc.list()) do 15 | local ipc_cmd = string.format('%s %s', cmd, name) 16 | local ok, value = pcall(require('ipc').call, interface, ipc_cmd) 17 | if not ok then 18 | printf("%s\nError when connecting to WireHub daemon.", value) 19 | return 20 | end 21 | 22 | local sock = value 23 | if not sock then 24 | print("Interface not attached to WireHub") 25 | return 26 | end 27 | 28 | now = wh.now() 29 | while true do 30 | local r = wh.select({sock}, {}, {}, now+1) 31 | now = wh.now() 32 | 33 | if not r[sock] then 34 | wh.close(sock) 35 | printf("timeout") 36 | return -1 -- timeout 37 | end 38 | 39 | local buf = wh.recv(sock, 65535) 40 | 41 | if not buf or #buf == 0 then 42 | break 43 | end 44 | 45 | local b64k, hostname, ip = string.match(buf, '([^%s]+)\t([^%s]*)\t([^%s]*)\n') 46 | 47 | if not b64k then 48 | break 49 | end 50 | 51 | if hostname and #hostname == 0 then 52 | hostname = nil 53 | end 54 | 55 | if ip and #ip == 0 then 56 | ip = nil 57 | end 58 | 59 | if hostname or ip then 60 | resolv[#resolv+1] = {interface, b64k, hostname, ip} 61 | end 62 | break 63 | end 64 | wh.close(sock) 65 | end 66 | 67 | return resolv 68 | end 69 | 70 | local is_host = true 71 | local resolv = resolve('gethostbyname', name) 72 | 73 | if #resolv == 0 then 74 | is_host = false 75 | resolv = resolve('gethostbyaddr', name) 76 | end 77 | 78 | if #resolv == 1 then 79 | local r = resolv[1][is_host and 4 or 3] 80 | if r then 81 | print(r) 82 | end 83 | elseif #resolv >= 2 then 84 | print("multiple results") 85 | for _, v in ipairs(resolv) do 86 | printf(" %s: %s", resolv[1], resolv[2]) 87 | end 88 | return -1 89 | end 90 | -------------------------------------------------------------------------------- /src/time.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.every(deadlines, obj, field_ts, value) 4 | local last_ts = obj[field_ts] 5 | 6 | if now - last_ts >= value then 7 | obj[field_ts] = now 8 | deadlines[#deadlines+1] = now + value 9 | return true 10 | else 11 | deadlines[#deadlines+1] = last_ts + value 12 | return false 13 | end 14 | end 15 | 16 | function M.retry_backoff(obj, retry_field, last_field, retry_max, backoff) 17 | local deadline 18 | 19 | -- wait for deadline 20 | deadline = ( 21 | (obj[last_field] or 0) + 22 | (obj[retry_field] or 0) * backoff 23 | ) 24 | 25 | if now <= deadline then 26 | return false, deadline 27 | end 28 | 29 | -- deadline is reached. If retry_max is reached too, timeout 30 | if retry_max ~= nil and (obj[retry_field] or 0) >= retry_max then 31 | return false, nil 32 | end 33 | 34 | -- action has to be performed; calculate next deadline 35 | obj[last_field] = now 36 | obj[retry_field] = (obj[retry_field] or 0) + 1 37 | deadline = ( 38 | obj[last_field] + 39 | obj[retry_field] * backoff 40 | ) 41 | 42 | return true, deadline 43 | 44 | end 45 | 46 | local function retry_ping_backoff_deadline(p, retry_every, backoff) 47 | local deadline 48 | 49 | if p.last_seen then 50 | deadline = p.last_seen + retry_every 51 | else 52 | if p.first_ping_ts == nil then 53 | p.first_ping_ts = now 54 | end 55 | 56 | deadline = p.first_ping_ts 57 | end 58 | 59 | deadline = deadline + (p.ping_retry or 0) * backoff 60 | 61 | return deadline 62 | end 63 | 64 | function M.retry_ping_backoff(p, retry_every, retry_max, backoff) 65 | local deadline 66 | deadline = retry_ping_backoff_deadline(p, retry_every, backoff) 67 | 68 | if now < deadline then 69 | return false, deadline 70 | end 71 | 72 | -- deadline is reached. If retry_max is reached too, timeout 73 | if retry_max ~= nil and (p.ping_retry or 0) >= retry_max then 74 | return false, nil 75 | end 76 | 77 | -- action has to be performed; calculate next deadline 78 | if p.last_ping == nil and p.first_tx_ts == nil then p.first_tx_ts = now end 79 | p.last_ping = now 80 | p.ping_retry = (p.ping_retry or 0) + 1 81 | deadline = retry_ping_backoff_deadline(p, retry_every, backoff) 82 | 83 | return true, deadline 84 | end 85 | 86 | return M 87 | 88 | -------------------------------------------------------------------------------- /src/core/pcap.c: -------------------------------------------------------------------------------- 1 | #include "net.h" 2 | 3 | pcap_t* sniff(const char* interface, pcap_direction_t direction, enum sniff_proto proto, const char* expr) { 4 | assert(interface); 5 | // expr may be NULL 6 | 7 | pcap_t* h; 8 | 9 | if (!(h=pcap_create(interface, NULL))) { 10 | return NULL; 11 | } 12 | 13 | if (pcap_set_timeout(h, 1000)) { 14 | pcap_close(h); 15 | return NULL; 16 | } 17 | 18 | /*const int wh_sniff_buffer_size = 64 * 1024; 19 | if (pcap_set_buffer_size(h, wh_sniff_buffer_size)) { 20 | pcap_close(h); 21 | return NULL; 22 | }*/ 23 | 24 | if (pcap_set_immediate_mode(h, 1)) { 25 | pcap_close(h); 26 | return NULL; 27 | } 28 | 29 | int err = pcap_activate(h); 30 | if (err != 0) { 31 | fprintf(stderr, "error: %s\n", pcap_geterr(h)); 32 | pcap_close(h); 33 | return NULL; 34 | } 35 | 36 | if (pcap_setnonblock(h, 1, NULL) == PCAP_ERROR) { 37 | pcap_close(h); 38 | return NULL; 39 | } 40 | 41 | if (pcap_setdirection(h, direction) == PCAP_ERROR) { 42 | pcap_close(h); 43 | return NULL; 44 | } 45 | 46 | // XXX COMPILER ASSERT 47 | assert(sizeof(wh_pkt_hdr)==4); 48 | 49 | char filter_exp[256]; 50 | switch (proto) { 51 | case SNIFF_PROTO_WG: 52 | snprintf(filter_exp, sizeof(filter_exp), 53 | "udp and udp[8] & 0xf8 == 0 and udp[9]==%d and udp[10]==%d and udp[11]==%d%s", 54 | (int)wh_pkt_hdr[1], 55 | (int)wh_pkt_hdr[2], 56 | (int)wh_pkt_hdr[3], 57 | expr ? expr : "" 58 | ); 59 | break; 60 | 61 | case SNIFF_PROTO_WH: 62 | snprintf(filter_exp, sizeof(filter_exp), 63 | "udp and udp[8]==%d and udp[9]==%d and udp[10]==%d and udp[11]==%d%s", 64 | (int)wh_pkt_hdr[0], 65 | (int)wh_pkt_hdr[1], 66 | (int)wh_pkt_hdr[2], 67 | (int)wh_pkt_hdr[3], 68 | expr ? expr : "" 69 | ); 70 | break; 71 | }; 72 | 73 | struct bpf_program filter; 74 | const int optimize = 0; 75 | if (pcap_compile(h, &filter, filter_exp, optimize, 0) == PCAP_ERROR) { 76 | fprintf(stderr, "error: %s\n", pcap_geterr(h)); 77 | pcap_close(h); 78 | return NULL; 79 | } 80 | 81 | int r = pcap_setfilter(h, &filter); 82 | pcap_freecode(&filter); 83 | 84 | if (r == PCAP_ERROR) { 85 | pcap_close(h); 86 | return NULL; 87 | } 88 | 89 | return h; 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/ns_keybase.lua: -------------------------------------------------------------------------------- 1 | -- Keybase Name reSolver example 2 | -- 3 | -- WireHub can assign names to peers through a private network configuration 4 | -- file. It does not provide any native decentralized name resolver to find the 5 | -- key of a peer given its name. This is considered out of the scope of WireHub, 6 | -- as many solutions exist and depend on the requirements of the private 7 | -- network (e.g. DNS, namecoin, Keybase, ...). 8 | -- 9 | -- However, it is possible to implement a name resolver and plug it to WireHub 10 | -- to make it able to resolve peer's names. This file shows an example of name 11 | -- resolving using Keybase (https://keybase.io/) 12 | -- 13 | -- # Example: Keybase 14 | -- 15 | -- This example takes advantage of the Keybase filesystem to resolve peer's 16 | -- name. Each Keybase user may make a directory named 'wirehub/' in the root of 17 | -- its public folder. Each file in the 'wirehub/' directory is named after a 18 | -- subdomain, and contains the base64 form of the peer's public key. 19 | -- 20 | -- Peer's names resolvable via Keybase are of the form: 21 | -- 22 | -- .kb.wh 23 | -- ... or ... 24 | -- ..kb.wh 25 | -- 26 | -- The base64 key of 'foo.bar.kb.wh' is stored in the file 27 | -- '/keybase/public/bar/wirehub/foo'. If no subdomain is defined, the file name 28 | -- is 'default' (e.g. 'bar.kb.wh' => '/keybase/public/bar/wirehub/default') 29 | -- 30 | -- For example, see https://keybase.pub/gawenr/wirehub/ 31 | 32 | local TIMEOUT = 2 33 | local CMD = string.format("curl -m %s -s ", TIMEOUT) 34 | 35 | local function generate_url(path) 36 | local hostname, user = string.match(path, "(.+)%.(.+)") 37 | 38 | if not hostname and not user then 39 | user = path 40 | hostname = "default" 41 | end 42 | 43 | return string.format("https://%s.keybase.pub/wirehub/%s", user, hostname) 44 | end 45 | 46 | return function(n, k, cb) 47 | local path = string.match(k, "(.+)%.kb.wh") 48 | 49 | if not path then 50 | return cb(nil) 51 | end 52 | 53 | local url = generate_url(path) 54 | local cmd = CMD .. url 55 | 56 | n.ns.worker:pcall( 57 | function(ok, resp) 58 | if resp then 59 | local ok, k = pcall(wh.fromb64, resp) 60 | 61 | if ok then 62 | return cb(k) 63 | end 64 | end 65 | 66 | return cb(nil) 67 | end, 68 | function(cmd) 69 | return io.popen(cmd):read() 70 | end, 71 | cmd 72 | ) 73 | end 74 | -------------------------------------------------------------------------------- /docker/0nc.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | require('wh') 4 | 5 | function execf(...) 6 | local cmd = string.format(...) 7 | --print(cmd) 8 | return os.execute(cmd) 9 | end 10 | 11 | function readb64(fp, mode) 12 | local fh = io.open(fp) 13 | if not fh then error(string.format('file not found: %s', fp)) end 14 | local buf = wh.fromb64(fh:read(), mode) 15 | fh:close() 16 | return buf 17 | end 18 | 19 | function writeb64(fp, buf, mode) 20 | local fh = io.open(fp, 'w') 21 | fh:write(wh.tob64(buf, mode) .. '\n') 22 | fh:close() 23 | end 24 | 25 | function WH(...) 26 | local args = string.format(...) 27 | 28 | args = args .. " &" 29 | 30 | if os.getenv("VALGRIND") then 31 | execf("WH_LOGPATH=/tmp/log valgrind /usr/local/bin/lua src/tools/cli.lua " .. args) 32 | else 33 | execf("WH_LOGPATH=/tmp/log wh " .. args) 34 | end 35 | 36 | end 37 | 38 | execf("make > /dev/null 2> /dev/null") 39 | 40 | execf('rm -f /tmp/config') 41 | execf('echo "name znc" >> /tmp/config') 42 | execf('echo "subnet 10.0.42.0/24" >> /tmp/config') 43 | execf('echo "workbit 8" >> /tmp/config') 44 | execf('echo "boot P17zMwXJFbBdJEn05RFIMADw9TX5_m2xgf31OgNKX3w bootstrap.wirehub.io" >> /tmp/config') 45 | 46 | execf("wh genkey /tmp/config | tee /tmp/znc.sk | wh pubkey > /tmp/znc.k") 47 | 48 | local k = readb64('/tmp/znc.k') 49 | 50 | local is_server = arg[1] == nil 51 | 52 | if is_server then 53 | execf("wh genkey /tmp/config | tee /tmp/alias.znc.sk | wh pubkey > /tmp/alias.znc.k") 54 | local alias_sk = readb64('/tmp/alias.znc.sk', 'wg') 55 | local alias_k = readb64('/tmp/alias.znc.k') 56 | 57 | local invit = wh.tob64(k .. alias_sk) 58 | print("znc invitation: " .. invit) 59 | 60 | execf('echo "trust server.znc %s" >> /tmp/config', wh.tob64(k)) 61 | execf('echo "alias client.znc %s" >> /tmp/config', wh.tob64(alias_k)) 62 | WH("up /tmp/config interface wh-0nc private-key /tmp/znc.sk mode nat") 63 | 64 | execf("sleep 1") 65 | execf("nc -l -p 1024 -v") 66 | else 67 | local keys = wh.fromb64(arg[1]) 68 | local server_k = string.sub(keys, 1, 32) 69 | local alias_sk = string.sub(keys, 33, 64) 70 | local alias_k = wh.publickey(alias_sk) 71 | 72 | writeb64('/tmp/alias.znc.sk', alias_sk, 'wg') 73 | 74 | execf('echo "trust server.znc %s" >> /tmp/config', wh.tob64(server_k)) 75 | execf('echo "alias client.znc %s" >> /tmp/config', wh.tob64(alias_k)) 76 | WH("up /tmp/config interface wh-0nc mode nat") 77 | 78 | execf("sleep 1") 79 | execf("wh auth wh-0nc %s /tmp/alias.znc.sk", wh.tob64(server_k)) 80 | 81 | execf("sleep 1") 82 | execf("nc server.znc 1024 -v") 83 | end 84 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest as builder 2 | 3 | RUN apk add --no-cache \ 4 | autoconf \ 5 | automake \ 6 | bison \ 7 | build-base \ 8 | curl \ 9 | flex \ 10 | git \ 11 | libmnl-dev \ 12 | libtool \ 13 | linux-headers \ 14 | readline-dev \ 15 | gdb pv strace valgrind vim # for debug only 16 | 17 | RUN mkdir -p \ 18 | /baseroot/opt/wh/tools \ 19 | /baseroot/usr/bin \ 20 | /baseroot/usr/lib \ 21 | /baseroot/usr/local/lib/lua/5.3 \ 22 | /baseroot/usr/share/bash-completion/completions 23 | 24 | WORKDIR /root 25 | RUN git clone https://github.com/jedisct1/libsodium && \ 26 | git clone https://github.com/miniupnp/miniupnp && \ 27 | curl -R -O http://www.tcpdump.org/release/libpcap-1.9.0.tar.gz && \ 28 | curl -R -O https://www.lua.org/ftp/lua-5.3.5.tar.gz && \ 29 | tar xfz libpcap-1.9.0.tar.gz && tar xfz lua-5.3.5.tar.gz 30 | 31 | # Build libpcap 32 | WORKDIR /root/libpcap-1.9.0 33 | RUN ./configure && \ 34 | make -j && \ 35 | make install 36 | 37 | # Build sodium 38 | WORKDIR /root/libsodium 39 | RUN git checkout stable && \ 40 | ./autogen.sh && \ 41 | ./configure && \ 42 | make -j && \ 43 | make install 44 | 45 | # Build Lua 46 | WORKDIR /root/lua-5.3.5 47 | #RUN sed -i 's/MYCFLAGS=/MYCFLAGS=-g/g' src/Makefile && sed -i 's/-O2//g' src/Makefile 48 | RUN make -j linux && \ 49 | make install 50 | 51 | # Build MiniUPNPc 52 | WORKDIR /root/miniupnp/miniupnpc 53 | RUN git checkout miniupnpc_2_1 && \ 54 | make -j && \ 55 | make install && \ 56 | make install DESTDIR=/baseroot 57 | 58 | # Build WireGuard tools 59 | WORKDIR /root/wh/ 60 | COPY deps deps 61 | WORKDIR /root/wh/deps/WireGuard/src/tools 62 | RUN make -j && \ 63 | make install DESTDIR=/baseroot 64 | 65 | # Prepare wh 66 | RUN printf "#!/bin/sh\nexport LUA_PATH=/opt/wh/?.lua\nlua /opt/wh/tools/cli.lua \$@\n" >> /baseroot/usr/bin/wh && \ 67 | chmod +x /baseroot/usr/bin/wh 68 | 69 | # Build WireHub 70 | WORKDIR /root/wh 71 | COPY Makefile . 72 | COPY src src 73 | RUN make -j && \ 74 | cp src/*.lua /baseroot/opt/wh && \ 75 | cp src/tools/*.lua /baseroot/opt/wh/tools && \ 76 | cp .obj/*.so /baseroot/usr/local/lib/lua/5.3/ 77 | 78 | COPY config/* /baseroot/etc/wirehub/ 79 | 80 | WORKDIR /baseroot 81 | RUN cp /usr/local/lib/*.so* usr/lib/ && \ 82 | cp /usr/local/bin/lua* usr/bin && \ 83 | tar cf /baseroot.tar . 84 | 85 | ## 86 | 87 | FROM alpine:latest as wh 88 | 89 | RUN apk add --no-cache \ 90 | iptables \ 91 | libmnl \ 92 | readline 93 | 94 | COPY --from=builder /baseroot.tar / 95 | RUN tar xf /baseroot.tar && \ 96 | rm /baseroot.tar && \ 97 | rm -rf /usr/include/* /usr/share/man/* /usr/lib/*.a 98 | 99 | -------------------------------------------------------------------------------- /src/kadstore.lua: -------------------------------------------------------------------------------- 1 | -- kademilia 2 | 3 | local peer = require('peer') 4 | 5 | local MT = { 6 | __index = {}, 7 | } 8 | 9 | -- Touch a peer with key k. If peer does not exist, creates it. Returns two 10 | -- values, the peer table and a boolean if the peer was created. 11 | function MT.__index.touch(t, k) 12 | if t.root.k == k then 13 | t.touched[k] = t.root 14 | return t.root 15 | end 16 | 17 | local bid = wh.bid(t.root.k, k) 18 | local b = t.buckets[bid] 19 | if not b then 20 | b = {} 21 | t.buckets[bid] = b 22 | end 23 | 24 | local p = b[k] 25 | local new_p = p == nil 26 | if p == nil then 27 | p = { 28 | k=k, 29 | } 30 | 31 | b[#b+1] = p 32 | b[k] = p 33 | end 34 | 35 | t.touched[p.k] = p 36 | 37 | return peer(p), new_p 38 | end 39 | 40 | function MT.__index.get(t, k) 41 | if t.root.k == k then 42 | return t.root 43 | end 44 | 45 | local bid = wh.bid(t.root.k, k) 46 | local b = t.buckets[bid] 47 | if not b then return end 48 | 49 | return b[k] 50 | end 51 | 52 | function MT.__index.clear_touched(t) 53 | t.touched = {} 54 | end 55 | 56 | function MT.__index.kclosest(t, k, count, filter_cb) 57 | local empty = {} 58 | if count == nil then count = t.K end 59 | local bid = wh.bid(t.root.k, k) 60 | 61 | local r = {} 62 | 63 | local function extend(i) 64 | for _, p in ipairs(t.buckets[i] or empty) do 65 | if (p.k and 66 | p.addr and 67 | not p.alias and ( 68 | not filter_cb or 69 | filter_cb(p) 70 | ) 71 | ) then 72 | r[#r+1] = {wh.xor(p.k, k), p} 73 | end 74 | end 75 | end 76 | 77 | extend(bid) 78 | 79 | if #r < count then 80 | for i = bid+1, #t.root.k*8 do extend(i) end 81 | end 82 | 83 | for i = bid-1, 1, -1 do 84 | if #r >= count then 85 | break 86 | end 87 | 88 | extend(i) 89 | end 90 | 91 | table.sort(r, function(a, b) return a[1] < b[1] end) 92 | return r 93 | end 94 | 95 | function MT.__index.unlink(t, p) 96 | local bid = wh.bid(t.root.k, p.k) 97 | local b = t.buckets[bid] 98 | if not b then return end 99 | 100 | local to_remove 101 | for i, pi in ipairs(b) do 102 | if p == pi then 103 | to_remove = i 104 | break 105 | end 106 | end 107 | 108 | if to_remove then 109 | table.remove(b, to_remove) 110 | b[p.k] = nil 111 | end 112 | end 113 | 114 | return function(root_k, kad_k) 115 | assert(root_k and kad_k) 116 | 117 | return setmetatable({ 118 | buckets={}, 119 | K=kad_k, 120 | touched={}, 121 | root={k=root_k}, 122 | }, MT) 123 | end 124 | 125 | -------------------------------------------------------------------------------- /src/peer.lua: -------------------------------------------------------------------------------- 1 | -- Peer's methods 2 | 3 | local M = {} 4 | local MT = { 5 | __index = {}, 6 | } 7 | 8 | function MT.__index.pack(p) 9 | local r = { p.k, p.addr:pack() } 10 | 11 | --if p.relay ~= nil then 12 | -- r[#r+1] = p.relay.k 13 | -- r[#r+1] = p.relay.addr:pack() 14 | --end 15 | 16 | return table.concat(r) 17 | end 18 | 19 | if DEBUG then 20 | function MT.__newindex(r, attr, val) 21 | local t = "" 22 | if false then 23 | t = debug.traceback() 24 | t = string.match(t, "[^\n]*\n[^\n]*\n\t.*src/([^\n]*) *\n.*") 25 | t = "$(reset)\t(" .. t .. ")" 26 | end 27 | 28 | if rawget(r, attr) ~= val then 29 | printf("$(darkgray)--$(reset) $(bold)%s$(reset).$(blue)%s$(reset) = $(blue)%s"..t, 30 | wh.key(r), attr, dump(val) 31 | ) 32 | end 33 | 34 | rawset(r, attr, val) 35 | end 36 | end 37 | 38 | function MT.__tostring(p) 39 | local r = {} 40 | 41 | local s = string.format("%s@%s", wh.key(p), p.addr) 42 | 43 | if p.is_nated then 44 | s = s .. ' (NAT)' 45 | end 46 | 47 | if p.relay then 48 | s = s .. string.format(" (relayed)") 49 | end 50 | 51 | return s 52 | end 53 | 54 | -- Returns two values: 55 | -- 1. the type of the peer 56 | -- 2. if it is considered as active 57 | function MT.__index.state(p) 58 | -- peer is an alias: a public-key of a private key given to the peer to 59 | -- authenticate in the future 60 | if p.alias then 61 | return 'alias', false 62 | 63 | -- peer may be contacted through a relay 64 | elseif p.relay then 65 | return 'relay', now-(p.last_seen or 0) <= wh.KEEPALIVE_NAT_TIMEOUT 66 | 67 | -- peer is connected in P2P, but behind a NAT 68 | elseif p.is_nated and p.addr then 69 | return 'nat', now-(p.last_seen or 0) <= wh.KEEPALIVE_NAT_TIMEOUT 70 | 71 | -- peer is direct 72 | elseif p.addr then 73 | return 'direct', now-(p.last_seen or 0) <= wh.KEEPALIVE_DIRECT_TIMEOUT 74 | 75 | -- no connection information 76 | else 77 | return nil, false 78 | end 79 | end 80 | 81 | function MT.__index.acquire(p, obj) 82 | if p.ref == nil then 83 | p.ref = {} 84 | end 85 | 86 | p.ref[obj] = true 87 | 88 | --dbg('acquire %s', dump(p)) 89 | return p 90 | end 91 | 92 | function MT.__index.release(p, obj) 93 | if p.ref == nil then 94 | return 95 | end 96 | 97 | p.ref[obj] = nil 98 | 99 | local empty = true 100 | for _ in ipairs(p.ref) do 101 | empty = false 102 | break 103 | end 104 | 105 | if empty then 106 | p.ref = nil 107 | end 108 | end 109 | 110 | function MT.__index.owned(p) 111 | return not not p.ref 112 | end 113 | 114 | return function(p) 115 | return setmetatable(p, MT) 116 | end 117 | 118 | -------------------------------------------------------------------------------- /src/getent.lua: -------------------------------------------------------------------------------- 1 | -- Resolve entitty name to key 2 | 3 | local function find_shorter(n, k, cb) 4 | local test = function(p) 5 | return string.find(wh.tob64(p.k), k) == 1 6 | end 7 | 8 | local match 9 | 10 | if test(n.kad.root) then 11 | match = n.kad.root 12 | end 13 | 14 | for _, bucket in ipairs(n.kad.buckets) do 15 | for _, p in ipairs(bucket) do 16 | if test(p) then 17 | if match then 18 | -- there's an possible ambiguity. fails 19 | return cb(nil) 20 | else 21 | match = p 22 | end 23 | end 24 | end 25 | end 26 | 27 | if match then 28 | return cb(match.k) 29 | else 30 | return cb(nil) 31 | end 32 | end 33 | 34 | local function find_b64_wh(n, k, cb) 35 | local ok, k = pcall(wh.fromb64, k, 'wh') 36 | if ok then 37 | if #k ~= 32 then k = nil end 38 | else 39 | k = nil 40 | end 41 | return cb(k) 42 | end 43 | 44 | local function find_b64_wg(n, k, cb) 45 | local ok, k = pcall(wh.fromb64, k, 'wg') 46 | if ok then 47 | if #k ~= 32 then k = nil end 48 | else 49 | k = nil 50 | end 51 | return cb(k) 52 | end 53 | 54 | local function find_local_hostname(n, h, cb) 55 | if n.kad.root.hostname == h then 56 | return cb(n.kad.root.k) 57 | end 58 | 59 | for _, bucket in ipairs(n.kad.buckets) do 60 | for _, p in ipairs(bucket) do 61 | if not p.alias and p.hostname == h and p.k then 62 | return cb(p.k) 63 | end 64 | end 65 | end 66 | 67 | return cb() 68 | end 69 | 70 | local function find_prefix(n, k, cb) 71 | if #k >= 43 then 72 | return cb() 73 | end 74 | 75 | local k2 = k .. string.rep("A", 43-#k) 76 | 77 | return find_b64_wh(n, k2, cb) 78 | end 79 | 80 | return function(n, hostname, result_cb, prefix) 81 | if hostname == nil then 82 | return nil 83 | end 84 | 85 | local cbs = { 86 | find_shorter, 87 | find_b64_wh, 88 | find_b64_wg, 89 | find_local_hostname, 90 | } 91 | 92 | if prefix then 93 | cbs[#cbs+1] = find_prefix 94 | end 95 | 96 | if n.ns then 97 | for _, ns in ipairs(n.ns) do 98 | cbs[#cbs+1] = ns 99 | end 100 | end 101 | 102 | local i = nil 103 | local cont_cb 104 | 105 | function cont_cb() 106 | local cb 107 | i, cb = next(cbs, i) 108 | 109 | if i and cb then 110 | return cb(n, hostname, function(k) 111 | if k then 112 | return result_cb(k) 113 | else 114 | return cont_cb() 115 | end 116 | end) 117 | else 118 | return result_cb(nil) 119 | end 120 | end 121 | 122 | return cont_cb() 123 | end 124 | 125 | -------------------------------------------------------------------------------- /src/tools/cli.lua: -------------------------------------------------------------------------------- 1 | -- Entry-point of 'wh' 2 | -- 3 | -- Read sub-commands and load the subcommands Lua program 4 | 5 | function help() 6 | print( 7 | "Usage: wh []\n" .. 8 | "\n" .. 9 | "Available setup subcommands\n" .. 10 | " down: Detach a Wireguard interface from a WireHub network (daemon)\n" .. 11 | " genkey: Generates a new private key for a WireHub network\n" .. 12 | " pubkey: Reads a private key from stdin and writes a public key to stdout\n" .. 13 | " reload: Reload the configuration\n" .. 14 | " up: Create a WireHub network and interface (daemon)\n" .. 15 | " workbit: Print workbits for a given WireGuard public key\n" .. 16 | "\n" .. 17 | "Available status subcommands\n" .. 18 | " show: Shows the current configuration\n" .. 19 | "\n" .. 20 | "Available network subcommands\n" .. 21 | " auth: Authenticate with an alias' private key\n" .. 22 | " forget: Forget one WireHub peer\n" .. 23 | " lookup Lookup for a WireHub peer\n" .. 24 | " p2p: Establish a peer-to-peer communication with a WireHub peer\n" .. 25 | " ping: Ping a WireHub peer\n" .. 26 | " resolve: Resolve a hostname among all WireHub networks\n" .. 27 | "\n" .. 28 | "Available advanced subcommands\n" .. 29 | " bid: Calculate BID between two keys\n" .. 30 | " check-wg: Check that WireGuard is ready to be used\n" .. 31 | " completion: Auto-completion helper\n" .. 32 | " inspect: Return low-level information on WireHub network\n" .. 33 | " ipc: Send a IPC command to a WireHub daemon\n" .. 34 | " orchid: Print the ORCHID IPv6 of a given node\n" .. 35 | "" 36 | ) 37 | end 38 | 39 | require('wh') 40 | require('helpers') 41 | 42 | SUBCMDS = { 43 | -- private methods 44 | '_completion', 45 | 46 | -- public methods 47 | "authenticate", 48 | "bid", 49 | "check-wg", 50 | "completion", 51 | "down", 52 | "forget", 53 | "genkey", 54 | "help", 55 | "inspect", 56 | "ipc", 57 | "lookup", 58 | "orchid", 59 | "p2p", 60 | "ping", 61 | "pubkey", 62 | "reload", 63 | "resolve", 64 | "show", 65 | "up", 66 | "workbit", 67 | } 68 | for _, k in ipairs(SUBCMDS) do SUBCMDS[k] = true end 69 | 70 | wh.ipc.prepare() 71 | 72 | local cmd = arg[1] or 'show' 73 | 74 | if cmd == 'auth' then cmd = 'authenticate' end 75 | if cmd == '_completion' then cmd = 'completion' end 76 | 77 | if not SUBCMDS[cmd] then 78 | printf("Invalid subcommand: `%s'", cmd) 79 | cmd = 'help' 80 | end 81 | 82 | if cmd == 'p2p' or cmd == 'ping' or cmd == 'lookup' then 83 | cmd = 'search' 84 | end 85 | 86 | -- secret cannot be revealed except in these modes 87 | if cmd ~= 'genkey' and 88 | cmd ~= 'genconf' 89 | then 90 | wh.revealsk = nil 91 | end 92 | 93 | if cmd == 'help' or cmd == '--help' then 94 | return help() 95 | end 96 | 97 | if cmd == 'up' then 98 | check_wg() 99 | end 100 | 101 | disable_globals() 102 | 103 | local retcode = cpcall(require, 'tools.' .. cmd) 104 | if retcode == true then retcode = 0 end 105 | 106 | _do_atexits() 107 | status(nil) 108 | os.exit(retcode or 0) 109 | 110 | -------------------------------------------------------------------------------- /src/hosts.lua: -------------------------------------------------------------------------------- 1 | -- XXX race condition! 2 | 3 | local M = {} 4 | 5 | local HOSTS_PATH = "/etc/hosts" 6 | --local COMMENT_BEGIN = "# WireHub hosts (automatically generated, DO NOT EDIT)" 7 | --local COMMENT_END = "# End of WireHub hosts" 8 | 9 | -- Returns a sorted table with all peers which should appear in /etc/hosts 10 | local function list_peers(n) 11 | 12 | local peers = {} 13 | 14 | for bid, bucket in pairs(n.kad.buckets) do 15 | for i, p in ipairs(bucket) do 16 | if p.trust and p.hostname and p.ip then 17 | peers[#peers+1] = p 18 | end 19 | end 20 | end 21 | 22 | table.sort(peers, function(a, b) 23 | return tostring(a.ip) < tostring(b.ip) 24 | end) 25 | 26 | return peers 27 | end 28 | 29 | local function match(l) 30 | local ip, hostname, b64interface, network, b64k = string.match(l, "([^%s]+)%s+([^%s]+)%s+# inserted by WireHub, interface: ([^%s]+) network: ([^%s]+) key: ([^%s]+)") 31 | 32 | local interface, k 33 | if b64interface then 34 | local ok 35 | ok, interface = pcall(wh.fromb64, b64interface) 36 | if not ok then return end 37 | end 38 | 39 | if b64k then 40 | local ok 41 | ok, k = pcall(wh.fromb64, b64k) 42 | if not ok then return end 43 | end 44 | 45 | return interface, network, k, ip, hostname 46 | end 47 | 48 | local function generate_host(n, map_cb) 49 | -- if n is not nil, add peers from n inside newly generated host file 50 | -- if map_cb is not nil, called for each wirehub peer entry. If map_cb 51 | -- returns false, the peer will be removed 52 | 53 | local r = {} 54 | local begin_idx 55 | local any_entry 56 | 57 | for line in io.lines(HOSTS_PATH) do 58 | local copy_line = true 59 | 60 | local interface, network, k, ip, hostname = match(line) 61 | 62 | if ip and hostname and interface and network and k then 63 | if map_cb and not map_cb(ip, hostname, interface, network, k) then 64 | copy_line = false 65 | end 66 | end 67 | 68 | if copy_line then 69 | r[#r+1] = line .. "\n" 70 | end 71 | end 72 | 73 | if n then 74 | for _, p in ipairs(list_peers(n)) do 75 | r[#r+1] = string.format("%s\t%s\t# inserted by WireHub, interface: %s network: %s key: %s\n", p.ip:addr(), p.hostname, wh.tob64(n.p.k), n.name, wh.tob64(p.k)) 76 | assert(match(r[#r]) ~= nil) 77 | end 78 | end 79 | 80 | return table.concat(r) 81 | end 82 | 83 | local function update_host(n, append) 84 | if not wh.EXPERIMENTAL_MODIFY_HOSTS then 85 | return 86 | end 87 | 88 | local append_n 89 | if append then 90 | append_n = n 91 | end 92 | 93 | local new_host = generate_host(append_n, function(interface, network) 94 | return interface == n.p.k and network == n.name 95 | end) 96 | 97 | local fh = io.open(HOSTS_PATH, "w") 98 | fh:write(new_host) 99 | fh:close() 100 | end 101 | 102 | -- Register trusted nodes of n 103 | function M.register(n) 104 | return update_host(n, true) 105 | end 106 | 107 | -- Unregister nodes from n 108 | function M.unregister(n) 109 | return update_host(n, false) 110 | end 111 | 112 | return M 113 | 114 | -------------------------------------------------------------------------------- /src/tools/search.lua: -------------------------------------------------------------------------------- 1 | local cmd = arg[1] 2 | 3 | function help() 4 | printf('Usage: wh %s ', cmd) 5 | end 6 | 7 | if arg[2] == 'help' then 8 | return help() 9 | end 10 | 11 | local interface = arg[2] 12 | local k = arg[3] 13 | 14 | if not interface or not k then 15 | return help() 16 | end 17 | 18 | local time_before_ping = wh.now() 19 | 20 | local ipc_cmd = string.format('%s %s', cmd, k) 21 | local ok, value = pcall(require('ipc').call, interface, ipc_cmd) 22 | if not ok then 23 | printf("%s\nError when connecting to WireHub daemon.", value) 24 | return 25 | end 26 | 27 | local sock = value 28 | if not sock then 29 | print("Interface not attached to WireHub") 30 | return 31 | end 32 | 33 | local b64k, via_b64k, addr, mode, is_nated, relay 34 | 35 | now = wh.now() 36 | while true do 37 | local r = wh.select({sock}, {}, {}, now+30) 38 | now = wh.now() 39 | 40 | if not r[sock] then 41 | wh.close(sock) 42 | printf("timeout") 43 | return -1 -- timeout 44 | end 45 | 46 | local buf = wh.recv(sock, 65535) 47 | 48 | if not buf or #buf == 0 then 49 | break 50 | end 51 | 52 | b64k, mode, addr, via_b64k = string.match(buf, '([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)\n') 53 | 54 | if not via_b64k or not addr or not mode then 55 | printf("$(red)bad format: %s", buf) 56 | wh.close(sock) 57 | return -1 58 | end 59 | 60 | addr = wh.address(addr, 0) 61 | 62 | if mode == '(direct)' then 63 | mode = 'direct' 64 | elseif mode == '(nat)' then 65 | mode = 'nat' 66 | is_nated = true 67 | else 68 | relay = wh.fromb64(mode) 69 | mode = 'relay' 70 | end 71 | end 72 | wh.close(sock) 73 | 74 | local found 75 | local m = {} 76 | if cmd == 'ping' then 77 | found = via_b64k and via_b64k == b64k 78 | elseif mode == 'relay' then 79 | found = true 80 | m[#m+1] = string.format('relay %s', wh.key(relay)) 81 | elseif mode == 'nat' then 82 | found = true 83 | m[#m+1] = string.format('nat %s', addr) 84 | elseif mode == 'direct' then 85 | found = true 86 | m[#m+1] = string.format('direct %s', addr) 87 | else 88 | found = false 89 | m[#m+1] = "not found" 90 | end 91 | 92 | m = table.concat(m) 93 | 94 | if cmd == 'lookup' then 95 | print(m) 96 | return found and 0 or -1 97 | 98 | elseif cmd == 'ping' then 99 | if found then 100 | printf('ping %s: time=%.2fms', m, (now-time_before_ping)*1000.0) 101 | else 102 | print('offline') 103 | end 104 | 105 | elseif cmd == 'p2p' then 106 | local k = wh.fromb64(b64k) 107 | if mode == 'relay' then 108 | printf('unable to open a p2p connection') 109 | return -1 110 | end 111 | 112 | local peer = { 113 | public_key=k, 114 | endpoint=addr, 115 | } 116 | 117 | if mode == 'nat' then 118 | peer.persistent_keepalive_interval = wh.NAT_TIMEOUT 119 | end 120 | 121 | 122 | local ok, err = pcall(wh.wg.set, {name=interface, peers={peer}}) 123 | 124 | if not ok then 125 | printf('error when setting up wireguard interface %s: %s', interface, err) 126 | return -1 127 | end 128 | 129 | printf('connected to %s', m) 130 | end 131 | 132 | return found and 0 or -1 133 | 134 | -------------------------------------------------------------------------------- /src/core/smartptr.c: -------------------------------------------------------------------------------- 1 | #include "luawh.h" 2 | 3 | static const char* _registry_field = "ptrmt"; 4 | 5 | static int _free(lua_State* L) { 6 | void** pp = lua_touserdata(L, 1); 7 | void(*del)(void*) = lua_touserdata(L, lua_upvalueindex(1)); 8 | 9 | if (*pp) { 10 | del(*pp), *pp = NULL; 11 | } 12 | 13 | return 0; 14 | } 15 | 16 | static int _tostring(lua_State* L) { 17 | void** pp = lua_touserdata(L, 1); 18 | 19 | lua_getmetatable(L, 1); 20 | lua_getfield(L, -1, "mt"); 21 | lua_remove(L, -2); 22 | 23 | if (*pp) { 24 | lua_pushfstring(L, "*: %p", *pp); 25 | } else { 26 | lua_pushstring(L, "*: "); 27 | } 28 | lua_concat(L, 2); 29 | 30 | return 1; 31 | } 32 | 33 | static void _newmt(lua_State* L, const char* mt, void(*del)(void*)) { 34 | if (luaL_newmetatable(L, mt)) { 35 | // build metatable 36 | lua_pushstring(L, mt); 37 | lua_setfield(L, -2, "mt"); 38 | 39 | lua_newtable(L); // __index 40 | 41 | lua_pushlightuserdata(L, del); 42 | lua_pushcclosure(L, _free, 1); 43 | lua_pushvalue(L, -1); 44 | lua_setfield(L, -3, "free"); 45 | lua_setfield(L, -3, "__gc"); 46 | lua_setfield(L, -2, "__index"); 47 | 48 | lua_pushcfunction(L, _tostring); 49 | lua_setfield(L, -2, "__tostring"); 50 | 51 | // lazy build of the registry's field 52 | if (lua_getfield(L, LUA_REGISTRYINDEX, _registry_field) == LUA_TNIL) { 53 | lua_pop(L, 1); 54 | lua_newtable(L); 55 | lua_pushvalue(L, -1); 56 | lua_setfield(L, LUA_REGISTRYINDEX, _registry_field); 57 | } 58 | 59 | // register metatable as a pointer's 60 | lua_pushvalue(L, -2); 61 | lua_pushboolean(L, 1); 62 | lua_settable(L, -3); 63 | lua_pop(L, 1); 64 | } 65 | } 66 | 67 | void luaW_declptr(lua_State* L, const char* mt, void(*del)(void*)) { 68 | _newmt(L, mt, del); 69 | lua_pop(L, 1); 70 | } 71 | 72 | void luaW_pushptr(lua_State* L, const char* mt, void* ptr) { 73 | void** pp = lua_newuserdata(L, sizeof(void*)); 74 | *pp = ptr; 75 | 76 | // free is given as a default deleter. if type was previously declared, free 77 | // will be ignored 78 | _newmt(L, mt, free); 79 | lua_setmetatable(L, -2); 80 | } 81 | 82 | static void* getptr(lua_State* L, int idx, const char* mt, int error, int own) { 83 | void **pp = luaL_testudata(L, idx, mt); 84 | if (!pp) { 85 | if (error) { 86 | luaL_error(L, "bad type (pointer %s expected, got %s)", mt, 87 | lua_typename(L, idx)); 88 | } else { 89 | return NULL; 90 | } 91 | } 92 | 93 | void *p = *pp; 94 | if (!p) { 95 | if (error) { 96 | luaL_error(L, "dangling pointer #%d", idx); 97 | } else { 98 | return NULL; 99 | } 100 | } 101 | 102 | if (own) { 103 | *pp = NULL; 104 | } 105 | 106 | return p; 107 | } 108 | 109 | void* luaW_checkptr(lua_State* L, int idx, const char* mt) { 110 | luaL_checkudata(L, idx, mt); 111 | return getptr(L, idx, mt, 1, 0); 112 | } 113 | 114 | void* luaW_ownptr(lua_State* L, int idx, const char* mt) { 115 | return getptr(L, idx, mt, 1, 1); 116 | } 117 | 118 | void* luaW_toptr(lua_State* L, int idx, const char* mt) { 119 | return getptr(L, idx, mt, 0, 0); 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/packet.lua: -------------------------------------------------------------------------------- 1 | -- WireHub protocol packets 2 | 3 | local M = {} 4 | 5 | local cmds = { 6 | -- PING. Requests a peer to PONG. 7 | 'ping', 8 | 9 | -- PONG. Response of the request PING. 10 | 'pong', 11 | 12 | -- SEARCH. Search for closest peers for a given key. 13 | 'search', 14 | 15 | -- RESULT. Response of a SEARCH. 16 | 'result', 17 | 18 | -- RELAY. Request recipient to relay WireHub packet to a given peer. 19 | 'relay', 20 | 21 | -- RELAYED. Sends a relayed packet to and for a recipient. Is sent after a 22 | -- RELAY. 23 | 'relayed', 24 | 25 | -- AUTH. Prove recipient that current peer has an alias. 26 | 'auth', 27 | 28 | -- AUTHED. Response after a AUTH, as authentication succeeded. 29 | 'authed', 30 | 31 | -- FRAGMENT. Send fragments of raw data. Used to relay WireGuard packets. 32 | 'fragment', 33 | } 34 | for i, str in ipairs(cmds) do cmds[str] = string.pack("B", i-1) end 35 | 36 | M.cmds = cmds 37 | 38 | function M.ping(arg, body) 39 | body = body or '' 40 | assert(#body <= 8) 41 | 42 | if arg == nil or arg == 'normal' then 43 | arg = "\x00" 44 | elseif arg == 'swapsrc' then 45 | arg = "\x01" 46 | elseif arg == 'direct' then 47 | arg = "\x02" 48 | end 49 | 50 | return table.concat{cmds.ping, arg, body or ''} 51 | end 52 | function M.pong(port_echo, src, body) 53 | return table.concat{ 54 | cmds.pong, 55 | src:pack(), 56 | string.pack("!H", port_echo), 57 | body, 58 | } 59 | end 60 | function M.search(k) 61 | return table.concat{cmds.search, k} 62 | end 63 | 64 | function M.result(k, closest) 65 | local m = {cmds.result, k} 66 | 67 | for i, c in ipairs(closest) do 68 | local p = c[2] 69 | 70 | if i > wh.KADEMILIA_K then 71 | break 72 | end 73 | 74 | if p.relay then 75 | m[#m+1] = "\x01" 76 | elseif p.is_nated then 77 | m[#m+1] = "\x02" 78 | else 79 | m[#m+1] = "\x00" 80 | end 81 | 82 | do 83 | m[#m+1] = p.k 84 | m[#m+1] = p.addr:pack() 85 | end 86 | 87 | if p.relay then 88 | m[#m+1] = p.relay.k 89 | m[#m+1] = p.relay.addr:pack() 90 | end 91 | end 92 | 93 | return table.concat(m) 94 | end 95 | 96 | function M.relay(dst, body) 97 | assert(#dst == 32) 98 | assert(type(body) == "string") 99 | return table.concat{ 100 | cmds.relay, 101 | dst, 102 | body, 103 | } 104 | end 105 | 106 | function M.relayed(src, body) 107 | return table.concat{ 108 | cmds.relayed, 109 | src.addr:pack(), 110 | body, 111 | } 112 | end 113 | 114 | function M.auth(n, dst) 115 | local m = n.k 116 | 117 | return table.concat{ 118 | cmds.auth, 119 | wh.packet(n.sk, dst.k, false, m), 120 | } 121 | end 122 | 123 | function M.authed(alias_k) 124 | return table.concat{ 125 | cmds.authed, 126 | alias_k, 127 | } 128 | end 129 | 130 | function M.fragment(id, num, mf, m) 131 | -- mf: More Fragment 132 | 133 | assert(id&0xffff==id) 134 | assert(num&0x7f==num) 135 | 136 | assert(#m <= wh.FRAGMENT_MTU) 137 | 138 | local b = num 139 | if mf then 140 | b = 0x80 | b 141 | end 142 | 143 | return table.concat{ 144 | cmds.fragment, 145 | string.pack(">HB", id, b), 146 | m 147 | } 148 | end 149 | 150 | return M 151 | 152 | -------------------------------------------------------------------------------- /src/auth.lua: -------------------------------------------------------------------------------- 1 | -- WireHub authentication 2 | -- 3 | -- An alias is a secret key given to a peer to authenticate itself. It is used 4 | -- to add a peer to a network when its public key is not known yet. 5 | -- 6 | -- Peer A wants to add peer B, but does not know B's key. Peer A generates a 7 | -- secret key and stores it as an alias of peer B. Peer B is given this alias 8 | -- through a secure channel (e.g. GPG, Keybase, ...), and sends a packet `AUTH` 9 | -- to peer A to prove it knows the alias secret key. Peer A will then discover 10 | -- peer A's key and register it as a new trusted peer. 11 | 12 | local packet = require('packet') 13 | 14 | local M = {} 15 | 16 | local function explain(n, fmt, ...) 17 | return n:explain("auth", fmt, ...) 18 | end 19 | 20 | function M.authenticate(n, k, alias_sk, cb) 21 | local a = { 22 | alias_sk = alias_sk, 23 | alias_k = wh.publickey(alias_sk), 24 | k = k, 25 | retry=0, 26 | req_ts=0, 27 | } 28 | 29 | a.cb = function(ok, ...) 30 | if not n.auths[a] then 31 | return 32 | end 33 | 34 | n.auths[a] = nil 35 | 36 | if a.s then 37 | n:stop_search(a.s) 38 | a.s = nil 39 | end 40 | 41 | if a.p then 42 | a.p:release(a) 43 | a.p = nil 44 | end 45 | 46 | if cb then 47 | cpcall(cb, ok, ...) 48 | end 49 | end 50 | 51 | a.s = n:search(a.k, 'lookup', function(s, p, via) 52 | if not a.s then 53 | return 54 | end 55 | a.s = nil 56 | 57 | n:stop_search(s) 58 | 59 | if not p then 60 | return a:cb(false, "not found") 61 | end 62 | 63 | a.p = p:acquire(a) 64 | end) 65 | 66 | n.auths[a] = true 67 | 68 | return a 69 | end 70 | 71 | function M.stop_authenticate(n, a) 72 | a:cb(false, 'interrupted') 73 | end 74 | 75 | 76 | function M.update(n, a, deadlines) 77 | -- still searching? 78 | if not a.p then 79 | return 80 | end 81 | 82 | local deadline = a.req_ts+a.retry+1 83 | 84 | if now >= deadline then 85 | if a.retry > wh.AUTH_RETRY then 86 | return a:cb(false, "could not auth") 87 | end 88 | 89 | explain(n, "auth as %s ? (retry: %d)", n:key(a.alias_k), a.retry) 90 | n:_sendto{ 91 | dst=a.p, 92 | m=packet.auth(n, a.p), 93 | sk=a.alias_sk, 94 | } 95 | 96 | a.retry = a.retry + 1 97 | a.req_ts = now 98 | a.last_seen = now 99 | 100 | deadline = a.req_ts+a.retry+1 101 | end 102 | 103 | deadlines[#deadlines+1] = deadline 104 | end 105 | 106 | function M.resolve_alias(n, alias, src) 107 | -- alias may be nil 108 | 109 | if alias then 110 | explain(n, "alias %s is %s", n:key(alias), n:key(src)) 111 | 112 | -- copy all attributes from alias to p 113 | src.relay = nil 114 | for k, v in pairs(alias) do 115 | if k ~= 'k' and k ~= 'alias' then 116 | src[k] = alias[k] 117 | end 118 | end 119 | 120 | -- remove alias 121 | n.kad:unlink(alias) 122 | 123 | 124 | end 125 | end 126 | 127 | function M.on_authed(n, alias_k, src) 128 | for a in pairs(n.auths) do 129 | if a.alias_k == alias_k then 130 | local alias = n.kad:get(alias_k) 131 | 132 | M.resolve_alias(n, alias, n.p) 133 | 134 | -- wg might need to be enabled 135 | if n.lo then 136 | n.lo:refresh() 137 | end 138 | 139 | if n.wgsync then 140 | n.wgsync:refresh() 141 | end 142 | 143 | return a:cb(true) 144 | end 145 | end 146 | end 147 | 148 | return M 149 | 150 | -------------------------------------------------------------------------------- /src/ipc.lua: -------------------------------------------------------------------------------- 1 | -- IPC server 2 | -- 3 | -- Used by the WireHub CLI tool. Listens on a UNIX socket and serves. 4 | 5 | local MT = {__index = {}} 6 | local I = MT.__index 7 | 8 | local function close(ipc, sock) 9 | local state = ipc.states[sock] 10 | ipc.states[sock] = nil 11 | 12 | if state == nil then 13 | return 14 | end 15 | 16 | if state.close_cb then 17 | cpcall(state.close_cb) 18 | end 19 | 20 | wh.close(sock) 21 | end 22 | 23 | function I.close(ipc) 24 | for sock in pairs(ipc.states) do 25 | close(ipc, sock) 26 | end 27 | ipc.states = {} 28 | 29 | if ipc.listen_sock then 30 | wh.close(ipc.listen_sock) 31 | ipc.listen_sock = nil 32 | end 33 | 34 | if ipc.close_cb then 35 | ipc.close_cb() 36 | ipc.close_cb = nil 37 | end 38 | end 39 | 40 | local function on_sock_readable(ipc, sock, cmd) 41 | local state = ipc.states[sock] 42 | 43 | if not state then 44 | return 45 | end 46 | 47 | if not cmd or #cmd == 0 then 48 | return close(ipc, sock) 49 | end 50 | 51 | if state.wait_cmd and cmd and #cmd > 0 then 52 | state.wait_cmd = false 53 | 54 | -- remove trailing \n 55 | while string.sub(cmd, -1) == '\n' do 56 | cmd = string.sub(cmd, 1, -2) 57 | end 58 | 59 | local function send(...) 60 | if not ipc.states[sock] then 61 | return 62 | end 63 | 64 | local s = string.format(...) 65 | if wh.send(sock, s) ~= #s then 66 | error("send truncated") 67 | end 68 | end 69 | 70 | local function _close() 71 | close(ipc, sock) 72 | end 73 | 74 | local done 75 | for pattern, cb in pairs(ipc.h) do 76 | done = (function(...) 77 | if ... == nil then 78 | return false 79 | end 80 | 81 | printf("$(blue)ipc: %s", cmd) 82 | 83 | state.close_cb = cpcall(cb, send, _close, ...) 84 | return true 85 | end)(string.match(cmd, pattern)) 86 | 87 | if done then 88 | break 89 | end 90 | end 91 | 92 | if not done then 93 | send('?\n') 94 | _close() 95 | end 96 | end 97 | 98 | end 99 | 100 | function I.on_readable(ipc, r) 101 | if ipc.listen_sock and r[ipc.listen_sock] then 102 | r[ipc.listen_sock] = nil 103 | 104 | local new_sock = wh.ipc.accept(ipc.listen_sock) 105 | ipc.states[new_sock] = {wait_cmd=true} 106 | end 107 | 108 | for sock in pairs(ipc.states) do 109 | if r[sock] then 110 | local cmd = wh.recv(sock, 65535) 111 | on_sock_readable(ipc, sock, cmd) 112 | end 113 | end 114 | end 115 | 116 | function I.update(ipc, socks) 117 | if ipc.listen_sock then 118 | socks[#socks+1] = ipc.listen_sock 119 | end 120 | 121 | for sock, state in pairs(ipc.states) do 122 | socks[#socks+1] = sock 123 | end 124 | 125 | return nil 126 | end 127 | 128 | local M = {} 129 | 130 | function M.bind(interface_name, h) 131 | assert(interface_name and h) 132 | local listen_sock, close_cb = wh.ipc.bind(interface_name, false) 133 | 134 | return setmetatable({ 135 | close_cb=close_cb, 136 | states={}, 137 | listen_sock=listen_sock, 138 | h=h, 139 | }, MT) 140 | end 141 | 142 | function M.call(interface_name, cmd) 143 | assert(interface_name) 144 | local sock = wh.ipc.connect(interface_name) 145 | 146 | if not sock then 147 | return 148 | end 149 | 150 | if wh.send(sock, cmd .. '\n') ~= (#cmd+1) then 151 | error("send truncated") 152 | end 153 | 154 | return sock 155 | end 156 | 157 | return M 158 | 159 | -------------------------------------------------------------------------------- /src/nat.lua: -------------------------------------------------------------------------------- 1 | -- NAT discovery mechanism 2 | -- 3 | -- Like STUN, but way ligther and less complete 4 | 5 | local time = require('time') 6 | local packet = require('packet') 7 | local peer = require('peer') 8 | 9 | local M = {} 10 | 11 | local function explain(n, d, fmt, ...) 12 | return n:explain("nat %s", fmt, n:key(d.k), ...) 13 | end 14 | 15 | function M.detect_nat(n, k, cb) 16 | local p 17 | if k == nil then 18 | -- XXX get the closest node which is public! 19 | local closest = n.kad:kclosest(n.k, 1, function(p) 20 | return p.bootstrap 21 | end) 22 | if #closest == 0 then 23 | return cb("offline") 24 | end 25 | 26 | p = closest[1][2] 27 | k = p.k 28 | else 29 | p = n.kad:get(k) 30 | 31 | if not p then 32 | error(string.format("no route to %s", n:key(k))) 33 | end 34 | end 35 | 36 | local d = { 37 | may_cone=true, 38 | may_offline=true, 39 | may_direct=true, 40 | k=k, 41 | req_ts=0, 42 | retry=0, 43 | uid=wh.randombytes(8), 44 | uid_echo=wh.randombytes(8), 45 | p=p, 46 | p_echo=nil, -- explicit 47 | } 48 | 49 | d.p:acquire(d) 50 | 51 | d.cb = function(...) 52 | d.p:release(d) 53 | n.nat_detectors[d] = nil 54 | return cb(...) 55 | end 56 | 57 | 58 | n.nat_detectors[d] = true 59 | end 60 | 61 | function M.update(n, d, deadlines) 62 | local to_remove = {} 63 | 64 | local do_ping, deadline 65 | 66 | -- check if offline. if node answers, peek its echo node 67 | if d.may_offline then 68 | do_ping, deadline = time.retry_backoff(d, 'retry', 'req_ts', wh.PING_RETRY, wh.PING_BACKOFF) 69 | 70 | if do_ping then 71 | explain(n, d, "offline? (retry: %d)", d.retry) 72 | n:_sendto{dst=d.p, m=packet.ping(nil, d.uid)} 73 | end 74 | 75 | if deadline == nil then 76 | return d.cb('offline') 77 | end 78 | end 79 | 80 | -- maybe public? ping node and ask to answer as an echo. if it is not 81 | -- received after several retry, consider the node as not public. 82 | if not d.may_offline and d.may_direct then 83 | assert(d.p_echo, 'a pong with the echo address should have been received') 84 | 85 | do_ping, deadline = time.retry_backoff(d, 'retry', 'req_ts', wh.PING_RETRY, wh.PING_BACKOFF) 86 | 87 | if do_ping then 88 | explain(n, d, "direct? (retry: %d)", d.retry) 89 | n:_sendto{dst=d.p, m=packet.ping('swapsrc', d.uid)} 90 | end 91 | 92 | if deadline == nil then 93 | explain(n, d, "not direct!") 94 | d.may_direct = false 95 | d.req_ts = 0 96 | d.retry = 0 97 | d.uid = wh.randombytes(8) 98 | end 99 | end 100 | 101 | if not d.may_offline and not d.may_direct and d.may_cone then 102 | do_ping, deadline = time.retry_backoff(d, 'retry', 'req_ts', wh.PING_RETRY, wh.PING_BACKOFF) 103 | 104 | if do_ping then 105 | -- UDP hole punch 106 | explain(n, d, "cone? (retry: %d)", d.retry) 107 | n:_sendto{dst=d.p_echo, m=packet.ping('normal', d.uid_echo)} 108 | n:_sendto{dst=d.p, m=packet.ping('swapsrc', d.uid)} 109 | end 110 | 111 | if deadline == nil then 112 | explain(n, d, "not cone!") 113 | d.may_cone = false 114 | d.req_ts = 0 115 | d.retry = 0 116 | d.uid=wh.randombytes(8) 117 | end 118 | end 119 | 120 | if not d.may_offline and not d.may_direct and not d.may_cone then 121 | return d.cb('blocked') 122 | end 123 | 124 | assert(deadline ~= nil) 125 | deadlines[#deadlines+1] = deadline 126 | end 127 | 128 | function M.on_pong(n, body, src) 129 | for d in pairs(n.nat_detectors) do 130 | if d.uid == body then 131 | if d.may_offline then 132 | assert(src.addr_echo) 133 | d.p_echo = peer{k=src.k, addr=src.addr_echo} 134 | d.may_offline = false 135 | d.req_ts = 0 136 | d.retry = 0 137 | d.uid=wh.randombytes(8) 138 | explain(n, d, "online!") 139 | elseif d.may_direct then 140 | d.cb("direct") 141 | elseif d.may_cone then 142 | d.cb("cone") 143 | end 144 | end 145 | end 146 | 147 | end 148 | 149 | return M 150 | 151 | -------------------------------------------------------------------------------- /src/tools/completion.lua: -------------------------------------------------------------------------------- 1 | if arg[1] == 'completion' then 2 | function help() 3 | printf( 4 | 'Usage: wh completion {get-bash}\n' .. 5 | '\n' .. 6 | 'To enable auto-completion with `bash-completion`, run:\n' .. 7 | ' wh completion get-bash | sudo tee /usr/share/bash-completion/completions/wh\n' 8 | ) 9 | return 10 | end 11 | 12 | if arg[2] == nil or arg[2] == 'help' then 13 | return help() 14 | end 15 | 16 | if arg[2] == "get-bash" then 17 | print( 18 | '_wh()\n' .. 19 | '{\n' .. 20 | ' local opts cur\n' .. 21 | ' _init_completion || return\n' .. 22 | '\n' .. 23 | ' opts=`wh _completion ${COMP_CWORD} ${COMP_WORDS[@]}`\n' .. 24 | '\n' .. 25 | ' #if [[ $cur == -* ]] ; then\n' .. 26 | ' COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )\n' .. 27 | ' return 0\n' .. 28 | ' #fi\n' .. 29 | '}\n' .. 30 | 'complete -F _wh wh\n' 31 | ) 32 | else 33 | printf("unknown argument: %s", arg[2]) 34 | return -1 35 | end 36 | 37 | return 38 | end 39 | 40 | assert(arg[1] == '_completion') 41 | 42 | if #arg < 3 then 43 | return 44 | end 45 | 46 | local opt_count = 0 47 | local function opt(s) 48 | print(s) 49 | opt_count = opt_count + 1 50 | end 51 | 52 | local function optlist(l) 53 | table.sort(l) 54 | for _, v in ipairs(l) do opt(v) end 55 | end 56 | 57 | local function listpeers(interface) 58 | local ok, value = pcall(require('ipc').call, interface, 'list') 59 | if not ok then 60 | return 61 | end 62 | 63 | local sock = value 64 | if not sock then 65 | return 66 | end 67 | 68 | local trusted = {} 69 | local untrusted = {} 70 | local buf = "" 71 | now = wh.now() 72 | while true do 73 | local r = wh.select({sock}, {}, {}, now+1) 74 | now = wh.now() 75 | 76 | if not r[sock] then 77 | break 78 | end 79 | 80 | buf = buf .. (wh.recv(sock, 65535) .. "") 81 | if #buf == 0 then 82 | break 83 | end 84 | 85 | while true do 86 | local name, trust, i = string.match(buf, '([^%s]+)%s+([^%s]+)\n()') 87 | 88 | if not name or not trust then 89 | break 90 | end 91 | buf = string.sub(buf, i) 92 | 93 | if trust == 'trusted' then 94 | trusted[#trusted+1] = name 95 | else 96 | untrusted[#untrusted+1] = name 97 | end 98 | end 99 | end 100 | wh.close(sock) 101 | 102 | return trusted, untrusted 103 | end 104 | 105 | local cur_idx = tonumber(arg[2]) 106 | local cmd = {} 107 | for i = 3, #arg do cmd[#cmd+1] = arg[i] end 108 | 109 | if cur_idx <= 1 then 110 | local public_subcmds = {} 111 | for _, v in ipairs(SUBCMDS) do 112 | local is_private = string.sub(v, 1, 1) == '_' 113 | if not is_private then 114 | public_subcmds[#public_subcmds+1] = v 115 | end 116 | end 117 | 118 | optlist(public_subcmds) 119 | return 120 | end 121 | 122 | local subcmd = cmd[2] 123 | 124 | if cur_idx == 2 then 125 | if ( 126 | subcmd == 'authenticate' or subcmd == 'auth' or 127 | subcmd == 'down' or 128 | subcmd == 'forget' or 129 | subcmd == 'inspect' or 130 | subcmd == 'ipc' or 131 | subcmd == 'lookup' or 132 | subcmd == 'p2p' or 133 | subcmd == 'ping' or 134 | subcmd == 'reload' or 135 | subcmd == 'show' 136 | ) then 137 | local interfaces = wh.ipc.list() 138 | 139 | if ( 140 | subcmd == 'show' or 141 | subcmd == 'inspect' 142 | ) and #interfaces > 1 then 143 | opt('all') 144 | end 145 | 146 | optlist(wh.ipc.list()) 147 | return 148 | end 149 | 150 | if ( 151 | subcmd == 'genkey' or 152 | subcmd == 'orchid' or 153 | --subcmd == 'show' or 154 | subcmd == 'up' or 155 | subcmd == 'workbit' 156 | ) then 157 | -- XXX complete conf file path? 158 | end 159 | end 160 | 161 | if cur_idx == 3 then 162 | if ( 163 | subcmd == 'authenticate' or subcmd == 'auth' or 164 | subcmd == 'forget' or 165 | subcmd == 'lookup' or 166 | subcmd == 'p2p' or 167 | subcmd == 'ping' 168 | ) then 169 | local trusted, untrusted = listpeers(cmd[3]) 170 | 171 | if trusted and untrusted then 172 | optlist(trusted) 173 | if cmd[cur_idx+1] or #trusted == 0 then 174 | optlist(untrusted) 175 | end 176 | end 177 | end 178 | 179 | if subcmd == 'show' then 180 | opt('all') 181 | opt('light') 182 | end 183 | end 184 | 185 | if cur_idx == 2 and ( 186 | cmd[cur_idx+1] ~= nil and string.sub(cmd[cur_idx+1], 1) == 'h' or 187 | opt_count ~= 1 188 | ) then 189 | opt('help') 190 | end 191 | 192 | -------------------------------------------------------------------------------- /src/core/workerlib.c: -------------------------------------------------------------------------------- 1 | #include "luawh.h" 2 | #include 3 | #include 4 | #include "serdes.h" 5 | 6 | #define MT "worker" 7 | 8 | struct pipefd { 9 | int r_fd; 10 | int w_fd; 11 | }; 12 | 13 | struct worker { 14 | struct pipefd req, resp; 15 | volatile int running; 16 | char* name; 17 | lua_State* L; 18 | pthread_t thread; 19 | }; 20 | 21 | static int _tostring(lua_State* L) { 22 | struct worker* w = luaW_toptr(L, 1, MT); 23 | 24 | if (w) { 25 | lua_pushfstring(L, "worker* '%s': %p", w->name, w); 26 | } else { 27 | lua_pushstring(L, "worker*: "); 28 | } 29 | 30 | return 1; 31 | } 32 | 33 | 34 | static void delete_worker(struct worker* w) { 35 | if (w->running) { 36 | luaW_writenone(w->req.w_fd); 37 | pthread_join(w->thread, NULL); 38 | } 39 | 40 | if (w->req.r_fd != -1) { close(w->req.r_fd), w->req.r_fd = -1; } 41 | if (w->req.w_fd != -1) { close(w->req.w_fd), w->req.w_fd = -1; } 42 | if (w->resp.r_fd != -1) { close(w->resp.r_fd), w->resp.r_fd = -1; } 43 | if (w->resp.w_fd != -1) { close(w->resp.w_fd), w->resp.w_fd = -1; } 44 | if (w->name) { free(w->name), w->name = NULL; } 45 | if (w->L) { lua_close(w->L), w->L = NULL; } 46 | if (w->name) { free(w->name), w->name = NULL; } 47 | 48 | free(w); 49 | } 50 | 51 | static void delete_worker_pvoid(void* w) { 52 | return delete_worker((struct worker*)w); 53 | } 54 | 55 | static void* worker(void* ud) { 56 | struct worker* w = ud; 57 | lua_State* L = w->L; 58 | 59 | for (;;) { 60 | lua_settop(L, 0); 61 | 62 | int success = luaW_readstack(L, w->req.r_fd) == 0; 63 | 64 | if (success && lua_gettop(L) < 2) { 65 | break; 66 | } 67 | 68 | success &= lua_pcall(L, lua_gettop(L)-2, LUA_MULTRET, 0) == LUA_OK; 69 | lua_pushboolean(L, success); 70 | lua_insert(L, 2); 71 | luaW_writestack(L, 1, w->resp.w_fd); 72 | } 73 | 74 | luaW_writestack(L, 1, w->resp.w_fd); 75 | w->running = 0; 76 | 77 | return NULL; 78 | } 79 | 80 | int luawh_pushworker(lua_State* L) { 81 | const char* name = lua_tostring(L, 1); 82 | 83 | struct worker* w = calloc(1, sizeof(struct worker)); 84 | assert(w); 85 | 86 | w->name = name ? strdup(name) : NULL; 87 | w->req.r_fd = w->req.w_fd = -1; 88 | w->resp.r_fd = w->resp.w_fd = -1; 89 | 90 | if (pipe((int*)&w->req)) { 91 | delete_worker(w); 92 | luaL_error(L, "pipe() failed: %s", strerror(errno)); 93 | } 94 | 95 | if (pipe((int*)&w->resp)) { 96 | delete_worker(w); 97 | luaL_error(L, "pipe() failed: %s", strerror(errno)); 98 | } 99 | 100 | w->L = luaL_newstate(); 101 | luaL_openlibs(w->L); 102 | 103 | if (pthread_create(&w->thread, NULL, worker, w)) { 104 | delete_worker(w); 105 | luaL_error(L, "pthread_create() failed: %s", strerror(errno)); 106 | } 107 | 108 | w->running = 1; 109 | 110 | luaW_pushptr(L, MT, w); 111 | return 1; 112 | } 113 | 114 | static int _pushwork(lua_State* L) { 115 | struct worker* w = luaW_checkptr(L, 1, MT); 116 | luaL_checktype(L, 2, LUA_TFUNCTION); 117 | luaL_checktype(L, 3, LUA_TFUNCTION); 118 | lua_pushvalue(L, 2); 119 | int ref = luaL_ref(L, LUA_REGISTRYINDEX); 120 | 121 | lua_pushinteger(L, ref); 122 | lua_replace(L, 2); 123 | luaW_writestack(L, 2, w->req.w_fd); 124 | 125 | return 0; 126 | } 127 | 128 | static int _update(lua_State* L) { 129 | struct worker* w = luaW_checkptr(L, 1, MT); 130 | luaL_checktype(L, 2, LUA_TTABLE); 131 | lua_pushinteger(L, w->resp.r_fd); 132 | lua_seti(L, 2, luaL_len(L, 2)+1); 133 | 134 | return 0; 135 | } 136 | 137 | static int _on_readable(lua_State* L) { 138 | struct worker* w = luaW_checkptr(L, 1, MT); 139 | luaL_checktype(L, 2, LUA_TTABLE); 140 | 141 | lua_pushinteger(L, w->resp.r_fd); 142 | lua_gettable(L, 2); 143 | 144 | if (lua_toboolean(L, -1)) { 145 | lua_settop(L, 1); 146 | 147 | if (luaW_readstack(L, w->resp.r_fd) != 0) { 148 | luaL_error(L, "deserialization failed"); 149 | } 150 | 151 | int ref = lua_tointeger(L, 2); 152 | lua_rawgeti(L, LUA_REGISTRYINDEX, ref); 153 | assert (lua_type(L, -1) == LUA_TFUNCTION); 154 | lua_replace(L, 2); 155 | lua_call(L, lua_gettop(L)-2, 0); 156 | 157 | luaL_unref(L, LUA_REGISTRYINDEX, ref); 158 | } 159 | 160 | return 0; 161 | } 162 | 163 | LUAMOD_API int luaopen_worker(lua_State* L) { 164 | luaW_declptr(L, MT, delete_worker_pvoid); 165 | 166 | luaL_getmetatable(L, MT); 167 | lua_getfield(L, -1, "__index"); 168 | 169 | lua_pushcfunction(L, _pushwork); 170 | lua_setfield(L, -2, "pcall"); 171 | 172 | lua_pushcfunction(L, _update); 173 | lua_setfield(L, -2, "update"); 174 | 175 | lua_pushcfunction(L, _on_readable); 176 | lua_setfield(L, -2, "on_readable"); 177 | 178 | lua_pop(L, 1); 179 | 180 | lua_pushcfunction(L, _tostring); 181 | lua_setfield(L, -2, "__tostring"); 182 | 183 | lua_pop(L, 1); 184 | 185 | lua_pushcfunction(L, luawh_pushworker); 186 | 187 | return 1; 188 | } 189 | 190 | -------------------------------------------------------------------------------- /src/core/serdes.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "serdes.h" 5 | #include "luawh.h" 6 | 7 | struct load_ud { 8 | int fd; 9 | size_t sz; 10 | char buf[256]; 11 | }; 12 | 13 | static const char* _deser_load(lua_State* L, void* data, size_t* psize) { 14 | (void)L; 15 | 16 | struct load_ud* ld = (struct load_ud*)data; 17 | 18 | *psize = ld->sz; 19 | 20 | if (*psize > sizeof(ld->buf)) { 21 | *psize = sizeof(ld->buf); 22 | } 23 | 24 | if (*psize > 0) { 25 | ssize_t ret = read(ld->fd, ld->buf, *psize); 26 | assert(ret>=0); 27 | assert((size_t)ret<=ld->sz); 28 | *psize = ret; 29 | ld->sz -= ret; 30 | } 31 | 32 | return ld->buf; 33 | } 34 | 35 | #define READ(var) assert(read(fd, &var, sizeof(var)) == sizeof(var)) 36 | int luaW_read(lua_State* L, int fd) { 37 | int type; 38 | uint8_t u8; 39 | size_t sz; 40 | lua_Number number; 41 | char* str; 42 | int load_ret; 43 | struct load_ud ld; 44 | 45 | READ(type); 46 | 47 | switch (type) { 48 | case LUA_TNONE: 49 | return 0; 50 | 51 | case LUA_TNIL: 52 | lua_pushnil(L); 53 | break; 54 | 55 | case LUA_TBOOLEAN: 56 | READ(u8); 57 | lua_pushboolean(L, u8); 58 | break; 59 | 60 | case LUA_TNUMBER: 61 | READ(number); 62 | lua_pushnumber(L, number); 63 | break; 64 | 65 | case LUA_TSTRING: 66 | READ(sz); 67 | // XXX stream? 68 | str = malloc(sz); 69 | assert(read(fd, str, sz) == (ssize_t)sz); 70 | lua_pushlstring(L, str, sz); 71 | free(str); 72 | break; 73 | 74 | case LUA_TTABLE: 75 | lua_newtable(L); 76 | for (;;) { 77 | int r = luaW_read(L, fd); 78 | if (r == -1) { 79 | return -1; 80 | } else if (r == 0) { 81 | break; 82 | } 83 | 84 | if (luaW_read(L, fd) < 0) { 85 | return -1; 86 | } 87 | 88 | lua_rawset(L, -3); 89 | } 90 | break; 91 | 92 | case LUA_TFUNCTION: 93 | READ(ld.sz); 94 | ld.fd = fd; 95 | load_ret = lua_load(L, _deser_load, &ld, "work", NULL); 96 | if (load_ret != LUA_OK) { 97 | lua_pushnumber(L, load_ret); 98 | lua_insert(L, -2); 99 | 100 | while(lua_gettop(L) > 2) { 101 | lua_remove(L, 1); 102 | } 103 | 104 | return -1; 105 | } 106 | break; 107 | 108 | case LUA_TLIGHTUSERDATA: 109 | case LUA_TUSERDATA: 110 | case LUA_TTHREAD: 111 | assert(0 && "unhandled type"); 112 | }; 113 | 114 | return 1; 115 | } 116 | 117 | int luaW_readstack(lua_State* L, int fd) { 118 | int ret; 119 | while((ret = luaW_read(L, fd)) != 0); 120 | return ret; 121 | } 122 | #undef READ 123 | 124 | #define WRITE(var) assert(write(fd, &var, sizeof(var)) == sizeof(var)) 125 | static int _ser_dump(lua_State *L, const void* b, size_t size, void* ud) { 126 | (void)L; 127 | luaL_Buffer* buf = (luaL_Buffer*)ud; 128 | luaL_addlstring(buf, (const char*)b, size); 129 | return 0; 130 | } 131 | 132 | int luaW_write(lua_State* L, int idx, int fd) { 133 | int type = lua_type(L, idx); 134 | uint8_t u8; 135 | lua_Number number; 136 | size_t sz; 137 | const char* str; 138 | luaL_Buffer buf; 139 | 140 | WRITE(type); 141 | 142 | switch (type) { 143 | case LUA_TNONE: 144 | return 0; 145 | 146 | case LUA_TNIL: 147 | break; 148 | 149 | case LUA_TBOOLEAN: 150 | u8 = lua_toboolean(L, idx); 151 | WRITE(u8); 152 | break; 153 | 154 | case LUA_TNUMBER: 155 | number = lua_tonumber(L, idx); 156 | WRITE(number); 157 | break; 158 | 159 | case LUA_TSTRING: 160 | str = lua_tolstring(L, idx, &sz); 161 | WRITE(sz); 162 | assert(write(fd, str, sz) == (ssize_t)sz); 163 | break; 164 | 165 | case LUA_TTABLE: 166 | lua_pushnil(L); /* first key */ 167 | while (lua_next(L, idx) != 0) { 168 | assert(luaW_write(L, lua_gettop(L)-1, fd) == 1); 169 | assert(luaW_write(L, lua_gettop(L), fd) == 1); 170 | lua_pop(L, 1); 171 | } 172 | type = LUA_TNONE; 173 | WRITE(type); 174 | break; 175 | 176 | case LUA_TFUNCTION: 177 | luaL_buffinit(L, &buf); 178 | lua_pushvalue(L, idx); 179 | if (lua_dump(L, _ser_dump, &buf, 0) != 0) { 180 | return luaL_error(L, "unable to dump given function"); 181 | } 182 | lua_pop(L, 1); 183 | luaL_pushresult(&buf); 184 | str = lua_tolstring(L, -1, &sz); 185 | WRITE(sz); 186 | assert(write(fd, str, sz) == (ssize_t)sz); 187 | lua_pop(L, 1); 188 | break; 189 | 190 | case LUA_TLIGHTUSERDATA: 191 | case LUA_TUSERDATA: 192 | case LUA_TTHREAD: 193 | luaL_error(L, "unhandled type: %s", lua_typename(L, type)); 194 | } 195 | 196 | return 1; 197 | } 198 | 199 | void luaW_writestack(lua_State* L, int idx, int fd) { 200 | if (idx < 0) { 201 | idx = lua_gettop(L)+idx+1; 202 | } 203 | 204 | for (; luaW_write(L, idx, fd); ++idx); 205 | } 206 | 207 | void luaW_writenone(int fd) { 208 | int type = LUA_TNONE; 209 | assert(write(fd, &type, sizeof(int)) == sizeof(int)); 210 | } 211 | 212 | #undef WRITE 213 | 214 | -------------------------------------------------------------------------------- /src/connectivity.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local function _work_upnp(n) 4 | local u = { 5 | enabled=false, 6 | peers={}, 7 | } 8 | 9 | local d, url 10 | d, u.iaddr, url = wh.upnp.discover_igd(1) 11 | 12 | if d then 13 | printf('UPnP device found: %s', url) 14 | local ok, val = wh.upnp.external_ip(d) 15 | 16 | if ok then 17 | u.external_ip = val 18 | else 19 | u.external_ip = nil 20 | end 21 | 22 | local function _add(port) 23 | local ok, err = wh.upnp.add_redirect(d, { 24 | desc = string.format('WireHub %s', wh.tob64(n.k)), 25 | eport=port, 26 | iaddr=u.iaddr, 27 | iport=port, 28 | lease=wh.UPNP_REFRESH_EVERY, 29 | protocol='udp', 30 | }) 31 | 32 | --if ok then 33 | -- printf('upsert UPnP redirection %s:%d -> %s:%d', 34 | -- u.external_ip or '???', n.port, u.iaddr, n.port 35 | -- ) 36 | --else 37 | -- printf('UPnP error: %s. ignore', err) 38 | --end 39 | 40 | return ok 41 | end 42 | 43 | -- do not check UPnP error, experience shows this is often buggy. 44 | _add(n.port) 45 | _add(n.port_echo) 46 | 47 | local port_ok, port_echo_ok 48 | 49 | for _, r in ipairs(wh.upnp.list_redirects(d)) do 50 | -- check redirections was a success. 51 | if r.iaddr == u.iaddr then 52 | if r.iport == n.port and r.eport == n.port then 53 | port_ok = true 54 | end 55 | 56 | if r.iport == n.port_echo and r.eport == n.port_echo then 57 | port_echo_ok = true 58 | end 59 | end 60 | 61 | local k = string.match(r.desc, "WireHub ([^ ]+)") 62 | 63 | if k then 64 | local ok 65 | ok, k = pcall(wh.fromb64, k) 66 | if not ok then k = nil end 67 | end 68 | 69 | if k and #k == 32 and n.k ~= k then 70 | -- XXX check version if necessary 71 | 72 | -- XXX for now, add the peer only if we know its public 73 | -- interface. When WireHub will be ready to handle private and 74 | -- public peer addresses, r.iaddr and r.iport could be used 75 | 76 | 77 | if u.external_ip then 78 | u.peers[k] = {u.external_ip, r.iport} 79 | else 80 | --u.peers[k] = {r.iaddr, r.iport} 81 | end 82 | end 83 | end 84 | 85 | u.enabled = port_ok and port_echo_ok 86 | 87 | if u.enabled then 88 | printf("UPnP redirection is $(green)enabled") 89 | else 90 | printf("UPnP redirection is $(green)not installed") 91 | end 92 | end 93 | 94 | return u 95 | end 96 | 97 | local function update_upnp(n, deadlines) 98 | assert(n.upnp) 99 | 100 | if n.mode ~= 'unknown' then 101 | return 102 | end 103 | 104 | local u = n.upnp 105 | 106 | local deadline = u.last_check + wh.UPNP_REFRESH_EVERY - 30 107 | 108 | if deadline <= now and not u.checking then 109 | u.checking = true 110 | 111 | n:explain('connectivity', "checking UPnP...") 112 | u.worker:pcall( 113 | function(ok, ...) 114 | u.checking = false 115 | 116 | if not ok then 117 | error(...) 118 | end 119 | local nu = ... 120 | 121 | local peers = nu.peers 122 | nu.peers = nil 123 | 124 | for k, v in pairs(nu) do 125 | u[k] = v 126 | end 127 | 128 | for k, addr in pairs(peers) do 129 | local addr = wh.address(addr[1], addr[2]) 130 | printf("found UPnP device $(yellow)%s$(reset) (%s)", wh.tob64(k), addr) 131 | 132 | local upnp_p = n.kad:touch(k) 133 | upnp_p.addr = addr 134 | end 135 | 136 | u.last_check = now 137 | n.last_connectivity_check = nil 138 | end, 139 | _work_upnp, 140 | { 141 | k=n.k, 142 | port=n.port, 143 | port_echo=n.port_echo, 144 | } 145 | ) 146 | 147 | elseif u.checking then 148 | deadline = nil 149 | else 150 | deadline = u.last_check + wh.UPNP_REFRESH_EVERY - 30 151 | end 152 | 153 | deadlines[#deadlines+1] = deadline 154 | end 155 | 156 | function M.update(n, deadlines) 157 | if n.upnp then 158 | update_upnp(n, deadlines) 159 | 160 | if n.upnp.checking then 161 | return 162 | end 163 | end 164 | 165 | if n.checking_connectivity then 166 | return 167 | end 168 | 169 | local deadline = (n.last_connectivity_check or 0) + wh.CONNECTIVITY_CHECK_EVERY 170 | if now > deadline then 171 | local function cont() 172 | n:explain('connectivity', "find self") 173 | n:search(n.k, 'lookup') -- center 174 | 175 | n.last_connectivity_check = now 176 | end 177 | 178 | if n.mode == 'unknown' then 179 | n:explain('connectivity', "checking connectivity...") 180 | n.checking_connectivity = true 181 | n:detect_nat(nil, function(mode) 182 | n.checking_connectivity = false 183 | 184 | n:explain('connectivity', "NAT is $(magenta)%s", mode) 185 | 186 | n.is_nated = mode ~= 'direct' 187 | 188 | return cont() 189 | end) 190 | 191 | deadline = nil 192 | else 193 | cont() 194 | 195 | deadline = n.last_connectivity_check + wh.CONNECTIVITY_CHECK_EVERY 196 | end 197 | end 198 | 199 | deadlines[#deadlines+1] = deadline 200 | end 201 | 202 | return M 203 | 204 | -------------------------------------------------------------------------------- /src/core/key.c: -------------------------------------------------------------------------------- 1 | // XXX protect secret data (sodium_malloc, sodium_mlock) 2 | 3 | #include "common.h" 4 | #include 5 | #include 6 | 7 | int trailing_0s_buf(const void* buf, size_t l) { 8 | assert(l % sizeof(uint32_t) == 0); 9 | unsigned int i; 10 | unsigned int r = 0; 11 | for(i=0; iklen; 62 | unsigned char* tohash = alloca(tohash_l); 63 | unsigned char hash[crypto_generichash_BYTES]; 64 | 65 | memcpy(tohash+crypto_scalarmult_curve25519_BYTES, s->k, s->klen); 66 | 67 | for (int i=0; !s->found; ++i) { 68 | if (i == 256) { 69 | __sync_add_and_fetch(&s->stat->count, i); 70 | i = 0; 71 | } 72 | 73 | crypto_sign_ed25519_keypair(ed25519_pk, ed25519_sk); 74 | 75 | // pk should be positive 76 | if ((ed25519_pk[crypto_sign_ed25519_PUBLICKEYBYTES-1] & 0x80) == 0x80) { 77 | continue; 78 | } 79 | 80 | // convert to curve25519 point 81 | if (crypto_sign_ed25519_pk_to_curve25519(tohash, ed25519_pk) < 0) { 82 | continue; 83 | } 84 | 85 | crypto_generichash(hash, sizeof(hash), tohash, tohash_l, NULL, 0); 86 | 87 | unsigned int wb = trailing_0s_buf(hash, sizeof(hash)); 88 | 89 | if (wb >= s->wb) { 90 | if (crypto_sign_ed25519_sk_to_curve25519(s->x25519_sk, ed25519_sk) < 0) { 91 | continue; 92 | } 93 | 94 | pthread_mutex_lock(&s->mutex); 95 | 96 | if (!s->found) { 97 | // found! 98 | s->found = 1; 99 | 100 | memcpy(s->ed25519_pk, ed25519_pk, sizeof(ed25519_pk)); 101 | memcpy(s->ed25519_sk, ed25519_sk, sizeof(ed25519_sk)); 102 | memcpy(s->x25519_pk, tohash, crypto_scalarmult_curve25519_BYTES); 103 | memcpy(s->hash, hash, sizeof(hash)); 104 | s->wb = wb; 105 | 106 | pthread_cond_broadcast(&s->cond); 107 | } 108 | 109 | pthread_mutex_unlock(&s->mutex); 110 | break; 111 | } 112 | } 113 | 114 | return 0; 115 | } 116 | 117 | static void* worker(void* ud) { 118 | struct search_st* s = (struct search_st*)ud; 119 | 120 | search(s); 121 | 122 | pthread_exit(NULL); 123 | return NULL; 124 | } 125 | 126 | int genkey( 127 | uint8_t* ed25519_sk, 128 | const char* key, 129 | int wb, 130 | int num_threads 131 | ) { 132 | int err = 0; 133 | struct stat_st stat; 134 | memset(&stat, 0, sizeof(stat)); 135 | 136 | struct search_st s = { 137 | .stat = &stat, 138 | .k = (const uint8_t*)key, 139 | .klen = strlen(key), 140 | .wb = wb, 141 | .mutex = PTHREAD_MUTEX_INITIALIZER, 142 | .cond = PTHREAD_COND_INITIALIZER, 143 | .found = 0, 144 | }; 145 | 146 | pthread_t* threads = calloc(num_threads, sizeof(pthread_t)); 147 | 148 | pthread_mutex_lock(&s.mutex); 149 | for (int i=0; i 0) { 170 | anim_i = (anim_i + 1) % (sizeof(anim)); 171 | } 172 | 173 | fprintf(stderr, " \r%c generating %dworkbit key for '%s' (%.1fkK/s, %ds)", 174 | anim[anim_i], 175 | s.wb, 176 | s.k, 177 | (double)h_per_s/1000.0, 178 | duration 179 | ); 180 | 181 | fflush(stdout); 182 | 183 | struct timespec to; 184 | 185 | clock_gettime(CLOCK_REALTIME, &to); 186 | to.tv_sec += 1; 187 | 188 | int retval = pthread_cond_timedwait(&s.cond, &s.mutex, &to); 189 | 190 | if (retval == 0) { 191 | break; 192 | } else if (retval != ETIMEDOUT) { 193 | err = -1; 194 | goto finally; 195 | } 196 | } 197 | #endif 198 | 199 | memcpy(ed25519_sk, s.ed25519_sk, crypto_sign_ed25519_SECRETKEYBYTES); 200 | pthread_mutex_unlock(&s.mutex); 201 | 202 | assert(err == 0); 203 | finally: 204 | for (int i=0; i 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int ipc_prepare(void) { 11 | return system("mkdir -p " WH_DEFAULT_SOCKPATH " 2> /dev/null"); 12 | } 13 | 14 | static int ipc_sockaddr_un(struct sockaddr_un* addr_un, const char* interface) { 15 | addr_un->sun_family = AF_UNIX; 16 | 17 | ssize_t sz = snprintf( 18 | addr_un->sun_path, sizeof(addr_un->sun_path), 19 | WH_DEFAULT_SOCKPATH "%s.sock", interface 20 | ); 21 | 22 | if (sz < 0 || (size_t)sz >= sizeof(addr_un->sun_path)) { 23 | return -1; 24 | } 25 | 26 | return 0; 27 | } 28 | 29 | static int ipc_unlink(const char* interface) { 30 | struct sockaddr_un addr_un; 31 | if (ipc_sockaddr_un(&addr_un, interface) < 0) { 32 | return -1; 33 | } 34 | 35 | if (unlink(addr_un.sun_path) < 0) { 36 | return -1; 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | static int ipc_bind(const char* interface, int force) { 43 | int sock = -1; 44 | 45 | struct sockaddr_un addr_un; 46 | if (ipc_sockaddr_un(&addr_un, interface) < 0) { 47 | goto err; 48 | } 49 | 50 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { 51 | goto err; 52 | } 53 | 54 | int bind_ret; 55 | if ((bind_ret = bind(sock, (struct sockaddr*)&addr_un, sizeof(addr_un))) < 0) { 56 | if (errno == EADDRINUSE && force) { 57 | if (unlink(addr_un.sun_path) < 0) { 58 | goto err; 59 | } 60 | 61 | bind_ret = bind(sock, (struct sockaddr*)&addr_un, sizeof(addr_un) < 0); 62 | } 63 | 64 | if (bind_ret < 0) { 65 | goto err; 66 | } 67 | } 68 | 69 | if (listen(sock, 1) < 0) { 70 | goto err; 71 | } 72 | 73 | return sock; 74 | err: 75 | if (sock != -1) { 76 | close(sock); 77 | } 78 | return -1; 79 | } 80 | 81 | static int ipc_connect(const char* interface) { 82 | int sock = -1; 83 | 84 | struct sockaddr_un addr_un; 85 | if (ipc_sockaddr_un(&addr_un, interface) < 0) { 86 | goto err; 87 | } 88 | 89 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { 90 | goto err; 91 | } 92 | 93 | if (connect(sock, (struct sockaddr*)&addr_un, sizeof(addr_un)) < 0) { 94 | goto err; 95 | } 96 | 97 | if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK) == -1) { 98 | goto err; 99 | } 100 | 101 | return sock; 102 | err: 103 | if (sock != -1) { 104 | close(sock); 105 | } 106 | 107 | return -1; 108 | } 109 | 110 | static int ipc_list(int(*cb)(const char*, void*), void* ud) { 111 | DIR* dirp; 112 | int ret = 0; 113 | 114 | if ((dirp = opendir(WH_DEFAULT_SOCKPATH)) == NULL) { 115 | return -1; 116 | } 117 | 118 | struct dirent* dp; 119 | while ((dp = readdir(dirp))) { 120 | char fullpath[PATH_MAX]; 121 | snprintf(fullpath, sizeof(fullpath), "%s%s", WH_DEFAULT_SOCKPATH, dp->d_name); 122 | 123 | struct stat st; 124 | if (stat(fullpath, &st) < 0) { 125 | continue; 126 | } 127 | 128 | if (!S_ISSOCK(st.st_mode)) { 129 | continue; 130 | } 131 | 132 | // remove folder's path and suffix '.sock' 133 | char* ext = strstr(dp->d_name, ".sock"); 134 | if (!ext) { 135 | continue; 136 | } 137 | *ext = 0; 138 | 139 | int ret = cb(dp->d_name, ud); 140 | if (ret < 0) { 141 | break; 142 | } 143 | } 144 | 145 | closedir(dirp), dirp = NULL; 146 | return ret; 147 | } 148 | 149 | static int ipc_accept(int sock) { 150 | int new_sock = -1; 151 | if ((new_sock = accept(sock, NULL, NULL)) < 0) { 152 | return -1; 153 | } 154 | 155 | if (fcntl(new_sock, F_SETFL, fcntl(new_sock, F_GETFL, 0) | O_NONBLOCK) == -1) { 156 | goto err; 157 | } 158 | 159 | return new_sock; 160 | 161 | err: 162 | if (new_sock == -1) { 163 | close(new_sock); 164 | } 165 | 166 | return -1; 167 | } 168 | 169 | static int _ipc_prepare(lua_State* L) { 170 | if (ipc_prepare() < 0) { 171 | luaL_error(L, "prepare ipc failed: %s", strerror(errno)); 172 | } 173 | 174 | return 0; 175 | } 176 | 177 | static int _ipc_connect(lua_State* L) { 178 | const char* interface = luaL_checkstring(L, 1); 179 | 180 | int sock; 181 | if ((sock = ipc_connect(interface)) < 0) { 182 | switch (errno) { 183 | case ENOENT: return 0; 184 | default: luaL_error(L, "connect ipc failed(): %s", strerror(errno)); 185 | }; 186 | } 187 | 188 | luaW_pushfd(L, sock); 189 | return 1; 190 | } 191 | 192 | static int _ipc_unlink(lua_State* L) { 193 | const char* interface = lua_tostring(L, lua_upvalueindex(1)); 194 | assert(interface); 195 | 196 | lua_pushboolean(L, ipc_unlink(interface) >= 0); 197 | return 1; 198 | } 199 | 200 | static int _ipc_bind(lua_State* L) { 201 | const char* interface = luaL_checkstring(L, 1); 202 | luaL_checktype(L, 2, LUA_TBOOLEAN); 203 | int force = lua_toboolean(L, 2); 204 | 205 | int sock; 206 | if ((sock = ipc_bind(interface, force)) < 0) { 207 | luaL_error(L, "connect ipc failed(): %s", strerror(errno)); 208 | } 209 | 210 | luaW_pushfd(L, sock); 211 | lua_pushstring(L, interface); 212 | lua_pushcclosure(L, _ipc_unlink, 1); 213 | return 2; 214 | } 215 | 216 | static int _ipc_accept(lua_State* L) { 217 | int sock = luaW_getfd(L, 1); 218 | 219 | int new_sock; 220 | if ((new_sock = ipc_accept(sock)) < 0) { 221 | luaL_error(L, "connect ipc failed(): %s", strerror(errno)); 222 | } 223 | 224 | luaW_pushfd(L, new_sock); 225 | return 1; 226 | } 227 | 228 | static int _ipc_list_cb(const char* name, void* ud) { 229 | lua_State* L = ud; 230 | 231 | lua_pushstring(L, name); 232 | lua_seti(L, -2, luaL_len(L, -2)+1); 233 | 234 | return 0; 235 | } 236 | 237 | static int _ipc_list(lua_State* L) { 238 | lua_newtable(L); 239 | if (ipc_list(_ipc_list_cb, L) && errno != ENOENT) { 240 | luaL_error(L, "IPC list failed: %s", strerror(errno)); 241 | } 242 | return 1; 243 | } 244 | 245 | static const luaL_Reg funcs[] = { 246 | {"accept", _ipc_accept}, 247 | {"bind", _ipc_bind}, 248 | {"connect", _ipc_connect}, 249 | {"prepare", _ipc_prepare}, 250 | {"list", _ipc_list}, 251 | {NULL, NULL}, 252 | }; 253 | 254 | LUAMOD_API int luaopen_ipc(lua_State* L) { 255 | luaL_checkversion(L); 256 | luaL_newlib(L, funcs); 257 | return 1; 258 | } 259 | 260 | -------------------------------------------------------------------------------- /src/lo.lua: -------------------------------------------------------------------------------- 1 | -- Loopback Management 2 | -- 3 | -- Application network traffic going through the WireGuard tunnel might be 4 | -- directed to peers whose routes are not known yet, or be relayed through the 5 | -- DHT if no peer-to-peer communications were possible. 6 | -- 7 | -- Therefore, WireGuard traffic needs to be redirected to the WireHub daemon to 8 | -- be routed. To do so, unknown or relayed peers have their WireGuard endpoints 9 | -- directed to a loopback address (127.0.0.0/8), which are binded by WireHub. 10 | -- WireGuard traffic is received by WireHub, which takes the right action to 11 | -- redirect the traffic to the WireGuard instances of the destination peers. 12 | -- 13 | -- This module defines this loopback mechanism (called "tunnels") and redirect 14 | -- ingoing and outgoing traffic to/from WireGuard. It searches for peers whose 15 | -- route is unknown, or relay traffic if necessary 16 | 17 | local hosts = require('hosts') 18 | 19 | local TRY_AUTOCONNECT_EVERY_S = 60 20 | 21 | local MT = { 22 | __index = {} 23 | } 24 | 25 | local function _alloc(lo) 26 | -- XXX manages lo.cidr 27 | -- XXX slow 28 | for i = 1024, 65535 do 29 | local a = wh.set_address_port(lo.addr, i) 30 | if not lo.addr_ks[a:pack()] then 31 | return a 32 | end 33 | end 34 | 35 | error("no more free space for ports") 36 | return nil 37 | end 38 | 39 | function MT.__index.touch(lo, k) 40 | local a = lo.k_addrs[k] 41 | if not a then 42 | a = _alloc(lo, k) 43 | lo.k_addrs[k] = a 44 | lo.addr_ks[a:pack()] = k 45 | end 46 | assert(a) 47 | 48 | return a 49 | end 50 | 51 | function MT.__index.free(lo, k) 52 | local a = lo.k_addrs[k] 53 | if a then 54 | lo.addr_ks[a:pack()] = nil 55 | lo.k_addrs[k] = nil 56 | end 57 | end 58 | 59 | function MT.__index.update(lo, socks) 60 | local deadlines = {} 61 | local timeout 62 | lo.sniff_fd, timeout = wh.get_pcap(lo.sniff) 63 | 64 | socks[#socks+1] = lo.sniff_fd 65 | if timeout then 66 | deadlines[#deadlines+1] = now+timeout 67 | end 68 | 69 | for _, c in pairs(lo.connects) do 70 | if not c.ac_deadline then 71 | -- do nothing 72 | elseif c.ac_deadline > now then 73 | deadlines[#deadlines+1] = c.ac_deadline 74 | else 75 | lo.connects[c.k] = nil 76 | end 77 | end 78 | 79 | return min(deadlines) 80 | end 81 | 82 | function MT.__index.touch_tunnel(lo, p) 83 | if not p.tunnel then 84 | p.tunnel = { 85 | lo_addr = lo:touch(p.k) 86 | } 87 | end 88 | return p.tunnel 89 | end 90 | 91 | function MT.__index.free_tunnel(lo, p) 92 | if p.tunnel then 93 | lo:free(p.k) 94 | p.tunnel = nil 95 | end 96 | end 97 | 98 | local function on_connect(lo, c, dst, p2p) 99 | --dbg(dump(dst)) 100 | if dst then 101 | if p2p then 102 | printf("$(green)auto-connect to %s succeed$(reset)", lo.n:key(dst)) 103 | else 104 | printf("$(orange)cannot establish P2P connection with %s$(reset)", lo.n:key(dst)) 105 | 106 | assert(dst.tunnel) 107 | dst.tunnel.last_tx = now 108 | end 109 | 110 | for _, m in ipairs(c.pkt_buf or {}) do 111 | -- XXX 112 | lo.n:send_datagram(dst, m) 113 | end 114 | 115 | else 116 | printf("$(red)could not find %s$(reset)", lo.n:key(c.k)) 117 | end 118 | 119 | c.ac_deadline = now + TRY_AUTOCONNECT_EVERY_S 120 | end 121 | 122 | function MT.__index.on_readable(lo, r) 123 | while r[lo.sniff_fd] do 124 | local src_addr, dst_lo_addr, m = wh.pcap_next_udp(lo.sniff) 125 | 126 | if not m then 127 | break 128 | end 129 | 130 | local dst_k = lo.addr_ks[dst_lo_addr:pack()] 131 | 132 | if not dst_k then 133 | printf("$(red)error: unknown lo addr: %s$(reset)", dst_lo_addr) 134 | return 135 | end 136 | 137 | local dst = lo.n.kad:get(dst_k) 138 | if not dst then 139 | return 140 | end 141 | 142 | -- is peer set with a tunnel? if so, redirect the wireguard packet. 143 | -- else, try to connect while buffering the packets 144 | 145 | if lo.auto_connect then 146 | local c = lo.connects[dst.k] 147 | if not c then 148 | printf("$(green)auto-connecting to %s$(reset)", lo.n:key(dst)) 149 | c = lo.n:connect(dst.k, nil, function(...) 150 | return on_connect(lo, ...) 151 | end) 152 | lo.connects[dst.k] = c 153 | end 154 | 155 | if not c.pkt_buf then 156 | c.pkt_buf = {} 157 | end 158 | 159 | c.pkt_buf[#c.pkt_buf+1] = m 160 | if #c.pkt_buf > lo.buffer_max then 161 | table.remove(c.pkt_buf, 1) 162 | end 163 | end 164 | 165 | if dst.tunnel then 166 | dst.tunnel.last_tx = now 167 | local through_tunnel = lo.n:send_datagram(dst, m) 168 | 169 | if not through_tunnel then 170 | lo:free_tunnel(dst) 171 | end 172 | end 173 | end 174 | end 175 | 176 | function MT.__index.forget(lo, k) 177 | lo.connects[k] = nil 178 | end 179 | 180 | function MT.__index.recv_datagram(lo, src, m) 181 | lo:touch_tunnel(src) 182 | src.tunnel.last_rx = now 183 | 184 | printf("$(orange)receive datagram %dB$(reset)", #m) 185 | 186 | local ret, errmsg = wh.sendto_raw_wg(lo.sock, m, src.tunnel.lo_addr, lo.n.port) 187 | 188 | if not ret then 189 | printf('$(red)error: could not send packet: %s', errmsg) 190 | end 191 | end 192 | 193 | function MT.__index.close(lo) 194 | if lo.sniff then 195 | wh.close_pcap(lo.sniff) 196 | lo.sniff = nil 197 | end 198 | 199 | if lo.sniff_fd then 200 | wh.close(lo.sniff_fd) 201 | lo.sniff_fd = nil 202 | end 203 | 204 | if lo.sock then 205 | wh.close(lo.sock) 206 | lo.sock = nil 207 | end 208 | 209 | hosts.unregister(lo.n) 210 | end 211 | 212 | function MT.__index.refresh(lo) 213 | -- register /etc/hosts 214 | hosts.register(lo.n) 215 | end 216 | 217 | return function(lo) 218 | assert(lo.n) 219 | 220 | if lo.auto_connect == nil then 221 | lo.auto_connect = true 222 | end 223 | 224 | if not lo.cidr then 225 | lo.cidr = 32 226 | end 227 | 228 | if not lo.addr then 229 | -- XXX 230 | lo.addr = wh.address(string.format("127.%d.%d.%d", 231 | randomrange(1, 254), 232 | randomrange(1, 254), 233 | randomrange(1, 254) 234 | ), 0) 235 | end 236 | lo.subnet = lo.addr:addr() .. '/' .. tostring(lo.cidr) 237 | 238 | lo.k_addrs = {} 239 | lo.addr_ks = {} 240 | lo.tunnels = {} 241 | lo.connects = {} 242 | 243 | if lo.auto_connect then 244 | if not lo.buffer_max then 245 | lo.buffer_max = 1 246 | end 247 | end 248 | 249 | -- XXX lazy? 250 | lo.sniff = wh.sniff('any', 'in', 'wg', " and dst net " .. lo.subnet) 251 | lo.sock = wh.socket_raw_udp('ip4_hdrincl') 252 | 253 | return setmetatable(lo, MT) 254 | end 255 | 256 | -------------------------------------------------------------------------------- /src/core/whupnplib.c: -------------------------------------------------------------------------------- 1 | #include "luawh.h" 2 | 3 | #if WH_ENABLE_MINIUPNPC 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | static const char* mt = "device_igd"; 10 | 11 | struct device_igd { 12 | struct UPNPUrls urls; 13 | struct IGDdatas data; 14 | int type; 15 | }; 16 | 17 | static int _discover_igd(lua_State* L) { 18 | int delay_ms = luaL_checknumber(L, 1) * 1000; 19 | int upnp_err = 0; 20 | struct UPNPDev* devices = upnpDiscover(delay_ms, NULL, NULL, UPNP_LOCAL_PORT_ANY, 0, 2, &upnp_err); 21 | 22 | if (upnp_err != 0) { 23 | luaL_error(L, "upnpDiscover failed(): %d", upnp_err); 24 | } 25 | 26 | if (!devices) { 27 | return 0; 28 | } 29 | 30 | struct device_igd* d = lua_newuserdata(L, sizeof(struct device_igd)); 31 | luaL_newmetatable(L, mt); 32 | lua_setmetatable(L, -2); 33 | 34 | char lanaddr[64] = ""; 35 | int ret = UPNP_GetValidIGD(devices, &d->urls, &d->data, lanaddr, sizeof(lanaddr)); 36 | 37 | if (ret < 0) { 38 | luaL_error(L, "UPNP_GetValidIGD failed(): %d", ret); 39 | } 40 | 41 | if (ret == 0) { 42 | return 0; 43 | } 44 | 45 | d->type = ret; 46 | 47 | if (lanaddr[0]) { 48 | lua_pushstring(L, lanaddr); 49 | } else { 50 | lua_pushnil(L); 51 | } 52 | 53 | lua_pushstring(L, d->urls.controlURL); 54 | 55 | return 3; 56 | } 57 | 58 | static int _external_ip(lua_State* L) { 59 | struct device_igd* d = luaL_checkudata(L, 1, mt); 60 | char ip[40]; 61 | 62 | int r = UPNP_GetExternalIPAddress(d->urls.controlURL, d->data.first.servicetype, ip); 63 | 64 | if (r != UPNPCOMMAND_SUCCESS) { 65 | lua_pushboolean(L, 0); 66 | lua_pushfstring(L, "GetExternalIPAddress() failed: %d (%s)", 67 | r, strupnperror(r) 68 | ); 69 | return 2; 70 | } 71 | 72 | lua_pushboolean(L, 1); 73 | lua_pushstring(L, ip); 74 | return 2; 75 | } 76 | 77 | static int _list_redirects(lua_State* L) { 78 | struct device_igd* d = luaL_checkudata(L, 1, mt); 79 | 80 | lua_newtable(L); 81 | 82 | for (int i = 0; ; ++i) { 83 | char index[16]; 84 | snprintf(index, sizeof(index), "%d", i); 85 | 86 | char int_client[40] = ""; 87 | char int_port[16] = ""; 88 | char ext_port[16] = ""; 89 | char protocol[4] = ""; 90 | char desc[80] = ""; 91 | char enabled[16] = ""; 92 | char host[64] = ""; 93 | char duration[16] = ""; 94 | 95 | 96 | int ret = UPNP_GetGenericPortMappingEntry( 97 | d->urls.controlURL, d->data.first.servicetype, index, ext_port, 98 | int_client, int_port, protocol, desc, enabled, host, duration 99 | ); 100 | 101 | if (ret != 0) { 102 | break; 103 | } 104 | 105 | lua_newtable(L); 106 | 107 | #define lua_pushstringnumber(L, s) \ 108 | do { \ 109 | if (lua_stringtonumber(L, s) == 0) { \ 110 | lua_pushnil(L); \ 111 | } \ 112 | } while(0) 113 | 114 | for (char* c=protocol; *c; ++c) { 115 | *c = tolower(*c); 116 | } 117 | 118 | lua_pushstring(L, protocol); 119 | lua_setfield(L, -2, "protocol"); 120 | 121 | lua_pushstringnumber(L, ext_port); 122 | lua_setfield(L, -2, "eport"); 123 | 124 | lua_pushstring(L, int_client); 125 | lua_setfield(L, -2, "iaddr"); 126 | 127 | lua_pushstringnumber(L, int_port); 128 | lua_setfield(L, -2, "iport"); 129 | 130 | lua_pushstring(L, desc); 131 | lua_setfield(L, -2, "desc"); 132 | 133 | lua_pushstring(L, host); 134 | lua_setfield(L, -2, "host"); 135 | 136 | lua_pushstringnumber(L, duration); 137 | lua_setfield(L, -2, "lease"); 138 | 139 | #undef lua_pushstringnumber 140 | 141 | lua_seti(L, -2, i+1); 142 | } 143 | 144 | return 1; 145 | } 146 | 147 | static int _add_redirect(lua_State* L) { 148 | struct device_igd* d = luaL_checkudata(L, 1, mt); 149 | luaL_checktype(L, 2, LUA_TTABLE); 150 | 151 | lua_getfield(L, -1, "protocol"); 152 | const char* protocol_const = lua_tostring(L, -1); 153 | if (!protocol_const) { luaL_error(L, "field 'protocol' is nil"); } 154 | char protocol[8]; 155 | strncpy(protocol, protocol_const, sizeof(protocol)-1); 156 | for (char* c=protocol; *c; ++c) { *c = toupper(*c); } 157 | 158 | lua_pop(L, 1); 159 | 160 | lua_getfield(L, -1, "eport"); 161 | char ext_port[6]; 162 | snprintf(ext_port, sizeof(ext_port), "%d", luaW_checkport(L, -1)); 163 | lua_pop(L, 1); 164 | 165 | lua_getfield(L, -1, "iport"); 166 | char int_port[6]; 167 | snprintf(int_port, sizeof(int_port), "%d", luaW_checkport(L, -1)); 168 | lua_pop(L, 1); 169 | 170 | lua_getfield(L, -1, "iaddr"); 171 | const char* int_client = lua_tostring(L, -1); 172 | lua_pop(L, 1); 173 | 174 | lua_getfield(L, -1, "lease"); 175 | char lease[16]; 176 | snprintf(lease, sizeof(lease), "%lld", lua_tointeger(L, -1)); 177 | lua_pop(L, 1); 178 | 179 | lua_getfield(L, -1, "desc"); 180 | const char* desc = lua_tostring(L, -1); 181 | lua_pop(L, 1); 182 | 183 | if (!int_client) { luaL_error(L, "field 'iaddr', is nil"); } 184 | 185 | int ret = UPNP_AddPortMapping( 186 | d->urls.controlURL, d->data.first.servicetype, ext_port, int_port, 187 | int_client, desc, protocol, NULL, lease 188 | ); 189 | 190 | if (ret != UPNPCOMMAND_SUCCESS) { 191 | lua_pushboolean(L, 0); 192 | lua_pushfstring(L, "AddPortMapping(%s, %s, %s) failed: %d (%s)", 193 | ext_port, int_port, int_client, ret, strupnperror(ret) 194 | ); 195 | return 2; 196 | } 197 | 198 | lua_pushboolean(L, 1); 199 | return 1; 200 | } 201 | 202 | static int _remove_redirect(lua_State* L) { 203 | struct device_igd* d = luaL_checkudata(L, 1, mt); 204 | luaL_checktype(L, 2, LUA_TTABLE); 205 | 206 | lua_getfield(L, -1, "protocol"); 207 | const char* protocol_const = lua_tostring(L, -1); 208 | if (!protocol_const) { luaL_error(L, "field 'protocol' is nil"); } 209 | char protocol[8]; 210 | strncpy(protocol, protocol_const, sizeof(protocol)-1); 211 | for (char* c=protocol; *c; ++c) { *c = toupper(*c); } 212 | lua_pop(L, 1); 213 | 214 | lua_getfield(L, -1, "eport"); 215 | char ext_port[6]; 216 | snprintf(ext_port, sizeof(ext_port), "%d", luaW_checkport(L, -1)); 217 | lua_pop(L, 1); 218 | 219 | int ret = UPNP_DeletePortMapping( 220 | d->urls.controlURL, d->data.first.servicetype, ext_port, protocol, NULL 221 | ); 222 | 223 | if (ret != UPNPCOMMAND_SUCCESS) { 224 | lua_pushboolean(L, 0); 225 | lua_pushfstring(L, "DeletePortMapping(%s, %s) failed: %d (%s)", 226 | ext_port, protocol, ret, strupnperror(ret) 227 | ); 228 | return 2; 229 | } 230 | 231 | lua_pushboolean(L, 1); 232 | return 1; 233 | } 234 | 235 | static const luaL_Reg funcs[] = { 236 | { "discover_igd", _discover_igd }, 237 | { "external_ip", _external_ip }, 238 | { "list_redirects", _list_redirects }, 239 | { "add_redirect", _add_redirect }, 240 | { "remove_redirect", _remove_redirect }, 241 | { NULL, NULL} 242 | }; 243 | 244 | LUAMOD_API int luaopen_upnp(lua_State* L) { 245 | luaL_checkversion(L); 246 | 247 | luaL_newlib(L, funcs); 248 | 249 | return 1; 250 | } 251 | 252 | #endif // WH_ENABLE_MINIUPNPC 253 | 254 | -------------------------------------------------------------------------------- /src/tools/up.lua: -------------------------------------------------------------------------------- 1 | -- Main WireHub server daemon 2 | -- 3 | -- This file initializes and runs a WireHub peer. The overall structure of the 4 | -- file is the following: 5 | -- 6 | -- Parse CLI arguments 7 | -- Read the private network configuration 8 | -- Check WireGuard interface, if any 9 | -- Initialize a WireHub node (wh.new()) 10 | -- Initialize WireHub IPC server 11 | -- Configure WireHub with peers (trusted, bootstrap, ...) 12 | -- Initialize loopback manager 13 | -- Initialize WireGuard <-> WireHub synchronization manager 14 | -- 15 | -- while running do -- main loop 16 | -- list file descriptors to wait for and ... 17 | -- ... calculate next deadline for the I/O event poller 18 | -- 19 | -- wait for I/O events 20 | -- 21 | -- ask managers to read readable file descriptors 22 | -- end 23 | -- 24 | -- De-initialize wgsync 25 | -- De-initialize loopback manager 26 | -- De-initialize WireHub node 27 | -- Exit 28 | 29 | local cmd = arg[1] 30 | 31 | function help() 32 | printf( 33 | "Usage: wh up [private-key ] [interface ] [listen-port ] [mode {unknown | direct | nat}]\n" .. 34 | "\n" .. 35 | "If the argument 'private-key' is not set, one ephemeron key will be generated\n" .. 36 | "for the session, and destroyed when the daemon stops.\n" .. 37 | "\n" .. 38 | "If a WireGuard 'interface' is not set, it will be be default the concatenation\n" .. 39 | "of 'wh-' and the 8 first characters of the base64 form of public key.\n" .. 40 | "\n" .. 41 | "If 'listen-port' is not set, it will be by default 0. If 'listen-port is 0,\n" .. 42 | "WireHub will pick a random listen port between 1024 and 65535.\n" .. 43 | "\n" .. 44 | "Example:\n" .. 45 | " Starts an ephemeron peer for network 'public'\n" .. 46 | " wh up public\n" .. 47 | "\n" .. 48 | " Starts a peer for network 'foo'\n" .. 49 | " wh up foo private-key /my/keys/foo.sk\n" .. 50 | "" 51 | ) 52 | end 53 | 54 | -- XXX move this after configuration is checked 55 | do 56 | local FG = os.getenv('FG') 57 | local FG = FG == 'y' or FG == '1' 58 | if not FG then 59 | wh.daemon() 60 | end 61 | end 62 | 63 | do 64 | local log_path = os.getenv('WH_LOGPATH') 65 | if log_path then 66 | local log_fh = io.open(log_path, "a") 67 | 68 | function LOG(s) 69 | log_fh:write(s .. '\n') 70 | log_fh:flush() 71 | end 72 | 73 | atexit(log_fh.close, log_fh) 74 | end 75 | end 76 | -- XXX ---------------------------------------- 77 | 78 | local conf 79 | do 80 | local err 81 | conf, err = openconf(arg[2]) 82 | if not conf then 83 | printf("error: %s", err) 84 | return help() 85 | end 86 | end 87 | 88 | local opts = parsearg(3, { 89 | interface = tostring, 90 | ["private-key"] = wh.readsk, 91 | ["listen-port"] = tonumber, 92 | mode = function(s) 93 | if s ~= 'unknown' and 94 | s ~= 'direct' and 95 | s ~= 'nat' then 96 | s = nil 97 | end 98 | return s 99 | end, 100 | }) 101 | 102 | if not opts then 103 | return help() 104 | end 105 | 106 | -- now is a global 107 | now = wh.now() 108 | local start_time = now 109 | 110 | status("starting...") 111 | 112 | -- if no private key is set, generate an ephemeron one 113 | if not opts['private-key'] then 114 | local workbit = conf.workbit or 0 115 | 116 | printf('no key specified. generates an ephemeron one (ns: %q, workbit: %d). this might be long...', 117 | conf.namespace, workbit) 118 | 119 | _, _, opts['private-key'], _ = wh.genkey(conf.namespace, workbit, 0) 120 | end 121 | assert(opts['private-key']) 122 | 123 | 124 | -- if the interface is not set, set a default one, which depends on the (public) 125 | -- key. 126 | if not opts.interface then 127 | local k = wh.publickey(opts['private-key']) 128 | opts.interface = 'wh-' .. string.sub(wh.tob64(k), 1, 8) 129 | end 130 | 131 | -- if no listen port is set, takes the default 132 | if not opts['listen-port'] then 133 | opts['listen-port'] = 0 134 | end 135 | 136 | -- if listen port is set to 0, pick one randomly between 1024 and 65535 137 | if opts['listen-port'] == 0 then 138 | opts['listen-port'] = randomrange(1024, 65535) 139 | end 140 | 141 | -- create node 142 | local n = wh.new{ 143 | name=name, 144 | sk=opts['private-key'], 145 | port=opts['listen-port'], 146 | port_echo=opts['listen-port']+1, -- XXX ? 147 | namespace=conf.namespace, 148 | workbit=conf.workbit, 149 | mode=opts.mode, 150 | log=tonumber(os.getenv('LOG')), 151 | ns={ 152 | require('ns_keybase'), 153 | }, 154 | confpath=conf.path, 155 | } 156 | 157 | atexit(n.close, n) 158 | 159 | if wh.WIREGUARD_ENABLED then 160 | n.lo = require('lo'){ 161 | n = n, 162 | auto_connect = true, 163 | } 164 | 165 | n.wgsync = require('wgsync').new{ 166 | n = n, 167 | interface = opts.interface, 168 | } 169 | end 170 | 171 | -- 172 | 173 | local ipc_conn 174 | local handlers = require('handlers_ipc')(n) 175 | ipc_conn = require('ipc').bind(opts.interface or wh.tob64(n.k), handlers) 176 | atexit(ipc_conn.close, ipc_conn) 177 | 178 | -- log 179 | 180 | do 181 | local msg = {"wirehub listening as $(yellow)", wh.tob64(n.k), "$(reset)"} 182 | if DEVICE then msg[#msg+1] = string.format(" for $(yellow)%s$(reset)", DEVICE) end 183 | msg[#msg+1] = string.format(" on $(yellow)%d$(reset) (port echo %d)", n.port, n.port_echo) 184 | msg[#msg+1] = string.format(" (mode: $(yellow)%s$(reset))", n.mode) 185 | printf(table.concat(msg)) 186 | end 187 | 188 | -- Load peers from configuration 189 | local ok, err = n:reload(conf) 190 | 191 | if not ok then 192 | print("Configuration incorrect: %s", err) 193 | return -1 194 | end 195 | 196 | local LOADING_CHARS = {'-', '\\', '|', '/'} 197 | local LOADING_CHARS = {'▄▄', '█▄', '█ ', '█▀', '▀▀', '▀█', ' █', '▄█'} 198 | local lc_idx = 1 199 | 200 | -- main loop 201 | now = wh.now() 202 | while n.running do 203 | local socks = {} 204 | local timeout 205 | 206 | -- update file descriptors to poll and next deadlines 207 | do 208 | local deadlines = {} 209 | 210 | if ipc_conn then 211 | deadlines[#deadlines+1] = ipc_conn:update(socks) 212 | end 213 | 214 | deadlines[#deadlines+1] = n:update(socks) 215 | 216 | -- 217 | 218 | local deadline = min(deadlines) 219 | 220 | if deadline ~= nil then 221 | timeout = deadline-now 222 | if timeout < 0 then timeout = 0 end 223 | end 224 | end 225 | 226 | status( 227 | '%s waiting (fds: %d, timeout: %s)', 228 | LOADING_CHARS[lc_idx], 229 | #socks, 230 | timeout and string.format('%.1fs', timeout) or '(none)' 231 | ) 232 | 233 | n.kad:clear_touched() 234 | 235 | -- I/O event poller 236 | local r 237 | do 238 | -- Not sure why, but one pcall is not enough to catch the "interrupted" 239 | -- launched by lua if the user press CTRL+C 240 | pcall(pcall, function() r = wh.select(socks, {}, {}, timeout) end) 241 | if not r then break end 242 | now = wh.now() 243 | end 244 | 245 | do 246 | lc_idx = (lc_idx % (#LOADING_CHARS)) + 1 247 | status('%s', LOADING_CHARS[lc_idx]) 248 | end 249 | 250 | -- notify something needs to be read 251 | do 252 | n:on_readable(r) 253 | 254 | if ipc_conn then 255 | ipc_conn:on_readable(r) 256 | end 257 | end 258 | end 259 | 260 | status('exiting...') 261 | -------------------------------------------------------------------------------- /src/wh.lua: -------------------------------------------------------------------------------- 1 | -- Entry-point of the WireHub engine. 2 | -- 3 | -- To import WireHub in Lua, do: 4 | -- 5 | -- require('helpers') 6 | -- require('wh') 7 | -- 8 | -- If you're looking for the main loop of a WireHub peer, please refer to 9 | -- `src/tools/up.lua`. 10 | -- 11 | -- # Overall structure 12 | -- 13 | -- * auth.lua: Peer authentication mechanism (alias) 14 | -- * bwlog.lua: BandWidth LOG code. Used by nodes to log their bandwidth. 15 | -- Optional. 16 | -- * conf.lua: WireHub configuration reader/writer. 17 | -- * connectivity.lua: Connectivity manager. Checks the network topology where 18 | -- a WireHub peer is (e.g. behind a NAT, what type, ...) and 19 | -- manages UPnP IGD if present. 20 | -- * handlers.lua: WireHub protocol packet handlers. 21 | -- * handlers_ipc.lua: WireHub IPC handlers. See ipc.lua 22 | -- * helpers.lua: Helpers for WireHub (e.g. printing, math, code security, ...) 23 | -- * ipc.lua: WireHub IPC manager. Used by the WireHub CLI tool. 24 | -- * kad.lua: Peer maintenance (e.g. check if alive, remove offline peers, ...) 25 | -- * kadstore.lua: Kademilia store object. 26 | -- * key.lua: Helpers to manipulate peer's keys. 27 | -- * lo.lua: Loopback manager. Used to detect application traffic going through 28 | -- a WireGuard tunnel and automatically take action to send traffic to 29 | -- WireHub peers. 30 | -- * nat.lua: NAT discovery mechanism. Think a very light version of STUN. 31 | -- * node.lua: WireHub node logic. Entry-point. 32 | -- * ns_keybase.lua: Name resolver using Keybase. Optional. 33 | -- * packet.lua: Defines WireHub protocol packets. 34 | -- * peer.lua: Define peer's methods. 35 | -- * queue.lua: FIFO queue implementation 36 | -- * search.lua: Peer DHT searching logic 37 | -- * sink-udp.lua: binds and receives UDP packets and discard them. 38 | -- * time.lua: Time helpers 39 | -- * wgsync.lua: WireGuard <-> WireHub data synchronization 40 | -- * wh.lua: this file. entry-point. 41 | -- 42 | -- # Native code 43 | -- 44 | -- Lua native code is stored in src/core/. The native module is 'whcore' and 45 | -- defined in src/core/whcorelib.c. Current module 'wh' inherits from 'whcore' 46 | -- and extends with Lua methods. 47 | -- 48 | -- # Nomenclature 49 | -- 50 | -- Short variable names are chosen to make the code more compact: 51 | -- 52 | -- * M: Module 53 | -- * MT: lua Meta-Table 54 | -- * a: Authentication session. See 'n.authenticate()' and 'auth.lua' 55 | -- * d: nat Detecting session. See 'n.detect_nat()' and 'nat.lua' 56 | -- * k: public Key of a peer, in its binary form 57 | -- * lo: LOopback manager. See 'lo.lua' 58 | -- * m: Message. Content of a received packet. 59 | -- * me: Message Encrypted. Content of a received packet, but still encrypted. 60 | -- * n: wirehub Node. See 'node.lua' 61 | -- * now: Current timestamp. Global. Updated after every return of the polling 62 | -- function (wh.select). 63 | -- * ns: Name reSolver. See 'ns_*.lua'. 64 | -- * p: Peer table, contains all information regarding a peer. See 'peer.lua'. 65 | -- * s: Search session. See 'n.search()' and 'search.lua' 66 | -- * sk: Secret Key (private key) of a peer, stored as a `secret` pointer 67 | -- * t: generic Table 68 | -- 69 | -- By default, a key is public (a key <=> a public key). A secret key <=> a 70 | -- private key 71 | 72 | -- wh is a global 73 | assert(wh == nil) 74 | _G['wh'] = require('whcore') 75 | 76 | -- check version 77 | local VERSION = {0, 1, 0} 78 | do 79 | local major, minor, revision = wh.version() 80 | 81 | if major ~= VERSION[1] or minor ~= VERSION[2] or revision ~= VERSION[3] then 82 | error(string.format("version mismatch: version is %d.%d.%d, core's is %d.%d.%d", 83 | major, minor, revision, 84 | VERSION[1], VERSION[2], VERSION[3] 85 | )) 86 | end 87 | 88 | wh.version = setmetatable(VERSION, { 89 | __tostring = function(v) 90 | return string.format('%d.%d.%d', table.unpack(VERSION)) 91 | end 92 | }) 93 | end 94 | 95 | do -- constants 96 | local constants = { 97 | -- Seconds. Minimum interval between checking if peers are alive. If one 98 | -- peer appears to be alive, it will not be checked during this amount 99 | -- of seconds) 100 | ALIVE_INTERVAL = 1 * 60, 101 | 102 | -- Authentication retry before failure. 103 | AUTH_RETRY = 4, 104 | 105 | -- Seconds. Interval to check connectivity. 106 | CONNECTIVITY_CHECK_EVERY = 5*60, 107 | 108 | -- Default WireHub key namespace 109 | DEFAULT_NAMESPACE = 'public', 110 | 111 | -- Default WireHub (and underlying WireGuard) port 112 | DEFAULT_PORT = 62096, 113 | 114 | -- Default workbit 115 | DEFAULT_WORKBIT = 8, 116 | 117 | -- True to modify /etc/hosts with WireHub trusted peers 118 | EXPERIMENTAL_MODIFY_HOSTS = false, 119 | 120 | -- Count of fragments to temporarily store while WireHub is trying to 121 | -- connect to a peer. 122 | FRAGMENT_MAX = 4, 123 | 124 | -- Bytes. WireHub fragment MTU. 125 | FRAGMENT_MTU = 1024, -- XXX 126 | 127 | -- Seconds. Timeout when to discard a fragment packet. 128 | FRAGMENT_TIMEOUT = 4, 129 | 130 | -- Ideal amount of peers to store in one Kademilia bucket (see Kademilia 131 | -- paper: http://www.scs.stanford.edu/%7Edm/home/papers/kpos.pdf) 132 | KADEMILIA_K = 20, 133 | 134 | -- Seconds. Keep-alive for direct peers timeout. 135 | KEEPALIVE_DIRECT_TIMEOUT = 5 * 60, 136 | 137 | -- Seconds. Keep-alive timeout for NAT-ed peers. Should be less than NAT timeout. 138 | KEEPALIVE_NAT_TIMEOUT = 25, 139 | 140 | -- Boolean. True if a WireGuard tunnel should be instantiated when IP 141 | -- traffic may be routed. If false, the WireHub peer will never share IP 142 | -- traffic, and will just be a "headless" part of the network. 143 | WIREGUARD_ENABLED = true, 144 | 145 | -- Maximum tentative of UDP hole punching before failure. 146 | MAX_PUNCH_RETRY = 10, 147 | 148 | -- Seconds. Time to wait between each UDP hole punching tentative 149 | MAX_PUNCH_TIMEOUT = .5, 150 | 151 | -- Seconds. NAT timeout. 152 | NAT_TIMEOUT = 25, 153 | 154 | -- Seconds. Amount of seconds to wait after each failed ping. 155 | PING_BACKOFF = .5, 156 | 157 | -- Maximum tentative of PING before stating peer is offline. 158 | PING_RETRY = 4, 159 | 160 | -- Maximum count of peers to keep while searching for a node. 161 | SEARCH_COUNT = 20, 162 | 163 | -- Seconds. Default peer searching timeout before search is stopped. 164 | SEARCH_TIMEOUT = 5, 165 | 166 | -- Seconds. Interval to refresh UPnP IGD router with port mapping. 167 | UPNP_REFRESH_EVERY = 10*60, 168 | } 169 | 170 | local env_prefix = 'WH_' 171 | for k, v in pairs(constants) do 172 | local env_v = os.getenv(env_prefix .. k) 173 | 174 | if env_v and env_v ~= '' then 175 | if env_v == 'true' then 176 | v = true 177 | elseif env_v == 'false' then 178 | v = false 179 | elseif tonumber(env_v) then 180 | v = tonumber(env_v) 181 | else 182 | error(string.format("env var %s is not true, false or a number", env_prefix .. k)) 183 | end 184 | end 185 | 186 | wh[k] = v 187 | end 188 | end 189 | 190 | -- sanity check (see n.send_datagram()) 191 | assert(wh.FRAGMENT_MTU >= 1024, "65536/MTU <= 64") 192 | 193 | -- additional extensions 194 | require('key') -- add method wh.key 195 | require('conf') -- add wh.fromconf & wh.toconf 196 | wh.new = require('node').new 197 | 198 | -- initialize time global 199 | _G['now'] = 0 200 | -------------------------------------------------------------------------------- /src/wgsync.lua: -------------------------------------------------------------------------------- 1 | -- WireGuard <-> WireHub synchronization 2 | -- 3 | -- Configure WireGuard with latest WireHub metadata about peers. Set keys, 4 | -- endpoints, persistent keep-alive, ... 5 | -- 6 | -- Update the last time each peer was seen by WireGuard, to have a unified view 7 | -- for WireHub and WireGuard. 8 | 9 | local REFRESH_EVERY = wh.NAT_TIMEOUT / 2 10 | 11 | local M = {} 12 | 13 | local MT = { 14 | __index = {} 15 | } 16 | 17 | local function explain(sy, fmt, ...) 18 | return sy.n:explain('wgsync', fmt, ...) 19 | end 20 | 21 | local function set_peers(sy, wg_peers) 22 | local wg_peers_remove = {} 23 | for _, wg_p in ipairs(wg_peers) do 24 | local action 25 | if wg_p.replace_me then 26 | action = "replace" 27 | wg_p.replace_me = nil 28 | wg_peers_remove[#wg_peers_remove+1] = {public_key=wg_p.public_key, remove_me=true} 29 | 30 | elseif wg_p.remove_me then 31 | action = "remove" 32 | else 33 | action = "upsert" 34 | end 35 | explain(sy, "%s peer %s", action, wh.tob64(wg_p.public_key, 'wg')) 36 | end 37 | 38 | wh.wg.set{name=sy.interface, peers=wg_peers_remove} 39 | wh.wg.set{name=sy.interface, peers=wg_peers} 40 | end 41 | 42 | -- Remove all WireGuard peers 43 | local function remove_all_peers(sy) 44 | local peers = {} 45 | 46 | local wg = wh.wg.get(sy.interface) 47 | for _, wg_p in ipairs(wg.peers) do 48 | peers[#peers+1] = { 49 | public_key = wg_p.public_key, 50 | remove_me = true, 51 | } 52 | end 53 | 54 | set_peers(sy, peers) 55 | end 56 | 57 | local function update_peer(sy, k, p, wg_peers) 58 | local n = sy.n 59 | 60 | -- ignore self 61 | if k == n.p.k then 62 | return 63 | end 64 | 65 | -- ignore alias 66 | if p.alias then 67 | return 68 | end 69 | 70 | if p and not p.trust then 71 | return 72 | end 73 | 74 | local wg_peer 75 | 76 | -- if peer is connected, remove loopback tunnel 77 | if p and p.addr and not p.relay and n.lo and p.tunnel then 78 | n.lo:free_tunnel(p) 79 | assert(not p.tunnel) 80 | end 81 | 82 | if p and p.trust and p.ip then 83 | wg_peer = { 84 | public_key = p.k, 85 | replace_allowedips=true, 86 | allowedips={}, 87 | } 88 | 89 | if p.ip then 90 | -- XXX check subnet 91 | -- XXX IPv6 Orchid 92 | 93 | wg_peer.allowedips[#wg_peer.allowedips+1] = {p.ip, 32} 94 | end 95 | 96 | p.endpoint = nil 97 | if n.p.ip then 98 | if p.tunnel then 99 | p.endpoint = 'lo' 100 | elseif p.addr and not p.relay then 101 | p.endpoint = p.addr 102 | elseif n.lo then 103 | n.lo:touch_tunnel(p) 104 | p.endpoint = 'lo' 105 | else 106 | p.endpoint = nil 107 | end 108 | end 109 | 110 | if p.endpoint ~= p._old_endpoint then 111 | if p.endpoint == 'lo' then 112 | wg_peer.endpoint = p.tunnel.lo_addr 113 | elseif p.endpoint then 114 | wg_peer.endpoint = p.endpoint 115 | else 116 | wg_peer.replace_me = true 117 | end 118 | 119 | p._old_endpoint = p.endpoint 120 | end 121 | 122 | if p.endpoint ~= nil and p.endpoint ~= 'lo' and p.is_nated then 123 | wg_peer.persistent_keepalive_interval = wh.NAT_TIMEOUT 124 | else 125 | wg_peer.persistent_keepalive_interval = 0 126 | end 127 | 128 | elseif not p then 129 | wg_peer = { 130 | public_key = p.k, 131 | remove_me = true, 132 | } 133 | end 134 | 135 | if wg_peer then 136 | wg_peers[#wg_peers+1] = wg_peer 137 | end 138 | end 139 | 140 | local function update_touched_peers(sy) 141 | local wg_peers_update = {} 142 | for k, p in pairs(sy.n.kad.touched) do 143 | update_peer(sy, k, p, wg_peers_update) 144 | end 145 | set_peers(sy, wg_peers_update) 146 | end 147 | 148 | function MT.__index.update(sy, socks) 149 | -- if wireguard is disabled, do nothing 150 | if not sy.wg_enabled then 151 | return 152 | end 153 | 154 | local deadlines = {} 155 | 156 | -- read WireGuard tunnel peer's activity and update p.last_seen 157 | local deadline = (sy.last_sync or 0) + REFRESH_EVERY 158 | if deadline <= now then 159 | local wg = wh.wg.get(sy.interface) 160 | for _, wg_p in pairs(wg.peers) do 161 | local k = wg_p.public_key 162 | local p = sy.n.kad:get(k) 163 | 164 | if p then 165 | p.wg_connected = wg_p.last_handshake_time > 0 166 | 167 | if (p.last_seen or 0) < wg_p.last_handshake_time then 168 | p.last_seen = wg_p.last_handshake_time 169 | end 170 | 171 | if wg_p.rx_bytes ~= (sy.p_rx[k] or 0) then 172 | p.last_seen = now 173 | sy.p_rx[k] = wg_p.rx_bytes 174 | end 175 | end 176 | end 177 | 178 | sy.last_sync = now 179 | deadline = (sy.last_sync or 0) + REFRESH_EVERY 180 | end 181 | deadlines[#deadlines+1] = deadline 182 | 183 | -- update WireGuard with touched peers 184 | update_touched_peers(sy) 185 | 186 | -- if peer's private IP changed, update 187 | if sy.n.p.ip ~= sy.ip then 188 | sy.ip = sy.n.p.ip 189 | 190 | if sy.ip then 191 | explain(sy, "private IP is %s/%d", sy.ip, sy.n.subnet.cidr) 192 | wh.wg.set_addr(sy.interface, sy.ip, sy.n.subnet.cidr) 193 | else 194 | wh.wg.set_addr(sy.interface) 195 | end 196 | end 197 | 198 | return min(deadlines) 199 | end 200 | 201 | function MT.__index.refresh(sy) 202 | local new_wg_enabled = sy.n.subnet and sy.n.p.ip and true 203 | 204 | -- if wg has been enabled 205 | if not sy.wg_enabled and new_wg_enabled then 206 | 207 | -- remove if already exists 208 | if wh.wg.get(sy.interface) then 209 | wh.wg.delete(sy.interface) 210 | end 211 | 212 | explain(sy, "enable WireGuard interface %s", sy.interface) 213 | wh.wg.add(sy.interface) 214 | wh.wg.set{name=sy.interface, listen_port=sy.n.port, private_key=sy.n.sk} 215 | wh.wg.set_link(sy.interface, true) 216 | 217 | -- if wg has been disabled 218 | elseif sy.wg_enabled and not new_wg_enabled then 219 | explain(sy, "disable WireGuard interface %s", sy.interface) 220 | wh.wg.delete(sy.interface) 221 | sy.ip = nil 222 | end 223 | 224 | sy.wg_enabled = new_wg_enabled 225 | 226 | -- if enabled, remove all non trusted peers 227 | if sy.wg_enabled then 228 | local wg = wh.wg.get(sy.interface) 229 | local wg_peers = {} 230 | for _, wg_p in ipairs(wg.peers) do 231 | wg_peers[wg_p.public_key] = wg_p 232 | end 233 | 234 | -- if a peer has a WireGuard tunnel but is not trusted (anymore), remove 235 | do 236 | local wg_peers_remove = {} 237 | for bid, bucket in pairs(sy.n.kad.buckets) do 238 | for i, p in ipairs(bucket) do 239 | if wg_peers[p.k] and not p.trust then 240 | wg_peers_remove[#wg_peers_remove+1] = { 241 | public_key = p.k, 242 | remove_me = true, 243 | } 244 | end 245 | end 246 | end 247 | set_peers(sy, wg_peers_remove) 248 | end 249 | 250 | update_touched_peers(sy) 251 | end 252 | end 253 | 254 | function MT.__index.close(sy) 255 | if sy.wg_enabled then 256 | wh.wg.delete(sy.interface) 257 | end 258 | end 259 | 260 | function M.new(sy) 261 | assert(sy.n and sy.interface) 262 | sy.p_rx = {} 263 | sy.wg_enabled = false 264 | return setmetatable(sy, MT) 265 | end 266 | 267 | return M 268 | 269 | -------------------------------------------------------------------------------- /src/core/net.c: -------------------------------------------------------------------------------- 1 | #include "net.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int parse_address(struct address* a, const char* endpoint, uint16_t port, int numeric) { 9 | struct addrinfo hint, *res = NULL; 10 | int ret; 11 | 12 | if (!endpoint) { 13 | return -1; 14 | } 15 | 16 | const char* addr_s = endpoint,* addr_end = NULL; 17 | // ip6? 18 | if (addr_s[0] == '[') { 19 | ++addr_s; 20 | const char* e = strchr(addr_s, ']'); 21 | if (!e) { 22 | return -1; 23 | } 24 | 25 | addr_end = e; 26 | } 27 | 28 | // ip4? 29 | else if ('0' <= addr_s[0] && addr_s[0] <= '9') { 30 | addr_end = strchr(addr_s, ':'); 31 | if (!addr_end) addr_end = addr_s + strlen(addr_s); 32 | } 33 | 34 | else { 35 | addr_end = strchr(addr_s, ':'); 36 | if (!addr_end) addr_end = addr_s + strlen(addr_s); 37 | } 38 | 39 | const char* port_s = strrchr(endpoint, ':'); 40 | // ignore : from the ip6 string 41 | if (port_s && port_s < addr_end) { 42 | port_s = NULL; 43 | } 44 | 45 | if (port_s) { 46 | int port_i = atoi(port_s+1); 47 | 48 | if (port_i < 0 || UINT16_MAX < port_i) { 49 | return -1; 50 | } 51 | 52 | port = (uint16_t)port_i; 53 | } 54 | 55 | memset(&hint, '\0', sizeof hint); 56 | 57 | hint.ai_family = PF_UNSPEC; 58 | hint.ai_socktype = SOCK_DGRAM; 59 | hint.ai_flags = AI_PASSIVE; // XXX why? 60 | 61 | if (numeric) { 62 | hint.ai_flags |= AI_NUMERICHOST; 63 | } 64 | 65 | char* addr = alloca(addr_end-addr_s+1); 66 | memcpy(addr, addr_s, addr_end-addr_s); 67 | addr[addr_end-addr_s] = 0; 68 | 69 | if ((ret = getaddrinfo(addr, NULL, &hint, &res))) { 70 | // more info gai_strerror(ret) 71 | return -1; 72 | } 73 | 74 | a->sa_family = res->ai_family; 75 | 76 | if(res->ai_family == AF_INET) { 77 | a->in4 = *(struct sockaddr_in*)res->ai_addr; 78 | a->in4.sin_port = htons(port); 79 | } 80 | 81 | else if (res->ai_family == AF_INET6) { 82 | a->in6 = *(struct sockaddr_in6*)res->ai_addr; 83 | a->in6.sin6_port = htons(port); 84 | } 85 | 86 | else { 87 | // "unknown address format %d\n",argv[1],res->ai_family); 88 | return -1; 89 | } 90 | 91 | freeaddrinfo(res); 92 | return 0; 93 | } 94 | 95 | socklen_t address_len(const struct address* a) { 96 | switch (a->sa_family) { 97 | case AF_INET: return sizeof(struct sockaddr_in); 98 | case AF_INET6: return sizeof(struct sockaddr_in6); 99 | default: return 0; 100 | }; 101 | } 102 | 103 | const char* format_address(const struct address* a, char* s, size_t sl) { 104 | assert(s); 105 | assert(a); 106 | 107 | // s needs to be at least 47 108 | // example: [e0be:b85d:88ed:6c3b:a1aa:3f57:ab3:c850]:65535 109 | 110 | socklen_t inl = address_len(a); 111 | if (inl == 0) { 112 | return NULL; 113 | } 114 | 115 | switch (a->sa_family) { 116 | case AF_INET: 117 | if (!inet_ntop(a->sa_family, &a->in4.sin_addr, s, sl-1)) { 118 | return NULL; 119 | } 120 | break; 121 | 122 | case AF_INET6: 123 | s[0] = '['; 124 | if (!inet_ntop(a->sa_family, &a->in6.sin6_addr, s+1, sl-2)) { 125 | return NULL; 126 | } 127 | strcat(s, "]"); 128 | break; 129 | default: 130 | return NULL; 131 | }; 132 | 133 | char buf[8]; 134 | snprintf(buf, sizeof(buf), ":%d", address_port(a)); 135 | strncat(s, buf, sl); 136 | 137 | return s; 138 | } 139 | 140 | int address_from_sockaddr(struct address* out, const struct sockaddr* in) { 141 | switch (*(sa_family_t*)in) { 142 | case AF_INET: 143 | out->sa_family = AF_INET; 144 | out->in4 = *(struct sockaddr_in*)in; 145 | break; 146 | 147 | case AF_INET6: 148 | out->sa_family = AF_INET6; 149 | out->in6 = *(struct sockaddr_in6*)in; 150 | break; 151 | 152 | default: 153 | return -1; 154 | }; 155 | 156 | return 0; 157 | } 158 | 159 | 160 | 161 | 162 | int socket_udp(const struct address* a) { 163 | int s = socket(a->sa_family, SOCK_DGRAM, 0); 164 | if (s == -1) { 165 | return -1; 166 | } 167 | 168 | if (fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK) == -1) { 169 | close(s); 170 | return -1; 171 | } 172 | 173 | if (bind(s, &a->in, address_len(a)) == -1) { 174 | close(s); 175 | return -1; 176 | } 177 | 178 | return s; 179 | } 180 | 181 | int socket_raw_udp(sa_family_t sa_family, int hdrincl) { 182 | int s = socket(sa_family, SOCK_RAW, IPPROTO_UDP); 183 | if (s == -1) { 184 | return -1; 185 | } 186 | 187 | /*if (fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK) == -1) { 188 | close(s); 189 | return -1; 190 | }*/ 191 | 192 | if (hdrincl) { 193 | int on = 1; 194 | if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) { 195 | close(s); 196 | return -1; 197 | } 198 | } 199 | 200 | return s; 201 | } 202 | 203 | int ip4_to_udp(const void* d, const void** pdata, size_t* psize, struct address* src, struct address* dst) { 204 | assert(d && psize); 205 | // src and dst may be NULL 206 | 207 | const void* p = d; 208 | 209 | if (*psize < 1) { 210 | return -1; 211 | } 212 | 213 | #define IPHDR ((struct ip*)(p)) 214 | size_t ip_hdr_sz = IPHDR->ip_hl*sizeof(uint32_t); 215 | if (*psize < ip_hdr_sz) { 216 | return -1; 217 | } 218 | 219 | if (IPHDR->ip_p != IPPROTO_UDP) { 220 | return -1; 221 | } 222 | 223 | if (src) { 224 | src->sa_family = src->in4.sin_family = AF_INET; 225 | memcpy(&src->in4.sin_addr, &IPHDR->ip_src, 4); 226 | } 227 | 228 | if (dst) { 229 | dst->sa_family = dst->in4.sin_family = AF_INET; 230 | memcpy(&dst->in4.sin_addr, &IPHDR->ip_dst, 4); 231 | } 232 | 233 | #undef IPHDR 234 | 235 | p += ip_hdr_sz; 236 | 237 | // XXX do IP6 238 | const int udp_hdr_sz = 8; 239 | 240 | if (*psize < ip_hdr_sz+udp_hdr_sz) { 241 | return -1; 242 | } 243 | 244 | #define UDPHDR ((struct udphdr*)(p)) 245 | 246 | // XXX should check checksum? 247 | 248 | if (src) { 249 | switch (src->sa_family) { 250 | case AF_INET: src->in4.sin_port = UDPHDR->uh_sport; break; 251 | case AF_INET6: src->in6.sin6_port = UDPHDR->uh_sport; break; 252 | }; 253 | } 254 | 255 | if (dst) { 256 | switch(dst->sa_family) { 257 | case AF_INET: dst->in4.sin_port = UDPHDR->uh_dport; break; 258 | case AF_INET6: dst->in6.sin6_port = UDPHDR->uh_dport; break; 259 | }; 260 | } 261 | 262 | 263 | uint16_t udp_sz = ntohs(UDPHDR->uh_ulen); 264 | if (udp_sz < udp_hdr_sz) { 265 | return -1; 266 | } 267 | 268 | if (*psize < ip_hdr_sz+udp_sz) { 269 | fprintf(stderr, "WARNING: *psize:%d, ip_hdr_sz:%d udp_sz:%d\n", 270 | (int)*psize, (int)ip_hdr_sz, (int)udp_sz 271 | ); 272 | 273 | FILE* fh = fopen("/tmp/packet.buf", "wb"); 274 | fwrite(d, *psize, 1, fh); 275 | fclose(fh); 276 | 277 | return -1; 278 | } 279 | 280 | #undef UDPHDR 281 | 282 | p += udp_hdr_sz; 283 | 284 | *pdata = p; 285 | *psize = udp_sz-udp_hdr_sz; 286 | 287 | return 0; 288 | } 289 | 290 | uint16_t checksum_ip(const void* buf_, int count) { 291 | register uint32_t sum = 0; 292 | uint16_t answer = 0; 293 | const uint16_t* buf = buf_; 294 | 295 | // Sum up 2-byte values until none or only one byte left. 296 | while (count > 1) { 297 | sum += *(buf++); 298 | count -= 2; 299 | } 300 | 301 | // Add left-over byte, if any. 302 | if (count > 0) { 303 | sum += *(uint8_t *) buf; 304 | } 305 | 306 | // Fold 32-bit sum into 16 bits; we lose information by doing this, 307 | // increasing the chances of a collision. 308 | // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits) 309 | while (sum >> 16) { 310 | sum = (sum & 0xffff) + (sum >> 16); 311 | } 312 | 313 | // Checksum is one's compliment of sum. 314 | answer = ~sum; 315 | 316 | return answer; 317 | } 318 | 319 | -------------------------------------------------------------------------------- /src/kad.lua: -------------------------------------------------------------------------------- 1 | local packet = require('packet') 2 | local time = require('time') 3 | 4 | local M = {} 5 | 6 | local function explain(n, p, fmt, ...) 7 | return n:explain('peer %s', fmt, n:key(p), ...) 8 | end 9 | 10 | local function update_fragment(p) 11 | if p.fragments then 12 | local to_remove = {} 13 | local count = #p.fragments 14 | for i, sess in ipairs(p.fragments) do 15 | if (count > wh.FRAGMENT_MAX or 16 | sess.deadline <= now) then 17 | 18 | to_remove[#to_remove+1] = i 19 | count = count - 1 20 | end 21 | end 22 | 23 | for i = #to_remove, 1, -1 do 24 | local sess_i = to_remove[i] 25 | local sess = p.fragments[sess_i] 26 | printf("$(red)drop fragment session %s$(reset)", wh.tob64(sess.id)) 27 | p.fragments[sess.id] = nil 28 | table.remove(p.fragments, sess_i) 29 | end 30 | end 31 | end 32 | 33 | local function update_peer(n, p, sess) 34 | -- keeps aliases for ever 35 | if p.alias then 36 | return 'inf' 37 | end 38 | 39 | local test_alive = false 40 | local reason 41 | 42 | -- if a WireGuard tunnel is enabled with this peer, make sure the peer is 43 | -- always alive. If the option 'persistent-keepalive' is enabled, WireGuard 44 | -- sends keep-alive packets which is taken into account by WireHub via the 45 | -- wg's sync. 46 | if p.wg_connected and p.addr then 47 | reason = 'wireguard is enabled' 48 | test_alive = true 49 | 50 | elseif sess.p_state == 'direct' then 51 | -- if there are too many direct peers in the bucket, make sure the stored 52 | -- ones are alive 53 | if sess.c_direct > wh.KADEMILIA_K then 54 | -- XXX wh.ALIVE_INTERVAL may depend on the peer's uptime (see 55 | -- fig. 1 of the Kademilia paper) 56 | local deadline = (p.last_seen or 0) + wh.ALIVE_INTERVAL 57 | 58 | -- if is peer considered as alive, do not ping, and ... 59 | if now < deadline then 60 | -- ... remove if peer is excedent and all previous peers were 61 | -- checked as alive 62 | if sess.i_direct > sess.c_direct and sess.all_direct_tested_alive then 63 | deadline = nil 64 | end 65 | 66 | return deadline 67 | 68 | -- else we do not if peer is alive 69 | -- if peer is in the Kth first, ping 70 | elseif sess.i_direct <= wh.KADEMILIA_K then 71 | reason = 'too many directs in bucket. test if peers is alive' 72 | test_alive = true 73 | sess.all_directed_tested_alive = false 74 | 75 | -- else, it must be a peer excedent peers. 76 | -- if all previous direct peers are online, forget 77 | elseif sess.all_directed_tested_alive then 78 | return nil 79 | 80 | -- else previous peers are being tested, therefore keep in the 81 | -- meantime 82 | else 83 | return 'inf' 84 | end 85 | 86 | -- XXX should only ping the closest direct peers, not all! 87 | -- XXX remove this by a session which searches for the closest direct peers 88 | elseif n.is_nated then 89 | reason = 'current peer is NAT-ed' 90 | test_alive = true 91 | 92 | -- check every now and then direct peers are still online 93 | else 94 | local deadline = (p.last_seen or 0) + wh.KEEPALIVE_DIRECT_TIMEOUT 95 | 96 | if now < deadline then 97 | return deadline 98 | end 99 | 100 | reason = 'keep-alive' 101 | test_alive = true 102 | end 103 | end 104 | 105 | -- test if peer is alive 106 | if test_alive then 107 | local ping_retry 108 | if not p.bootstrap then 109 | ping_retry = wh.PING_RETRY 110 | end 111 | 112 | local do_ping, deadline = time.retry_ping_backoff( 113 | p, 114 | wh.NAT_TIMEOUT - n.jitter_rand, 115 | ping_retry, 116 | wh.PING_BACKOFF 117 | ) 118 | 119 | if do_ping then 120 | explain(n, p, "alive? (%s)", reason) 121 | n:_sendto{dst=p, m=packet.ping()} 122 | end 123 | 124 | return deadline 125 | end 126 | 127 | -- if address was forgotten 128 | if p.addr == nil then 129 | if p.trust then 130 | return 'inf' 131 | else 132 | return nil 133 | end 134 | end 135 | 136 | if sess.p_state == 'direct' then 137 | return 'inf' 138 | end 139 | 140 | if p:owned() then 141 | return 'inf' 142 | end 143 | 144 | assert(sess.p_state == 'nat' or sess.p_state == 'relay') 145 | 146 | -- p is NAT-ed. Forget if it does not contact current peer after a certain 147 | -- amount of time 148 | local deadline = (p.last_seen or 0) + wh.NAT_TIMEOUT * 2 149 | if deadline <= now then 150 | return nil 151 | end 152 | 153 | return deadline 154 | end 155 | 156 | function M.update(n, deadlines) 157 | -- maintain the Kademilia tree 158 | 159 | for bid, bucket in pairs(n.kad.buckets) do 160 | table.sort(bucket, function(a, b) return (a.first_seen or 0) < (b.first_seen or 0) end) 161 | 162 | local to_remove = {} 163 | 164 | local sess = { 165 | c_direct = 0, 166 | c_nat = 0, 167 | i_direct = 0, 168 | i_nat = 0, 169 | all_direct_tested_alive = true, 170 | } 171 | for i, p in ipairs(bucket) do 172 | -- take advantage of iterating over all nodes to remove fragments, 173 | -- without management of any deadlines 174 | update_fragment(p) 175 | 176 | -- if relay was forgotten 177 | if p.relay and p.relay.addr == nil then 178 | p.relay = nil 179 | end 180 | 181 | local p_state = p:state() 182 | if p_state == 'direct' then 183 | sess.c_direct = sess.c_direct + 1 184 | elseif p_state == 'nat' or p_state == 'relay' then 185 | sess.c_nat = sess.c_nat + 1 186 | end 187 | end 188 | 189 | for i, p in ipairs(bucket) do 190 | sess.p_state = p:state() 191 | if sess.p_state == 'direct' then 192 | sess.i_direct = sess.i_direct + 1 193 | elseif sess.p_state == 'nat' or sess.p_state == 'relay' then 194 | sess.i_nat = sess.i_nat + 1 195 | end 196 | 197 | local deadline = update_peer(n, p, sess) 198 | 199 | if deadline == nil then 200 | p.addr = nil 201 | p.addr_echo = nil 202 | p.is_nated = nil 203 | p.relay = nil 204 | 205 | if p.trust then 206 | explain(n, p, "forget!") 207 | n.kad.touched[p.k] = p 208 | else 209 | n.kad.touched[p.k] = nil 210 | 211 | if not p:owned() then 212 | to_remove[#to_remove+1] = i 213 | --else 214 | -- dbg("notice: do not remove peer %s", wh.key(p.k)) 215 | end 216 | 217 | if sess.p_state == 'direct' then 218 | sess.c_direct = sess.c_direct - 1 219 | sess.i_direct = sess.i_direct - 1 220 | elseif sess.p_state == 'nat' or sess.p_state == 'relay' then 221 | sess.c_nat = sess.c_nat - 1 222 | sess.i_nat = sess.i_nat - 1 223 | end 224 | end 225 | elseif deadline ~= 'inf' then 226 | deadlines[#deadlines+1] = deadline 227 | end 228 | end 229 | 230 | for i = #to_remove, 1, -1 do 231 | local p = bucket[to_remove[i]] 232 | explain(n, p, "remove!") 233 | table.remove(bucket, to_remove[i]) 234 | bucket[p.k] = nil 235 | end 236 | end 237 | end 238 | 239 | function M.on_pong(n, src) 240 | if (src.ping_retry or 0) > 0 then 241 | explain(n, src, "alive!") 242 | end 243 | 244 | src.ping_retry = 0 245 | end 246 | 247 | return M 248 | 249 | -------------------------------------------------------------------------------- /src/handlers.lua: -------------------------------------------------------------------------------- 1 | local packet = require('packet') 2 | local peer = require('peer') 3 | 4 | local auth = require('auth') 5 | local kad = require('kad') 6 | local nat = require('nat') 7 | local search = require('search') 8 | 9 | local function log_cmd(n, m, src, fmt, ...) 10 | --printf("%s - %s - %d - %s", src, wh.todate(now), #m, string.format(fmt, ...)) 11 | if n.log >= 2 then 12 | printf("%s -> :%d: %s (%dB)", src, n.port, string.format(fmt, ...), #m) 13 | end 14 | end 15 | 16 | -- Table of WireHub packet handlers 17 | local H = {} 18 | 19 | H[packet.cmds.ping] = function(n, m, src, via) 20 | local arg = string.sub(m, 2, 2) 21 | if arg == '\x00' then 22 | arg = 'normal' 23 | elseif arg == '\x01' then 24 | arg = 'swapsrc' 25 | elseif arg == '\x02' then 26 | arg = 'direct' 27 | end 28 | 29 | local body = string.sub(m, 3) 30 | 31 | --if src.lazy and (arg ~= "normal" or #body ~= 0) then return end 32 | 33 | -- ignore ping with body bigger than 8 34 | if #body > 8 then 35 | printf("$(red)drop too big ping") 36 | return 37 | end 38 | 39 | -- by default, respond via same port, except if argument is 'swapsrc' 40 | local echo 41 | 42 | if via == 'relay' then 43 | echo = false 44 | else 45 | echo = via == 'echo' 46 | if arg == 'swapsrc' then echo = not echo end 47 | end 48 | 49 | if arg == 'direct' then 50 | -- remove relay 51 | src = {addr=src.addr, k=src.k} 52 | end 53 | 54 | log_cmd(n, m, src, "$(yellow)ping$(reset)(%s, %s)", arg, wh.tob64(body)) 55 | n:_sendto{ 56 | dst=src, 57 | m=packet.pong(n.port_echo, src.addr, body), 58 | from_echo=echo 59 | } 60 | end 61 | 62 | H[packet.cmds.pong] = function(n, m, src) 63 | --if src.lazy then return end 64 | 65 | local i = 2 66 | local l 67 | 68 | local src_port_echo, src_addr_echo, public_addr 69 | 70 | public_addr, l = wh.unpack_address(string.sub(m, i)) 71 | i = i + l 72 | 73 | src_port_echo = string.unpack("!H", string.sub(m, i, i+1)) 74 | src_addr_echo = wh.set_address_port(src.addr, src_port_echo) 75 | i = i + 2 76 | 77 | src.addr_echo = src_addr_echo 78 | 79 | local body = string.sub(m, i) 80 | 81 | log_cmd(n, m, src, "$(yellow)pong$(reset)(%s, port_echo=%s, self=%s)", wh.tob64(body), src_addr_echo, public_addr) 82 | 83 | kad.on_pong(n, src) 84 | nat.on_pong(n, body, src) 85 | search.on_pong(n, body, src) 86 | end 87 | 88 | H[packet.cmds.search] = function(n, m, src) 89 | local k = string.sub(m, 2) 90 | log_cmd(n, m, src, "$(yellow)search$(reset)(%s)", n:key(k)) 91 | 92 | local closest = n.kad:kclosest(k, wh.KADEMILIA_K, function(p) 93 | return p.k == k or p:state() == 'direct' 94 | end) 95 | 96 | n:_sendto{dst=src, m=packet.result(k, closest)} 97 | end 98 | 99 | H[packet.cmds.result] = function(n, m, src) 100 | if src.lazy then return end 101 | 102 | local pks = string.sub(m, 2, 33) 103 | local closest = {} 104 | local i = 34 105 | local l 106 | 107 | while i < #m do 108 | local p = {} 109 | 110 | local flag = string.sub(m, i, i) 111 | i = i + 1 112 | 113 | do 114 | p.k = string.sub(m, i, i+31) 115 | i = i + 32 116 | 117 | p.addr, l = wh.unpack_address(string.sub(m, i)) 118 | i = i + l 119 | end 120 | 121 | if flag == '\x01' then 122 | local relay = {} 123 | relay.k = string.sub(m, i, i+31) 124 | i = i + 32 125 | relay.addr, l = wh.unpack_address(string.sub(m, i)) 126 | i = i + l 127 | 128 | -- prefer own source 129 | p.relay = n.kad:get(relay.k) or peer(relay) 130 | 131 | elseif flag == '\x02' then 132 | p.relay = src 133 | end 134 | 135 | closest[#closest+1] = peer(p) 136 | end 137 | 138 | log_cmd(n, m, src, "$(yellow)result$(reset)(#%d)", #closest) 139 | 140 | search.on_result(n, pks, closest, src) 141 | end 142 | 143 | H[packet.cmds.relay] = function(n, m, src) 144 | local i = 2 145 | local l 146 | 147 | local dst_k = string.sub(m, i, i+32-1) 148 | i = i + 32 149 | if #dst_k ~= 32 then 150 | return 151 | end 152 | 153 | local relayed_m = string.sub(m, i) 154 | 155 | local dst = n.kad:get(dst_k) 156 | if not dst then 157 | return 158 | end 159 | 160 | -- XXX bandwidth limit 161 | -- XXX whitelist management 162 | -- XXX keep source in kademilia for some time as it is currently relaying 163 | -- with dst 164 | -- XXX SECURITY ISSUE! 165 | -- Do not let anyone send a 'relayed' packet with any type of body. Just 166 | -- sign the digest, not the entire body! (AEAD?) 167 | -- OK for the POC 168 | -- XXX make sure to keep dst in the kademilia table 169 | 170 | log_cmd(n, m, src, "$(blue)relay$(reset)(%s, %d)", n:key(dst_k), #relayed_m) 171 | 172 | if dst.relay then 173 | printf("$(red)cannot forward relayed packet to relayed peer %s$(reset)", n:key(dst)) 174 | return 175 | end 176 | 177 | if not dst.addr then 178 | printf("$(red)unknown route for relayed packet to %s$(reset)", n:key(dst)) 179 | return 180 | end 181 | 182 | n:_sendto{dst=dst, m=packet.relayed(src, relayed_m)} 183 | end 184 | 185 | H[packet.cmds.relayed] = function(n, m, relay) 186 | local i = 2 187 | local l 188 | 189 | local src_addr 190 | src_addr, l = wh.unpack_address(string.sub(m, i)) 191 | i = i + l 192 | assert(src_addr) -- XXX 193 | 194 | local me = string.sub(m, i) 195 | 196 | log_cmd(n, m, relay, "$(blue)relayed$(reset)(%s, %d)", src_addr, #me) 197 | 198 | local src_k, time, src_is_nated 199 | src_k, src_is_nated, time, m = wh.open_packet(n.sk, me) 200 | 201 | if m == nil then 202 | printf("$(red)relayed packet dropped!$(reset)") 203 | return 204 | end 205 | 206 | -- XXX SECURITY ISSUE! 207 | -- a node received a relayed must check that the source indeed sent the 208 | -- relayed packet through the relay! 209 | 210 | local src_relay 211 | if src_is_nated then 212 | src_relay = relay 213 | end 214 | 215 | return n:read( 216 | m, 217 | src_addr, 218 | src_k, 219 | src_is_nated, 220 | time, 221 | 'relay', 222 | src_relay 223 | ) 224 | end 225 | 226 | H[packet.cmds.auth] = function(n, m, alias) 227 | if not alias.alias then 228 | log_cmd(n, m, alias, "$(yellow)auth$(reset)($(red)not an alias$(reset))") 229 | return 230 | end 231 | 232 | local me = string.sub(m, 2) 233 | local src_k, src_is_nated, src_time, src_m = wh.open_packet(n.sk, me) 234 | 235 | if src_m == nil then 236 | log_cmd(n, m, alias, "$(yellow)auth$(reset)($(red)bad$(reset))") 237 | return 238 | end 239 | 240 | if src_k ~= src_m then 241 | log_cmd(n, m, alias, "$(yellow)auth$(reset)($(red)invalid$(reset))") 242 | return 243 | end 244 | 245 | log_cmd(n, m, alias, "$(yellow)auth$(reset)(%s)", n:key(src_k)) 246 | 247 | local src = n.kad:touch(src_k) 248 | 249 | auth.resolve_alias(n, alias, src) 250 | 251 | n:_sendto{ 252 | dst=src, 253 | m=packet.authed(alias.k), 254 | } 255 | end 256 | 257 | H[packet.cmds.authed] = function(n, m, src) 258 | local alias_k = string.sub(m, 2) 259 | 260 | log_cmd(n, m, alias, "$(yellow)authed$(reset)(%s)", n:key(alias_k)) 261 | 262 | auth.on_authed(n, alias_k, src) 263 | end 264 | 265 | H[packet.cmds.fragment] = function(n, m, src) 266 | if not n.lo then 267 | return 268 | end 269 | 270 | local id, b = string.unpack(">HB", string.sub(m, 2, 4)) 271 | local num = b&0x7f 272 | local mf = b&0x80==0x80 273 | m = string.sub(m, 5) 274 | 275 | log_cmd(n, m, alias, "$(yellow)fragment$(reset)(id:%.4x, num:%d, mf:%s, m:%d)", id, num, mf, #m) 276 | 277 | if not src.fragments then 278 | src.fragments = {} 279 | end 280 | local id = src.k .. string.pack("H", id) 281 | local sess = src.fragments[id] 282 | if not sess then 283 | sess = { 284 | deadline=now+wh.FRAGMENT_TIMEOUT, 285 | id=id, 286 | } 287 | src.fragments[#src.fragments+1] = sess 288 | table.sort(src.fragments, function(a, b) 289 | return a.deadline < b.deadline 290 | end) 291 | src.fragments[sess.id] = sess 292 | end 293 | 294 | sess[num+1] = m 295 | 296 | -- if last fragment was received 297 | if not mf then 298 | sess.last = num+1 299 | end 300 | 301 | if not sess.last or #sess ~= sess.last then 302 | return 303 | end 304 | 305 | local m = table.concat(sess) 306 | 307 | n.lo:recv_datagram(src, m) 308 | 309 | -- clean 310 | src.fragments[id] = nil 311 | for i, v in ipairs(src.fragments) do 312 | if v == sess then 313 | table.remove(src.fragments, i) 314 | break 315 | end 316 | end 317 | 318 | --if #src.fragments == 0 then 319 | -- src.fragments = nil 320 | --end 321 | end 322 | 323 | return H 324 | 325 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WireHub 2 | 3 | WireHub (in a shell, *wh*) builds decentralized, peer-to-peer and secure overlay 4 | networks. It is small (<10KLOC) and tends to be simple-to-use and easily 5 | extendable. 6 | 7 | It is built upon [WireGuard tunnels][wireguard] and provides distributed peer 8 | discovery & routing capabilities, NAT trasversal, extendable name resolving, ... 9 | 10 | ⚠️ **Not ready for production!** This is still a work-in-progress. It still 11 | requires some work to be clean and secure. The current code is provided for 12 | testing only. 13 | 14 | ## Features 15 | 16 | - **Single file network description**: a configuration of a network is a list 17 | of the public key, private IPs and hostnames for each node. 18 | 19 | - **Decentralized peer discovery**: WireHub peers form a authentified [Kademilia 20 | DHT][kademilia] network, which is the by-default discovery mechanism to find 21 | new peers. [Sybil attack][sybil] is mitigated with a configurable 22 | Proof-of-Work parameter (see `workbits`); 23 | 24 | - **Peer-to-peer and relayed communication**: WireHub goes through NATs, using 25 | [UPnP IGD][igd] to map new ports on compatible routers, or using [UDP Hole 26 | Punching][udp-hole-punching] techniques. If a P2P communication cannot be 27 | established, network traffic is relayed through the DHT. 28 | 29 | ## Getting started 30 | 31 | ### Quickstart with Docker 32 | 33 | Run a minimal environment with WireHub installed. 34 | 35 | ```bash 36 | docker run -it --cap-add NET_ADMIN wirehub/wh /bin/sh 37 | ``` 38 | 39 | Run a testing environment with auto-completion enabled, testing scripts and 40 | debug tools installed, ... 41 | 42 | ```bash 43 | docker run -it --cap-add NET_ADMIN wirehub/sandbox /bin/bash 44 | ``` 45 | 46 | If you want to compile the Docker images from source, 47 | 48 | ```bash 49 | git clone --recursive https://github.com/gawen/wirehub 50 | cd wirehub 51 | make docker docker-sandbox 52 | ``` 53 | 54 | ### A simple network with two nodes 55 | 56 | First, generate two keys, one for each node. 57 | 58 | ```bash 59 | $ wh genkey | tee node_a.sk | wh pubkey | tee node_a.k 60 | zW-1lBeQ7IkT6NW6hL_NsV4eOPOwJi_rt1vO-omOEmQ 61 | $ wh genkey | tee node_b.sk | wh pubkey | tee node_b.k 62 | g878Bf9ZDc4IzFSUhWFTO1VYFVmHD5XfvEsVn83Dsho 63 | ``` 64 | 65 | The private keys are stored in the `.sk` files. The public keys are stored in 66 | the `.k` files. 67 | 68 | Generate a WireHub configuration 69 | 70 | ```bash 71 | echo "name tutorial 72 | subnet 10.0.42.0/24 73 | 74 | boot P17zMwXJFbBdJEn05RFIMADw9TX5_m2xgf31OgNKX3w bootstrap.wirehub.io 75 | trust node_a `cat node_a.k` 76 | trust node_b `cat node_b.k`" > config 77 | ``` 78 | 79 | File `config` should be like this: 80 | 81 | ``` 82 | name tutorial # name of network 83 | subnet 10.0.42.0/24 # private subnetwork 84 | 85 | # one DHT bootstrap node 86 | boot P17zMwXJFbBdJEn05RFIMADw9TX5_m2xgf31OgNKX3w bootstrap.wirehub.io 87 | 88 | # two nodes, node_a & node_b 89 | trust node_a zW-1lBeQ7IkT6NW6hL_NsV4eOPOwJi_rt1vO-omOEmQ 90 | trust node_b g878Bf9ZDc4IzFSUhWFTO1VYFVmHD5XfvEsVn83Dsho 91 | ``` 92 | 93 | To start the network, run on `node_a` ... 94 | 95 | ```bash 96 | wh up ./config private-key ./node_a.sk 97 | ``` 98 | 99 | ... and on `node_b` ... 100 | 101 | ```bash 102 | wh up ./config private-key ./node_b.sk 103 | ``` 104 | 105 | After some time, each node should be able to ping themselves. 106 | 107 | ``` 108 | # ping node_b 109 | PING 10.0.42.3 (10.0.42.3): 56 data bytes 110 | 64 bytes from 10.0.42.2: seq=0 ttl=64 time=106.801 ms 111 | 64 bytes from 10.0.42.2: seq=1 ttl=64 time=49.778 ms 112 | 113 | ``` 114 | 115 | You can check the overlay network status 116 | 117 | ``` 118 | # wh 119 | interface wh-zW-1lBeQ7, network tutorial, node node_a 120 | public key: zW-1lBeQ7IkT6NW6hL_NsV4eOPOwJi_rt1vO-omOEmQ 121 | 122 | peers 123 | node_b 124 | 125 | ``` 126 | 127 | While the daemon is running, you can modify the network configuration and reload 128 | it. 129 | 130 | ``` 131 | # echo "trust node_c 9OtorxsAqPqZkJ-fAYNRAPr9piMWKMLnGqOVVpMUvXY" >> ./config 132 | # wh reload wh-zW-1lBeQ7 133 | ``` 134 | 135 | You may stop the WireHub node as so: 136 | 137 | ```bash 138 | wh down wh-zW-1lBeQ7 139 | ``` 140 | 141 | Advise: use auto-completion to avoid writing wirehub interface, peer's keys or 142 | other arguments. For example, 143 | 144 | ``` 145 | # wh do 146 | wh down 147 | wh down wh-zW-1lBeQ7 148 | ``` 149 | 150 | ### A use-case with WireHub: zero-netcat 151 | 152 | [![demo](https://asciinema.org/a/217931.svg)](https://asciinema.org/a/217931?autoplay=1) 153 | 154 | Zero Netcat, or `0nc`, is a modified version of [Netcat][netcat] which runs over 155 | WireHub. It has the nice property to be secure, peer-to-peer and agnostic of the 156 | network topology. 157 | 158 | On one node, run the WireHub sandbox. 159 | 160 | ``` 161 | $ docker run -it --cap-add NET_ADMIN wirehub/sandbox /bin/bash 162 | ``` 163 | 164 | Run `0nc`. 165 | 166 | ``` 167 | node_a # 0nc.lua 168 | znc invitation: ncuJonSJOS1DlFtb3HdgDJczPilrs0oPR9pwRpa_7WXwO0z-xioe_g9cdcMZkpV2b5lN7j3eLILjplBffvjdcw 169 | ``` 170 | 171 | Copy the znc invitation. Run another WireHub sandbox, call `0nc` with the 172 | invitation as argument. 173 | 174 | ``` 175 | node_b # 0nc.lua ncuJonSJOS1DlFtb3HdgDJczPilrs0oPR9pwRpa_7WXwO0z-xioe_g9cdcMZkpV2b5lN7j3eLILjplBffvjdcw 176 | ``` 177 | 178 | `STDIN` of `node_a` is now pipe-d into `STDOUT` of `node_b`, and vice-versa. 179 | 180 | ### Start a public node 181 | 182 | The minimal configuration for a node is something like this, 183 | 184 | ``` 185 | name public 186 | workbit 8 187 | boot P17zMwXJFbBdJEn05RFIMADw9TX5_m2xgf31OgNKX3w bootstrap.wirehub.io 188 | ``` 189 | 190 | Only a bootstrap node is listed, but no trusted nodes. A node with this 191 | configuration will join the WireHub DHT and only provide support for discovery 192 | peers and relaying data (which is a good thing for the DHT's health). 193 | 194 | Start a public node, 195 | 196 | ```bash 197 | curl https://raw.githubusercontent.com/gawen/wirehub/master/config/public > ./config 198 | wh up ./config 199 | ``` 200 | 201 | Check the neighbour peers in the DHT, 202 | 203 | ``` 204 | # wh show wh-gOVQwCSUxK all 205 | interface wh-gOVQwCSUxK, network public, node <> 206 | public key: gOVQwCSUxKUhUrkUSF0aDvssDfWVrrnm47ZMp5GJtDg 207 | 208 | peers 209 | ◒ BB_O_4Qxzw: 1.2.3.4:55329 (bucket:1) 210 | ◒ C4mfi1ltU9: 1.2.3.4:46276 (bucket:1) 211 | ◒ Dng_TaMHei: 1.2.3.4:6465 (bucket:1) 212 | ◒ GjIX1RdmDj: 1.2.3.4:53850 (bucket:1) 213 | ◒ G9qk6znNL5: 1.2.3.4:4523 (bucket:1) 214 | ◒ J_RXehMJiw: 1.2.3.4:13962 (bucket:1) 215 | ◒ PgjYqFfsyS: 1.2.3.4:39582 (bucket:1) 216 | ● P17zMwXJFb: 51.15.227.165:62096 (bucket:1) 217 | [...] 218 | ``` 219 | 220 | ## Dependencies 221 | 222 | - [Libpcap][libpcap] 223 | - [Libsodium][libsodium] 224 | - [Lua][lua] 225 | - [miniupnpc][miniupnpc] 226 | - [WireGuard][wireguard] 227 | - optionally, [Docker][docker] 228 | 229 | ## Requirements 230 | 231 | - Linux or Docker 232 | - WireGuard 233 | 234 | ## Current limitations 235 | 236 | - **Untrusted cryptography**: even if WireHub basics cryptographic routines are 237 | based on the trusted [Libsodium][libsodium], the WireHub cryptographic 238 | architecture has not been audited yet. If you're interested to contribute on 239 | this part, help is very welcome! 240 | 241 | - **Automatic testing**: a lot of work needs to be done to make real automatic 242 | testing possible with WireHub. Current efforts are on branch 243 | [`dev-testbed`](https://github.com/Gawen/WireHub/tree/develop-testbed) and 244 | [`micronet`][micronet]. 245 | 246 | - **Still panic**: still quite rough to use. Do not expect the daemon to be stable; 247 | 248 | - **Poor documentation**: WireHub was a side project and still lacks 249 | documentation. 250 | 251 | - **For a relayed peer, only one relay is used**: the traffic is not distributed 252 | yet between several relays, which makes a single point of failure of WireHub 253 | relay mechanisms; 254 | 255 | - **Only IPv4 private addresses**: implemeting IPv6 private addresses requires 256 | some additional work; 257 | 258 | - and related to WireGuard, which is still under active development. 259 | 260 | ## Future 261 | 262 | - **Zero-configuration IP6 networking** with IPv6 [ORCHID][orchid] addresses, to 263 | automatically allocate each peer a default private IP (see `wh orchid`); 264 | 265 | ## Overall source code architecture 266 | 267 | WireHub's source code is stored in `src/`. `wh.lua` is the main Lua module to 268 | import WireHub's engine. 269 | 270 | The source code of the CLI tool `wh` is stored in `src/tools/`. Its entry point is `src/tools/cli.lua`. 271 | 272 | The core of WireHub is written in C and stored in `src/core/`. It is a native 273 | Lua module called `whcore`, defined in `src/core/whcorelib.c`. 274 | 275 | Please refer to the documentation in each files for more info. 276 | 277 | [curve25519]: https://cr.yp.to/ecdh.html 278 | [docker]: https://www.docker.com/ 279 | [igd]: https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol 280 | [kademilia]: https://en.wikipedia.org/wiki/Kademlia 281 | [libpcap]: https://www.tcpdump.org/ 282 | [libsodium]: https://download.libsodium.org/doc/ 283 | [lua]: https://www.lua.org/ 284 | [micronet]: https://github.com/Gawen/WireHub/tree/develop-testbed/contrib/micronet 285 | [miniupnpc]: http://miniupnp.free.fr/ 286 | [netcat]: https://en.wikipedia.org/wiki/Netcat 287 | [orchid]: https://datatracker.ietf.org/doc/rfc7343/ 288 | [pow]: https://en.wikipedia.org/wiki/Proof-of-work_system 289 | [sybil]: https://en.wikipedia.org/wiki/Sybil_attack 290 | [udp-hole-punching]: https://en.wikipedia.org/wiki/UDP_hole_punching 291 | [wireguard]: https://www.wireguard.com/ 292 | --------------------------------------------------------------------------------