examples: add nat-hole-punching

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2016-08-23 03:56:42 +02:00
parent aae568937e
commit 62fe72133c
3 changed files with 354 additions and 0 deletions

View file

@ -0,0 +1,41 @@
== NAT Hole Punching Example ==
This code should never be used, ever. But, it's a nice demonstration of how
to punch holes and have two NAT'd peers talk to each other.
Compile with:
$ gcc nat-punch-client.c -o client -lresolv
$ gcc nat-punch-server.c -o server
Server is 1.2.3.4 and is on the public internet accepting UDP:49918.
Client A is NAT'd and doesnt't know its IP address.
Client B is NAT'd and doesnt't know its IP address.
Server runs:
$ ./server
Client A runs:
# ip link add wg0 type wireguard
# ip addr add 10.200.200.1 peer 10.200.200.2 dev wg0
# wg set wg0 private-key ... peer ... allowed-ips 10.200.200.2/32
# ./client 1.2.3.4 wg0
# ping 10.200.200.2
Client B runs:
# ip link add wg0 type wireguard
# ip addr add 10.200.200.2 peer 10.200.200.1 dev wg0
# wg set wg0 private-key ... peer ... allowed-ips 10.200.200.1/32
# ./client 1.2.3.4 wg0
# ping 10.200.200.1
And voila! Client A and Client B can speak from behind NAT.
-----
Keep in mind that this is proof-of-concept example code. It is not code that
should be used in production, ever. It is woefully insecure, and is unsuitable
for any real usage. With that said, this is useful as a learning example of
how NAT hole punching might work within a more developed solution.

View file

@ -0,0 +1,203 @@
/* Example only. Do not run in production. */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <linux/filter.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdarg.h>
#include <string.h>
#include <resolv.h>
#include <stdint.h>
#include <stdbool.h>
enum { MAX_PEERS = 65536, PORT = 49918 };
static struct {
uint8_t base64_key[45];
bool have_seen;
} peers[MAX_PEERS];
static unsigned int total_peers;
static const char *cmd(const char *line, ...)
{
static char buf[2048];
char full_cmd[2048] = { 0 };
size_t len;
FILE *f;
va_list args;
va_start(args, line);
vsnprintf(full_cmd, 2047, line, args);
va_end(args);
f = popen(full_cmd, "r");
if (!f) {
perror("popen");
exit(errno);
}
if (!fgets(buf, 2048, f)) {
pclose(f);
return NULL;
}
pclose(f);
len = strlen(buf);
if (!len)
return NULL;
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
return buf;
}
static void read_peers(const char *interface)
{
char full_cmd[2048] = { 0 };
size_t len;
FILE *f;
snprintf(full_cmd, 2047, "wg show %s peers", interface);
f = popen(full_cmd, "r");
if (!f) {
perror("popen");
exit(errno);
}
for (;;) {
if (!fgets(peers[total_peers].base64_key, 45, f))
break;
len = strlen(peers[total_peers].base64_key);
if (len != 44 && len != 45)
continue;
if (peers[total_peers].base64_key[len - 1] == '\n')
peers[total_peers].base64_key[len - 1] = '\0';
++total_peers;
}
pclose(f);
}
static void unbase64(uint8_t dstkey[32], const char *srckey)
{
uint8_t buf[33];
if (b64_pton(srckey, buf, 33) != 32) {
fprintf(stderr, "Could not parse base64 key: %s\n", srckey);
exit(EINVAL);
}
memcpy(dstkey, buf, 32);
}
static void apply_bpf(int sock, uint16_t port, uint32_t ip)
{
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12 /* src ip */),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ip, 0, 5),
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 /* src port */),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, PORT, 0, 3),
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 22 /* dst port */),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 0, 1),
BPF_STMT(BPF_RET + BPF_K, -1),
BPF_STMT(BPF_RET + BPF_K, 0)
};
struct sock_fprog filter_prog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = filter
};
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)) < 0) {
perror("setsockopt(bpf)");
exit(errno);
}
}
int main(int argc, char *argv[])
{
struct sockaddr_in addr = {
.sin_family = AF_INET
};
struct {
struct udphdr udp;
uint8_t my_pubkey[32];
uint8_t their_pubkey[32];
} __attribute__((packed)) packet = {
.udp = {
.len = htons(sizeof(packet)),
.dest = htons(PORT)
}
};
struct {
struct iphdr iphdr;
struct udphdr udp;
uint32_t ip;
uint16_t port;
} __attribute__((packed)) reply;
ssize_t len;
int sock, i;
bool repeat;
struct hostent *ent;
const char *server = argv[1], *interface = argv[2];
if (argc < 3) {
fprintf(stderr, "Usage: %s SERVER WIREGUARD_INTERFACE\nExample:\n %s demo.wireguard.io wg0\n", argv[0], argv[0]);
return EINVAL;
}
if (getuid() != 0) {
fprintf(stderr, "Must be root!\n");
return EPERM;
}
ent = gethostbyname2(server, AF_INET);
if (!ent) {
herror("gethostbyname2");
return h_errno;
}
addr.sin_addr = *(struct in_addr *)ent->h_addr;
read_peers(interface);
cmd("ip link set %s up", interface);
unbase64(packet.my_pubkey, cmd("wg show %s public-key", interface));
packet.udp.source = htons(atoi(cmd("wg show %s listen-port", interface)));
/* We use raw sockets so that the WireGuard interface can actually own the real socket. */
sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (sock < 0) {
perror("socket");
return errno;
}
apply_bpf(sock, ntohs(packet.udp.source), ntohl(addr.sin_addr.s_addr));
check_again:
repeat = false;
for (i = 0; i < total_peers; ++i) {
if (peers[i].have_seen)
continue;
printf("[+] Requesting IP and port of %s: ", peers[i].base64_key);
unbase64(packet.their_pubkey, peers[i].base64_key);
if (sendto(sock, &packet, sizeof(packet), 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
putchar('\n');
perror("sendto");
return errno;
}
len = recv(sock, &reply, sizeof(reply), 0);
if (len < 0) {
putchar('\n');
perror("recv");
return errno;
}
if (len != sizeof(reply)) {
printf("server does not yet have it\n");
repeat = true;
} else {
printf("%s:%d\n", inet_ntoa(*(struct in_addr *)&reply.ip), ntohs(reply.port));
peers[i].have_seen = true;
cmd("wg set %s peer %s persistent-keepalive 25 endpoint %s:%d", interface, peers[i].base64_key, inet_ntoa(*(struct in_addr *)&reply.ip), ntohs(reply.port));
}
}
if (repeat) {
sleep(2);
goto check_again;
}
close(sock);
return 0;
}

View file

@ -0,0 +1,110 @@
/* Example only. Do not run in production. */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
struct entry {
uint8_t pubkey[32];
uint32_t ip;
uint16_t port;
};
enum { MAX_ENTRIES = 65536, PORT = 49918 };
static struct entry entries[MAX_ENTRIES];
static unsigned int next_entry;
/* XX: this should use a hash table */
static struct entry *find_entry(uint8_t key[32])
{
int i;
for (i = 0; i < MAX_ENTRIES; ++i) {
if (!memcmp(entries[i].pubkey, key, 32))
return &entries[i];
}
return NULL;
}
/* XX: this is obviously vulnerable to DoS */
static struct entry *find_or_insert_entry(uint8_t key[32])
{
struct entry *entry = find_entry(key);
if (!entry) {
entry = &entries[next_entry++ % MAX_ENTRIES];
memcpy(entry->pubkey, key, 32);
}
return entry;
}
int main(int argc, char *argv[])
{
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr = { .s_addr = htonl(INADDR_ANY) },
.sin_port = htons(PORT)
};
struct {
uint8_t my_pubkey[32];
uint8_t their_pubkey[32];
} __attribute__((packed)) packet;
struct {
uint32_t ip;
uint16_t port;
} __attribute__((packed)) reply;
struct entry *entry;
socklen_t len;
ssize_t retlen;
int optval;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return errno;
}
optval = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
perror("setsockopt");
return errno;
}
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
return errno;
}
for (;;) {
len = sizeof(addr);
if (recvfrom(sock, &packet, sizeof(packet), 0, (struct sockaddr *)&addr, &len) != sizeof(packet)) {
perror("recvfrom");
continue;
}
entry = find_or_insert_entry(packet.my_pubkey);
entry->ip = addr.sin_addr.s_addr;
entry->port = addr.sin_port;
entry = find_entry(packet.their_pubkey);
if (entry) {
reply.ip = entry->ip;
reply.port = entry->port;
if (sendto(sock, &reply, sizeof(reply), 0, (struct sockaddr *)&addr, len) < 0) {
perror("sendto");
continue;
}
} else {
if (sendto(sock, NULL, 0, 0, (struct sockaddr *)&addr, len) < 0) {
perror("sendto");
continue;
}
}
}
close(sock);
return 0;
}