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: 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..2a4b49b 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 @@ -341,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 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/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) } } } - diff --git a/miniupnpd/getifaddr.c b/miniupnpd/getifaddr.c index 8016d63..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 */ @@ -259,3 +260,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 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 */ + { 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/miniupnpd.c b/miniupnpd/miniupnpd.c index 21badca..789f32a 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" @@ -990,6 +991,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; } } } @@ -1022,6 +1029,45 @@ 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; + } + + 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); + } else { + syslog(LOG_INFO, "STUN: ... done"); + } + + use_ext_ip_addr = ext_addr_str; + disable_port_forwarding = restrictive_nat; + return 0; +} + /* fill uuidvalue_wan and uuidvalue_wcd based on uuidvalue_igd */ void complete_uuidvalues(void) { @@ -1070,6 +1116,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; @@ -1134,6 +1181,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(PERFORMSTUNMASK); + 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) @@ -1330,6 +1387,10 @@ init(int argc, char * * argv, struct runtime_vars * v) return 1; } #endif /* ENABLE_PCP */ + if (GETFLAG(PERFORMSTUNMASK) && !ext_stun_host) { + fprintf(stderr, "You must specify ext_stun_host= when ext_perform_stun=yes\n"); + return 1; + } } #endif /* DISABLE_CONFIG_FILE */ @@ -1354,9 +1415,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': @@ -1606,6 +1672,22 @@ init(int argc, char * * argv, struct runtime_vars * v) goto print_usage; } + 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; + } + + 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(); @@ -1778,6 +1860,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 @@ -1923,6 +2006,26 @@ main(int argc, char * * argv) GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "", ext_if_name, upnp_bootid); + if(GETFLAG(PERFORMSTUNMASK)) + { + if (update_ext_ip_addr_from_stun(1) != 0) { + syslog(LOG_ERR, "Performing STUN failed. EXITING"); + return 1; + } + } + 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_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; + } + } + if(GETFLAG(ENABLEUPNPMASK)) { unsigned short listen_port; @@ -2114,6 +2217,23 @@ 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(PERFORMSTUNMASK)) + update_ext_ip_addr_from_stun(0); + 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); + syslog(LOG_INFO, "You are probably behind NAT, enable option ext_perform_stun=yes to detect public IP address"); + } + disable_port_forwarding = reserved; + } + } #ifdef ENABLE_NATPMP if(GETFLAG(ENABLENATPMPMASK)) SendNATPMPPublicAddressChangeNotification(snatpmp, addr_count); 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/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/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/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/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); 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; 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/upnpglobalvars.c b/miniupnpd/upnpglobalvars.c index 290059c..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; @@ -25,6 +29,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..3950a43 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; @@ -26,6 +30,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; @@ -69,6 +77,8 @@ extern int runtime_flags; #define FORCEIGDDESCV1MASK 0x0800 #endif +#define PERFORMSTUNMASK 0x1000 + #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask) #define CLEARFLAG(mask) runtime_flags &= ~mask 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; diff --git a/miniupnpd/upnpsoap.c b/miniupnpd/upnpsoap.c index b2a3742..e7193c0 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); @@ -356,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); diff --git a/miniupnpd/upnpstun.c b/miniupnpd/upnpstun.c new file mode 100644 index 0000000..3ffe245 --- /dev/null +++ b/miniupnpd/upnpstun.c @@ -0,0 +1,467 @@ +/* $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 + * in the LICENCE file provided within the distribution */ + +#include +#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]; + + 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; + 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 fdset; + struct timeval timeout; + int max_fd; + int ret; + int i; + + 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(&fdset); + for (i = 0; i < 4; i++) { + FD_SET(fds[i], &fdset); + } + + 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], &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]) + 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[i]); + } + + /* 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