miniupnpd/natpmp.c: avoid hang when all external ports in use

The NAT-PMP code attempts to find a different eport if the
requested one is already in use. If all eports are in use, that
would previously cause the code to iterate through the range of
eports forever. To avoid this case, we keep track of the first
eport we attempted to use and abort the loop once we've cycled
through all possible values exactly once (which takes us back
to the initial eport).
This commit is contained in:
Daniel Becker 2014-02-28 00:00:26 -08:00
parent c492b6f56f
commit c6a8879c87
1 changed files with 52 additions and 42 deletions

View File

@ -205,6 +205,7 @@ void ProcessIncomingNATPMPPacket(int s, unsigned char *msg_buff, int len,
int proto; int proto;
char iaddr_old[16]; char iaddr_old[16];
unsigned short iport_old; unsigned short iport_old;
unsigned short eport_first;
unsigned int timestamp; unsigned int timestamp;
iport = ntohs(*((uint16_t *)(req+4))); iport = ntohs(*((uint16_t *)(req+4)));
@ -266,56 +267,65 @@ void ProcessIncomingNATPMPPacket(int s, unsigned char *msg_buff, int len,
} else if(iport==0 } else if(iport==0
|| !check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, senderaddr->sin_addr, iport)) { || !check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, senderaddr->sin_addr, iport)) {
resp[3] = 2; /* Not Authorized/Refused */ resp[3] = 2; /* Not Authorized/Refused */
} else do { } else {
r = get_redirect_rule(ext_if_name, eport, proto, eport_first = eport;
iaddr_old, sizeof(iaddr_old), do {
&iport_old, 0, 0, 0, 0, r = get_redirect_rule(ext_if_name, eport, proto,
&timestamp, 0, 0); iaddr_old, sizeof(iaddr_old),
if(r==0) { &iport_old, 0, 0, 0, 0,
if(strcmp(senderaddrstr, iaddr_old)==0 &timestamp, 0, 0);
&& iport==iport_old) { if(r==0) {
/* redirection allready existing */ if(strcmp(senderaddrstr, iaddr_old)==0
syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing", && iport==iport_old) {
eport, (proto==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old); /* redirection allready existing */
/* remove and then add again */ syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing",
if(_upnp_delete_redir(eport, proto) < 0) { eport, (proto==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old);
syslog(LOG_ERR, "failed to remove port mapping"); /* remove and then add again */
break; if(_upnp_delete_redir(eport, proto) < 0) {
syslog(LOG_ERR, "failed to remove port mapping");
break;
}
} else {
eport++;
if(eport == eport_first) { /* no external port available */
syslog(LOG_ERR, "Failed to find available eport for NAT-PMP %hu %s->%s:%hu",
eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport);
resp[3] = 3; /* Failure */
break;
}
continue;
} }
} else {
eport++;
continue;
} }
} { /* do the redirection */
{ /* do the redirection */ char desc[64];
char desc[64];
#if 0 #if 0
timestamp = (unsigned)(time(NULL) - startup_time) timestamp = (unsigned)(time(NULL) - startup_time)
+ lifetime; + lifetime;
snprintf(desc, sizeof(desc), "NAT-PMP %u", timestamp); snprintf(desc, sizeof(desc), "NAT-PMP %u", timestamp);
#else #else
timestamp = time(NULL) + lifetime; timestamp = time(NULL) + lifetime;
snprintf(desc, sizeof(desc), "NAT-PMP %hu %s", snprintf(desc, sizeof(desc), "NAT-PMP %hu %s",
eport, (proto==IPPROTO_TCP)?"tcp":"udp"); eport, (proto==IPPROTO_TCP)?"tcp":"udp");
#endif #endif
/* TODO : check return code */ /* TODO : check return code */
if(upnp_redirect_internal(NULL, eport, senderaddrstr, if(upnp_redirect_internal(NULL, eport, senderaddrstr,
iport, proto, desc, iport, proto, desc,
timestamp) < 0) { timestamp) < 0) {
syslog(LOG_ERR, "Failed to add NAT-PMP %hu %s->%s:%hu '%s'", syslog(LOG_ERR, "Failed to add NAT-PMP %hu %s->%s:%hu '%s'",
eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport, desc); eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport, desc);
resp[3] = 3; /* Failure */ resp[3] = 3; /* Failure */
#if 0 #if 0
} else if( !nextnatpmptoclean_eport } else if( !nextnatpmptoclean_eport
|| timestamp < nextnatpmptoclean_timestamp) { || timestamp < nextnatpmptoclean_timestamp) {
nextnatpmptoclean_timestamp = timestamp; nextnatpmptoclean_timestamp = timestamp;
nextnatpmptoclean_eport = eport; nextnatpmptoclean_eport = eport;
nextnatpmptoclean_proto = proto; nextnatpmptoclean_proto = proto;
#endif #endif
}
break;
} }
break; } while(r==0);
} }
} while(r==0);
*((uint16_t *)(resp+8)) = htons(iport); /* private port */ *((uint16_t *)(resp+8)) = htons(iport); /* private port */
*((uint16_t *)(resp+10)) = htons(eport); /* public port */ *((uint16_t *)(resp+10)) = htons(eport); /* public port */
*((uint32_t *)(resp+12)) = htonl(lifetime); /* Port Mapping lifetime */ *((uint32_t *)(resp+12)) = htonl(lifetime); /* Port Mapping lifetime */