1 /* $Id: upnpstun.c,v 1.5 2020/04/21 21:21:59 nanard Exp $ */
2 /* vim: tabstop=4 shiftwidth=4 noexpandtab
3 * MiniUPnP project
4 * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
5 * (c) 2020 Thomas Bernard
6 * (c) 2018 Pali Rohár
7 * This software is subject to the conditions detailed
8 * in the LICENCE file provided within the distribution */
9
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <unistd.h>
16 #include <netdb.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <syslog.h>
20 #include <string.h>
21 #include <errno.h>
22
23 #ifndef TEST_LINUX_DEBUG_APP
24 #include "config.h"
25 #endif
26
27 #include "upnputils.h"
28 #include "upnpstun.h"
29
30 #if defined(USE_NETFILTER)
31 #include "netfilter/iptcrdr.h"
32 #endif
33 #if defined(USE_PF)
34 #include "pf/obsdrdr.h"
35 #endif
36 #if defined(USE_IPF)
37 #include "ipf/ipfrdr.h"
38 #endif
39 #if defined(USE_IPFW)
40 #include "ipfw/ipfwrdr.h"
41 #endif
42
43 #ifdef TEST_LINUX_DEBUG_APP
44 static int add_filter_rule2(const char *ifname, const char *rhost, const char *iaddr, unsigned short eport, unsigned short iport, int proto, const char *desc);
45 static int delete_filter_rule(const char * ifname, unsigned short port, int proto);
46 #define syslog(priority, format, ...) do { switch(priority) { case LOG_ERR: printf("Error: "); break; case LOG_WARNING: printf("Warning: "); } printf(format, ##__VA_ARGS__); putchar('\n'); } while (0)
47 #endif
48
49 /* Generate random STUN Transaction Id */
generate_transaction_id(unsigned char transaction_id[12])50 static void generate_transaction_id(unsigned char transaction_id[12])
51 {
52 size_t i;
53
54 for (i = 0; i < 12; i++)
55 transaction_id[i] = random() & 255;
56 }
57
58 /* Create and fill STUN Binding Request */
fill_request(unsigned char buffer[28],int change_ip,int change_port)59 static void fill_request(unsigned char buffer[28], int change_ip, int change_port)
60 {
61 /* Type: Binding Request */
62 buffer[0] = 0x00;
63 buffer[1] = 0x01;
64
65 /* Length: One 8-byte attribute */
66 buffer[2] = 0x00;
67 buffer[3] = 0x08;
68
69 /* RFC5389 Magic Cookie: 0x2120A442 */
70 buffer[4] = 0x21;
71 buffer[5] = 0x12;
72 buffer[6] = 0xA4;
73 buffer[7] = 0x42;
74
75 /* Transaction Id */
76 generate_transaction_id(buffer+8);
77
78 /* Attribute Type: Change Request */
79 buffer[20] = 0x00;
80 buffer[21] = 0x03;
81
82 /* Attribute Length: 4 bytes */
83 buffer[22] = 0x00;
84 buffer[23] = 0x04;
85
86 buffer[24] = 0x00;
87 buffer[25] = 0x00;
88 buffer[26] = 0x00;
89 buffer[27] = 0x00;
90
91 /* Change IP */
92 buffer[27] |= change_ip ? 0x4 : 0x00;
93
94 /* Change Port */
95 buffer[27] |= change_port ? 0x2 : 0x00;
96 }
97
98 /* Resolve STUN host+port and return sockaddr_in structure */
99 /* When port is 0 then use default STUN port */
resolve_stun_host(const char * stun_host,unsigned short stun_port,struct sockaddr_in * sock_addr)100 static int resolve_stun_host(const char *stun_host, unsigned short stun_port, struct sockaddr_in *sock_addr)
101 {
102 int have_sock;
103 struct addrinfo hints;
104 struct addrinfo *result, *rp;
105 char service[6];
106 int r;
107
108 if (stun_port == 0)
109 stun_port = (unsigned short)3478;
110 snprintf(service, sizeof(service), "%hu", stun_port);
111
112 memset(&hints, 0, sizeof(hints));
113 hints.ai_family = AF_INET;
114 hints.ai_socktype = SOCK_DGRAM;
115 hints.ai_protocol = IPPROTO_UDP;
116 hints.ai_flags = AI_NUMERICSERV;
117
118 r = getaddrinfo(stun_host, service, &hints, &result);
119 if (r != 0) {
120 syslog(LOG_ERR, "%s: getaddrinfo(%s, %s, ...) failed : %s",
121 "resolve_stun_host", stun_host, service, gai_strerror(r));
122 errno = EHOSTUNREACH;
123 return -1;
124 }
125
126 have_sock = 0;
127 for (rp = result; rp != NULL; rp = rp->ai_next) {
128 if (rp->ai_addrlen > sizeof(*sock_addr) || rp->ai_addr->sa_family != AF_INET)
129 continue;
130 memcpy(sock_addr, rp->ai_addr, rp->ai_addrlen);
131 have_sock = 1;
132 break;
133 }
134
135 freeaddrinfo(result);
136
137 if (!have_sock) {
138 syslog(LOG_WARNING, "%s: failed to resolve IPv4 address for %s:%s",
139 "resolve_stun_host", stun_host, service);
140 errno = EHOSTUNREACH;
141 return -1;
142 } else {
143 char addr_str[48];
144 if (sockaddr_to_string((struct sockaddr *)sock_addr, addr_str, sizeof(addr_str)) > 0) {
145 syslog(LOG_DEBUG, "%s: %s:%s => %s",
146 "resolve_stun_host", stun_host, service, addr_str);
147 }
148 }
149
150 return 0;
151 }
152
153 /* Create a new UDP socket for STUN connection and return file descriptor and local UDP port */
stun_socket(unsigned short * local_port)154 static int stun_socket(unsigned short *local_port)
155 {
156 int fd;
157 socklen_t addr_len;
158 struct sockaddr_in local_addr;
159
160 fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
161 if (fd < 0) {
162 syslog(LOG_ERR, "%s: socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP): %m",
163 "stun_socket");
164 return -1;
165 }
166
167 memset(&local_addr, 0, sizeof(local_addr));
168 local_addr.sin_family = AF_INET;
169 local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
170 local_addr.sin_port = 0;
171
172 if (bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) != 0) {
173 syslog(LOG_ERR, "%s: bind(): %m",
174 "stun_socket");
175 close(fd);
176 return -1;
177 }
178
179 addr_len = sizeof(local_addr);
180 if (getsockname(fd, (struct sockaddr *)&local_addr, &addr_len) != 0) {
181 syslog(LOG_ERR, "%s: getsockname(): %m",
182 "stun_socket");
183 close(fd);
184 return -1;
185 }
186
187 *local_port = ntohs(local_addr.sin_port);
188
189 return fd;
190 }
191
192 /* Receive STUN response message for specified Transaction Id and returns message and peer address */
receive_stun_response(int fd,unsigned char * buffer,unsigned char transaction_id[12],size_t buffer_len,struct sockaddr_in * peer_addr)193 static size_t receive_stun_response(int fd, unsigned char *buffer, unsigned char transaction_id[12], size_t buffer_len, struct sockaddr_in *peer_addr)
194 {
195 ssize_t len;
196 socklen_t peer_addr_len = sizeof(*peer_addr);
197
198 len = recvfrom(fd, buffer, buffer_len, 0, (struct sockaddr *)peer_addr, &peer_addr_len);
199 if (len < 0) {
200 syslog(LOG_ERR, "%s: recvfrom(): %m", "receive_stun_response");
201 return 0;
202 }
203 if (peer_addr_len != sizeof(*peer_addr)) {
204 syslog(LOG_ERR, "%s: recvfrom(): peer_addr_len mismatch",
205 "receive_stun_response");
206 return 0;
207 }
208 if (len < 20) {
209 syslog(LOG_WARNING, "%s: response too short : %ld",
210 "receive_stun_response", (long)len);
211 return 0;
212 }
213
214 /* Check that buffer is STUN message with class Response
215 * and Binding method with transaction id */
216 if ((buffer[0] & 0xFF) != 0x01 || (buffer[1] & 0xEF) != 0x01) {
217 syslog(LOG_WARNING, "%s: STUN message type is 0x%02x%02x",
218 "receive_stun_response", buffer[0], buffer[1]);
219 return 0;
220 }
221 if (memcmp(buffer+8, transaction_id, 12) != 0) {
222 syslog(LOG_WARNING, "%s: transaction id mismatch",
223 "receive_stun_response");
224 return 0;
225 }
226
227 return len;
228 }
229
230 /* Wait for STUN response messages and try to receive them */
wait_for_stun_responses(int fds[4],unsigned char * transaction_ids[4],unsigned char * buffers[4],size_t buffers_lens[4],struct sockaddr_in peer_addrs[4],size_t lens[4])231 static int wait_for_stun_responses(int fds[4], unsigned char *transaction_ids[4], unsigned char *buffers[4], size_t buffers_lens[4], struct sockaddr_in peer_addrs[4], size_t lens[4])
232 {
233 fd_set fdset;
234 struct timeval timeout;
235 int max_fd;
236 int ret;
237 int i;
238
239 max_fd = fds[0];
240 for (i = 1; i < 4; i++) {
241 if (fds[i] > max_fd)
242 max_fd = fds[i];
243 }
244
245 timeout.tv_sec = 3;
246 timeout.tv_usec = 0;
247
248 while (timeout.tv_sec > 0 || timeout.tv_usec > 0) {
249
250 FD_ZERO(&fdset);
251 for (i = 0; i < 4; i++) {
252 FD_SET(fds[i], &fdset);
253 }
254
255 syslog(LOG_DEBUG, "%s: waiting %ld secs and %ld usecs", "wait_for_stun_responses", (long)timeout.tv_sec, (long)timeout.tv_usec);
256 ret = select(max_fd+1, &fdset, NULL, NULL, &timeout);
257 if (ret < 0) {
258 if (errno == EINTR)
259 continue;
260 syslog(LOG_ERR, "%s: select(): %m", "wait_for_stun_responses");
261 return -1;
262 }
263 if (ret == 0) {
264 syslog(LOG_DEBUG, "%s: select(): no more responses", "wait_for_stun_responses");
265 return 0;
266 }
267
268 for (i = 0; i < 4; ++i)
269 if (FD_ISSET(fds[i], &fdset))
270 lens[i] = receive_stun_response(fds[i], buffers[i], transaction_ids[i], buffers_lens[i], &peer_addrs[i]);
271
272 syslog(LOG_DEBUG, "%s: received responses: %u", "wait_for_stun_responses", (unsigned)(!!lens[0] + !!lens[1] + !!lens[2] + !!lens[3]));
273
274 if (lens[0] && lens[1] && lens[2] && lens[3])
275 return 0;
276 }
277
278 return 0;
279 }
280
281 /* Parse Mapped Address (with port) from STUN response message */
parse_stun_response(unsigned char * buffer,size_t len,struct sockaddr_in * mapped_addr)282 static int parse_stun_response(unsigned char *buffer, size_t len, struct sockaddr_in *mapped_addr)
283 {
284 unsigned char *ptr, *end;
285 uint16_t attr_type;
286 uint16_t attr_len;
287 int have_address;
288 int have_xor_mapped_address;
289
290 if (len < 20)
291 return -1;
292
293 syslog(LOG_DEBUG, "%s: Type 0x%04x, Length %hu, Magic Cookie %02x%02x%02x%02x",
294 "parse_stun_response", ((uint16_t)buffer[0] << 8) + buffer[1],
295 (uint16_t)((buffer[2] << 8) + buffer[3]),
296 buffer[4], buffer[5], buffer[6], buffer[7]);
297
298 /* Check that buffer is STUN message with class Response and Binding method */
299 if (buffer[0] != 0x01 || (buffer[1] & 0xEF) != 0x01)
300 return -1;
301
302 /* Check that STUN message is not longer as buffer length */
303 if (((size_t)buffer[2] << 8) + buffer[3] + 20 > len) {
304 syslog(LOG_ERR, "%s: truncated STUN response", "parse_stun_response");
305 return -1;
306 }
307
308 ptr = buffer + 20;
309 end = buffer + len;
310 have_address = 0;
311 have_xor_mapped_address = 0;
312
313 while (ptr + 4 <= end) {
314
315 attr_type = ((uint16_t)ptr[0] << 8) + ptr[1];
316 attr_len = ((uint16_t)ptr[2] << 8) + ptr[3];
317 ptr += 4;
318
319 if (ptr + attr_len > end) {
320 syslog(LOG_WARNING, "%s: truncated attribute", "parse_stun_response");
321 break;
322 }
323
324 switch (attr_type) {
325 case 0x0001: /* MAPPED-ADDRESS */
326 case 0x0020: /* XOR-MAPPED-ADDRESS (RFC 5389) */
327 case 0x8020: /* XOR-MAPPED-ADDRESS (2005 draft) */
328 /* Mapped Address or XOR Mapped Address */
329 if (attr_len == 8 && ptr[1] == 1) {
330 /* IPv4 address */
331 if ((attr_type & 0x7fff) == 0x0020) {
332 /* Restore XOR Mapped Address */
333 ptr[2] ^= buffer[4];
334 ptr[3] ^= buffer[5];
335 ptr[4] ^= buffer[4];
336 ptr[5] ^= buffer[5];
337 ptr[6] ^= buffer[6];
338 ptr[7] ^= buffer[7];
339 }
340
341 syslog(LOG_DEBUG, "%s: %s %hhu.%hhu.%hhu.%hhu:%hu",
342 "parse_stun_response",
343 ((attr_type & 0x7fff) == 0x0020) ? "XOR-MAPPED-ADDRESS" : "MAPPED-ADDRESS",
344 ptr[4], ptr[5], ptr[6], ptr[7],
345 (uint16_t)((ptr[2] << 8) + ptr[3]));
346
347 /* Prefer XOR Mapped Address, some NATs change IP addresses in UDP packets */
348 if (!have_xor_mapped_address) {
349 mapped_addr->sin_family = AF_INET;
350 mapped_addr->sin_port = htons(((uint16_t)ptr[2] << 8) + ptr[3]);
351 mapped_addr->sin_addr.s_addr = htonl(((uint32_t)ptr[4] << 24) + (ptr[5] << 16) + (ptr[6] << 8) + ptr[7]);
352 }
353
354 if ((attr_type & 0x7fff) == 0x0020)
355 have_xor_mapped_address = 1;
356
357 have_address = 1;
358 }
359 break;
360 case 0x0009: /* ERROR-CODE */
361 if (attr_len >= 4) {
362 syslog(LOG_WARNING, "%s: ERROR-CODE %u %.*s",
363 "parse_stun_response", (unsigned)ptr[2] * 100 + ptr[3],
364 attr_len - 4, ptr + 4);
365 }
366 break;
367 case 0x0004: /* SOURCE-ADDRESS (RFC 3489) */
368 case 0x0005: /* CHANGED-ADDRESS (RFC 3489) */
369 case 0x802b: /* RESPONSE-ORIGIN (RFC 5780) */
370 case 0x802c: /* OTHER-ADDRESS (RFC 5780) */
371 if (attr_len == 8 && ptr[1] == 1) {
372 syslog(LOG_DEBUG, "%s: %s %hhu.%hhu.%hhu.%hhu:%hu",
373 "parse_stun_response",
374 (attr_type == 0x0004) ? "SOURCE-ADDRESS" :
375 (attr_type == 0x0005) ? "CHANGED-ADDRESS" :
376 (attr_type == 0x802b) ? "RESPONSE-ORIGIN" : "OTHER-ADDRESS",
377 ptr[4], ptr[5], ptr[6], ptr[7],
378 (uint16_t)((ptr[2] << 8) + ptr[3]));
379 }
380 break;
381 case 0x8022: /* SOFTWARE (RFC 5780) */
382 syslog(LOG_DEBUG, "%s: SOFTWARE %.*s", "parse_stun_response", attr_len, ptr);
383 break;
384 default:
385 syslog(LOG_WARNING, "%s: unknown attribute type 0x%04x (len=%hu)",
386 "parse_stun_response", attr_type, attr_len);
387 }
388
389 ptr += attr_len;
390 }
391
392 return have_address ? 0 : -1;
393 }
394
395 /* Perform main STUN operation, return external IP address and check
396 * if host is behind restrictive, symmetric NAT or behind firewall.
397 * Restrictive NAT means any NAT which do some filtering and
398 * which is not static full-cone NAT 1:1, basically NAT which is not usable
399 * for port forwarding */
perform_stun(const char * if_name,const char * if_addr,const char * stun_host,unsigned short stun_port,struct in_addr * ext_addr,int * restrictive_nat)400 int perform_stun(const char *if_name, const char *if_addr, const char *stun_host, unsigned short stun_port, struct in_addr *ext_addr, int *restrictive_nat)
401 {
402 int fds[4];
403 size_t responses_lens[4];
404 unsigned char responses_bufs[4][1024];
405 unsigned char *responses[4];
406 size_t responses_sizes[4];
407 unsigned char requests[4][28];
408 unsigned char *transaction_ids[4];
409 int have_mapped_addr, mapped_addrs_count;
410 struct sockaddr_in remote_addr, peer_addrs[4], mapped_addrs[4];
411 unsigned short local_ports[4];
412 int have_ext_addr;
413 int i, j;
414
415 if (resolve_stun_host(stun_host, stun_port, &remote_addr) != 0)
416 return -1;
417
418 /* Prepare four different STUN requests */
419 for (i = 0; i < 4; ++i) {
420
421 responses_lens[i] = 0;
422 responses[i] = responses_bufs[i];
423 responses_sizes[i] = sizeof(responses_bufs[i]);
424
425 fds[i] = stun_socket(&local_ports[i]);
426 if (fds[i] < 0) {
427 for (j = 0; j < i; ++j)
428 close(fds[j]);
429 return -1;
430 }
431
432 fill_request(requests[i], i/2, i%2);
433 transaction_ids[i] = requests[i]+8;
434 }
435
436 syslog(LOG_INFO, "%s: local ports %hu %hu %hu %hu",
437 "perform_stun", local_ports[0], local_ports[1],
438 local_ports[2], local_ports[3]);
439
440 /* Unblock local ports */
441 for (i = 0; i < 4; ++i) {
442 if (add_filter_rule2(if_name, NULL, if_addr, local_ports[i], local_ports[i], IPPROTO_UDP, "stun test") < 0) {
443 syslog(LOG_ERR, "%s: add_filter_rule2(..., %hu, ...) FAILED",
444 "perform_stun", local_ports[i]);
445 }
446 }
447
448 /* Send STUN requests and wait for responses */
449 for (j = 0; j < 3; ++j) {
450
451 for (i = 0; i < 4; ++i) {
452 ssize_t n;
453 if (responses_lens[i])
454 continue;
455 n = sendto(fds[i], requests[i], sizeof(requests[i]), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr));
456 if (n != sizeof(requests[i])) {
457 syslog(LOG_ERR, "%s: #%d,%d sendto(): %m", "perform_stun", j, i);
458 break;
459 }
460 }
461
462 if (wait_for_stun_responses(fds, transaction_ids, responses, responses_sizes, peer_addrs, responses_lens) != 0)
463 break;
464
465 if (responses_lens[0] && responses_lens[1] && responses_lens[2] && responses_lens[3])
466 break;
467
468 }
469
470 /* Remove unblock for local ports */
471 for (i = 0; i < 4; ++i) {
472 delete_filter_rule(if_name, local_ports[i], IPPROTO_UDP);
473 close(fds[i]);
474 }
475
476 /* Parse received STUN messages */
477 have_ext_addr = 0;
478 have_mapped_addr = 0;
479 mapped_addrs_count = 0;
480 for (i = 0; i < 4; ++i) {
481 if (parse_stun_response(responses[i], responses_lens[i], &mapped_addrs[i]) == 0) {
482 mapped_addrs_count++;
483 have_mapped_addr |= (1 << i);
484 if (!have_ext_addr) {
485 memcpy(ext_addr, &mapped_addrs[i].sin_addr, sizeof(*ext_addr));
486 have_ext_addr = 1;
487 }
488 }
489 }
490
491 /* We have no external address */
492 if (!have_ext_addr) {
493 errno = ENXIO;
494 return -1;
495 }
496
497 *restrictive_nat = 0;
498
499 if (mapped_addrs_count < 4) {
500 /* We have not received all four responses,
501 * therefore NAT or firewall is doing some filtering */
502 syslog(LOG_NOTICE, "%s: %d response out of 4 received",
503 "perform_stun", mapped_addrs_count);
504 *restrictive_nat = 1;
505 }
506
507 if (memcmp(&remote_addr, &peer_addrs[0], sizeof(peer_addrs[0])) != 0) {
508 /* We received STUN response from different address
509 * even we did not asked for it, so some strange NAT is active */
510 syslog(LOG_NOTICE, "%s: address changed",
511 "perform_stun");
512 *restrictive_nat = 1;
513 }
514
515 for (i = 0; i < 4; ++i) {
516 if (!(have_mapped_addr & (1 << i)))
517 continue;
518 if (ntohs(mapped_addrs[i].sin_port) != local_ports[i] || memcmp(&mapped_addrs[i].sin_addr, ext_addr, sizeof(*ext_addr)) != 0) {
519 /* External IP address or port was changed,
520 * therefore symmetric NAT is active */
521 syslog(LOG_NOTICE, "%s: #%d external address or port changed",
522 "perform_stun", i);
523 *restrictive_nat = 1;
524 }
525 }
526
527 /* Otherwise we are either directly connected or behind unrestricted full-cone NAT 1:1 without filtering */
528 /* There is no filtering, so port forwarding would work fine */
529 return 0;
530 }
531
532 #ifdef TEST_LINUX_DEBUG_APP
533
534 /* This linux test application for debugging purposes can be compiled as: */
535 /* gcc upnpstun.c upnputils.o -o upnpstun -g3 -W -Wall -O2 -DTEST_LINUX_DEBUG_APP */
536
537 #include <arpa/inet.h>
538 #include <time.h>
539
540 #include "upnpglobalvars.h"
541 struct lan_addr_list lan_addrs;
542 int runtime_flags = 0;
543 time_t startup_time = 0;
544
add_filter_rule2(const char * ifname,const char * rhost,const char * iaddr,unsigned short eport,unsigned short iport,int proto,const char * desc)545 static int add_filter_rule2(const char *ifname, const char *rhost, const char *iaddr, unsigned short eport, unsigned short iport, int proto, const char *desc)
546 {
547 char buffer[100];
548 ifname = ifname;
549 rhost = rhost;
550 iaddr = iaddr;
551 iport = iport;
552 desc = desc;
553 snprintf(buffer, sizeof(buffer), "/sbin/iptables -t filter -I INPUT -p %d --dport %hu -j ACCEPT", proto, eport);
554 printf("Executing: %s\n", buffer);
555 return system(buffer);
556 }
557
delete_filter_rule(const char * ifname,unsigned short port,int proto)558 static int delete_filter_rule(const char * ifname, unsigned short port, int proto)
559 {
560 char buffer[100];
561 ifname = ifname;
562 snprintf(buffer, sizeof(buffer), "/sbin/iptables -t filter -D INPUT -p %d --dport %hu -j ACCEPT", proto, port);
563 printf("Executing: %s\n", buffer);
564 return system(buffer);
565 }
566
main(int argc,char * argv[])567 int main(int argc, char *argv[])
568 {
569 struct in_addr ext_addr;
570 int restrictive_nat;
571 int ret;
572 char str[INET_ADDRSTRLEN];
573
574 if (argc != 3 && argc != 2) {
575 printf("Usage: %s stun_host [stun_port]\n", argv[0]);
576 return 1;
577 }
578
579 if (geteuid() != 0) {
580 printf("You need to run this application as root\n");
581 return 1;
582 }
583
584 if (argc == 2)
585 argv[2] = "0";
586
587 srandom(time(NULL) * getpid());
588
589 ret = perform_stun(NULL, NULL, argv[1], atoi(argv[2]), &ext_addr, &restrictive_nat);
590 if (ret != 0) {
591 printf("STUN Failed: %s\n", strerror(errno));
592 return 1;
593 }
594
595 if (!inet_ntop(AF_INET, &ext_addr, str, INET_ADDRSTRLEN))
596 str[0] = 0;
597
598 printf("External IP address: %s\n", str);
599 printf("Restrictive NAT: %s\n", restrictive_nat ? "active (port forwarding impossible)" : "not used (ready for port forwarding)");
600 return 0;
601 }
602
603 #endif
604