examples: add nat-hole-punching
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
		
							parent
							
								
									aae568937e
								
							
						
					
					
						commit
						62fe72133c
					
				
							
								
								
									
										41
									
								
								contrib/nat-hole-punching/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								contrib/nat-hole-punching/README
									
									
									
									
									
										Normal 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.
 | 
			
		||||
							
								
								
									
										203
									
								
								contrib/nat-hole-punching/nat-punch-client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								contrib/nat-hole-punching/nat-punch-client.c
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								contrib/nat-hole-punching/nat-punch-server.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								contrib/nat-hole-punching/nat-punch-server.c
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user