]> git.ipfire.org Git - thirdparty/wireguard-tools.git/commitdiff
examples: add nat-hole-punching
authorJason A. Donenfeld <Jason@zx2c4.com>
Tue, 23 Aug 2016 01:56:42 +0000 (03:56 +0200)
committerJason A. Donenfeld <Jason@zx2c4.com>
Wed, 24 Aug 2016 13:47:31 +0000 (15:47 +0200)
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
contrib/nat-hole-punching/README [new file with mode: 0644]
contrib/nat-hole-punching/nat-punch-client.c [new file with mode: 0644]
contrib/nat-hole-punching/nat-punch-server.c [new file with mode: 0644]

diff --git a/contrib/nat-hole-punching/README b/contrib/nat-hole-punching/README
new file mode 100644 (file)
index 0000000..46e6201
--- /dev/null
@@ -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.
diff --git a/contrib/nat-hole-punching/nat-punch-client.c b/contrib/nat-hole-punching/nat-punch-client.c
new file mode 100644 (file)
index 0000000..a72b5f6
--- /dev/null
@@ -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;
+}
diff --git a/contrib/nat-hole-punching/nat-punch-server.c b/contrib/nat-hole-punching/nat-punch-server.c
new file mode 100644 (file)
index 0000000..198e0f8
--- /dev/null
@@ -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;
+}