├── .gitignore ├── Makefile ├── README.md ├── main.c ├── nat_traversal.c ├── nat_traversal.h ├── nat_type.c ├── nat_type.h └── punch_server.go /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | *.o 3 | .DS_Store 4 | *.swp 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -g -Wall 3 | 4 | all: nat_traversal 5 | 6 | # clang warn about unused argument, it requires -pthread when compiling but not when linking 7 | nat_traversal: main.o nat_traversal.o nat_type.o 8 | $(CC) $(CFLAGS) -o nat_traversal main.o nat_traversal.o nat_type.o -pthread 9 | 10 | main.o: main.c 11 | $(CC) $(CFLAGS) -c main.c 12 | 13 | nat_traversal.o: nat_traversal.c 14 | $(CC) $(CFLAGS) -c nat_traversal.c 15 | 16 | nat_type.o: nat_type.c 17 | $(CC) $(CFLAGS) -c nat_type.c 18 | 19 | clean: 20 | $(RM) nat_traversal *.o *~ 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nat_traversal 2 | An implementation of this paper: [A New Method for Symmetric NAT Traversal in UDP and TCP](http://www.goto.info.waseda.ac.jp/~wei/file/wei-apan-v10.pdf) 3 | 4 | According to [RFC 3489](http://tools.ietf.org/html/rfc3489), a symmetric NAT is one where all requests from the same internal IP address and port, to a specific destination IP address and port, are mapped to the same external IP address and 5 | port. If the same host sends a packet with the same source address and port, but to a different destination, a different mapping is used. Furthermore, only the external host that receives a packet can send a UDP packet back to the internal host. 6 | 7 | So, if we know the port allocation rule of the Symmetric NAT, we can traverse Symmetric NAT. This paper proposes a new method for traversing Symmetric NAT which is based on port prediction and limited TTL values. 8 | 9 | This method is based on limited TTL values, port prediction(if it's not predictable, use large number of holes, namely a 1000 connections at once, which will be punched and increase the success rate). 10 | ## Usage 11 | *** 12 | It's just an experimental project, I just wanna test whether UDP punching is possible if both nodes are behind symmetric NAT. It works this way: 13 | 14 | A peer got its own NAT info(external ip/port, NAT type ), then registers to server, the server replies to the peer with a unique peer ID. if you specify `-d peer ID` option, the node tries to get the info of that specified peer from the server, then connects to it directly. So when you specify '-d' option, make sure that peer is connected with server. 15 | 16 | To run it, you should run `punch_server.go` in a machine with public IP, so that both nodes can connect to it to exchange info(just node info, not to relay payload like [TURN](https://tools.ietf.org/html/rfc6062)), then run `nat_traversal [-s punch server] [-d if you wanna connect to peer]` on clients, you can also specify other arguments, such as, STUN server by `-H` option, source IP by `-i`, source port by `-p`. 17 | This program is not 100% guaranteed to make 2 peers behind symmetric NATs connect 18 | to each other, it's possible in theory. Actually, I just happen to succeed once. Hope more people can test it. -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "nat_traversal.h" 11 | 12 | #define DEFAULT_SERVER_PORT 9988 13 | #define MSG_BUF_SIZE 512 14 | 15 | // use public stun servers to detect port allocation rule 16 | static char *stun_servers[] = { 17 | "stun.ideasip.com", 18 | "stun.ekiga.net", 19 | "203.183.172.196" 20 | }; 21 | 22 | // definition checked against extern declaration 23 | int verbose = 0; 24 | 25 | int main(int argc, char** argv) 26 | { 27 | char* stun_server = stun_servers[0]; 28 | char local_ip[16] = "0.0.0.0"; 29 | uint16_t stun_port = DEFAULT_STUN_SERVER_PORT; 30 | uint16_t local_port = DEFAULT_LOCAL_PORT; 31 | char* punch_server = NULL; 32 | uint32_t peer_id = 0; 33 | int ttl = 10; 34 | 35 | static char usage[] = "usage: [-h] [-H STUN_HOST] [-t ttl] [-P STUN_PORT] [-s punch server] [-d id] [-i SOURCE_IP] [-p SOURCE_PORT] [-v verbose]\n"; 36 | int opt; 37 | while ((opt = getopt (argc, argv, "H:h:t:P:p:s:d:i:v")) != -1) 38 | { 39 | switch (opt) 40 | { 41 | case 'h': 42 | printf("%s", usage); 43 | break; 44 | case 'H': 45 | stun_server = optarg; 46 | break; 47 | case 't': 48 | ttl = atoi(optarg); 49 | break; 50 | case 'P': 51 | stun_port = atoi(optarg); 52 | break; 53 | case 'p': 54 | local_port = atoi(optarg); 55 | break; 56 | case 's': 57 | punch_server = optarg; 58 | break; 59 | case 'd': 60 | peer_id = atoi(optarg); 61 | break; 62 | case 'i': 63 | strncpy(local_ip, optarg, 16); 64 | break; 65 | case 'v': 66 | verbose = 1; 67 | break; 68 | case '?': 69 | default: 70 | printf("invalid option: %c\n", opt); 71 | printf("%s", usage); 72 | 73 | return -1; 74 | } 75 | } 76 | 77 | char ext_ip[16] = {0}; 78 | uint16_t ext_port = 0; 79 | 80 | // TODO we should try another STUN server if failed 81 | nat_type type = detect_nat_type(stun_server, stun_port, local_ip, local_port, ext_ip, &ext_port); 82 | 83 | printf("NAT type: %s\n", get_nat_desc(type)); 84 | if (ext_port) { 85 | printf("external address: %s:%d\n", ext_ip, ext_port); 86 | } else { 87 | return -1; 88 | } 89 | 90 | if (!punch_server) { 91 | printf("please specify punch server\n"); 92 | return -1; 93 | } 94 | struct peer_info self; 95 | strncpy(self.ip, ext_ip, 16); 96 | self.port = ext_port; 97 | self.type = type; 98 | 99 | struct sockaddr_in server_addr; 100 | server_addr.sin_family = AF_INET; 101 | server_addr.sin_addr.s_addr = inet_addr(punch_server); 102 | server_addr.sin_port = htons(DEFAULT_SERVER_PORT); 103 | 104 | client c; 105 | c.type = type; 106 | c.ttl = ttl; 107 | if (enroll(self, server_addr, &c) < 0) { 108 | printf("failed to enroll\n"); 109 | 110 | return -1; 111 | } 112 | printf("enroll successfully, ID: %d\n", c.id); 113 | 114 | if (peer_id) { 115 | printf("connecting to peer %d\n", peer_id); 116 | if (connect_to_peer(&c, peer_id) < 0) { 117 | printf("failed to connect to peer %d\n", peer_id); 118 | 119 | return -1; 120 | } 121 | } 122 | 123 | pthread_t tid = wait_for_command(&c.sfd); 124 | 125 | pthread_join(tid, NULL); 126 | return 0; 127 | } 128 | -------------------------------------------------------------------------------- /nat_traversal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "nat_traversal.h" 15 | 16 | #define MAX_PORT 65535 17 | #define MIN_PORT 1025 18 | #define NUM_OF_PORTS 700 19 | 20 | #define MSG_BUF_SIZE 512 21 | 22 | // file scope variables 23 | static int ports[MAX_PORT - MIN_PORT]; 24 | 25 | static int send_to_punch_server(client* c) { 26 | int n = send(c->sfd, c->buf, c->msg_buf - c->buf, 0); 27 | c->msg_buf= c->buf; 28 | 29 | return n; 30 | } 31 | 32 | static int get_peer_info(client* cli, uint32_t peer_id, struct peer_info *peer) { 33 | cli->msg_buf = encode16(cli->msg_buf, GetPeerInfo); 34 | cli->msg_buf = encode32(cli->msg_buf, peer_id); 35 | if (-1 == send_to_punch_server(cli)) { 36 | return -1; 37 | } 38 | 39 | int n_bytes = recv(cli->sfd, (void*)peer, sizeof(struct peer_info), 0); 40 | if (n_bytes <= 0) { 41 | return -1; 42 | } else if (n_bytes == 1) { 43 | // offline 44 | return 1; 45 | } else { 46 | peer->port = ntohs(peer->port); 47 | peer->type = ntohs(peer->type); 48 | 49 | return 0; 50 | } 51 | } 52 | 53 | static int send_dummy_udp_packet(int fd, struct sockaddr_in addr) { 54 | char dummy = 'c'; 55 | 56 | struct timeval tv = {5, 0}; 57 | setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)); 58 | 59 | return sendto(fd, &dummy, 1, 0, (struct sockaddr *)&addr, sizeof(addr)); 60 | } 61 | 62 | 63 | static int punch_hole(struct sockaddr_in peer_addr, int ttl) { 64 | int hole = socket(AF_INET, SOCK_DGRAM, 0); 65 | if (hole != -1) { 66 | //struct sockaddr_in local_addr; 67 | //local_addr.sin_family = AF_INET; 68 | //local_addr.sin_addr.s_addr = htonl(INADDR_ANY); 69 | //local_addr.sin_port = htons(DEFAULT_LOCAL_PORT + 1); 70 | //int reuse_addr = 1; 71 | //setsockopt(hole, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_addr, sizeof(reuse_addr)); 72 | //if (bind(hole, (struct sockaddr *)&local_addr, sizeof(local_addr))) { 73 | // if (errno == EADDRINUSE) { 74 | // printf("addr already in use, try another port\n"); 75 | // return -1; 76 | // } 77 | //} 78 | 79 | /* TODO we can use traceroute to get the number of hops to the peer 80 | * to make sure this packet woudn't reach the peer but get through the NAT in front of itself 81 | */ 82 | setsockopt(hole, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); 83 | 84 | // send short ttl packets to avoid triggering flooding protection of NAT in front of peer 85 | if (send_dummy_udp_packet(hole, peer_addr) < 0) { 86 | return -1; 87 | } 88 | } 89 | 90 | return hole; 91 | } 92 | 93 | static int wait_for_peer(int* socks, int sock_num, struct timeval *timeout) { 94 | fd_set fds; 95 | int max_fd = 0; 96 | FD_ZERO(&fds); 97 | 98 | int i; 99 | for (i = 0; i < sock_num; ++i) { 100 | FD_SET(socks[i], &fds); 101 | if (socks[i] > max_fd) { 102 | max_fd = socks[i]; 103 | } 104 | } 105 | int ret = select(max_fd + 1, &fds, NULL, NULL, timeout); 106 | 107 | int index = -1; 108 | if (ret > 0) { 109 | for (i = 0; i < sock_num; ++i) { 110 | if (FD_ISSET(socks[i], &fds)) { 111 | index = i; 112 | break; 113 | } 114 | } 115 | } else { 116 | // timeout or error 117 | } 118 | 119 | // one of the fds is ready, close others 120 | if (index != -1) { 121 | for (i = 0; i < sock_num; ++i) { 122 | if (index != i) { 123 | close(socks[i]); 124 | } 125 | } 126 | 127 | return socks[index]; 128 | } 129 | 130 | return -1; 131 | } 132 | 133 | static void shuffle(int *num, int len) { 134 | srand(time(NULL)); 135 | 136 | // Fisher-Yates shuffle algorithm 137 | int i, r, temp; 138 | for (i = len - 1; i > 0; i--) { 139 | r = rand() % i; 140 | 141 | temp = num[i]; 142 | num[i] = num[r]; 143 | num[r] = temp; 144 | } 145 | } 146 | 147 | static int connect_to_symmetric_nat(client* c, uint32_t peer_id, struct peer_info remote_peer) { 148 | // TODO choose port prediction strategy 149 | 150 | /* 151 | * according to birthday paradox, probability that port randomly chosen from [1024, 65535] 152 | * will collide with another one chosen by the same way is 153 | * p(n) = 1-(64511!/(64511^n*64511!)) 154 | * where '!' is the factorial operator, n is the number of ports chosen. 155 | * P(100)=0.073898 156 | * P(200)=0.265667 157 | * P(300)=0.501578 158 | * P(400)=0.710488 159 | * P(500)=0.856122 160 | * P(600)=0.938839 161 | * but symmetric NAT has port sensitive filter for incoming packet 162 | * which makes the probalility decline dramatically. 163 | * Moreover, symmetric NATs don't really allocate ports randomly. 164 | */ 165 | struct sockaddr_in peer_addr; 166 | 167 | peer_addr.sin_family = AF_INET; 168 | peer_addr.sin_addr.s_addr = inet_addr(remote_peer.ip); 169 | 170 | int *holes = malloc(NUM_OF_PORTS * sizeof(int)); 171 | shuffle(ports, MAX_PORT - MIN_PORT + 1); 172 | 173 | int i = 0; 174 | for (; i < NUM_OF_PORTS;) { 175 | uint16_t port = ports[i]; 176 | if (port != remote_peer.port) { // exclude the used one 177 | peer_addr.sin_port = htons(port); 178 | 179 | if ((holes[i] = punch_hole(peer_addr, c->ttl)) < 0) { 180 | // NAT in front of us wound't tolerate too many ports used by one application 181 | verbose_log("failed to punch hole, error: %s\n", strerror(errno)); 182 | break; 183 | } 184 | // sleep for a while to avoid flooding protection 185 | usleep(1000 * 100); 186 | ++i; 187 | } else { 188 | ports[i] = ports[1000]; 189 | continue; 190 | } 191 | } 192 | 193 | // hole punched, notify remote peer via punch server 194 | c->msg_buf = encode16(c->msg_buf, NotifyPeer); 195 | c->msg_buf = encode32(c->msg_buf, peer_id); 196 | send_to_punch_server(c); 197 | 198 | struct timeval timeout={100, 0}; 199 | int fd = wait_for_peer(holes, i, &timeout); 200 | if (fd > 0) { 201 | on_connected(fd); 202 | } else { 203 | int j = 0; 204 | for (; j < i; ++j) { 205 | close(holes[j]); 206 | } 207 | printf("timout, not connected\n"); 208 | } 209 | 210 | return 0; 211 | } 212 | 213 | // run in another thread 214 | static void* server_notify_handler(void* data) { 215 | int server_sock = *(int*)data; 216 | struct peer_info peer; 217 | 218 | // wait for notification 219 | printf("waiting for notification...\n"); 220 | for (; ;) { 221 | if (recv(server_sock, &peer, sizeof peer, 0) > 0) { 222 | break; 223 | } 224 | } 225 | 226 | peer.port = ntohs(peer.port); 227 | peer.type = ntohs(peer.type); 228 | 229 | printf("recved command, ready to connect to %s:%d\n", peer.ip, peer.port); 230 | 231 | struct sockaddr_in peer_addr; 232 | 233 | peer_addr.sin_family = AF_INET; 234 | peer_addr.sin_addr.s_addr = inet_addr(peer.ip); 235 | 236 | int sock_array[NUM_OF_PORTS]; 237 | int i = 0; 238 | 239 | shuffle(ports, MAX_PORT - MIN_PORT + 1); 240 | // send probe packets, check if connected with peer, if yes, stop probing 241 | for (; i < NUM_OF_PORTS;) { 242 | if (ports[i] == peer.port) { 243 | ports[i] = ports[1000]; // TODO 244 | continue; 245 | } 246 | if ((sock_array[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 247 | printf("failed to create socket, send %d probe packets\n", i); 248 | break; 249 | } 250 | 251 | peer_addr.sin_port = htons(ports[i]); 252 | 253 | // let OS choose available ports 254 | if (send_dummy_udp_packet(sock_array[i], peer_addr) < 0) { 255 | printf("may trigger flooding protection\n"); 256 | break; 257 | } 258 | 259 | // wait for a while 260 | struct timeval tv = {0, 1000 * 100}; 261 | int fd = wait_for_peer(sock_array, ++i, &tv); 262 | if (fd > 0) { 263 | // connected 264 | on_connected(fd); 265 | 266 | // TODO 267 | return NULL; 268 | } 269 | } 270 | 271 | printf("holes punched, waiting for peer\n"); 272 | struct timeval tv = {100, 0}; 273 | int fd = wait_for_peer(sock_array, i, &tv); 274 | if (fd > 0) { 275 | on_connected(fd); 276 | } else { 277 | int j = 0; 278 | for (j = 0; j < i; ++j) { 279 | close(sock_array[j]); 280 | } 281 | } 282 | 283 | // TODO wait for next notification 284 | 285 | return NULL; 286 | } 287 | 288 | int enroll(struct peer_info self, struct sockaddr_in punch_server, client* c) { 289 | int i, temp; 290 | for (i = 0, temp = MIN_PORT; temp <= MAX_PORT; i++, temp++) { 291 | ports[i] = temp; 292 | } 293 | 294 | int server_sock = socket(AF_INET, SOCK_STREAM, 0); 295 | 296 | if (connect(server_sock, (struct sockaddr *)&punch_server, sizeof(punch_server)) < 0) { 297 | printf("failed to connect to punch server\n"); 298 | 299 | return -1; 300 | } 301 | 302 | c->sfd = server_sock; 303 | c->msg_buf = c->buf; 304 | c->msg_buf = encode16(c->msg_buf, Enroll); 305 | c->msg_buf = encode(c->msg_buf, self.ip, 16); 306 | c->msg_buf = encode16(c->msg_buf, self.port); 307 | c->msg_buf = encode16(c->msg_buf, self.type); 308 | 309 | if (-1 == send_to_punch_server(c)) { 310 | return -1; 311 | } 312 | 313 | // wait for server reply to get own ID 314 | uint32_t peer_id = 0; 315 | struct timeval tv; 316 | tv.tv_sec = 5; 317 | tv.tv_usec = 0; 318 | setsockopt(server_sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); 319 | int n = recv(server_sock, &peer_id, sizeof(uint32_t), 0); 320 | if (n != sizeof(uint32_t)) { 321 | return -1; 322 | } 323 | 324 | c->id = ntohl(peer_id); 325 | 326 | return 0; 327 | } 328 | 329 | pthread_t wait_for_command(int* server_sock) 330 | { 331 | // wait for command from punch server in another thread 332 | pthread_t thread_id; 333 | pthread_create(&thread_id, NULL, server_notify_handler, (void*)server_sock); 334 | 335 | return thread_id; 336 | } 337 | 338 | void on_connected(int sock) { 339 | char buf[MSG_BUF_SIZE] = {0}; 340 | struct sockaddr_in remote_addr; 341 | socklen_t fromlen = sizeof remote_addr; 342 | recvfrom(sock, buf, MSG_BUF_SIZE, 0, (struct sockaddr *)&remote_addr, &fromlen); 343 | printf("recv %s\n", buf); 344 | 345 | printf("connected with peer from %s:%d\n", inet_ntoa(remote_addr.sin_addr), ntohs(remote_addr.sin_port)); 346 | 347 | // restore the ttl 348 | int ttl = 64; 349 | setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); 350 | sendto(sock, "hello, peer", strlen("hello, peer"), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr)); 351 | } 352 | 353 | int connect_to_peer(client* cli, uint32_t peer_id) { 354 | struct peer_info peer; 355 | int n = get_peer_info(cli, peer_id, &peer); 356 | if (n) { 357 | verbose_log("get_peer_info() return %d\n", n); 358 | printf("failed to get info of remote peer\n"); 359 | 360 | return -1; 361 | } 362 | 363 | printf("peer %d: %s:%d, nat type: %s\n", peer_id, peer.ip, peer.port, get_nat_desc(peer.type)); 364 | 365 | // choose less restricted peer as initiator 366 | switch(peer.type) { 367 | case OpenInternet: 368 | // todo 369 | break; 370 | case FullCone: 371 | break; 372 | case RestricNAT: 373 | // todo 374 | break; 375 | case RestricPortNAT: 376 | // todo 377 | break; 378 | case SymmetricNAT: 379 | if (cli->type == SymmetricNAT) { 380 | connect_to_symmetric_nat(cli, peer_id, peer); 381 | } 382 | else { 383 | // todo 384 | } 385 | break; 386 | default: 387 | printf("unknown nat type\n"); 388 | return -1; 389 | // log 390 | } 391 | 392 | return 0; 393 | } 394 | 395 | -------------------------------------------------------------------------------- /nat_traversal.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "nat_type.h" 4 | 5 | typedef struct client client; 6 | struct client { 7 | int sfd; 8 | uint32_t id; 9 | char buf[128]; 10 | //use a stack-based buffer to prevent memory allocation every time 11 | char* msg_buf; 12 | nat_type type; 13 | char ext_ip[16]; 14 | uint16_t ext_port; 15 | // ttl of hole punching packets, 16 | // it should be greater than the number of hops between host to NAT of own side 17 | // and less than the number of hops between host to NAT of remote side, 18 | // so that the hole punching packets just die in the way 19 | int ttl; 20 | }; 21 | 22 | struct peer_info { 23 | char ip[16]; 24 | uint16_t port; 25 | uint16_t type; 26 | }; 27 | 28 | enum msg_type { 29 | Enroll = 0x01, 30 | GetPeerInfo = 0x02, 31 | NotifyPeer = 0x03, 32 | }; 33 | 34 | // public functions 35 | int enroll(struct peer_info self, struct sockaddr_in punch_server, client* c); 36 | pthread_t wait_for_command(int* server_sock); 37 | int connect_to_peer(client* cli, uint32_t peer_id); 38 | void on_connected(int sock); 39 | -------------------------------------------------------------------------------- /nat_type.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "nat_type.h" 13 | 14 | #define MAX_RETRIES_NUM 3 15 | 16 | static const char* nat_types[] = { 17 | "blocked", 18 | "open internet", 19 | "full cone", 20 | "restricted NAT", 21 | "port-restricted cone", 22 | "symmetric NAT", 23 | "error" 24 | }; 25 | 26 | char* encode16(char* buf, uint16_t data) 27 | { 28 | uint16_t ndata = htons(data); 29 | memcpy(buf, (void*)(&ndata), sizeof(uint16_t)); 30 | return buf + sizeof(uint16_t); 31 | } 32 | 33 | char* encode32(char* buf, uint32_t data) 34 | { 35 | uint32_t ndata = htonl(data); 36 | memcpy(buf, (void*)(&ndata), sizeof(uint32_t)); 37 | 38 | return buf + sizeof(uint32_t); 39 | } 40 | 41 | char* encodeAtrUInt32(char* ptr, uint16_t type, uint32_t value) 42 | { 43 | ptr = encode16(ptr, type); 44 | ptr = encode16(ptr, 4); 45 | ptr = encode32(ptr, value); 46 | 47 | return ptr; 48 | } 49 | 50 | char* encode(char* buf, const char* data, unsigned int length) 51 | { 52 | memcpy(buf, data, length); 53 | return buf + length; 54 | } 55 | 56 | static int stun_parse_atr_addr( char* body, unsigned int hdrLen, StunAtrAddress* result ) 57 | { 58 | if (hdrLen != 8 /* ipv4 size */ && hdrLen != 20 /* ipv6 size */ ) { 59 | return -1; 60 | } 61 | body++; // Skip pad 62 | result->family = *body++; 63 | 64 | uint16_t nport; 65 | memcpy(&nport, body, 2); 66 | body+=2; 67 | result->port = ntohs(nport); 68 | 69 | if (result->family == IPv4Family) { 70 | uint32_t naddr; 71 | memcpy(&naddr, body, sizeof(uint32_t)); body+=sizeof(uint32_t); 72 | result->addr.ipv4 = ntohl(naddr); 73 | // Note: addr.ipv4 is stored in host byte order 74 | return 0; 75 | } else if (result->family == IPv6Family) { 76 | printf("ipv6 is not implemented yet"); 77 | } 78 | 79 | return -1; 80 | } 81 | 82 | static void gen_random_string(char *s, const int len) { 83 | static const char alphanum[] = 84 | "0123456789" 85 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 86 | "abcdefghijklmnopqrstuvwxyz"; 87 | 88 | int i = 0; 89 | for (; i < len; ++i) { 90 | s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; 91 | } 92 | } 93 | 94 | static int send_bind_request(int sock, const char* remote_host, uint16_t remote_port, uint32_t change_ip, uint32_t change_port, StunAtrAddress* addr_array) { 95 | char* buf = malloc(MAX_STUN_MESSAGE_LENGTH); 96 | char* ptr = buf; 97 | 98 | StunHeader h; 99 | h.msgType = BindRequest; 100 | 101 | gen_random_string((char*)&h.magicCookieAndTid, 16); 102 | 103 | ptr = encode16(ptr, h.msgType); 104 | char* lengthp = ptr; 105 | ptr = encode16(ptr, 0); 106 | ptr = encode(ptr, (const char*)&h.id, sizeof(h.id)); 107 | 108 | if (change_ip || change_port) { 109 | ptr = encodeAtrUInt32(ptr, ChangeRequest, change_ip | change_port); 110 | 111 | // length of stun body 112 | encode16(lengthp, ptr - buf - sizeof(StunHeader)); 113 | } 114 | 115 | struct hostent *server = gethostbyname(remote_host); 116 | if (server == NULL) { 117 | fprintf(stderr, "no such host, %s\n", remote_host); 118 | free(buf); 119 | 120 | return -1; 121 | } 122 | struct sockaddr_in remote_addr; 123 | 124 | remote_addr.sin_family = AF_INET; 125 | memcpy(&remote_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length); 126 | remote_addr.sin_port = htons(remote_port); 127 | 128 | int retries; 129 | for (retries = 0; retries < MAX_RETRIES_NUM; retries++) { 130 | if (-1 == sendto(sock, buf, ptr - buf, 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr))) { 131 | // sendto() barely failed 132 | free(buf); 133 | 134 | return -1; 135 | } 136 | 137 | socklen_t fromlen = sizeof remote_addr; 138 | 139 | struct timeval tv; 140 | tv.tv_sec = 3; 141 | tv.tv_usec = 0; 142 | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); 143 | 144 | if (recvfrom(sock, buf, 512, 0, (struct sockaddr *)&remote_addr, &fromlen) <= 0) { 145 | if (errno != EAGAIN || errno != EWOULDBLOCK) { 146 | free(buf); 147 | 148 | return -1; 149 | } 150 | //timout, retry 151 | } else { 152 | // got response 153 | break; 154 | } 155 | } 156 | 157 | if (retries == MAX_RETRIES_NUM) 158 | return -1; 159 | 160 | StunHeader reply_header; 161 | memcpy(&reply_header, buf, sizeof(StunHeader)); 162 | 163 | uint16_t msg_type = ntohs(reply_header.msgType); 164 | 165 | if (msg_type == BindResponse) { 166 | char* body = buf + sizeof(StunHeader); 167 | uint16_t size = ntohs(reply_header.msgLength); 168 | 169 | StunAtrHdr* attr; 170 | unsigned int attrLen; 171 | unsigned int attrLenPad; 172 | int atrType; 173 | 174 | while (size > 0) { 175 | attr = (StunAtrHdr*)(body); 176 | 177 | attrLen = ntohs(attr->length); 178 | // attrLen may not be on 4 byte boundary, in which case we need to pad to 4 bytes when advancing to next attribute 179 | attrLenPad = attrLen % 4 == 0 ? 0 : 4 - (attrLen % 4); 180 | atrType = ntohs(attr->type); 181 | 182 | if ( attrLen + attrLenPad + 4 > size ) { 183 | free(buf); 184 | 185 | return -1; 186 | } 187 | 188 | body += 4; // skip the length and type in attribute header 189 | size -= 4; 190 | 191 | switch (atrType) { 192 | case MappedAddress: 193 | if (stun_parse_atr_addr(body, attrLen, addr_array)) { 194 | free(buf); 195 | 196 | return -1; 197 | } 198 | break; 199 | case ChangedAddress: 200 | if (stun_parse_atr_addr( body, attrLen, addr_array + 1)) { 201 | free(buf); 202 | 203 | return -1; 204 | } 205 | break; 206 | default: 207 | // ignore other attributes 208 | break; 209 | } 210 | body += attrLen + attrLenPad; 211 | size -= attrLen + attrLenPad; 212 | } 213 | } 214 | 215 | free(buf); 216 | 217 | return 0; 218 | } 219 | 220 | const char* get_nat_desc(nat_type type) { 221 | return nat_types[type]; 222 | } 223 | 224 | nat_type detect_nat_type(const char* stun_host, uint16_t stun_port, const char* local_ip, uint16_t local_port, char* ext_ip, uint16_t* ext_port) { 225 | uint32_t mapped_ip = 0; 226 | uint16_t mapped_port = 0; 227 | int s = socket(AF_INET, SOCK_DGRAM, 0); 228 | if (s <= 0) { 229 | return Error; 230 | } 231 | 232 | nat_type nat_type; 233 | 234 | int reuse_addr = 1; 235 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_addr, sizeof(reuse_addr)); 236 | 237 | struct sockaddr_in local_addr; 238 | local_addr.sin_family = AF_INET; 239 | local_addr.sin_addr.s_addr = inet_addr(local_ip); 240 | 241 | local_addr.sin_port = htons(local_port); 242 | if (bind(s, (struct sockaddr *)&local_addr, sizeof(local_addr))) { 243 | if (errno == EADDRINUSE) { 244 | printf("addr in use, try another port\n"); 245 | nat_type = Error; 246 | goto cleanup_sock; 247 | } 248 | } 249 | 250 | // 0 for mapped addr, 1 for changed addr 251 | StunAtrAddress bind_result[2]; 252 | 253 | memset(bind_result, 0, sizeof(StunAtrAddress) * 2); 254 | if (send_bind_request(s, stun_host, stun_port, 0, 0, bind_result)) { 255 | nat_type = Blocked; 256 | goto cleanup_sock; 257 | } 258 | 259 | mapped_ip = bind_result[0].addr.ipv4; // in host byte order 260 | mapped_port = bind_result[0].port; 261 | uint32_t changed_ip = bind_result[1].addr.ipv4; 262 | uint16_t changed_port = bind_result[1].port; 263 | 264 | struct in_addr mapped_addr; 265 | mapped_addr.s_addr = htonl(mapped_ip); 266 | 267 | /* 268 | * it's complicated to get the RECEIVER address of UDP packet, 269 | * For Linux/Windows, set IP_PKTINFO option to enable ancillary 270 | * message that contains a pktinfo structure that supplies 271 | * some information about the incoming packets 272 | */ 273 | 274 | /* TODO use getifaddrs() to get interface address, 275 | * then compare it with mapped address to determine 276 | * if it's open Internet 277 | */ 278 | 279 | if (!strcmp(local_ip, inet_ntoa(mapped_addr))) { 280 | nat_type = OpenInternet; 281 | goto cleanup_sock; 282 | } else { 283 | if (changed_ip != 0 && changed_port != 0) { 284 | if (send_bind_request(s, stun_host, stun_port, ChangeIpFlag, ChangePortFlag, bind_result)) { 285 | struct in_addr addr = {htonl(changed_ip)}; 286 | char* alt_host = inet_ntoa(addr); 287 | 288 | memset(bind_result, 0, sizeof(StunAtrAddress) * 2); 289 | 290 | if (send_bind_request(s, alt_host, changed_port, 0, 0, bind_result)) { 291 | printf("failed to send request to alterative server\n"); 292 | nat_type = Error; 293 | goto cleanup_sock; 294 | } 295 | 296 | if (mapped_ip != bind_result[0].addr.ipv4 || mapped_port != bind_result[0].port) { 297 | nat_type = SymmetricNAT; 298 | goto cleanup_sock; 299 | } 300 | 301 | if (send_bind_request(s, alt_host, changed_port, 0, ChangePortFlag, bind_result)) { 302 | nat_type = RestricPortNAT; 303 | goto cleanup_sock; 304 | } 305 | 306 | nat_type = RestricNAT; 307 | goto cleanup_sock; 308 | } 309 | else { 310 | nat_type = FullCone; 311 | goto cleanup_sock; 312 | } 313 | } else { 314 | printf("no alterative server, can't detect nat type\n"); 315 | nat_type = Error; 316 | goto cleanup_sock; 317 | } 318 | } 319 | cleanup_sock: 320 | close(s); 321 | struct in_addr ext_addr; 322 | ext_addr.s_addr = htonl(mapped_ip); 323 | strcpy(ext_ip, inet_ntoa(ext_addr)); 324 | *ext_port = mapped_port; 325 | 326 | return nat_type; 327 | } 328 | -------------------------------------------------------------------------------- /nat_type.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | Blocked, 5 | OpenInternet, 6 | FullCone, 7 | RestricNAT, 8 | RestricPortNAT, 9 | SymmetricNAT, 10 | Error, 11 | } nat_type; 12 | 13 | #define DEFAULT_STUN_SERVER_PORT 3478 14 | #define DEFAULT_LOCAL_PORT 34780 15 | #define MAX_STUN_MESSAGE_LENGTH 512 16 | 17 | // const static constants cannot be used in case label 18 | #define MappedAddress 0x0001 19 | #define SourceAddress 0x0004 20 | #define ChangedAddress 0x0005 21 | 22 | // define stun constants 23 | const static uint8_t IPv4Family = 0x01; 24 | const static uint8_t IPv6Family = 0x02; 25 | 26 | const static uint32_t ChangeIpFlag = 0x04; 27 | const static uint32_t ChangePortFlag = 0x02; 28 | 29 | const static uint16_t BindRequest = 0x0001; 30 | const static uint16_t BindResponse = 0x0101; 31 | 32 | const static uint16_t ResponseAddress = 0x0002; 33 | const static uint16_t ChangeRequest = 0x0003; /* removed from rfc 5389.*/ 34 | const static uint16_t MessageIntegrity = 0x0008; 35 | const static uint16_t ErrorCode = 0x0009; 36 | const static uint16_t UnknownAttribute = 0x000A; 37 | const static uint16_t XorMappedAddress = 0x0020; 38 | 39 | typedef struct { uint32_t longpart[4]; } UInt128; 40 | typedef struct { uint32_t longpart[3]; } UInt96; 41 | 42 | typedef struct 43 | { 44 | uint32_t magicCookie; // rfc 5389 45 | UInt96 tid; 46 | } Id; 47 | 48 | typedef struct 49 | { 50 | uint16_t msgType; 51 | uint16_t msgLength; // length of stun body 52 | union 53 | { 54 | UInt128 magicCookieAndTid; 55 | Id id; 56 | }; 57 | } StunHeader; 58 | 59 | typedef struct 60 | { 61 | uint16_t type; 62 | uint16_t length; 63 | } StunAtrHdr; 64 | 65 | typedef struct 66 | { 67 | uint8_t family; 68 | uint16_t port; 69 | union 70 | { 71 | uint32_t ipv4; // in host byte order 72 | UInt128 ipv6; // in network byte order 73 | } addr; 74 | } StunAtrAddress; 75 | 76 | char* encode16(char* buf, uint16_t data); 77 | char* encode32(char* buf, uint32_t data); 78 | char* encode(char* buf, const char* data, unsigned int length); 79 | extern int verbose; 80 | 81 | #define verbose_log(format, ...) do { \ 82 | if (verbose) \ 83 | printf(format, ##__VA_ARGS__); \ 84 | } while(0) 85 | 86 | nat_type detect_nat_type(const char* stun_host, uint16_t stun_port, const char* local_host, uint16_t local_port, char* ext_ip, uint16_t* ext_port); 87 | 88 | const char* get_nat_desc(nat_type type); 89 | -------------------------------------------------------------------------------- /punch_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type nat_info struct { 11 | Ip [16]byte 12 | Port uint16 13 | Nat_type uint16 14 | } 15 | 16 | const ( 17 | Enroll = 1 18 | GetPeerInfo = 2 19 | NotifyPeer = 3 20 | ) 21 | 22 | var seq uint32 = 1 23 | var peers map[uint32]nat_info 24 | var peers_conn map[uint32]net.Conn 25 | var m sync.Mutex 26 | 27 | func main() { 28 | peers = make(map[uint32]nat_info) 29 | peers_conn = make(map[uint32]net.Conn) 30 | 31 | l, _ := net.Listen("tcp", ":9988") 32 | 33 | defer l.Close() 34 | 35 | for { 36 | conn, err := l.Accept() 37 | if err != nil { 38 | continue 39 | } 40 | 41 | go handleConn(conn) 42 | } 43 | } 44 | 45 | // 2 bytes for message type 46 | func handleConn(c net.Conn) { 47 | defer c.Close() 48 | var peerID uint32 = 0 49 | for { 50 | // read message type first 51 | data := make([]byte, 2) 52 | _, err := c.Read(data) 53 | if err != nil { 54 | m.Lock() 55 | fmt.Printf("error: %v, peer %d disconnected\n", err, peerID) 56 | delete(peers, peerID) 57 | delete(peers_conn, peerID) 58 | m.Unlock() 59 | return 60 | } 61 | 62 | switch binary.BigEndian.Uint16(data[:]) { 63 | case Enroll: 64 | var peer nat_info 65 | err = binary.Read(c, binary.BigEndian, &peer) 66 | if err != nil { 67 | continue 68 | } 69 | 70 | fmt.Println("peer enrolled, addr: ", string(peer.Ip[:]), peer.Port, peer.Nat_type) 71 | 72 | m.Lock() 73 | seq++ 74 | peerID = seq 75 | peers[peerID] = peer 76 | peers_conn[peerID] = c 77 | fmt.Println("new peer, id : ", peerID) 78 | m.Unlock() 79 | err = binary.Write(c, binary.BigEndian, peerID) 80 | if err != nil { 81 | continue 82 | } 83 | case GetPeerInfo: 84 | var peer_id uint32 85 | binary.Read(c, binary.BigEndian, &peer_id) 86 | if val, ok := peers[peer_id]; ok { 87 | binary.Write(c, binary.BigEndian, val) 88 | } else { 89 | var offline uint8 = 0 90 | binary.Write(c, binary.BigEndian, offline) 91 | fmt.Printf("%d offline\n", peer_id) 92 | } 93 | case NotifyPeer: 94 | var peer_id uint32 95 | binary.Read(c, binary.BigEndian, &peer_id) 96 | fmt.Println("notify to peer", peer_id) 97 | if val, ok := peers_conn[peer_id]; ok { 98 | if err = binary.Write(val, binary.BigEndian, peers[peerID]); err != nil { 99 | // unable to notify peer 100 | fmt.Println("offline") 101 | } 102 | } else { 103 | fmt.Println("offline") 104 | } 105 | default: 106 | fmt.Println("illegal message") 107 | } 108 | } 109 | 110 | return 111 | } 112 | --------------------------------------------------------------------------------