add update_portmapping() / update_portmapping_desc_timestamp() functions

This commit is contained in:
Thomas Bernard 2016-02-12 15:01:30 +01:00
parent 34f80a011f
commit bdac007771
8 changed files with 447 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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; i<total_rules-1; i++) {
const struct ip_fw const * ptr = &rules[i];
if (proto == ptr->fw_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;
}

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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),
&timestamp, 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

View File

@ -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);