From cd7284785b26d031b30b8c01ba55dc4e74d6a7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 13:31:02 +0200 Subject: [PATCH 01/14] miniupnpd: Add function delete_filter_rule() also for PF and Linux Netfilter It is needed for STUN implementation. --- miniupnpd/netfilter/iptcrdr.c | 63 +++++++++++++++++++++++++++++++++++ miniupnpd/netfilter/iptcrdr.h | 3 ++ miniupnpd/pf/obsdrdr.c | 8 ++++- miniupnpd/pf/obsdrdr.h | 3 ++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/miniupnpd/netfilter/iptcrdr.c b/miniupnpd/netfilter/iptcrdr.c index 48c6dbb..d800f94 100644 --- a/miniupnpd/netfilter/iptcrdr.c +++ b/miniupnpd/netfilter/iptcrdr.c @@ -621,6 +621,69 @@ delete_rule_and_commit(unsigned int index, IPTC_HANDLE h, return r; } +/* delete_filter_rule() + */ +int +delete_filter_rule(const char * ifname, unsigned short port, int proto) +{ + int r = -1; + unsigned index = 0; + unsigned i = 0; + IPTC_HANDLE h; + const struct ipt_entry * e; + const struct ipt_entry_match *match; + UNUSED(ifname); + + if((h = iptc_init("filter"))) + { + i = 0; + /* we must find the right index for the filter rule */ +#ifdef IPTABLES_143 + for(e = iptc_first_rule(miniupnpd_forward_chain, h); + e; + e = iptc_next_rule(e, h), i++) +#else + for(e = iptc_first_rule(miniupnpd_forward_chain, &h); + e; + e = iptc_next_rule(e, &h), i++) +#endif + { + if(proto==e->ip.proto) + { + match = (const struct ipt_entry_match *)&e->elems; + /*syslog(LOG_DEBUG, "filter rule #%u: %s %s", + i, match->u.user.name, inet_ntoa(e->ip.dst));*/ + if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) + { + const struct ipt_tcp * info; + info = (const struct ipt_tcp *)match->data; + if(port != info->dpts[0]) + continue; + } + else + { + const struct ipt_udp * info; + info = (const struct ipt_udp *)match->data; + if(port != info->dpts[0]) + continue; + } + index = i; + /*syslog(LOG_INFO, "Trying to delete filter rule at index %u", index);*/ + r = delete_rule_and_commit(index, h, miniupnpd_forward_chain, "delete_filter_rule"); + h = NULL; + break; + } + } + } + if(h) +#ifdef IPTABLES_143 + iptc_free(h); +#else + iptc_free(&h); +#endif + return r; +} + /* delete_redirect_and_filter_rules() */ int diff --git a/miniupnpd/netfilter/iptcrdr.h b/miniupnpd/netfilter/iptcrdr.h index 21d5405..a7310e4 100644 --- a/miniupnpd/netfilter/iptcrdr.h +++ b/miniupnpd/netfilter/iptcrdr.h @@ -32,6 +32,9 @@ add_filter_rule2(const char * ifname, int delete_redirect_and_filter_rules(unsigned short eport, int proto); +int +delete_filter_rule(const char * ifname, unsigned short port, int proto); + int add_peer_dscp_rule2(const char * ifname, const char * rhost, unsigned short rport, diff --git a/miniupnpd/pf/obsdrdr.c b/miniupnpd/pf/obsdrdr.c index 0b00ad9..72d4070 100644 --- a/miniupnpd/pf/obsdrdr.c +++ b/miniupnpd/pf/obsdrdr.c @@ -790,7 +790,7 @@ syslog(LOG_DEBUG, "%2d port=%hu proto=%d addr=%8x", #endif if( (iport == ntohs(pr.rule.dst.port[0])) && (pr.rule.proto == proto) && - (iaddr == pr.rule.dst.addr.v.a.addr.v4.s_addr) + (iaddr == 0 || iaddr == pr.rule.dst.addr.v.a.addr.v4.s_addr) ) { pr.action = PF_CHANGE_GET_TICKET; @@ -814,6 +814,12 @@ error: #endif } +int +delete_filter_rule(const char * ifname, unsigned short port, int proto) +{ + return priv_delete_filter_rule(ifname, port, proto, 0); +} + int delete_redirect_and_filter_rules(const char * ifname, unsigned short eport, int proto) diff --git a/miniupnpd/pf/obsdrdr.h b/miniupnpd/pf/obsdrdr.h index 3defa8e..7a70263 100644 --- a/miniupnpd/pf/obsdrdr.h +++ b/miniupnpd/pf/obsdrdr.h @@ -58,6 +58,9 @@ int delete_redirect_and_filter_rules(const char * ifname, unsigned short eport, int proto); +int +delete_filter_rule(const char * ifname, unsigned short port, int proto); + #ifdef TEST int clear_redirect_rules(void); From 4f53b322fd2540b7cd02e441acff7eb7eeee9684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 13:31:08 +0200 Subject: [PATCH 02/14] miniupnpd: Add function perform_stun() for detecting external IP address and restrictive NAT via STUN protocol It automatically unblock selected UDP ports for incoming responses and after finishing ports unblock is removed. --- miniupnpd/Makefile | 1 + miniupnpd/Makefile.linux | 1 + miniupnpd/Makefile.linux_nft | 1 + miniupnpd/Makefile.macosx | 1 + miniupnpd/Makefile.sunos | 1 + miniupnpd/upnpstun.c | 464 +++++++++++++++++++++++++++++++++++ miniupnpd/upnpstun.h | 12 + 7 files changed, 481 insertions(+) create mode 100644 miniupnpd/upnpstun.c create mode 100644 miniupnpd/upnpstun.h diff --git a/miniupnpd/Makefile b/miniupnpd/Makefile index a9cd6b4..022728d 100644 --- a/miniupnpd/Makefile +++ b/miniupnpd/Makefile @@ -101,6 +101,7 @@ STDOBJS = miniupnpd.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpredirect.o getifaddr.o daemonize.o upnpglobalvars.o \ options.o upnppermissions.o minissdp.o natpmp.o pcpserver.o \ upnpevents.o upnputils.o getconnstatus.o \ + upnpstun.o \ upnppinhole.o asyncsendto.o portinuse.o BSDOBJS = bsd/getifstats.o bsd/ifacewatcher.o bsd/getroute.o SUNOSOBJS = solaris/getifstats.o bsd/ifacewatcher.o bsd/getroute.o diff --git a/miniupnpd/Makefile.linux b/miniupnpd/Makefile.linux index e978fd2..bfb7894 100644 --- a/miniupnpd/Makefile.linux +++ b/miniupnpd/Makefile.linux @@ -48,6 +48,7 @@ BASEOBJS = miniupnpd.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpredirect.o getifaddr.o daemonize.o upnpglobalvars.o \ options.o upnppermissions.o minissdp.o natpmp.o pcpserver.o \ upnpevents.o upnputils.o getconnstatus.o \ + upnpstun.o \ upnppinhole.o pcplearndscp.o asyncsendto.o LNXOBJS = linux/getifstats.o linux/ifacewatcher.o linux/getroute.o diff --git a/miniupnpd/Makefile.linux_nft b/miniupnpd/Makefile.linux_nft index 0c03ca3..9ae5661 100644 --- a/miniupnpd/Makefile.linux_nft +++ b/miniupnpd/Makefile.linux_nft @@ -42,6 +42,7 @@ BASEOBJS = miniupnpd.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpredirect.o getifaddr.o daemonize.o upnpglobalvars.o \ options.o upnppermissions.o minissdp.o natpmp.o pcpserver.o \ upnpevents.o upnputils.o getconnstatus.o \ + upnpstun.o \ upnppinhole.o pcplearndscp.o asyncsendto.o LNXOBJS = linux/getifstats.o linux/ifacewatcher.o linux/getroute.o diff --git a/miniupnpd/Makefile.macosx b/miniupnpd/Makefile.macosx index 3a01f7f..7e5157c 100644 --- a/miniupnpd/Makefile.macosx +++ b/miniupnpd/Makefile.macosx @@ -31,6 +31,7 @@ STD_OBJS = miniupnpd.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpredirect.o getifaddr.o daemonize.o upnpglobalvars.o \ options.o upnppermissions.o minissdp.o natpmp.o \ upnpevents.o getconnstatus.o upnputils.o \ + upnpstun.o \ upnppinhole.o asyncsendto.o portinuse.o pcpserver.o MAC_OBJS = mac/getifstats.o bsd/ifacewatcher.o bsd/getroute.o IPFW_OBJS = ipfw/ipfwrdr.o ipfw/ipfwaux.o diff --git a/miniupnpd/Makefile.sunos b/miniupnpd/Makefile.sunos index 704d766..3d16be6 100644 --- a/miniupnpd/Makefile.sunos +++ b/miniupnpd/Makefile.sunos @@ -50,6 +50,7 @@ STDOBJS = miniupnpd.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpredirect.o getifaddr.o daemonize.o upnpglobalvars.o \ options.o upnppermissions.o minissdp.o natpmp.o pcpserver.o \ upnpevents.o upnputils.o getconnstatus.o \ + upnpstun.o \ upnppinhole.o asyncsendto.o portinuse.o BSDOBJS = bsd/getifstats.o bsd/ifacewatcher.o bsd/getroute.o SUNOSOBJS = solaris/getifstats.o bsd/ifacewatcher.o bsd/getroute.o diff --git a/miniupnpd/upnpstun.c b/miniupnpd/upnpstun.c new file mode 100644 index 0000000..ac0b13c --- /dev/null +++ b/miniupnpd/upnpstun.c @@ -0,0 +1,464 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2018 Pali Rohár + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TEST_LINUX_DEBUG_APP +#include "config.h" +#endif + +#include "upnpstun.h" + +#if defined(USE_NETFILTER) +#include "netfilter/iptcrdr.h" +#endif +#if defined(USE_PF) +#include "pf/obsdrdr.h" +#endif +#if defined(USE_IPF) +#include "ipf/ipfrdr.h" +#endif +#if defined(USE_IPFW) +#include "ipfw/ipfwrdr.h" +#endif + +#ifdef TEST_LINUX_DEBUG_APP +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); +static int delete_filter_rule(const char * ifname, unsigned short port, int proto); +#endif + +/* Generate random STUN Transaction Id */ +static void generate_transaction_id(unsigned char transaction_id[12]) +{ + int i; + + for (i = 0; i < 12; ++i) + transaction_id[i] = random()%255; +} + +/* Create and fill STUN Binding Request */ +static void fill_request(unsigned char buffer[28], int change_ip, int change_port) +{ + /* Type: Binding Request */ + buffer[0] = 0x00; + buffer[1] = 0x01; + + /* Length: One 8-byte attribute */ + buffer[2] = 0x00; + buffer[3] = 0x08; + + /* Magic Cookie: 0x2120A442 */ + buffer[4] = 0x21; + buffer[5] = 0x12; + buffer[6] = 0xA4; + buffer[7] = 0x42; + + /* Transaction Id */ + generate_transaction_id(buffer+8); + + /* Attribute Type: Change Request */ + buffer[20] = 0x00; + buffer[21] = 0x03; + + /* Attribute Length: 4 bytes */ + buffer[22] = 0x00; + buffer[23] = 0x04; + + buffer[24] = 0x00; + buffer[25] = 0x00; + buffer[26] = 0x00; + buffer[27] = 0x00; + + /* Change IP */ + buffer[27] |= change_ip ? 0x4 : 0x00; + + /* Change Port */ + buffer[27] |= change_port ? 0x2 : 0x00; +} + +/* Resolve STUN host+port and return sockaddr_in structure */ +/* When port is 0 then use default STUN port */ +static int resolve_stun_host(const char *stun_host, unsigned short stun_port, struct sockaddr_in *sock_addr) +{ + int have_sock; + struct addrinfo hints; + struct addrinfo *result, *rp; + char service[6]; + + snprintf(service, sizeof(service), "%hu", stun_port ? stun_port : (unsigned short)3478); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_NUMERICSERV; + + if (getaddrinfo(stun_host, service, &hints, &result) != 0) { + errno = EHOSTUNREACH; + return -1; + } + + have_sock = 0; + for (rp = result; rp != NULL; rp = rp->ai_next) { + if (rp->ai_addrlen > sizeof(*sock_addr) || rp->ai_addr->sa_family != AF_INET) + continue; + memcpy(sock_addr, rp->ai_addr, rp->ai_addrlen); + have_sock = 1; + break; + } + + freeaddrinfo(result); + + if (!have_sock) { + errno = EHOSTUNREACH; + return -1; + } + + return 0; +} + +/* Create a new UDP socket for STUN connection and return file descriptor and local UDP port */ +static int stun_socket(unsigned short *local_port) +{ + int fd; + socklen_t addr_len; + struct sockaddr_in local_addr; + + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = htonl(INADDR_ANY); + local_addr.sin_port = 0; + + if (bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) != 0) { + close(fd); + return -1; + } + + addr_len = sizeof(local_addr); + if (getsockname(fd, (struct sockaddr *)&local_addr, &addr_len) != 0) { + close(fd); + return -1; + } + + *local_port = ntohs(local_addr.sin_port); + + return fd; +} + +/* Receive STUN response message for specified Transaction Id and returns message and peer address */ +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) +{ + ssize_t len; + socklen_t peer_addr_len = sizeof(*peer_addr); + + len = recvfrom(fd, buffer, buffer_len, 0, (struct sockaddr *)peer_addr, &peer_addr_len); + if (len < 20 || peer_addr_len != sizeof(*peer_addr)) + return 0; + + /* Check that buffer is STUN message with class Response and Binding method with transaction id */ + if ((buffer[0] & 0xFF) != 0x01 || (buffer[1] & 0xEF) != 0x01 || memcmp(buffer+8, transaction_id, 12) != 0) + return 0; + + return len; +} + +/* Wait for STUN response messages and try to receive them */ +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]) +{ + fd_set fd_set; + struct timeval timeout; + int tmp1, tmp2, max_fd; + int ret; + int i; + + tmp1 = fds[0] > fds[1] ? fds[0] : fds[1]; + tmp2 = fds[2] > fds[3] ? fds[2] : fds[3]; + max_fd = tmp1 > tmp2 ? tmp1 : tmp2; + + timeout.tv_sec = 3; + timeout.tv_usec = 0; + + while (timeout.tv_sec > 0 || timeout.tv_usec > 0) { + + FD_ZERO(&fd_set); + FD_SET(fds[0], &fd_set); + FD_SET(fds[1], &fd_set); + FD_SET(fds[2], &fd_set); + FD_SET(fds[3], &fd_set); + + ret = select(max_fd+1, &fd_set, NULL, NULL, &timeout); + if (ret < 0) + return -1; + if (ret == 0) + return 0; + + for (i = 0; i < 4; ++i) + if (FD_ISSET(fds[i], &fd_set)) + lens[i] = receive_stun_response(fds[i], buffers[i], transaction_ids[i], buffers_lens[i], &peer_addrs[i]); + + if (lens[0] && lens[1] && lens[2] && lens[3]) + return 0; + } + + return 0; +} + +/* Parse Mapped Address (with port) from STUN response message */ +static int parse_stun_response(unsigned char *buffer, size_t len, struct sockaddr_in *mapped_addr) +{ + unsigned char *ptr, *end; + uint16_t attr_type; + uint16_t attr_len; + int have_address; + + if (len < 20) + return -1; + + /* Check that buffer is STUN message with class Success Response and Binding method */ + if (buffer[0] != 0x01 || buffer[1] != 0x01) + return -1; + + /* Check that STUN message is not longer as buffer length */ + if (((size_t)buffer[2] << 8) + buffer[3] + 20 > len) + return -1; + + ptr = buffer + 20; + end = buffer + len; + have_address = 0; + + while (ptr + 4 <= end) { + + attr_type = ((uint16_t)ptr[0] << 8) + ptr[1]; + attr_len = ((uint16_t)ptr[2] << 8) + ptr[3]; + ptr += 4; + + if (ptr + attr_len > end) + break; + + if (attr_type == 0x0001 || attr_type == 0x8020) { + /* Mapped Address or XOR Mapped Address */ + if (attr_len == 8 && ptr[1] == 1) { + /* IPv4 address */ + if (attr_type == 0x8020) { + /* Restore XOR Mapped Address */ + ptr[2] ^= buffer[4]; + ptr[3] ^= buffer[5]; + ptr[4] ^= buffer[4]; + ptr[5] ^= buffer[5]; + ptr[6] ^= buffer[6]; + ptr[7] ^= buffer[7]; + } + + mapped_addr->sin_family = AF_INET; + mapped_addr->sin_port = htons(((uint16_t)ptr[2] << 8) + ptr[3]); + mapped_addr->sin_addr.s_addr = htonl(((uint32_t)ptr[4] << 24) + (ptr[5] << 16) + (ptr[6] << 8) + ptr[7]); + + /* Prefer XOR Mapped Address, some NATs change IP addresses in UDP packets */ + if (attr_type == 0x8020) + return 0; + + have_address = 1; + } + } + + ptr += attr_len; + } + + return have_address ? 0 : -1; +} + +/* Perform main STUN operation, return external IP address and check if host is behind restrictive NAT */ +/* Restrictive NAT means any NAT which do some filtering and which is not static 1:1, basically NAT which is not usable for port forwarding */ +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) +{ + int fds[4]; + size_t responses_lens[4]; + unsigned char responses_bufs[4][1024]; + unsigned char *responses[4]; + size_t responses_sizes[4]; + unsigned char requests[4][28]; + unsigned char *transaction_ids[4]; + int have_mapped_addrs[4]; + struct sockaddr_in remote_addr, peer_addrs[4], mapped_addrs[4]; + unsigned short local_ports[4]; + int have_ext_addr; + int i, j; + + if (resolve_stun_host(stun_host, stun_port, &remote_addr) != 0) + return -1; + + /* Prepare four different STUN requests */ + for (i = 0; i < 4; ++i) { + + responses_lens[i] = 0; + responses[i] = responses_bufs[i]; + responses_sizes[i] = sizeof(responses_bufs[i]); + + fds[i] = stun_socket(&local_ports[i]); + if (fds[i] < 0) { + for (j = 0; j < i; ++j) + close(fds[j]); + return -1; + } + + fill_request(requests[i], i/2, i%2); + transaction_ids[i] = requests[i]+8; + + } + + /* Unblock local ports */ + for (i = 0; i < 4; ++i) + add_filter_rule2(if_name, NULL, if_addr, local_ports[i], local_ports[i], IPPROTO_UDP, "stun test"); + + /* Send STUN requests and wait for responses */ + for (j = 0; j < 3; ++j) { + + for (i = 0; i < 4; ++i) { + if (responses_lens[i]) + continue; + if (sendto(fds[i], requests[i], sizeof(requests[i]), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) != sizeof(requests[i])) + break; + } + + if (wait_for_stun_responses(fds, transaction_ids, responses, responses_sizes, peer_addrs, responses_lens) != 0) + break; + + if (responses_lens[0] && responses_lens[1] && responses_lens[2] && responses_lens[3]) + break; + + } + + /* Remove unblock for local ports */ + for (i = 0; i < 4; ++i) + delete_filter_rule(if_name, local_ports[i], IPPROTO_UDP); + + close(fds[0]); + close(fds[1]); + close(fds[2]); + close(fds[3]); + + /* Parse received STUN messages */ + have_ext_addr = 0; + for (i = 0; i < 4; ++i) { + if (parse_stun_response(responses[i], responses_lens[i], &mapped_addrs[i]) == 0) + have_mapped_addrs[i] = 1; + else + have_mapped_addrs[i] = 0; + if (!have_ext_addr && have_mapped_addrs[i]) { + memcpy(ext_addr, &mapped_addrs[i].sin_addr, sizeof(*ext_addr)); + have_ext_addr = 1; + } + } + + /* We have no external address */ + if (!have_ext_addr) { + errno = ENXIO; + return -1; + } + + for (i = 0; i < 4; ++i) { + if (!have_mapped_addrs[i]) { + /* We have not received all four responses, therefore NAT or firewall is doing some filtering */ + *restrictive_nat = 1; + return 0; + } + } + + if (memcmp(&remote_addr, &peer_addrs[0], sizeof(peer_addrs[0])) != 0) { + /* We received STUN response from different address even we did not asked for it, so some strange NAT is active */ + *restrictive_nat = 1; + return 0; + } + + for (i = 0; i < 4; ++i) { + if (ntohs(mapped_addrs[i].sin_port) != local_ports[i] || memcmp(&mapped_addrs[i].sin_addr, ext_addr, sizeof(*ext_addr)) != 0) { + /* External IP address or port was changed, therefore symmetric NAT is active */ + *restrictive_nat = 1; + return 0; + } + } + + /* Otherwise we are either directly connected or behind unrestricted NAT 1:1 */ + /* There is no filtering, so port forwarding would work fine */ + *restrictive_nat = 0; + return 0; +} + +#ifdef TEST_LINUX_DEBUG_APP + +/* This linux test application for debugging purposes can be compiled as: */ +/* gcc upnpstun.c -o upnpstun -g3 -W -Wall -O2 -DTEST_LINUX_DEBUG_APP */ + +#include +#include + +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) +{ + char buffer[100]; + ifname = ifname; + rhost = rhost; + iaddr = iaddr; + iport = iport; + desc = desc; + snprintf(buffer, sizeof(buffer), "/sbin/iptables -t filter -I INPUT -p %d --dport %hu -j ACCEPT", proto, eport); + printf("Executing: %s\n", buffer); + return system(buffer); +} + +static int delete_filter_rule(const char * ifname, unsigned short port, int proto) +{ + char buffer[100]; + ifname = ifname; + snprintf(buffer, sizeof(buffer), "/sbin/iptables -t filter -D INPUT -p %d --dport %hu -j ACCEPT", proto, port); + printf("Executing: %s\n", buffer); + return system(buffer); +} + +int main(int argc, char *argv[]) +{ + struct in_addr ext_addr; + int restrictive_nat; + int ret; + char str[INET_ADDRSTRLEN]; + + if (argc != 3 && argc != 2) { + printf("Usage: %s stun_host [stun_port]\n", argv[0]); + return 1; + } + + if (argc == 2) + argv[2] = "0"; + + srandom(time(NULL) * getpid()); + + ret = perform_stun(NULL, NULL, argv[1], atoi(argv[2]), &ext_addr, &restrictive_nat); + if (ret != 0) { + printf("STUN Failed: %s\n", strerror(errno)); + return 1; + } + + if (!inet_ntop(AF_INET, &ext_addr, str, INET_ADDRSTRLEN)) + str[0] = 0; + + printf("External IP address: %s\n", str); + printf("Restrictive NAT: %s\n", restrictive_nat ? "active (port forwarding impossible)" : "not used (ready for port forwarding)"); + return 0; +} + +#endif diff --git a/miniupnpd/upnpstun.h b/miniupnpd/upnpstun.h new file mode 100644 index 0000000..6bb349e --- /dev/null +++ b/miniupnpd/upnpstun.h @@ -0,0 +1,12 @@ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2018 Pali Rohár + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef UPNPSTUN_H_INCLUDED +#define UPNPSTUN_H_INCLUDED + +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); + +#endif From c35935c61d4278e0e875ba6e88fb764bd7a19a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 13:31:14 +0200 Subject: [PATCH 03/14] miniupnpd: Add function addr_is_reserved() to check if address is private/reserved and therefore not for public port forwarding --- miniupnpd/getifaddr.c | 40 +++++++++++++++++++++++++++++++++++++ miniupnpd/getifaddr.h | 4 ++++ miniupnpd/testupnpdescgen.c | 6 ++++++ 3 files changed, 50 insertions(+) diff --git a/miniupnpd/getifaddr.c b/miniupnpd/getifaddr.c index 8016d63..4567491 100644 --- a/miniupnpd/getifaddr.c +++ b/miniupnpd/getifaddr.c @@ -259,3 +259,43 @@ find_ipv6_addr(const char * ifname, } #endif +/* List of IP address blocks which are private / reserved and therefore not suitable for public external IP addresses */ +/* If interface has IP address from one of this block, then it is either behind NAT or port forwarding is impossible */ +#define IP(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) +#define MSK(m) (32-(m)) +static struct { uint32_t address; uint32_t rmask; } reserved[] = { + { IP( 0, 0, 0, 0), MSK( 8) }, /* RFC1122 "This host on this network" */ + { IP( 10, 0, 0, 0), MSK( 8) }, /* RFC1918 Private-Use */ + { IP(100, 64, 0, 0), MSK(10) }, /* RFC6598 Shared Address Space */ + { IP(127, 0, 0, 0), MSK( 8) }, /* RFC1122 Loopback */ + { IP(169, 254, 0, 0), MSK(16) }, /* RFC3927 Link-Local */ + { IP(172, 16, 0, 0), MSK(12) }, /* RFC1918 Private-Use */ + { IP(192, 0, 0, 0), MSK(24) }, /* RFC6890 IETF Protocol Assignments */ + { IP(192, 0, 2, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-1) */ + { IP(192, 31, 196, 0), MSK(24) }, /* RFC7535 AS112-v4 */ + { IP(192, 52, 193, 0), MSK(24) }, /* RFC7450 AMT */ + { IP(192, 88, 99, 0), MSK(24) }, /* RFC7526 6to4 Relay Anycast */ + { IP(192, 168, 0, 0), MSK(16) }, /* RFC1918 Private-Use */ + { IP(192, 175, 48, 0), MSK(16) }, /* RFC7534 Direct Delegation AS112 Service */ + { IP(198, 18, 0, 0), MSK(15) }, /* RFC2544 Benchmarking */ + { IP(198, 51, 100, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-2) */ + { IP(203, 0, 113, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-3) */ + { IP(224, 0, 0, 0), MSK( 4) }, /* RFC1112 Multicast */ + { IP(240, 0, 0, 0), MSK( 4) }, /* RFC1112 Reserved for Future Use + RFC919 Limited Broadcast */ +}; +#undef IP +#undef MSK + +int +addr_is_reserved(struct in_addr * addr) +{ + uint32_t address = ntohl(addr->s_addr); + size_t i; + + for (i = 0; i < sizeof(reserved)/sizeof(reserved[0]); ++i) { + if ((address >> reserved[i].rmask) == (reserved[i].address >> reserved[i].rmask)) + return 1; + } + + return 0; +} diff --git a/miniupnpd/getifaddr.h b/miniupnpd/getifaddr.h index 2be2f45..d1cf742 100644 --- a/miniupnpd/getifaddr.h +++ b/miniupnpd/getifaddr.h @@ -28,5 +28,9 @@ int find_ipv6_addr(const char * ifname, char * dst, int n); +/* check if address is in private / reserved block (e.g. local area network) */ +int +addr_is_reserved(struct in_addr * addr); + #endif diff --git a/miniupnpd/testupnpdescgen.c b/miniupnpd/testupnpdescgen.c index 5b88c2a..bb2ae0c 100644 --- a/miniupnpd/testupnpdescgen.c +++ b/miniupnpd/testupnpdescgen.c @@ -54,6 +54,12 @@ int getifaddr(const char * ifname, char * buf, int len, struct in_addr * addr, s return 0; } +int addr_is_reserved(struct in_addr * addr) +{ + UNUSED(addr); + return 0; +} + int upnp_get_portmapping_number_of_entries(void) { return 42; From cce19781e67364d36a9068a42d5275836ee88c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 13:31:26 +0200 Subject: [PATCH 04/14] miniupnpd: Add validation that public ip address is not reserved and is really public This ensures that all requests for getting public IP address (either via UPnP IGD or PCP/PMP) would contain correct public IP address or an error (instead of some invalid private/reserved IP address). --- miniupnpd/miniupnpd.c | 18 ++++++++++++++++++ miniupnpd/natpmp.c | 6 +++++- miniupnpd/upnpdescgen.c | 3 ++- miniupnpd/upnpsoap.c | 5 ++++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index 74911a6..b11bc7e 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -990,6 +990,12 @@ parselanaddr(struct lan_addr_s * lan_addr, const char * str) if(!inet_aton(lan_addr->ext_ip_str, &lan_addr->ext_ip_addr)) { /* error */ fprintf(stderr, "Error parsing address : %s\n", lan_addr->ext_ip_str); + return -1; + } + if(addr_is_reserved(&lan_addr->ext_ip_addr)) { + /* error */ + fprintf(stderr, "Error: option ext_ip address contains reserved / private address : %s\n", lan_addr->ext_ip_str); + return -1; } } } @@ -1070,6 +1076,7 @@ init(int argc, char * * argv, struct runtime_vars * v) int pid; int debug_flag = 0; int openlog_option; + struct in_addr addr; struct sigaction sa; /*const char * logfilename = 0;*/ const char * presurl = 0; @@ -1606,6 +1613,17 @@ init(int argc, char * * argv, struct runtime_vars * v) goto print_usage; } + if (use_ext_ip_addr) { + if (inet_pton(AF_INET, use_ext_ip_addr, &addr) != 1) { + fprintf(stderr, "Error: option ext_ip contains invalid address %s\n", use_ext_ip_addr); + return 1; + } + if (addr_is_reserved(&addr)) { + fprintf(stderr, "Error: option ext_ip contains reserved / private address %s, not public routable\n", use_ext_ip_addr); + return 1; + } + } + if(debug_flag) { pid = getpid(); diff --git a/miniupnpd/natpmp.c b/miniupnpd/natpmp.c index 0ed09e0..8648dfb 100644 --- a/miniupnpd/natpmp.c +++ b/miniupnpd/natpmp.c @@ -94,6 +94,7 @@ error: static void FillPublicAddressResponse(unsigned char * resp, in_addr_t senderaddr) { #ifndef MULTIPLE_EXTERNAL_IP + struct in_addr addr; char tmp[16]; UNUSED(senderaddr); @@ -103,10 +104,13 @@ static void FillPublicAddressResponse(unsigned char * resp, in_addr_t senderaddr if(!ext_if_name || ext_if_name[0]=='\0') { resp[3] = 3; /* Network Failure (e.g. NAT box itself * has not obtained a DHCP lease) */ - } else if(getifaddr(ext_if_name, tmp, INET_ADDRSTRLEN, NULL, NULL) < 0) { + } else if(getifaddr(ext_if_name, tmp, INET_ADDRSTRLEN, &addr, NULL) < 0) { syslog(LOG_ERR, "Failed to get IP for interface %s", ext_if_name); resp[3] = 3; /* Network Failure (e.g. NAT box itself * has not obtained a DHCP lease) */ + } else if (addr_is_reserved(&addr)) { + resp[3] = 3; /* Network Failure, box has not obtained + public IP address */ } else { inet_pton(AF_INET, tmp, resp+8); /* ok */ } diff --git a/miniupnpd/upnpdescgen.c b/miniupnpd/upnpdescgen.c index 88e00c1..b2583eb 100644 --- a/miniupnpd/upnpdescgen.c +++ b/miniupnpd/upnpdescgen.c @@ -1281,8 +1281,9 @@ genEventVars(int * len, const struct serviceDesc * s) if(use_ext_ip_addr) str = strcat_str(str, len, &tmplen, use_ext_ip_addr); else { + struct in_addr addr; char ext_ip_addr[INET_ADDRSTRLEN]; - if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, NULL, NULL) < 0) { + if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, &addr, NULL) < 0 || addr_is_reserved(&addr)) { str = strcat_str(str, len, &tmplen, "0.0.0.0"); } else { str = strcat_str(str, len, &tmplen, ext_ip_addr); diff --git a/miniupnpd/upnpsoap.c b/miniupnpd/upnpsoap.c index b4f156c..5a3eafc 100644 --- a/miniupnpd/upnpsoap.c +++ b/miniupnpd/upnpsoap.c @@ -332,17 +332,20 @@ GetExternalIPAddress(struct upnphttp * h, const char * action, const char * ns) * There is usually no NAT with IPv6 */ #ifndef MULTIPLE_EXTERNAL_IP + struct in_addr addr; if(use_ext_ip_addr) { strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN); ext_ip_addr[INET_ADDRSTRLEN - 1] = '\0'; } - else if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, NULL, NULL) < 0) + else if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, &addr, NULL) < 0) { syslog(LOG_ERR, "Failed to get ip address for interface %s", ext_if_name); strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); } + if (addr_is_reserved(&addr)) + strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); #else struct lan_addr_s * lan_addr; strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN); From 8e10a1aeab9b8cd4d3b2e964b02e9ad409cf3aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 13:32:04 +0200 Subject: [PATCH 05/14] miniupnpd: Disable port forwarding when we are behind restrictive nat with reserved / private IP address In this case port forwarding is impossible, so rather return error code to the client instead of silently trying to do something and informing clients that port forwarding is enabled. --- miniupnpd/miniupnpd.c | 28 ++++++++++++++++++++++++++++ miniupnpd/upnpglobalvars.c | 4 ++++ miniupnpd/upnpglobalvars.h | 4 ++++ miniupnpd/upnpredirect.c | 2 ++ 4 files changed, 38 insertions(+) diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index b11bc7e..b374374 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -1941,6 +1941,20 @@ main(int argc, char * * argv) GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "", ext_if_name, upnp_bootid); + if (!use_ext_ip_addr) + { + char if_addr[INET_ADDRSTRLEN]; + struct in_addr addr; + if (getifaddr(ext_if_name, if_addr, INET_ADDRSTRLEN, &addr, NULL) < 0) { + syslog(LOG_ERR, "Cannot get IP address for ext interface %s. EXITING", ext_if_name); + return 1; + } + if (addr_is_reserved(&addr)) { + syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name); + disable_port_forwarding = 1; + } + } + if(GETFLAG(ENABLEUPNPMASK)) { unsigned short listen_port; @@ -2132,6 +2146,20 @@ main(int argc, char * * argv) if(should_send_public_address_change_notif) { syslog(LOG_INFO, "should send external iface address change notification(s)"); + if (!use_ext_ip_addr) + { + char if_addr[INET_ADDRSTRLEN]; + struct in_addr addr; + if (getifaddr(ext_if_name, if_addr, INET_ADDRSTRLEN, &addr, NULL) == 0) { + int reserved = addr_is_reserved(&addr); + if (disable_port_forwarding && !reserved) { + syslog(LOG_INFO, "Public IP address %s on ext interface %s: Port forwarding is enabled", if_addr, ext_if_name); + } else if (!disable_port_forwarding && reserved) { + syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name); + } + disable_port_forwarding = reserved; + } + } #ifdef ENABLE_NATPMP if(GETFLAG(ENABLENATPMPMASK)) SendNATPMPPublicAddressChangeNotification(snatpmp, addr_count); diff --git a/miniupnpd/upnpglobalvars.c b/miniupnpd/upnpglobalvars.c index 290059c..a493151 100644 --- a/miniupnpd/upnpglobalvars.c +++ b/miniupnpd/upnpglobalvars.c @@ -25,6 +25,10 @@ const char* lease_file = 0; * when NULL, getifaddr() is used */ const char * use_ext_ip_addr = 0; +/* disallow all port forwarding requests when + * we are behind restrictive nat */ +int disable_port_forwarding = 0; + unsigned long downstream_bitrate = 0; unsigned long upstream_bitrate = 0; diff --git a/miniupnpd/upnpglobalvars.h b/miniupnpd/upnpglobalvars.h index 80f7a02..b9a87ae 100644 --- a/miniupnpd/upnpglobalvars.h +++ b/miniupnpd/upnpglobalvars.h @@ -26,6 +26,10 @@ extern const char * lease_file; * when NULL, getifaddr() is used */ extern const char * use_ext_ip_addr; +/* disallow all port forwarding requests when + * we are behind restrictive nat */ +extern int disable_port_forwarding; + /* parameters to return to upnp client when asked */ extern unsigned long downstream_bitrate; extern unsigned long upstream_bitrate; diff --git a/miniupnpd/upnpredirect.c b/miniupnpd/upnpredirect.c index 2306ca5..fd33a7c 100644 --- a/miniupnpd/upnpredirect.c +++ b/miniupnpd/upnpredirect.c @@ -440,6 +440,8 @@ upnp_redirect_internal(const char * rhost, unsigned short eport, { /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s", eport, iaddr, iport, protocol, desc); */ + if(disable_port_forwarding) + return -1; if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto, desc, timestamp) < 0) { return -1; From 8c97654d70fdc46963dc817cd5b4fc5764b002a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 13:32:42 +0200 Subject: [PATCH 06/14] miniupnpd: When enabled perform STUN to learn external IP address and NAT type Also enable port forwarding when direct (non-NAT) connection or unrestricted NAT 1:1 (without any filtering) is detected. --- miniupnpd/miniupnpd.c | 73 +++++++++++++++++++++++++++++++++++++- miniupnpd/miniupnpd.conf | 19 ++++++++++ miniupnpd/options.c | 3 ++ miniupnpd/options.h | 3 ++ miniupnpd/upnpglobalvars.c | 4 +++ miniupnpd/upnpglobalvars.h | 6 ++++ 6 files changed, 107 insertions(+), 1 deletion(-) diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index b374374..86caafd 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -64,6 +64,7 @@ #include "minissdp.h" #include "upnpredirect.h" #include "upnppinhole.h" +#include "upnpstun.h" #include "miniupnpdtypes.h" #include "daemonize.h" #include "upnpevents.h" @@ -1028,6 +1029,44 @@ parselan_error: return -1; } +static char ext_addr_str[INET_ADDRSTRLEN]; + +int update_ext_ip_addr_from_stun(int init) +{ + struct in_addr if_addr, ext_addr; + int restrictive_nat; + char if_addr_str[INET_ADDRSTRLEN]; + + syslog(LOG_INFO, "STUN: Performing with host=%s and port=%u ...", ext_stun_host, (unsigned)ext_stun_port); + + if (getifaddr(ext_if_name, if_addr_str, INET_ADDRSTRLEN, &if_addr, NULL) < 0) { + syslog(LOG_ERR, "STUN: Cannot get IP address for ext interface %s", ext_if_name); + return 1; + } + if (perform_stun(ext_if_name, if_addr_str, ext_stun_host, ext_stun_port, &ext_addr, &restrictive_nat) != 0) { + syslog(LOG_ERR, "STUN: Performing STUN failed: %s", strerror(errno)); + return 1; + } + if (!inet_ntop(AF_INET, &ext_addr, ext_addr_str, sizeof(ext_addr_str))) { + syslog(LOG_ERR, "STUN: Function inet_ntop for IP address returned by STUN failed: %s", strerror(errno)); + return 1; + } + + syslog(LOG_INFO, "STUN: ... done"); + + if ((init || disable_port_forwarding) && !restrictive_nat) { + if (addr_is_reserved(&if_addr)) + syslog(LOG_INFO, "STUN: ext interface %s with IP address %s is now behind unrestricted NAT 1:1 with public IP address %s: Port forwarding is now enabled", ext_if_name, if_addr_str, ext_addr_str); + else + syslog(LOG_INFO, "STUN: ext interface %s has now public IP address %s: Port forwarding is now enabled", ext_if_name, if_addr_str); + } else if ((init || !disable_port_forwarding) && restrictive_nat) { + syslog(LOG_INFO, "STUN: ext interface %s with IP address %s is now behind restrictive NAT with public IP address %s: Port forwarding is now impossible", ext_if_name, if_addr_str, ext_addr_str); + } + + disable_port_forwarding = restrictive_nat; + return 0; +} + /* fill uuidvalue_wan and uuidvalue_wcd based on uuidvalue_igd */ void complete_uuidvalues(void) { @@ -1141,6 +1180,16 @@ init(int argc, char * * argv, struct runtime_vars * v) case UPNPEXT_IP: use_ext_ip_addr = ary_options[i].value; break; + case UPNPEXT_PERFORM_STUN: + if(strcmp(ary_options[i].value, "yes") == 0) + SETFLAG(PERFORMSTUN); + break; + case UPNPEXT_STUN_HOST: + ext_stun_host = ary_options[i].value; + break; + case UPNPEXT_STUN_PORT: + ext_stun_port = atoi(ary_options[i].value); + break; case UPNPLISTENING_IP: lan_addr = (struct lan_addr_s *) malloc(sizeof(struct lan_addr_s)); if (lan_addr == NULL) @@ -1337,6 +1386,10 @@ init(int argc, char * * argv, struct runtime_vars * v) return 1; } #endif /* ENABLE_PCP */ + if (GETFLAG(PERFORMSTUN) && !ext_stun_host) { + fprintf(stderr, "You must specify ext_stun_host= when ext_perform_stun=yes\n"); + return 1; + } } #endif /* DISABLE_CONFIG_FILE */ @@ -1613,6 +1666,11 @@ init(int argc, char * * argv, struct runtime_vars * v) goto print_usage; } + if (use_ext_ip_addr && GETFLAG(PERFORMSTUN)) { + fprintf(stderr, "Error: options ext_ip= and ext_perform_stun=yes cannot be specified together\n"); + return 1; + } + if (use_ext_ip_addr) { if (inet_pton(AF_INET, use_ext_ip_addr, &addr) != 1) { fprintf(stderr, "Error: option ext_ip contains invalid address %s\n", use_ext_ip_addr); @@ -1941,7 +1999,16 @@ main(int argc, char * * argv) GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "", ext_if_name, upnp_bootid); - if (!use_ext_ip_addr) + if(GETFLAG(PERFORMSTUN)) + { + int ret = update_ext_ip_addr_from_stun(1); + if (ret != 0) { + syslog(LOG_ERR, "Performing STUN failed. EXITING"); + return 1; + } + use_ext_ip_addr = ext_addr_str; + } + else if (!use_ext_ip_addr) { char if_addr[INET_ADDRSTRLEN]; struct in_addr addr; @@ -1951,6 +2018,7 @@ main(int argc, char * * argv) } if (addr_is_reserved(&addr)) { syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name); + syslog(LOG_INFO, "You are probably behind NAT, enable option ext_perform_stun=yes to detect public IP address"); disable_port_forwarding = 1; } } @@ -2146,6 +2214,8 @@ main(int argc, char * * argv) if(should_send_public_address_change_notif) { syslog(LOG_INFO, "should send external iface address change notification(s)"); + if(GETFLAG(PERFORMSTUN)) + update_ext_ip_addr_from_stun(0); if (!use_ext_ip_addr) { char if_addr[INET_ADDRSTRLEN]; @@ -2156,6 +2226,7 @@ main(int argc, char * * argv) syslog(LOG_INFO, "Public IP address %s on ext interface %s: Port forwarding is enabled", if_addr, ext_if_name); } else if (!disable_port_forwarding && reserved) { syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name); + syslog(LOG_INFO, "You are probably behind NAT, enable option ext_perform_stun=yes to detect public IP address"); } disable_port_forwarding = reserved; } diff --git a/miniupnpd/miniupnpd.conf b/miniupnpd/miniupnpd.conf index 133566f..12bb3c0 100644 --- a/miniupnpd/miniupnpd.conf +++ b/miniupnpd/miniupnpd.conf @@ -4,6 +4,25 @@ # If the WAN interface has several IP addresses, you # can specify the one to use below #ext_ip= +# WAN interface must have public IP address. Otherwise it is behind NAT +# and port forwarding is impossible. In some cases WAN interface can be +# behind unrestricted NAT 1:1 when all incoming traffic is NAT-ed and +# routed to WAN interfaces without any filtering. In this cases miniupnpd +# needs to know public IP address and it can be learnt by asking external +# server via STUN protocol. Following option enable retrieving external +# public IP address from STUN server and detection of NAT type. You need +# to specify also external STUN server in stun_host option below. +# This option is disabled by default. +#ext_perform_stun=yes +# Specify STUN server, either hostname or IP address +# Some public STUN servers: +# stun.stunprotocol.org +# stun.sipgate.net +# stun.xten.com +# stun.l.google.com (on non standard port 19302) +#ext_stun_host=stun.stunprotocol.org +# Specify STUN UDP port, by default it is standard port 3478. +#ext_stun_port=3478 # LAN network interfaces IPs / networks # There can be multiple listening IPs for SSDP traffic, in that case diff --git a/miniupnpd/options.c b/miniupnpd/options.c index 9448ba8..8520a7b 100644 --- a/miniupnpd/options.c +++ b/miniupnpd/options.c @@ -30,6 +30,9 @@ static const struct { } optionids[] = { { UPNPEXT_IFNAME, "ext_ifname" }, { UPNPEXT_IP, "ext_ip" }, + { UPNPEXT_PERFORM_STUN, "ext_perform_stun" }, + { UPNPEXT_STUN_HOST, "ext_stun_host" }, + { UPNPEXT_STUN_PORT, "ext_stun_port" }, { UPNPLISTENING_IP, "listening_ip" }, #ifdef ENABLE_IPV6 { UPNPIPV6_LISTENING_IP, "ipv6_listening_ip" }, diff --git a/miniupnpd/options.h b/miniupnpd/options.h index eb1f866..ffc9729 100644 --- a/miniupnpd/options.h +++ b/miniupnpd/options.h @@ -17,6 +17,9 @@ enum upnpconfigoptions { UPNP_INVALID = 0, UPNPEXT_IFNAME = 1, /* ext_ifname */ UPNPEXT_IP, /* ext_ip */ + UPNPEXT_PERFORM_STUN, /* ext_perform_stun */ + UPNPEXT_STUN_HOST, /* ext_stun_host */ + UPNPEXT_STUN_PORT, /* ext_stun_port */ UPNPLISTENING_IP, /* listening_ip */ #ifdef ENABLE_IPV6 UPNPIPV6_LISTENING_IP, /* listening address for IPv6 */ diff --git a/miniupnpd/upnpglobalvars.c b/miniupnpd/upnpglobalvars.c index a493151..16598fe 100644 --- a/miniupnpd/upnpglobalvars.c +++ b/miniupnpd/upnpglobalvars.c @@ -16,6 +16,10 @@ /* network interface for internet */ const char * ext_if_name = 0; +/* stun host/port configuration */ +const char * ext_stun_host = 0; +uint16_t ext_stun_port = 0; + /* file to store leases */ #ifdef ENABLE_LEASEFILE const char* lease_file = 0; diff --git a/miniupnpd/upnpglobalvars.h b/miniupnpd/upnpglobalvars.h index b9a87ae..0456b9a 100644 --- a/miniupnpd/upnpglobalvars.h +++ b/miniupnpd/upnpglobalvars.h @@ -17,6 +17,10 @@ /* name of the network interface used to access internet */ extern const char * ext_if_name; +/* stun host/port configuration */ +extern const char * ext_stun_host; +extern uint16_t ext_stun_port; + /* file to store all leases */ #ifdef ENABLE_LEASEFILE extern const char * lease_file; @@ -73,6 +77,8 @@ extern int runtime_flags; #define FORCEIGDDESCV1MASK 0x0800 #endif +#define PERFORMSTUN 0x1000 + #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask) #define CLEARFLAG(mask) runtime_flags &= ~mask From c1472ffe4e31e31fb42f2fa12378cda09d4b98b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 19 May 2018 17:28:43 +0200 Subject: [PATCH 07/14] miniupnpd: GetExternalIPAddress(): Instead of invalid IP address 0.0.0.0 returns error 501 IP address 0.0.0.0 is filled when it is not possible to retrieve IP address. According to specification, GetExternalIPAddress() can return error 501 when action failed. --- miniupnpd/upnpsoap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/miniupnpd/upnpsoap.c b/miniupnpd/upnpsoap.c index 5a3eafc..dc68d7e 100644 --- a/miniupnpd/upnpsoap.c +++ b/miniupnpd/upnpsoap.c @@ -359,6 +359,11 @@ GetExternalIPAddress(struct upnphttp * h, const char * action, const char * ns) } } #endif + if (strcmp(ext_ip_addr, "0.0.0.0") == 0) + { + SoapError(h, 501, "Action Failed"); + return; + } bodylen = snprintf(body, sizeof(body), resp, action, ns, /*SERVICE_TYPE_WANIPC,*/ ext_ip_addr, action); From 8c91ff515eb7984248fe378987107cfb7ad97dd6 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:20:30 +0200 Subject: [PATCH 08/14] reserved[] is const --- miniupnpd/getifaddr.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/miniupnpd/getifaddr.c b/miniupnpd/getifaddr.c index 4567491..f2fb261 100644 --- a/miniupnpd/getifaddr.c +++ b/miniupnpd/getifaddr.c @@ -1,7 +1,8 @@ /* $Id: getifaddr.c,v 1.19 2013/12/13 14:28:40 nanard Exp $ */ -/* MiniUPnP project - * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2014 Thomas Bernard +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * MiniUPnP project + * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ @@ -263,7 +264,7 @@ find_ipv6_addr(const char * ifname, /* If interface has IP address from one of this block, then it is either behind NAT or port forwarding is impossible */ #define IP(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) #define MSK(m) (32-(m)) -static struct { uint32_t address; uint32_t rmask; } reserved[] = { +static const struct { uint32_t address; uint32_t rmask; } reserved[] = { { IP( 0, 0, 0, 0), MSK( 8) }, /* RFC1122 "This host on this network" */ { IP( 10, 0, 0, 0), MSK( 8) }, /* RFC1918 Private-Use */ { IP(100, 64, 0, 0), MSK(10) }, /* RFC6598 Shared Address Space */ From 8bc6d6f5564cf64e99f1c2cf46022f9221af123f Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:23:22 +0200 Subject: [PATCH 09/14] PERFORMSTUN => PERFORMSTUNMASK. allow to specify stun using -o option --- miniupnpd/miniupnpd.c | 22 ++++++++++++++-------- miniupnpd/upnpglobalvars.h | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index 86caafd..bfefa57 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -1182,7 +1182,7 @@ init(int argc, char * * argv, struct runtime_vars * v) break; case UPNPEXT_PERFORM_STUN: if(strcmp(ary_options[i].value, "yes") == 0) - SETFLAG(PERFORMSTUN); + SETFLAG(PERFORMSTUNMASK); break; case UPNPEXT_STUN_HOST: ext_stun_host = ary_options[i].value; @@ -1386,7 +1386,7 @@ init(int argc, char * * argv, struct runtime_vars * v) return 1; } #endif /* ENABLE_PCP */ - if (GETFLAG(PERFORMSTUN) && !ext_stun_host) { + if (GETFLAG(PERFORMSTUNMASK) && !ext_stun_host) { fprintf(stderr, "You must specify ext_stun_host= when ext_perform_stun=yes\n"); return 1; } @@ -1414,9 +1414,14 @@ init(int argc, char * * argv, struct runtime_vars * v) fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 'o': - if(i+1 < argc) - use_ext_ip_addr = argv[++i]; - else + if(i+1 < argc) { + i++; + if (0 == strncasecmp(argv[i], "STUN:", 5)) { + SETFLAG(PERFORMSTUNMASK); + ext_stun_host = argv[i] + 5; + } else + use_ext_ip_addr = argv[i]; + } else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 't': @@ -1666,7 +1671,7 @@ init(int argc, char * * argv, struct runtime_vars * v) goto print_usage; } - if (use_ext_ip_addr && GETFLAG(PERFORMSTUN)) { + if (use_ext_ip_addr && GETFLAG(PERFORMSTUNMASK)) { fprintf(stderr, "Error: options ext_ip= and ext_perform_stun=yes cannot be specified together\n"); return 1; } @@ -1854,6 +1859,7 @@ print_usage: "\tDefault pid file is '%s'.\n" "\tDefault config file is '%s'.\n" "\tWith -d miniupnpd will run as a standard program.\n" + "\t-o argument is either an IPv4 address or \"STUN:xx.xx.xx.xx\".\n" #if defined(USE_PF) || defined(USE_IPF) "\t-L sets packet log in pf and ipf on.\n" #endif @@ -1999,7 +2005,7 @@ main(int argc, char * * argv) GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "", ext_if_name, upnp_bootid); - if(GETFLAG(PERFORMSTUN)) + if(GETFLAG(PERFORMSTUNMASK)) { int ret = update_ext_ip_addr_from_stun(1); if (ret != 0) { @@ -2214,7 +2220,7 @@ main(int argc, char * * argv) if(should_send_public_address_change_notif) { syslog(LOG_INFO, "should send external iface address change notification(s)"); - if(GETFLAG(PERFORMSTUN)) + if(GETFLAG(PERFORMSTUNMASK)) update_ext_ip_addr_from_stun(0); if (!use_ext_ip_addr) { diff --git a/miniupnpd/upnpglobalvars.h b/miniupnpd/upnpglobalvars.h index 0456b9a..3950a43 100644 --- a/miniupnpd/upnpglobalvars.h +++ b/miniupnpd/upnpglobalvars.h @@ -77,7 +77,7 @@ extern int runtime_flags; #define FORCEIGDDESCV1MASK 0x0800 #endif -#define PERFORMSTUN 0x1000 +#define PERFORMSTUNMASK 0x1000 #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask) From 15b6f3e9c27c2cc31aa85f2f6d8cf565ed79c780 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:29:33 +0200 Subject: [PATCH 10/14] fixes in update_ext_ip_addr_from_stun() Signed-off-by: Thomas Bernard --- miniupnpd/miniupnpd.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index bfefa57..1524374 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -1052,8 +1052,6 @@ int update_ext_ip_addr_from_stun(int init) return 1; } - syslog(LOG_INFO, "STUN: ... done"); - if ((init || disable_port_forwarding) && !restrictive_nat) { if (addr_is_reserved(&if_addr)) syslog(LOG_INFO, "STUN: ext interface %s with IP address %s is now behind unrestricted NAT 1:1 with public IP address %s: Port forwarding is now enabled", ext_if_name, if_addr_str, ext_addr_str); @@ -1061,8 +1059,11 @@ int update_ext_ip_addr_from_stun(int init) syslog(LOG_INFO, "STUN: ext interface %s has now public IP address %s: Port forwarding is now enabled", ext_if_name, if_addr_str); } else if ((init || !disable_port_forwarding) && restrictive_nat) { syslog(LOG_INFO, "STUN: ext interface %s with IP address %s is now behind restrictive NAT with public IP address %s: Port forwarding is now impossible", ext_if_name, if_addr_str, ext_addr_str); + } else { + syslog(LOG_INFO, "STUN: ... done"); } + use_ext_ip_addr = ext_addr_str; disable_port_forwarding = restrictive_nat; return 0; } @@ -2007,22 +2008,18 @@ main(int argc, char * * argv) if(GETFLAG(PERFORMSTUNMASK)) { - int ret = update_ext_ip_addr_from_stun(1); - if (ret != 0) { + if (update_ext_ip_addr_from_stun(1) != 0) { syslog(LOG_ERR, "Performing STUN failed. EXITING"); return 1; } - use_ext_ip_addr = ext_addr_str; } else if (!use_ext_ip_addr) { char if_addr[INET_ADDRSTRLEN]; struct in_addr addr; if (getifaddr(ext_if_name, if_addr, INET_ADDRSTRLEN, &addr, NULL) < 0) { - syslog(LOG_ERR, "Cannot get IP address for ext interface %s. EXITING", ext_if_name); - return 1; - } - if (addr_is_reserved(&addr)) { + syslog(LOG_WARNING, "Cannot get IP address for ext interface %s. Network is down", ext_if_name); + } else if (addr_is_reserved(&addr)) { syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name); syslog(LOG_INFO, "You are probably behind NAT, enable option ext_perform_stun=yes to detect public IP address"); disable_port_forwarding = 1; From 1da39554c7cf53ba629c3dc462288999585fc34a Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:31:26 +0200 Subject: [PATCH 11/14] fixes in upnpstun.c Signed-off-by: Thomas Bernard --- miniupnpd/upnpstun.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/miniupnpd/upnpstun.c b/miniupnpd/upnpstun.c index ac0b13c..3ffe245 100644 --- a/miniupnpd/upnpstun.c +++ b/miniupnpd/upnpstun.c @@ -1,4 +1,6 @@ -/* MiniUPnP project +/* $Id: $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2018 Pali Rohár * This software is subject to the conditions detailed @@ -8,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +100,9 @@ static int resolve_stun_host(const char *stun_host, unsigned short stun_port, st struct addrinfo *result, *rp; char service[6]; - snprintf(service, sizeof(service), "%hu", stun_port ? stun_port : (unsigned short)3478); + if (stun_port == 0) + stun_port = (unsigned short)3478; + snprintf(service, sizeof(service), "%hu", stun_port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; @@ -181,35 +186,36 @@ static size_t receive_stun_response(int fd, unsigned char *buffer, unsigned char /* Wait for STUN response messages and try to receive them */ 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]) { - fd_set fd_set; + fd_set fdset; struct timeval timeout; - int tmp1, tmp2, max_fd; + int max_fd; int ret; int i; - tmp1 = fds[0] > fds[1] ? fds[0] : fds[1]; - tmp2 = fds[2] > fds[3] ? fds[2] : fds[3]; - max_fd = tmp1 > tmp2 ? tmp1 : tmp2; + max_fd = fds[0]; + for (i = 1; i < 4; i++) { + if (fds[i] > max_fd) + max_fd = fds[i]; + } timeout.tv_sec = 3; timeout.tv_usec = 0; while (timeout.tv_sec > 0 || timeout.tv_usec > 0) { - FD_ZERO(&fd_set); - FD_SET(fds[0], &fd_set); - FD_SET(fds[1], &fd_set); - FD_SET(fds[2], &fd_set); - FD_SET(fds[3], &fd_set); + FD_ZERO(&fdset); + for (i = 0; i < 4; i++) { + FD_SET(fds[i], &fdset); + } - ret = select(max_fd+1, &fd_set, NULL, NULL, &timeout); + ret = select(max_fd+1, &fdset, NULL, NULL, &timeout); if (ret < 0) return -1; if (ret == 0) return 0; for (i = 0; i < 4; ++i) - if (FD_ISSET(fds[i], &fd_set)) + if (FD_ISSET(fds[i], &fdset)) lens[i] = receive_stun_response(fds[i], buffers[i], transaction_ids[i], buffers_lens[i], &peer_addrs[i]); if (lens[0] && lens[1] && lens[2] && lens[3]) @@ -345,13 +351,10 @@ int perform_stun(const char *if_name, const char *if_addr, const char *stun_host } /* Remove unblock for local ports */ - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; ++i) { delete_filter_rule(if_name, local_ports[i], IPPROTO_UDP); - - close(fds[0]); - close(fds[1]); - close(fds[2]); - close(fds[3]); + close(fds[i]); + } /* Parse received STUN messages */ have_ext_addr = 0; From 18ec4e88e795eb397bce4acb8c7542cb82436dc1 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:31:44 +0200 Subject: [PATCH 12/14] asyncsendto.c: 2018 --- miniupnpd/asyncsendto.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/miniupnpd/asyncsendto.c b/miniupnpd/asyncsendto.c index 8d47ca2..3f28b47 100644 --- a/miniupnpd/asyncsendto.c +++ b/miniupnpd/asyncsendto.c @@ -1,7 +1,7 @@ /* $Id: asyncsendto.c,v 1.8 2017/05/24 22:51:57 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2017 Thomas Bernard + * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ @@ -345,4 +345,3 @@ void finalize_sendto(void) } } } - From b2343c87a70fa9d7f4f04729c6e5a28951430668 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:33:33 +0200 Subject: [PATCH 13/14] Add STUN support see #307 --- miniupnpd/Changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/miniupnpd/Changelog.txt b/miniupnpd/Changelog.txt index 4d87d42..19e3c73 100644 --- a/miniupnpd/Changelog.txt +++ b/miniupnpd/Changelog.txt @@ -1,5 +1,8 @@ $Id: Changelog.txt,v 1.441 2018/05/08 21:34:18 nanard Exp $ +2018/07/06: + STUN support + VERSION 2.1 : released on 2018/05/08 2018/05/02: From 012cad411191ffb2c0716ddd1ba782af4dfb15e6 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 6 Jul 2018 13:36:23 +0200 Subject: [PATCH 14/14] Makefile.linux: fix depends --- miniupnpd/Makefile.linux | 1 + 1 file changed, 1 insertion(+) diff --git a/miniupnpd/Makefile.linux b/miniupnpd/Makefile.linux index bfb7894..2a4b49b 100644 --- a/miniupnpd/Makefile.linux +++ b/miniupnpd/Makefile.linux @@ -342,3 +342,4 @@ testasyncsendto.o: miniupnpdtypes.h config.h upnputils.h asyncsendto.h testportinuse.o: macros.h config.h portinuse.h miniupnpdctl.o: macros.h testssdppktgen.o: macros.h config.h miniupnpdpath.h upnphttp.h +upnpstun.o: config.h upnpstun.h netfilter/iptcrdr.h