diff --git a/miniupnpd/.gitignore b/miniupnpd/.gitignore index f97b1ce..d6c6eb9 100644 --- a/miniupnpd/.gitignore +++ b/miniupnpd/.gitignore @@ -9,6 +9,7 @@ testgetifstats testupnpdescgen testupnppermissions testgetroute +testasyncsendto netfilter/testiptcrdr netfilter/testiptcrdr_dscp netfilter/testiptcrdr_peer diff --git a/miniupnpd/Makefile b/miniupnpd/Makefile index 09f776b..e404609 100644 --- a/miniupnpd/Makefile +++ b/miniupnpd/Makefile @@ -80,7 +80,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 \ - upnppinhole.o + upnppinhole.o asyncsendto.o BSDOBJS = bsd/getifstats.o bsd/ifacewatcher.o bsd/getroute.o SUNOSOBJS = solaris/getifstats.o bsd/ifacewatcher.o bsd/getroute.o MACOBJS = mac/getifstats.o bsd/ifacewatcher.o bsd/getroute.o @@ -116,10 +116,11 @@ TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o TESTUPNPPERMISSIONSOBJS = testupnppermissions.o upnppermissions.o TESTGETIFADDROBJS = testgetifaddr.o getifaddr.o MINIUPNPDCTLOBJS = miniupnpdctl.o +TESTASYNCSENDTOOBJS = testasyncsendto.o asyncsendto.o upnputils.o EXECUTABLES = miniupnpd testupnpdescgen testgetifstats \ testupnppermissions miniupnpdctl \ - testgetifaddr testgetroute + testgetifaddr testgetroute testasyncsendto .if $(OSNAME) == "Darwin" LIBS = .else @@ -142,7 +143,7 @@ clean: $(RM) $(STDOBJS) $(BSDOBJS) $(SUNOSOBJS) $(MACOBJS) $(EXECUTABLES) \ testupnpdescgen.o \ $(MISCOBJS) config.h testgetifstats.o testupnppermissions.o \ - miniupnpdctl.o testgetifaddr.o testgetroute.o \ + miniupnpdctl.o testgetifaddr.o testgetroute.o testasyncsendto.o \ $(PFOBJS) $(IPFOBJS) $(IPFWOBJS) install: miniupnpd genuuid @@ -199,6 +200,9 @@ testupnppermissions: config.h $(TESTUPNPPERMISSIONSOBJS) testgetroute: config.h $(TESTGETROUTEOBJS) $(CC) $(CFLAGS) -o $@ $(TESTGETROUTEOBJS) +testasyncsendto: config.h $(TESTASYNCSENDTOOBJS) + $(CC) $(CFLAGS) -o $@ $(TESTASYNCSENDTOOBJS) + # gmake : # $(CC) $(CFLAGS) -o $@ $^ # BSDmake : diff --git a/miniupnpd/Makefile.linux b/miniupnpd/Makefile.linux index a9bab57..270c72d 100644 --- a/miniupnpd/Makefile.linux +++ b/miniupnpd/Makefile.linux @@ -47,7 +47,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 \ - upnppinhole.o pcplearndscp.o + upnppinhole.o pcplearndscp.o asyncsendto.o LNXOBJS = linux/getifstats.o linux/ifacewatcher.o linux/getroute.o NETFILTEROBJS = netfilter/iptcrdr.o netfilter/iptpinhole.o netfilter/nfct_get.o @@ -150,7 +150,7 @@ TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o EXECUTABLES = miniupnpd testupnpdescgen testgetifstats \ testupnppermissions miniupnpdctl testgetifaddr \ - testgetroute + testgetroute testasyncsendto .PHONY: all clean install depend genuuid @@ -161,7 +161,7 @@ clean: $(RM) $(EXECUTABLES) $(RM) testupnpdescgen.o testgetifstats.o $(RM) testupnppermissions.o testgetifaddr.o - $(RM) testgetroute.o + $(RM) testgetroute.o testasyncsendto.o $(RM) miniupnpdctl.o install: miniupnpd miniupnpd.8 miniupnpd.conf genuuid \ @@ -205,6 +205,8 @@ testgetifaddr: testgetifaddr.o getifaddr.o testgetroute: testgetroute.o linux/getroute.o upnputils.o -lnfnetlink +testasyncsendto: testasyncsendto.o asyncsendto.o upnputils.o + miniupnpdctl: miniupnpdctl.o config.h: genconfig.sh VERSION @@ -214,7 +216,7 @@ depend: config.h makedepend -f$(MAKEFILE_LIST) -Y \ $(ALLOBJS:.o=.c) $(TESTUPNPDESCGENOBJS:.o=.c) \ testgetifstats.c testupnppermissions.c testgetifaddr.c \ - testgetroute.c miniupnpdctl.c 2>/dev/null + testgetroute.c testasyncsendto.c miniupnpdctl.c 2>/dev/null # DO NOT DELETE diff --git a/miniupnpd/Makefile.macosx b/miniupnpd/Makefile.macosx index 7520124..27280c6 100644 --- a/miniupnpd/Makefile.macosx +++ b/miniupnpd/Makefile.macosx @@ -32,7 +32,8 @@ FWNAME = $(shell [ `uname -r | cut -d. -f1` -ge 11 ] && echo "pf" || echo "ipfw 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 + upnpevents.o getconnstatus.o upnputils.o \ + asyncsendto.o MAC_OBJS = mac/getifstats.o bsd/ifacewatcher.o IPFW_OBJS = ipfw/ipfwrdr.o ipfw/ipfwaux.o PF_OBJS = pf/obsdrdr.o pf/pfpinhole.o diff --git a/miniupnpd/asyncsendto.c b/miniupnpd/asyncsendto.c new file mode 100644 index 0000000..5cd2a3d --- /dev/null +++ b/miniupnpd/asyncsendto.c @@ -0,0 +1,219 @@ +/* $Id: $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2014 Thomas Bernard + * 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 "asyncsendto.h" + +/* state diagram for a packet : + * + * | + * V + * -> ESCHEDULED -> ESENDNOW -> sent + * ^ | + * | V + * EWAITREADY -> sent + */ +struct scheduled_send { + LIST_ENTRY(scheduled_send) entries; + struct timeval ts; + enum {ESCHEDULED=1, EWAITREADY=2, ESENDNOW=3} state; + int sockfd; + const void * buf; + size_t len; + int flags; + const struct sockaddr *dest_addr; + socklen_t addrlen; + char data[]; +}; + +static LIST_HEAD(listhead, scheduled_send) send_list = { NULL }; + +/* + * ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + * const struct sockaddr *dest_addr, socklen_t addrlen); + */ + +/* delay = milli seconds */ +ssize_t +sendto_schedule(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen, + unsigned int delay) +{ + enum {ESCHEDULED, EWAITREADY, ESENDNOW} state; + ssize_t n; + struct timeval tv; + struct scheduled_send * elt; + + if(delay == 0) { + /* first try to send at once */ + n = sendto(sockfd, buf, len, flags, dest_addr, addrlen); + if(n >= 0) + return n; + else if(errno == EAGAIN || errno == EWOULDBLOCK) { + /* use select() on this socket */ + state = EWAITREADY; + } else if(errno == EINTR) { + state = ESENDNOW; + } else { + /* uncatched error */ + return n; + } + } else { + state = ESCHEDULED; + } + + /* schedule */ + if(gettimeofday(&tv, 0) < 0) { + return -1; + } + /* allocate enough space for structure + buffers */ + elt = malloc(sizeof(struct scheduled_send) + len + addrlen); + if(elt == NULL) { + syslog(LOG_ERR, "malloc failed to allocate %u bytes", + (unsigned)(sizeof(struct scheduled_send) + len + addrlen)); + return -1; + } + elt->state = state; + /* time the packet should be sent */ + elt->ts.tv_sec = tv.tv_sec + (delay / 1000); + elt->ts.tv_usec = tv.tv_usec + (delay % 1000) * 1000; + if(elt->ts.tv_usec > 1000000) { + elt->ts.tv_sec++; + elt->ts.tv_usec -= 1000000; + } + elt->sockfd = sockfd; + elt->flags = flags; + memcpy(elt->data, dest_addr, addrlen); + elt->dest_addr = (struct sockaddr *)elt->data; + elt->addrlen = addrlen; + memcpy(elt->data + addrlen, buf, len); + elt->buf = (void *)(elt->data + addrlen); + elt->len = len; + /* insert */ + LIST_INSERT_HEAD( &send_list, elt, entries); + return 0; +} + + +/* try to send at once, and queue the packet if needed */ +ssize_t +sendto_or_schedule(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + return sendto_schedule(sockfd, buf, len, flags, dest_addr, addrlen, 0); +} + +/* get_next_scheduled_send() return number of scheduled send in list */ +int get_next_scheduled_send(struct timeval * next_send) +{ + int n = 0; + struct scheduled_send * elt; + if(next_send == NULL) + return -1; + for(elt = send_list.lh_first; elt != NULL; elt = elt->entries.le_next) { + if(n == 0 || (elt->ts.tv_sec < next_send->tv_sec) || + (elt->ts.tv_sec == next_send->tv_sec && elt->ts.tv_usec < next_send->tv_usec)) { + next_send->tv_sec = elt->ts.tv_sec; + next_send->tv_usec = elt->ts.tv_usec; + } + n++; + } + return n; +} + +/* update writefds for select() call + * return the number of packets to try to send at once */ +int get_sendto_fds(fd_set * writefds, int * max_fd, const struct timeval * now) +{ + int n = 0; + struct scheduled_send * elt; + for(elt = send_list.lh_first; elt != NULL; elt = elt->entries.le_next) { + if(elt->state == EWAITREADY) { + /* last sendto() call returned EAGAIN/EWOULDBLOCK */ + FD_SET(elt->sockfd, writefds); + if(elt->sockfd > *max_fd) + *max_fd = elt->sockfd; + n++; + } else if((elt->ts.tv_sec < now->tv_sec) || + (elt->ts.tv_sec == now->tv_sec && elt->ts.tv_usec <= now->tv_usec)) { + /* we waited long enough, now send ! */ + elt->state = ESENDNOW; + n++; + } + } + return n; +} + +/* executed sendto() when needed */ +int try_sendto(fd_set * writefds) +{ + ssize_t n; + struct scheduled_send * elt; + struct scheduled_send * next; + for(elt = send_list.lh_first; elt != NULL; elt = next) { + next = elt->entries.le_next; + if((elt->state == ESENDNOW) || + (elt->state == EWAITREADY && FD_ISSET(elt->sockfd, writefds))) { + syslog(LOG_DEBUG, "try_sendto(): %d bytes on socket %d", + (int)elt->len, elt->sockfd); + n = sendto(elt->sockfd, elt->buf, elt->len, elt->flags, + elt->dest_addr, elt->addrlen); + if(n < 0) { + if(errno == EINTR) { + /* retry at once */ + elt->state = ESENDNOW; + continue; + } else if(errno == EAGAIN || errno == EWOULDBLOCK) { + /* retry once the socket is ready for writing */ + elt->state = EWAITREADY; + continue; + } + /* uncatched error */ + /* remove from the list */ + LIST_REMOVE(elt, entries); + free(elt); + return n; + } else { + /* remove from the list */ + LIST_REMOVE(elt, entries); + free(elt); + } + } + } + return 0; +} + +/* empty the list */ +void finalize_sendto(void) +{ + ssize_t n; + struct scheduled_send * elt; + struct scheduled_send * next; + /* TODO : improve with a select() and a short timeout */ + for(elt = send_list.lh_first; elt != NULL; elt = next) { + next = elt->entries.le_next; + syslog(LOG_DEBUG, "finalize_sendto(): %d bytes on socket %d", + (int)elt->len, elt->sockfd); + n = sendto(elt->sockfd, elt->buf, elt->len, elt->flags, + elt->dest_addr, elt->addrlen); + if(n < 0) { + syslog(LOG_WARNING, "sendto(): %m"); + } + /* remove from the list */ + LIST_REMOVE(elt, entries); + free(elt); + } +} + diff --git a/miniupnpd/asyncsendto.h b/miniupnpd/asyncsendto.h new file mode 100644 index 0000000..60c04de --- /dev/null +++ b/miniupnpd/asyncsendto.h @@ -0,0 +1,38 @@ +/* $Id: $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2014 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef ASYNCSENDTO_H_INCLUDED +#define ASYNCSENDTO_H_INCLUDED + +/* sendto_schedule() : see sendto(2) + * schedule sendto() call after delay (milliseconds) */ +ssize_t +sendto_schedule(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen, + unsigned int delay); + +/* sendto_schedule() : see sendto(2) + * try sendto() at once and schedule if EINTR/EAGAIN/EWOULDBLOCK */ +ssize_t +sendto_or_schedule(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); + +/* get_next_scheduled_send() + * return number of scheduled sendto + * set next_send to timestamp to send next packet */ +int get_next_scheduled_send(struct timeval * next_send); + +/* execute sendto() for needed packets */ +int try_sendto(fd_set * writefds); + +/* set writefds before select() */ +int get_sendto_fds(fd_set * writefds, int * max_fd, const struct timeval * now); + +/* empty the list */ +void finalize_sendto(void); + +#endif diff --git a/miniupnpd/genconfig.sh b/miniupnpd/genconfig.sh index 1a4e785..8b13389 100755 --- a/miniupnpd/genconfig.sh +++ b/miniupnpd/genconfig.sh @@ -2,7 +2,7 @@ # $Id: genconfig.sh,v 1.69 2014/02/24 18:41:25 nanard Exp $ # miniupnp daemon # http://miniupnp.free.fr or http://miniupnp.tuxfamily.org/ -# (c) 2006-2013 Thomas Bernard +# (c) 2006-2014 Thomas Bernard # This software is subject to the conditions detailed in the # LICENCE file provided within the distribution @@ -83,7 +83,7 @@ ${RM} ${CONFIGFILE} echo "/* MiniUPnP Project" >> ${CONFIGFILE} echo " * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/" >> ${CONFIGFILE} -echo " * (c) 2006-2013 Thomas Bernard" >> ${CONFIGFILE} +echo " * (c) 2006-2014 Thomas Bernard" >> ${CONFIGFILE} echo " * generated by $0 on `date`" >> ${CONFIGFILE} echo " * using command line options $* */" >> ${CONFIGFILE} echo "#ifndef $CONFIGMACRO" >> ${CONFIGFILE} @@ -471,6 +471,14 @@ else fi echo "" >> ${CONFIGFILE} +echo "/* Wait a little before answering M-SEARCH request */" >> ${CONFIGFILE} +if [ -n "$STRICT" ] ; then + echo "#define DELAY_MSEARCH_RESPONSE" >> ${CONFIGFILE} +else + echo "/*#define DELAY_MSEARCH_RESPONSE*/" >> ${CONFIGFILE} +fi +echo "" >> ${CONFIGFILE} + echo "/* disable reading and parsing of config file (miniupnpd.conf) */" >> ${CONFIGFILE} echo "/*#define DISABLE_CONFIG_FILE*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} diff --git a/miniupnpd/minissdp.c b/miniupnpd/minissdp.c index 31110dc..dcee00c 100644 --- a/miniupnpd/minissdp.c +++ b/miniupnpd/minissdp.c @@ -24,6 +24,7 @@ #include "minissdp.h" #include "upnputils.h" #include "getroute.h" +#include "asyncsendto.h" #include "codelength.h" /* SSDP ip/port */ @@ -330,11 +331,13 @@ EXT: * st, st_len : ST: header * suffix : suffix for USN: header * host, port : our HTTP host, port + * delay : in milli-seconds */ static void SendSSDPResponse(int s, const struct sockaddr * addr, const char * st, int st_len, const char * suffix, - const char * host, unsigned short port, const char * uuidvalue) + const char * host, unsigned short port, const char * uuidvalue, + unsigned int delay) { int l, n; char buf[512]; @@ -399,15 +402,14 @@ SendSSDPResponse(int s, const struct sockaddr * addr, } addrlen = (addr->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); - n = sendto(s, buf, l, 0, - addr, addrlen); + n = sendto_schedule(s, buf, l, 0, + addr, addrlen, delay); sockaddr_to_string(addr, addr_str, sizeof(addr_str)); syslog(LOG_INFO, "SSDP Announce %d bytes to %s ST: %.*s",n, addr_str, l, buf); if(n < 0) { - /* XXX handle EINTR, EAGAIN, EWOULDBLOCK */ syslog(LOG_ERR, "sendto(udp): %m"); } } @@ -488,7 +490,7 @@ SendSSDPNotify(int s, const struct sockaddr * dest, syslog(LOG_WARNING, "SendSSDPNotify(): truncated output"); l = sizeof(bufr) - 1; } - n = sendto(s, bufr, l, 0, dest, + n = sendto_or_schedule(s, bufr, l, 0, dest, #ifdef ENABLE_IPV6 ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in) #else @@ -497,7 +499,6 @@ SendSSDPNotify(int s, const struct sockaddr * dest, ); if(n < 0) { - /* XXX handle EINTR, EAGAIN, EWOULDBLOCK */ syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, host ? host : "NULL"); } @@ -505,6 +506,22 @@ SendSSDPNotify(int s, const struct sockaddr * dest, { syslog(LOG_NOTICE, "sendto() sent %d out of %d bytes", n, l); } + /* Due to the unreliable nature of UDP, devices SHOULD send the entire + * set of discovery messages more than once with some delay between + * sets e.g. a few hundred milliseconds. To avoid network congestion + * discovery messages SHOULD NOT be sent more than three times. */ + n = sendto_schedule(s, bufr, l, 0, dest, +#ifdef ENABLE_IPV6 + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in), +#else + sizeof(struct sockaddr_in), +#endif + 250); + if(n < 0) + { + syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, + host ? host : "NULL"); + } } static void @@ -634,8 +651,20 @@ ProcessSSDPData(int s, const char *bufr, int n, #ifdef ENABLE_IPV6 char announced_host_buf[64]; #endif +#endif +#if defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE) int mx_value = -1; #endif + unsigned int delay = 0; + /* UPnP Device Architecture v1.1. 1.3.3 Search response : + * Devices responding to a multicast M-SEARCH SHOULD wait a random period + * of time between 0 seconds and the number of seconds specified in the + * MX field value of the search request before responding, in order to + * avoid flooding the requesting control point with search responses + * from multiple devices. If the search request results in the need for + * a multiple part response from the device, those multiple part + * responses SHOULD be spread at random intervals through the time period + * from 0 to the number of seconds specified in the MX header field. */ /* get the string representation of the sender address */ sockaddr_to_string(sender, sender_str, sizeof(sender_str)); @@ -678,7 +707,7 @@ ProcessSSDPData(int s, const char *bufr, int n, /*while(bufr[i+j]!='\r') j++;*/ /*syslog(LOG_INFO, "%.*s", j, bufr+i);*/ } -#ifdef UPNP_STRICT +#if defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE) else if((i < n - 3) && (strncasecmp(bufr+i, "mx:", 3) == 0)) { const char * mx; @@ -696,16 +725,32 @@ ProcessSSDPData(int s, const char *bufr, int n, #endif } #ifdef UPNP_STRICT + /* For multicast M-SEARCH requests, if the search request does + * not contain an MX header field, the device MUST silently + * discard and ignore the search request. */ if(mx_value < 0) { syslog(LOG_INFO, "ignoring SSDP packet missing MX: header"); return; + } else if(mx_value > 5) { + /* If the MX header field specifies a field value greater + * than 5, the device SHOULD assume that it contained the + * value 5 or less. */ + mx_value = 5; + } +#elif defined(DELAY_MSEARCH_RESPONSE) + if(mx_value < 0) { + mx_value = 1; + } else if(mx_value > 5) { + /* If the MX header field specifies a field value greater + * than 5, the device SHOULD assume that it contained the + * value 5 or less. */ + mx_value = 5; } #endif /*syslog(LOG_INFO, "SSDP M-SEARCH packet received from %s", sender_str );*/ if(st && (st_len > 0)) { - /* TODO : doesnt answer at once but wait for a random time */ syslog(LOG_INFO, "SSDP M-SEARCH from %s ST: %.*s", sender_str, st_len, st); /* find in which sub network the client is */ @@ -789,6 +834,12 @@ ProcessSSDPData(int s, const char *bufr, int n, snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version); #endif syslog(LOG_INFO, "Single search found"); +#ifdef DELAY_MSEARCH_RESPONSE + delay = random() / (1 + RAND_MAX / (1000 * mx_value)); +#ifdef DEBUG + syslog(LOG_DEBUG, "mx=%dsec delay=%ums", mx_value, delay); +#endif +#endif SendSSDPResponse(s, sender, #ifdef SSDP_RESPOND_SAME_VERSION st, st_len, "", @@ -796,7 +847,8 @@ ProcessSSDPData(int s, const char *bufr, int n, known_service_types[i].s, l, ver_str, #endif announced_host, port, - known_service_types[i].uuid); + known_service_types[i].uuid, + delay); break; } } @@ -804,9 +856,15 @@ ProcessSSDPData(int s, const char *bufr, int n, /* strlen("ssdp:all") == 8 */ if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8))) { +#ifdef DELAY_MSEARCH_RESPONSE + unsigned int delay_increment = (mx_value * 1000) / 15; +#endif syslog(LOG_INFO, "ssdp:all found"); for(i=0; known_service_types[i].s; i++) { +#ifdef DELAY_MSEARCH_RESPONSE + delay += delay_increment; +#endif if(i==0) ver_str[0] = '\0'; else @@ -815,37 +873,53 @@ ProcessSSDPData(int s, const char *bufr, int n, SendSSDPResponse(s, sender, known_service_types[i].s, l, ver_str, announced_host, port, - known_service_types[i].uuid); + known_service_types[i].uuid, + delay); } /* also answer for uuid */ +#ifdef DELAY_MSEARCH_RESPONSE + delay += delay_increment; +#endif SendSSDPResponse(s, sender, uuidvalue_igd, strlen(uuidvalue_igd), "", - announced_host, port, uuidvalue_igd); + announced_host, port, uuidvalue_igd, delay); +#ifdef DELAY_MSEARCH_RESPONSE + delay += delay_increment; +#endif SendSSDPResponse(s, sender, uuidvalue_wan, strlen(uuidvalue_wan), "", - announced_host, port, uuidvalue_wan); + announced_host, port, uuidvalue_wan, delay); +#ifdef DELAY_MSEARCH_RESPONSE + delay += delay_increment; +#endif SendSSDPResponse(s, sender, uuidvalue_wcd, strlen(uuidvalue_wcd), "", - announced_host, port, uuidvalue_wcd); + announced_host, port, uuidvalue_wcd, delay); } /* responds to request by UUID value */ l = (int)strlen(uuidvalue_igd); if(l==st_len) { +#ifdef DELAY_MSEARCH_RESPONSE + delay = random() / (1 + RAND_MAX / (1000 * mx_value)); +#endif if(0 == memcmp(st, uuidvalue_igd, l)) { syslog(LOG_INFO, "ssdp:uuid (IGD) found"); SendSSDPResponse(s, sender, st, st_len, "", - announced_host, port, uuidvalue_igd); + announced_host, port, uuidvalue_igd, + delay); } else if(0 == memcmp(st, uuidvalue_wan, l)) { syslog(LOG_INFO, "ssdp:uuid (WAN) found"); SendSSDPResponse(s, sender, st, st_len, "", - announced_host, port, uuidvalue_wan); + announced_host, port, uuidvalue_wan, + delay); } else if(0 == memcmp(st, uuidvalue_wcd, l)) { syslog(LOG_INFO, "ssdp:uuid (WCD) found"); SendSSDPResponse(s, sender, st, st_len, "", - announced_host, port, uuidvalue_wcd); + announced_host, port, uuidvalue_wcd, + delay); } } } @@ -895,7 +969,7 @@ SendSSDPbyebye(int s, const struct sockaddr * dest, syslog(LOG_WARNING, "SendSSDPbyebye(): truncated output"); l = sizeof(bufr) - 1; } - n = sendto(s, bufr, l, 0, dest, + n = sendto_or_schedule(s, bufr, l, 0, dest, #ifdef ENABLE_IPV6 ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in) #else diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index 805d788..10667a7 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -63,6 +63,7 @@ #include "miniupnpdtypes.h" #include "daemonize.h" #include "upnpevents.h" +#include "asyncsendto.h" #ifdef ENABLE_NATPMP #include "natpmp.h" #ifdef ENABLE_PCP @@ -705,7 +706,10 @@ void complete_uuidvalues(void) * 5) check and write pid file * 6) set startup time stamp * 7) compute presentation URL - * 8) set signal handlers */ + * 8) set signal handlers + * 9) init random generator (srandom()) + * 10) init redirection engine + * 11) reload mapping from leasefile */ static int init(int argc, char * * argv, struct runtime_vars * v) { @@ -1263,6 +1267,10 @@ init(int argc, char * * argv, struct runtime_vars * v) syslog(LOG_NOTICE, "Failed to set %s handler", "SIGUSR1"); } + /* initialize random number generator */ + srandom((unsigned int)time(NULL)); + + /* initialize redirection engine (and pinholes) */ if(init_redirect() < 0) { syslog(LOG_ERR, "Failed to init redirection engine. EXITING"); @@ -1749,6 +1757,38 @@ main(int argc, char * * argv) upnpevents_selectfds(&readset, &writeset, &max_fd); #endif + /* queued "sendto" */ + { + struct timeval next_send; + i = get_next_scheduled_send(&next_send); + if(i > 0) { +#ifdef DEBUG + syslog(LOG_DEBUG, "%d queued sendto", i); +#endif + i = get_sendto_fds(&writeset, &max_fd, &timeofday); + if(timeofday.tv_sec > next_send.tv_sec || + (timeofday.tv_sec == next_send.tv_sec && timeofday.tv_usec >= next_send.tv_usec)) { + if(i > 0) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + } + } else { + struct timeval tmp_timeout; + tmp_timeout.tv_sec = (next_send.tv_sec - timeofday.tv_sec); + tmp_timeout.tv_usec = (next_send.tv_usec - timeofday.tv_usec); + if(tmp_timeout.tv_usec < 0) { + tmp_timeout.tv_usec += 1000000; + tmp_timeout.tv_sec--; + } + if(timeout.tv_sec > tmp_timeout.tv_sec + || (timeout.tv_sec == tmp_timeout.tv_sec && timeout.tv_usec > tmp_timeout.tv_usec)) { + timeout.tv_sec = tmp_timeout.tv_sec; + timeout.tv_usec = tmp_timeout.tv_usec; + } + } + } + } + if(select(max_fd+1, &readset, &writeset, 0, &timeout) < 0) { if(quitting) goto shutdown; @@ -1757,6 +1797,9 @@ main(int argc, char * * argv) syslog(LOG_ERR, "Failed to select open sockets. EXITING"); return 1; /* very serious cause of error */ } + if(try_sendto(&writeset) < 0) { + syslog(LOG_ERR, "try_sendto: %m"); + } #ifdef USE_MINIUPNPDCTL for(ectl = ctllisthead.lh_first; ectl;) { @@ -1983,6 +2026,21 @@ main(int argc, char * * argv) } /* end of main loop */ shutdown: + /* send good-bye */ + if (GETFLAG(ENABLEUPNPMASK)) + { +#ifndef ENABLE_IPV6 + if(SendSSDPGoodbye(snotify, addr_count) < 0) +#else + if(SendSSDPGoodbye(snotify, addr_count * 2) < 0) +#endif + { + syslog(LOG_ERR, "Failed to broadcast good-bye notifications"); + } + } + /* try to send pending packets */ + finalize_sendto(); + /* close out open sockets */ while(upnphttphead.lh_first != NULL) { @@ -2022,14 +2080,6 @@ shutdown: if (GETFLAG(ENABLEUPNPMASK)) { -#ifndef ENABLE_IPV6 - if(SendSSDPGoodbye(snotify, addr_count) < 0) -#else - if(SendSSDPGoodbye(snotify, addr_count * 2) < 0) -#endif - { - syslog(LOG_ERR, "Failed to broadcast good-bye notifications"); - } #ifndef ENABLE_IPV6 for(i = 0; i < addr_count; i++) #else @@ -2038,6 +2088,7 @@ shutdown: close(snotify[i]); } + /* remove pidfile */ if(pidfilename && (unlink(pidfilename) < 0)) { syslog(LOG_ERR, "Failed to remove pidfile %s: %m", pidfilename); diff --git a/miniupnpd/natpmp.c b/miniupnpd/natpmp.c index 8a127a0..860c41b 100644 --- a/miniupnpd/natpmp.c +++ b/miniupnpd/natpmp.c @@ -1,6 +1,6 @@ /* $Id: natpmp.c,v 1.36 2014/02/01 17:17:35 nanard Exp $ */ /* MiniUPnP project - * (c) 2007-2013 Thomas Bernard + * (c) 2007-2014 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 */ @@ -23,6 +23,7 @@ #include "upnpredirect.h" #include "commonrdr.h" #include "upnputils.h" +#include "asyncsendto.h" #ifdef ENABLE_NATPMP @@ -324,7 +325,7 @@ void ProcessIncomingNATPMPPacket(int s, unsigned char *msg_buff, int len, default: resp[3] = 5; /* Unsupported OPCODE */ } - n = sendto(s, resp, resplen, 0, + n = sendto_or_schedule(s, resp, resplen, 0, (struct sockaddr *)senderaddr, sizeof(*senderaddr)); if(n<0) { syslog(LOG_ERR, "sendto(natpmp): %m"); @@ -378,7 +379,7 @@ void SendNATPMPPublicAddressChangeNotification(int * sockets, int n_sockets) #endif /* Port to use in 2006 version of the NAT-PMP specification */ sockname.sin_port = htons(NATPMP_PORT); - n = sendto(sockets[j], notif, 12, 0, + n = sendto_or_schedule(sockets[j], notif, 12, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if(n < 0) { @@ -388,7 +389,7 @@ void SendNATPMPPublicAddressChangeNotification(int * sockets, int n_sockets) } /* Port to use in 2008 version of the NAT-PMP specification */ sockname.sin_port = htons(NATPMP_NOTIF_PORT); - n = sendto(sockets[j], notif, 12, 0, + n = sendto_or_schedule(sockets[j], notif, 12, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if(n < 0) { diff --git a/miniupnpd/pcpserver.c b/miniupnpd/pcpserver.c index 7354fb3..61a6208 100644 --- a/miniupnpd/pcpserver.c +++ b/miniupnpd/pcpserver.c @@ -58,6 +58,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "upnpredirect.h" #include "commonrdr.h" #include "getifaddr.h" +#include "asyncsendto.h" #include "pcp_msg_struct.h" #ifdef PCP_PEER @@ -1325,7 +1326,7 @@ int ProcessIncomingPCPPacket(int s, unsigned char *buff, int len, len = PCP_MIN_LEN; else len = (len + 3) & ~3; /* round up resp. length to multiple of 4 */ - len = sendto(s, buff, len, 0, + len = sendto_or_schedule(s, buff, len, 0, (struct sockaddr *)senderaddr, sizeof(struct sockaddr_in)); if( len < 0 ) { syslog(LOG_ERR, "sendto(pcpserver): %m"); diff --git a/miniupnpd/testasyncsendto.c b/miniupnpd/testasyncsendto.c new file mode 100644 index 0000000..7a4eb6e --- /dev/null +++ b/miniupnpd/testasyncsendto.c @@ -0,0 +1,126 @@ +/* $Id: $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2014 Thomas Bernard + * 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 "miniupnpdtypes.h" +#include "upnputils.h" +#include "asyncsendto.h" + +struct lan_addr_list lan_addrs; + +#define DEST_IP "239.255.255.250" +#define DEST_PORT 1900 +/* +ssize_t +sendto_schedule(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen, + unsigned int delay) +*/ + +int test(void) +{ + int s; + ssize_t n; + int i; + struct sockaddr_in addr; + struct sockaddr_in dest_addr; + struct timeval next_send; + if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + syslog(LOG_ERR, "socket(): %m"); + return 1; + } + set_non_blocking(s); + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + if(bind(s, &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "bind(): %m"); + close(s); + return 1; + } + memset(&dest_addr, 0, sizeof(struct sockaddr_in)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr.s_addr = inet_addr(DEST_IP); + dest_addr.sin_port = htons(DEST_PORT); + n = sendto_or_schedule(s, "1234", 4, 0, + (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + syslog(LOG_DEBUG, "sendto_or_schedule : %d", (int)n); + n = sendto_schedule(s, "1234", 4, 0, + (struct sockaddr *)&dest_addr, sizeof(dest_addr), + 4400); + syslog(LOG_DEBUG, "sendto_schedule : %d", (int)n); + n = sendto_schedule(s, "1234", 4, 0, + (struct sockaddr *)&dest_addr, sizeof(dest_addr), + 3000); + syslog(LOG_DEBUG, "sendto_schedule : %d", (int)n); + while ((i = get_next_scheduled_send(&next_send)) > 0) { + fd_set writefds; + int max_fd; + struct timeval timeout; + struct timeval now; + syslog(LOG_DEBUG, "get_next_scheduled_send : %d next_send=%ld.%06ld", + i, next_send.tv_sec, next_send.tv_usec); + FD_ZERO(&writefds); + max_fd = 0; + gettimeofday(&now, NULL); + i = get_sendto_fds(&writefds, &max_fd, &now); + if(now.tv_sec > next_send.tv_sec || + (now.tv_sec == next_send.tv_sec && now.tv_usec >= next_send.tv_usec)) { + if(i > 0) { + /* dont wait */ + timeout.tv_sec = 0; + } else { + /* wait 10sec :) */ + timeout.tv_sec = 10; + } + timeout.tv_usec = 0; + } else { + /* ... */ + timeout.tv_sec = (next_send.tv_sec - now.tv_sec); + timeout.tv_usec = (next_send.tv_usec - now.tv_usec); + if(timeout.tv_usec < 0) { + timeout.tv_usec += 1000000; + timeout.tv_sec--; + } + } + syslog(LOG_DEBUG, "get_sendto_fds() returned %d", i); + syslog(LOG_DEBUG, "select(%d, NULL, xx, NULL, %ld.%06ld)", + max_fd, timeout.tv_sec, timeout.tv_usec); + i = select(max_fd, NULL, &writefds, NULL, &timeout); + if(i < 0) { + syslog(LOG_ERR, "select: %m"); + if(errno != EINTR) + break; + } else if(try_sendto(&writefds) < 0) { + syslog(LOG_ERR, "try_sendto: %m"); + break; + } + } + close(s); + return 0; +} + +int main(int argc, char * * argv) +{ + int r; + (void)argc; + (void)argv; + openlog("testasyncsendto", LOG_CONS|LOG_PERROR, LOG_USER); + r = test(); + closelog(); + return r; +} +