From bdac0077713122d53f534da50039198e5b843b1d Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 12 Feb 2016 15:01:30 +0100 Subject: [PATCH] add update_portmapping() / update_portmapping_desc_timestamp() functions --- miniupnpd/Changelog.txt | 1 + miniupnpd/commonrdr.h | 17 ++- miniupnpd/ipfw/ipfwrdr.c | 72 ++++++++- miniupnpd/netfilter/iptcrdr.c | 243 ++++++++++++++++++++++++++++++ miniupnpd/netfilter/testiptcrdr.c | 2 + miniupnpd/pf/obsdrdr.c | 73 ++++++++- miniupnpd/upnpredirect.c | 60 ++++++-- miniupnpd/upnpsoap.c | 8 - 8 files changed, 447 insertions(+), 29 deletions(-) diff --git a/miniupnpd/Changelog.txt b/miniupnpd/Changelog.txt index a010c37..7707ec4 100644 --- a/miniupnpd/Changelog.txt +++ b/miniupnpd/Changelog.txt @@ -3,6 +3,7 @@ $Id: Changelog.txt,v 1.421 2016/02/12 12:34:37 nanard Exp $ 2016/02/12: return error 729 - ConflictWithOtherMechanisms if IGD v2 is enabled. add iptc_init() check in iptcrdr.c/init_redirect() + add update_portmapping() / update_portmapping_desc_timestamp() functions 2016/02/11: use Linux libuuid uuid_generate() / BSD uuid_create() API diff --git a/miniupnpd/commonrdr.h b/miniupnpd/commonrdr.h index 7c0bd7c..61352e4 100644 --- a/miniupnpd/commonrdr.h +++ b/miniupnpd/commonrdr.h @@ -1,6 +1,6 @@ -/* $Id: commonrdr.h,v 1.9 2014/02/11 09:36:15 nanard Exp $ */ +/* $Id: commonrdr.h,v 1.10 2016/02/12 12:34:39 nanard Exp $ */ /* MiniUPnP project - * (c) 2006-2014 Thomas Bernard + * (c) 2006-2016 Thomas Bernard * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ @@ -52,5 +52,16 @@ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number); -#endif +/* update the port mapping internal port, decription and timestamp */ +int +update_portmapping(const char * ifname, unsigned short eport, int proto, + unsigned short iport, const char * desc, + unsigned int timestamp); +/* update the port mapping decription and timestamp */ +int +update_portmapping_desc_timestamp(const char * ifname, + unsigned short eport, int proto, + const char * desc, unsigned int timestamp); + +#endif diff --git a/miniupnpd/ipfw/ipfwrdr.c b/miniupnpd/ipfw/ipfwrdr.c index d19e631..ce37af3 100644 --- a/miniupnpd/ipfw/ipfwrdr.c +++ b/miniupnpd/ipfw/ipfwrdr.c @@ -1,9 +1,9 @@ -/* $Id: ipfwrdr.c,v 1.12 2012/02/11 13:10:57 nanard Exp $ */ +/* $Id: ipfwrdr.c,v 1.16 2016/02/12 13:44:01 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009 Jardel Weyrich - * (c) 2011-2012 Thomas Bernard + * (c) 2011-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ @@ -478,3 +478,71 @@ error: return array; } +int +update_portmapping_desc_timestamp(const char * ifname, + unsigned short eport, int proto, + const char * desc, unsigned int timestamp) +{ + UNUSED(ifname); + del_desc_time(eport, proto); + add_desc_time(eport, proto, desc, timestamp); + return 0; +} + +int +update_portmapping(const char * ifname, unsigned short eport, int proto, + unsigned short iport, const char * desc, + unsigned int timestamp) +{ + int i, count_rules, total_rules = 0; + struct ip_fw * rules = NULL; + int r = -1; + char iaddr[16]; + char rhost[16]; + int found; + + if (ipfw_validate_protocol(proto) < 0) + return -1; + if (ipfw_validate_ifname(ifname) < 0) + return -1; + + do { + count_rules = ipfw_fetch_ruleset(&rules, &total_rules, 10); + if (count_rules < 0) + goto error; + } while (count_rules == 10); + + found = 0; + iaddr[0] = '\0'; + rhost[0] = '\0'; + + for (i=0; ifw_prot && eport == ptr->fw_uar.fw_pts[0]) { + if (inet_ntop(AF_INET, &ptr->fw_fwd_ip.sin_addr, iaddr, sizeof(iaddr)) == NULL) { + syslog(LOG_ERR, "inet_ntop(): %m"); + goto error; + } + if ((ptr->fw_src.s_addr != 0) && + (inet_ntop(AF_INET, &ptr->fw_src.s_addr, rhost, sizeof(rhost)) == NULL)) { + syslog(LOG_ERR, "inet_ntop(): %m"); + goto error; + } + found = 1; + if (ipfw_exec(IP_FW_DEL, (struct ip_fw *)ptr, sizeof(*ptr)) < 0) + goto error; + del_desc_time(eport, proto); + break; + } + } + ipfw_free_ruleset(&rules); + rules = NULL; + + if(found) + r = add_redirect_rule2(ifname, rhost, eport, iaddr, iport, proto, desc, timestamp); + +error: + if (rules != NULL) + ipfw_free_ruleset(&rules); + return r; +} diff --git a/miniupnpd/netfilter/iptcrdr.c b/miniupnpd/netfilter/iptcrdr.c index d62aa22..df83da1 100644 --- a/miniupnpd/netfilter/iptcrdr.c +++ b/miniupnpd/netfilter/iptcrdr.c @@ -1552,6 +1552,249 @@ get_portmappings_in_range(unsigned short startport, unsigned short endport, return array; } +int +update_portmapping_desc_timestamp(const char * ifname, + unsigned short eport, int proto, + const char * desc, unsigned int timestamp) +{ + UNUSED(ifname); + del_redirect_desc(eport, proto); + add_redirect_desc(eport, proto, desc, timestamp); + return 0; +} + +static int +update_rule_and_commit(const char * table, const char * chain, + unsigned index, const struct ipt_entry * e) +{ + IPTC_HANDLE h; + int r = 0; + + h = iptc_init(table); + if(!h) + { + syslog(LOG_ERR, "%s() : iptc_init() failed : %s", + "update_rule_and_commit", iptc_strerror(errno)); + return -1; + } +#ifdef IPTABLES_143 + if(!iptc_replace_entry(chain, e, index, h)) +#else + if(!iptc_replace_entry(chain, e, index, &h)) +#endif + { + syslog(LOG_ERR, "%s(): iptc_replace_entry: %s", + "update_rule_and_commit", iptc_strerror(errno)); + r = -1; + } +#ifdef IPTABLES_143 + else if(!iptc_commit(h)) +#else + else if(!iptc_commit(&h)) +#endif + { + syslog(LOG_ERR, "%s(): iptc_commit: %s", + "update_rule_and_commit", iptc_strerror(errno)); + r = -1; + } +#ifdef IPTABLES_143 + iptc_free(h); +#else + iptc_free(&h); +#endif + return r; +} + +int +update_portmapping(const char * ifname, unsigned short eport, int proto, + unsigned short iport, const char * desc, + unsigned int timestamp) +{ + int r = 0; + int found = 0; + unsigned index = 0; + unsigned i = 0; + IPTC_HANDLE h; + const struct ipt_entry * e; + struct ipt_entry * new_e = NULL; + size_t entry_len; + const struct ipt_entry_target * target; + struct ip_nat_multi_range * mr; + const struct ipt_entry_match *match; + uint32_t iaddr = 0; + unsigned short old_iport; + + h = iptc_init("nat"); + if(!h) + { + syslog(LOG_ERR, "%s() : iptc_init() failed : %s", + "update_portmapping", iptc_strerror(errno)); + return -1; + } + /* First step : find the right nat rule */ + if(!iptc_is_chain(miniupnpd_nat_chain, h)) + { + syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain); + r = -1; + } + else + { +#ifdef IPTABLES_143 + for(e = iptc_first_rule(miniupnpd_nat_chain, h); + e; + e = iptc_next_rule(e, h), i++) +#else + for(e = iptc_first_rule(miniupnpd_nat_chain, &h); + e; + e = iptc_next_rule(e, &h), i++) +#endif + { + if(proto==e->ip.proto) + { + match = (const struct ipt_entry_match *)&e->elems; + if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) + { + const struct ipt_tcp * info; + info = (const struct ipt_tcp *)match->data; + if(eport != info->dpts[0]) + continue; + } + else + { + const struct ipt_udp * info; + info = (const struct ipt_udp *)match->data; + if(eport != info->dpts[0]) + continue; + } + /* we found the right rule */ + found = 1; + index = i; + target = (void *)e + e->target_offset; + mr = (struct ip_nat_multi_range *)&target->data[0]; + iaddr = mr->range[0].min_ip; + old_iport = ntohs(mr->range[0].min.all); + entry_len = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; + new_e = malloc(entry_len); + if(new_e == NULL) { + syslog(LOG_ERR, "%s: malloc(%u) error", + "update_portmapping", (unsigned)entry_len); + r = -1; + } + else + { + memcpy(new_e, e, entry_len); + } + break; + } + } + } +#ifdef IPTABLES_143 + iptc_free(h); +#else + iptc_free(&h); +#endif + if(!found || r < 0) + return -1; + syslog(LOG_INFO, "Trying to update nat rule at index %u", index); + target = (void *)new_e + new_e->target_offset; + mr = (struct ip_nat_multi_range *)&target->data[0]; + mr->range[0].min.all = mr->range[0].max.all = htons(iport); + /* first update the nat rule */ + r = update_rule_and_commit("nat", miniupnpd_nat_chain, index, new_e); + free(new_e); new_e = NULL; + if(r < 0) + return r; + + /* update filter rule */ + h = iptc_init("filter"); + if(!h) + { + syslog(LOG_ERR, "%s() : iptc_init() failed : %s", + "update_portmapping", iptc_strerror(errno)); + return -1; + } + i = 0; found = 0; + if(!iptc_is_chain(miniupnpd_forward_chain, h)) + { + syslog(LOG_ERR, "chain %s not found", miniupnpd_forward_chain); + } + else + { + /* 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) + continue; + target = (void *)e + e->target_offset; + match = (const struct ipt_entry_match *)&e->elems; + if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) + { + const struct ipt_tcp * info; + info = (const struct ipt_tcp *)match->data; + if(old_iport != info->dpts[0]) + continue; + } + else + { + const struct ipt_udp * info; + info = (const struct ipt_udp *)match->data; + if(old_iport != info->dpts[0]) + continue; + } + if(iaddr != e->ip.dst.s_addr) + continue; + index = i; + found = 1; + entry_len = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; + new_e = malloc(entry_len); + if(new_e == NULL) { + syslog(LOG_ERR, "%s: malloc(%u) error", + "update_portmapping", (unsigned)entry_len); + r = -1; + } else { + memcpy(new_e, e, entry_len); + } + break; + } + } +#ifdef IPTABLES_143 + iptc_free(h); +#else + iptc_free(&h); +#endif + if(!found || r < 0) + return -1; + + syslog(LOG_INFO, "Trying to update filter rule at index %u", index); + match = (struct ipt_entry_match *)&new_e->elems; + if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) + { + struct ipt_tcp * info; + info = (struct ipt_tcp *)match->data; + info->dpts[0] = info->dpts[1] = iport; + } + else + { + struct ipt_udp * info; + info = (struct ipt_udp *)match->data; + info->dpts[0] = info->dpts[1] = iport; + } + r = update_rule_and_commit("filter", miniupnpd_forward_chain, index, new_e); + free(new_e); new_e = NULL; + if(r < 0) + return r; + + return update_portmapping_desc_timestamp(ifname, eport, proto, desc, timestamp); +} + /* ================================ */ #ifdef DEBUG static int diff --git a/miniupnpd/netfilter/testiptcrdr.c b/miniupnpd/netfilter/testiptcrdr.c index 3a6a749..59663f7 100644 --- a/miniupnpd/netfilter/testiptcrdr.c +++ b/miniupnpd/netfilter/testiptcrdr.c @@ -53,6 +53,8 @@ main(int argc, char ** argv) fprintf(stderr, "addpeenatrule failed\n"); } #endif + /*update_portmapping_desc_timestamp(NULL, eport, proto, "updated desc", time(NULL)+42);*/ + update_portmapping(NULL, eport, proto, iport+1, "updated rule", time(NULL)+42); /* test */ { unsigned short p1, p2; diff --git a/miniupnpd/pf/obsdrdr.c b/miniupnpd/pf/obsdrdr.c index 7c6799b..3a75f43 100644 --- a/miniupnpd/pf/obsdrdr.c +++ b/miniupnpd/pf/obsdrdr.c @@ -1,7 +1,7 @@ -/* $Id: obsdrdr.c,v 1.84 2015/02/08 08:55:55 nanard Exp $ */ +/* $Id: obsdrdr.c,v 1.86 2016/02/12 13:11:03 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2006-2015 Thomas Bernard + * (c) 2006-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ @@ -607,7 +607,7 @@ error: static int priv_delete_redirect_rule(const char * ifname, unsigned short eport, int proto, unsigned short * iport, - in_addr_t * iaddr) + in_addr_t * iaddr, char * rhost, int rhostlen) { int i, n; struct pfioc_rule pr; @@ -683,6 +683,14 @@ priv_delete_redirect_rule(const char * ifname, unsigned short eport, *iaddr = pr.rule.rdr.addr.v.a.addr.v4.s_addr; } #endif + if(rhost && rhostlen > 0) + { + if (pr.rule.src.addr.v.a.addr.v4.s_addr == 0) + rhost[0] = '\0'; /* empty string */ + else + inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr, + rhost, rhostlen); + } pr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { @@ -708,7 +716,7 @@ int delete_redirect_rule(const char * ifname, unsigned short eport, int proto) { - return priv_delete_redirect_rule(ifname, eport, proto, NULL, NULL); + return priv_delete_redirect_rule(ifname, eport, proto, NULL, NULL, NULL, 0); } static int @@ -782,7 +790,7 @@ delete_redirect_and_filter_rules(const char * ifname, unsigned short eport, int r; unsigned short iport; in_addr_t iaddr; - r = priv_delete_redirect_rule(ifname, eport, proto, &iport, &iaddr); + r = priv_delete_redirect_rule(ifname, eport, proto, &iport, &iaddr, NULL, 0); if(r == 0) { r = priv_delete_filter_rule(ifname, iport, proto, iaddr); @@ -978,6 +986,61 @@ get_portmappings_in_range(unsigned short startport, unsigned short endport, return array; } +/* update the port mapping internal port, decription and timestamp */ +int +update_portmapping(const char * ifname, unsigned short eport, int proto, + unsigned short iport, const char * desc, + unsigned int timestamp) +{ + unsigned short old_iport; + in_addr_t iaddr; + char iaddr_str[16]; + char rhost[32]; + + if(priv_delete_redirect_rule(ifname, eport, proto, &old_iport, &iaddr, rhost, sizeof(rhost)) < 0) + return -1; + if (priv_delete_filter_rule(ifname, old_iport, proto, iaddr) < 0) + return -1; + + inet_ntop(AF_INET, &iaddr, iaddr_str, sizeof(iaddr_str)); + + if(add_redirect_rule2(ifname, rhost, eport, iaddr_str, iport, proto, + desc, timestamp) < 0) + return -1; + if(add_filter_rule2(ifname, rhost, iaddr_str, eport, iport, proto, desc) < 0) + return -1; + + return 0; +} + +/* update the port mapping decription and timestamp */ +int +update_portmapping_desc_timestamp(const char * ifname, + unsigned short eport, int proto, + const char * desc, unsigned int timestamp) +{ + unsigned short iport; + in_addr_t iaddr; + char iaddr_str[16]; + char rhost[32]; + + if(priv_delete_redirect_rule(ifname, eport, proto, &iport, &iaddr, rhost, sizeof(rhost)) < 0) + return -1; + if (priv_delete_filter_rule(ifname, iport, proto, iaddr) < 0) + return -1; + + inet_ntop(AF_INET, &iaddr, iaddr_str, sizeof(iaddr_str)); + + if(add_redirect_rule2(ifname, rhost, eport, iaddr_str, iport, proto, + desc, timestamp) < 0) + return -1; + if(add_filter_rule2(ifname, rhost, iaddr_str, eport, iport, proto, desc) < 0) + return -1; + + return 0; +} + + /* this function is only for testing */ #if TEST void diff --git a/miniupnpd/upnpredirect.c b/miniupnpd/upnpredirect.c index 4e1c037..fcb4b51 100644 --- a/miniupnpd/upnpredirect.c +++ b/miniupnpd/upnpredirect.c @@ -264,6 +264,7 @@ upnp_redirect(const char * rhost, unsigned short eport, { int proto, r; char iaddr_old[32]; + char rhost_old[32]; unsigned short iport_old; struct in_addr address; unsigned int timestamp; @@ -280,20 +281,59 @@ upnp_redirect(const char * rhost, unsigned short eport, "%hu->%s:%hu %s", eport, iaddr, iport, protocol); return -3; } + /* IGDv1 (WANIPConnection:1 Service Template Version 1.01 / Nov 12, 2001) + * - 2.2.20.PortMappingDescription : + * Overwriting Previous / Existing Port Mappings: + * If the RemoteHost, ExternalPort, PortMappingProtocol and InternalClient + * are exactly the same as an existing mapping, the existing mapping values + * for InternalPort, PortMappingDescription, PortMappingEnabled and + * PortMappingLeaseDuration are overwritten. + * Rejecting a New Port Mapping: + * In cases where the RemoteHost, ExternalPort and PortMappingProtocol + * are the same as an existing mapping, but the InternalClient is + * different, the action is rejected with an appropriate error. + * Add or Reject New Port Mapping behavior based on vendor implementation: + * In cases where the ExternalPort, PortMappingProtocol and InternalClient + * are the same, but RemoteHost is different, the vendor can choose to + * support both mappings simultaneously, or reject the second mapping + * with an appropriate error. + * + * - 2.4.16.AddPortMapping + * This action creates a new port mapping or overwrites an existing + * mapping with the same internal client. If the ExternalPort and + * PortMappingProtocol pair is already mapped to another internal client, + * an error is returned. + * + * IGDv2 (WANIPConnection:2 Service Standardized DCP (SDCP) Sep 10, 2010) + * Protocol ExternalPort RemoteHost InternalClient Result + * = = ≠ ≠ Failure + * = = ≠ = Failure or success + * (vendor specific) + * = = = ≠ Failure + * = = = = Success (overwrite) + */ + rhost_old[0] = '\0'; r = get_redirect_rule(ext_if_name, eport, proto, iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0, - 0, 0, + rhost_old, sizeof(rhost_old), ×tamp, 0, 0); if(r == 0) { - /* if existing redirect rule matches redirect request return success - * xbox 360 does not keep track of the port it redirects and will - * redirect another port when receiving ConflictInMappingEntry */ - if(strcmp(iaddr, iaddr_old)==0 && iport==iport_old) { - syslog(LOG_INFO, "ignoring redirect request as it matches existing redirect"); + if(strcmp(iaddr, iaddr_old)==0 && + ((rhost == NULL && rhost_old[0]=='\0') || + (rhost && (strcmp(rhost, "*") == 0) && rhost_old[0]=='\0') || + (rhost && (strcmp(rhost, rhost_old) == 0)))) { + syslog(LOG_INFO, "updating existing port mapping %hu %s (rhost '%s') => %s:%hu", + eport, protocol, rhost_old, iaddr_old, iport_old); + timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0; + /* TODO : update lease file */ + if(iport != iport_old) { + return update_portmapping(ext_if_name, eport, proto, iport, desc, timestamp); + } else { + return update_portmapping_desc_timestamp(ext_if_name, eport, proto, desc, timestamp); + } } else { - - syslog(LOG_INFO, "port %hu protocol %s already redirected to %s:%hu", - eport, protocol, iaddr_old, iport_old); + syslog(LOG_INFO, "port %hu %s (rhost '%s') already redirected to %s:%hu", + eport, protocol, rhost_old, iaddr_old, iport_old); return -2; } #ifdef CHECK_PORTINUSE @@ -309,8 +349,6 @@ upnp_redirect(const char * rhost, unsigned short eport, return upnp_redirect_internal(rhost, eport, iaddr, iport, proto, desc, timestamp); } - - return 0; } int diff --git a/miniupnpd/upnpsoap.c b/miniupnpd/upnpsoap.c index 759bde9..ed75569 100644 --- a/miniupnpd/upnpsoap.c +++ b/miniupnpd/upnpsoap.c @@ -445,14 +445,6 @@ AddPortMapping(struct upnphttp * h, const char * action, const char * ns) action, eport, int_ip, iport, protocol, desc, leaseduration, r_host ? r_host : "NULL"); - /* TODO : be compliant with IGD spec for updating existing port mappings. - See "WANIPConnection:1 Service Template Version 1.01" 2.2.20.PortMappingDescription : - Overwriting Previous / Existing Port Mappings: - If the RemoteHost, ExternalPort, PortMappingProtocol and InternalClient are - exactly the same as an existing mapping, the existing mapping values for InternalPort, - PortMappingDescription, PortMappingEnabled and PortMappingLeaseDuration are - overwritten. - */ r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration); ClearNameValueList(&data);