miniupnpd-2.3.7/Makefile.bsd010064400017500000024000000167011463564776400151520ustar00nanardstaff# $Id: Makefile.bsd,v 1.107 2024/06/22 16:48:53 nanard Exp $ # MiniUPnP project # http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ # Author: Thomas Bernard # # Makefile for miniupnpd (MiniUPnP daemon) # # This Makefile should work for *BSD and SunOS/Solaris. # On Mac OS X, use "bsdmake" to build. # This Makefile is NOT compatible with GNU Make. # Linux users, please use Makefile.linux # # options can be passed to configure through CONFIG_OPTIONS : # $ CONFIG_OPTIONS="--ipv6 --igd2" make # CFLAGS ?= -pipe -Os #CFLAGS = -pipe -O -g -DDEBUG #CFLAGS += -ansi CFLAGS += -Wall CFLAGS += -W CFLAGS += -Wstrict-prototypes #CFLAGS += -Wdeclaration-after-statement #CFLAGS += -Wno-missing-field-initializers CFLAGS += -fno-common CC ?= gcc RM = rm -f MV = mv INSTALL = install STRIP = strip DOXYGEN ?= doxygen .ifndef CONFIG_OPTIONS CONFIG_OPTIONS != cat .configure.cache .endif # OSNAME and FWNAME are used for building OS or FW dependent code. OSNAME != uname -s ARCH != uname -m .ifndef FWNAME .include "bsdmake.inc" .endif # Solaris specific CFLAGS .if $(OSNAME) == "SunOS" CPPFLAGS += -DSOLARIS2=`uname -r | cut -d. -f2` .if $(ARCH) == "amd64" CFLAGS += -m64 -mcmodel=kernel -mno-red-zone -ffreestanding .elif $(ARCH) == "sparc64" CFLAGS += -m64 -mcmodel=medlow .endif .endif ISGITREPO != git rev-parse --is-inside-work-tree 2> /dev/null || echo "false" .if $(ISGITREPO) == "true" GITREF != git describe --exact-match --tags 2> /dev/null || echo "`git rev-parse --abbrev-ref HEAD`-`git rev-parse --short HEAD`" CPPFLAGS += -DMINIUPNPD_GIT_REF=\"$(GITREF)\" .endif 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 \ pcplearndscp.o \ upnpevents.o upnputils.o getconnstatus.o \ upnpstun.o \ upnppinhole.o asyncsendto.o portinuse.o OS_OBJS = getifstats.o ifacewatcher.o getroute.o PFOBJS = obsdrdr.o pfpinhole.o IPFOBJS = ipfrdr.o IPFWOBJS = ipfwrdr.o ipfwaux.o MISCOBJS = upnpreplyparse.o minixml.o ALLOBJS = $(STDOBJS) $(MISCOBJS) ALLSRCS := $(ALLOBJS:S/^/$(SRCDIR)\//:S/.o$/.c/) TESTGETIFSTATSOBJS = testgetifstats.o getifstats.o TESTGETROUTEOBJS = testgetroute.o upnputils.o getroute.o ALLOBJS += $(OS_OBJS) .if $(OSNAME) == "SunOS" ALLSRCS += $(SRCDIR)/solaris/getifstats.c .elif $(OSNAME) == "Darwin" ALLSRCS += $(SRCDIR)/mac/getifstats.c .else ALLSRCS += $(SRCDIR)/bsd/getifstats.c .endif ALLSRCS += $(SRCDIR)/bsd/ifacewatcher.c $(SRCDIR)/bsd/getroute.c .if $(FWNAME) == "pf" ALLOBJS += $(PFOBJS) ALLSRCS += $(PFOBJS:S/^/$(SRCDIR)\/pf\//:S/.o$/.c/) .elif $(FWNAME) == "ipfw" ALLOBJS += $(IPFWOBJS) ALLSRCS += $(IPFWOBJS:S/^/$(SRCDIR)\/ipfw\//:S/.o$/.c/) .else ALLOBJS += $(IPFOBJS) ALLSRCS += $(IPFOBJS:S/^/$(SRCDIR)\/ipf\//:S/.o$/.c/) .endif 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 getroute.o TESTPORTINUSEOBJS = testportinuse.o portinuse.o getifaddr.o TESTMINISSDPOBJS = testminissdp.o minissdp.o upnputils.o upnpglobalvars.o \ asyncsendto.o getroute.o EXECUTABLES = miniupnpd testupnpdescgen testgetifstats \ testupnppermissions miniupnpdctl \ testgetifaddr testgetroute testasyncsendto \ testportinuse testssdppktgen testminissdp .if $(OSNAME) != "Darwin" LIBS += -lkvm .endif .if $(OSNAME) == "SunOS" LIBS += -lsocket -lnsl -lkstat -lresolv .endif LIBS += -lssl -lcrypto # set PREFIX variable to install in the wanted place INSTALLBINDIR = $(PREFIX)/sbin INSTALLETCDIR = $(PREFIX)/etc MANPREFIX ?= $(PREFIX) INSTALLMANDIR = $(MANPREFIX)/man all: $(EXECUTABLES) clean: $(RM) $(ALLOBJS) $(RM) $(EXECUTABLES) $(RM) $(TESTUPNPDESCGENOBJS) $(RM) $(TESTUPNPPERMISSIONSOBJS) $(RM) $(TESTGETIFADDROBJS) $(RM) $(MINIUPNPDCTLOBJS) $(RM) $(TESTASYNCSENDTOOBJS) $(RM) $(TESTPORTINUSEOBJS) $(RM) $(TESTMINISSDPOBJS) $(RM) validateupnppermissions validategetifaddr validatessdppktgen install: miniupnpd genuuid $(STRIP) miniupnpd $(INSTALL) -d $(DESTDIR)$(INSTALLBINDIR) $(INSTALL) -m 755 miniupnpd $(DESTDIR)$(INSTALLBINDIR) $(INSTALL) -d $(DESTDIR)$(INSTALLETCDIR) $(INSTALL) -b miniupnpd.conf $(DESTDIR)$(INSTALLETCDIR) $(INSTALL) -d $(DESTDIR)$(INSTALLMANDIR) $(INSTALL) -m 644 miniupnpd.8 $(DESTDIR)$(INSTALLMANDIR)/man8/miniupnpd.8 # genuuid is using the uuid cli tool available under OpenBSD 4.0 in # the uuid-1.5.0 package # any other cli tool returning a uuid on stdout should work. UUID != if which uuidgen 2>&1 > /dev/null; then \ echo `uuidgen` ; \ elif which uuid 2>&1 > /dev/null; then \ echo `uuid` ; \ else echo "00000000-0000-0000-0000-000000000000"; \ fi genuuid: $(MV) miniupnpd.conf miniupnpd.conf.before sed -e "s/^uuid=[-0-9a-fA-F]*/uuid=$(UUID)/" miniupnpd.conf.before > miniupnpd.conf $(RM) miniupnpd.conf.before check: validateupnppermissions validategetifaddr validatessdppktgen validateupnppermissions: $(SRCDIR)/testupnppermissions.sh testupnppermissions $(SRCDIR)/testupnppermissions.sh touch $@ validategetifaddr: $(SRCDIR)/testgetifaddr.sh testgetifaddr $(SRCDIR)/testgetifaddr.sh touch $@ validatessdppktgen: testssdppktgen ./testssdppktgen touch $@ depend: $(ALLSRCS) mkdep $(CPPFLAGS) $(.ALLSRC) dox: miniupnpd.doxyconf $(DOXYGEN) $> miniupnpd: $(ALLOBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(ALLOBJS) $(LIBS) # BSDmake : # $(CC) $(LDFLAGS) -o $@ $> $(LIBS) miniupnpdctl: config.h $(MINIUPNPDCTLOBJS) $(CC) $(LDFLAGS) -o $@ $(MINIUPNPDCTLOBJS) testupnpdescgen: config.h $(TESTUPNPDESCGENOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTUPNPDESCGENOBJS) $(LIBS) testgetifstats: config.h $(TESTGETIFSTATSOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTGETIFSTATSOBJS) $(LIBS) testgetifaddr: config.h $(TESTGETIFADDROBJS) $(CC) $(LDFLAGS) -o $@ $(TESTGETIFADDROBJS) $(LIBS) testupnppermissions: config.h $(TESTUPNPPERMISSIONSOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTUPNPPERMISSIONSOBJS) $(LIBS) testgetroute: config.h $(TESTGETROUTEOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTGETROUTEOBJS) $(LIBS) testasyncsendto: config.h $(TESTASYNCSENDTOOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTASYNCSENDTOOBJS) testportinuse: config.h $(TESTPORTINUSEOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTPORTINUSEOBJS) $(LIBS) testminissdp: config.h $(TESTMINISSDPOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTMINISSDPOBJS) $(LIBS) testssdppktgen: testssdppktgen.o $(CC) $(LDFLAGS) -o $@ $(.ALLSRC) $(LIBS) # gmake : # $(CC) $(CFLAGS) -o $@ $^ # BSDmake : # $(CC) $(CFLAGS) -o $@ $> config.h: $(SRCDIR)/configure $(SRCDIR)/VERSION $(SRCDIR)/Makefile.bsd $(SRCDIR)/configure $(CONFIG_OPTIONS) $(STDOBJS) $(MISCOBJS): $(SRCDIR)/$(@:.o=.c) $(PFOBJS): $(SRCDIR)/pf/$(@:.o=.c) $(IPFOBJS): $(SRCDIR)/ipf/$(@:.o=.c) $(IPFWOBJS): $(SRCDIR)/ipfw/$(@:.o=.c) .if $(OSNAME) == "SunOS" getifstats.o: $(SRCDIR)/solaris/getifstats.c .elif $(OSNAME) == "Darwin" getifstats.o: $(SRCDIR)/mac/getifstats.c .else getifstats.o: $(SRCDIR)/bsd/getifstats.c .endif ifacewatcher.o getroute.o: $(SRCDIR)/bsd/$(@:.o=.c) # make(1) specifies that $ is escaped with a backslash, # not a preceding $ as usual, but FreeBSD 12.1 make does otherwise .if $(OSNAME) == "FreeBSD" $(EXECUTABLES:S/$$/.o/): $(SRCDIR)/$(@:.o=.c) .else $(EXECUTABLES:S/\$/.o/): $(SRCDIR)/$(@:.o=.c) .endif .SUFFIXES: .o .c .c.o: $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< # $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $(.TARGET) $(.IMPSRC) miniupnpd-2.3.7/Makefile.linux010064400017500000024000000151761463564776400155460ustar00nanardstaff# $Id: Makefile.linux,v 1.111 2024/06/22 16:51:28 nanard Exp $ # MiniUPnP project # (c) 2006-2021 Thomas Bernard # http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ # Author : Thomas Bernard # for use with GNU Make # # options can be passed to configure through CONFIG_OPTIONS : # $ CONFIG_OPTIONS="--ipv6 --igd2" make # # To install use : # $ DESTDIR=/dummyinstalldir make install # or : # $ INSTALLPREFIX=/usr/local make install # or : # $ make install # (default INSTALLPREFIX is /usr) # # if your system hasn't iptables libiptc headers and binary correctly # installed, you need to get iptables sources from http://netfilter.org/ # ./configure them and build them then miniupnpd will build using : # $ IPTABLESPATH=/path/to/iptables-1.4.1 make # CONFIG_OPTIONS ?= $(cat .configure.cache) CONFIG_OPTIONS += --firewall=iptables #CFLAGS = -O -g -DDEBUG CFLAGS ?= -Os CFLAGS += -fno-strict-aliasing CFLAGS += -fno-common CFLAGS += -fstack-protector -fPIE CFLAGS += -D_FORTIFY_SOURCE=2 CPPFLAGS += -D_GNU_SOURCE CFLAGS += -Wall CFLAGS += -Wextra -Wstrict-prototypes -Wdeclaration-after-statement #CFLAGS += -Wno-missing-field-initializers #CFLAGS += -ansi # iptables headers does use typeof which is a gcc extension LDFLAGS += -Wl,-z,now -Wl,-z,relro -pie CC ?= gcc RM = rm -f INSTALL = install STRIP ?= strip PKG_CONFIG ?= pkg-config CP = cp DOXYGEN ?= doxygen DEPFLAGS = -MM -MG -MT $(patsubst %.d,%.o,$@) -MT $@ # -M : with system headers, -MM : without INSTALLPREFIX ?= $(PREFIX)/usr SBININSTALLDIR ?= $(INSTALLPREFIX)/sbin ETCINSTALLDIR = $(PREFIX)/etc/miniupnpd MANINSTALLDIR = $(INSTALLPREFIX)/share/man/man8 include config.mk include $(SRCDIR)/gitrev.mk include $(SRCDIR)/objects.mk # sources in netfilter/ directory NETFILTEROBJS = iptcrdr.o iptpinhole.o nfct_get.o ALLOBJS = $(BASEOBJS) $(LNXOBJS) $(NETFILTEROBJS) DEP = $(ALLOBJS:.o=.d) NETFILTER_SCRIPTS = $(addprefix $(SRCDIR)/netfilter/, \ iptables_init.sh iptables_removeall.sh \ ip6tables_init.sh ip6tables_removeall.sh \ miniupnpd_functions.sh) ifneq ($(IPTABLES_PCFILE_FOUND),1) ifeq "$(wildcard /etc/gentoo-release )" "" LDLIBS ?= -liptc else # gentoo # the following is better, at least on gentoo with iptables 1.4.6 # see http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1618 # and http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=2183 LDLIBS ?= -lip4tc CPPFLAGS := -DIPTABLES_143 $(CPPFLAGS) endif endif # ifneq ($(IPTABLES_PCFILE_FOUND),1) #LDLIBS += -lnfnetlink # OpenWrt packager disables https server for IGD v2 and hardcodes libuuid support ifeq ($(TARGET_OPENWRT),) LDLIBS += $(shell $(PKG_CONFIG) --static --libs-only-l libssl) TEST := $(shell $(PKG_CONFIG) --exists uuid && echo 1) ifeq ($(TEST),1) LDLIBS += $(shell $(PKG_CONFIG) --static --libs-only-l uuid) else $(info please install uuid-dev package / libuuid) endif # ($(TEST),1) endif # ($(TARGET_OPENWRT,) ifneq ($(shell ldd --version | grep GLIBC),) GLIBC_VERSION := $(shell ldd --version | head -n 1 | sed 's/^.* //') GLIBC_VERSION_MAJOR = $(shell echo $(GLIBC_VERSION) | cut -f 1 -d . ) GLIBC_VERSION_MINOR = $(shell echo $(GLIBC_VERSION) | cut -f 2 -d . ) # clock_gettime() needs -lrt when glibc version < 2.17 LDLIBS += $(shell if [ $(GLIBC_VERSION_MAJOR) -lt 2 ] \ || [ \( $(GLIBC_VERSION_MAJOR) -eq 2 \) -a \( $(GLIBC_VERSION_MINOR) -lt 17 \) ] ; \ then echo "-lrt" ; fi ) endif TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o EXECUTABLES = miniupnpd testupnpdescgen testgetifstats \ testupnppermissions miniupnpdctl testgetifaddr \ testgetroute testasyncsendto testportinuse \ testssdppktgen testminissdp .PHONY: all clean install depend dox all: $(EXECUTABLES) clean: $(RM) config.h $(RM) $(ALLOBJS) $(RM) $(DEP) $(RM) $(EXECUTABLES) $(RM) testupnpdescgen.o testgetifstats.o $(RM) testupnppermissions.o testgetifaddr.o $(RM) testgetroute.o testasyncsendto.o $(RM) testportinuse.o $(RM) testminissdp.o $(RM) testssdppktgen.o $(RM) miniupnpdctl.o $(RM) validateupnppermissions validategetifaddr validatessdppktgen $(RM) -r dox/ install: miniupnpd $(SRCDIR)/miniupnpd.8 $(SRCDIR)/miniupnpd.conf \ $(NETFILTER_SCRIPTS) \ $(SRCDIR)/linux/miniupnpd.init.d.script $(STRIP) miniupnpd $(INSTALL) -d $(DESTDIR)$(SBININSTALLDIR) $(INSTALL) miniupnpd $(DESTDIR)$(SBININSTALLDIR) $(INSTALL) -d $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter/iptables_init.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter/iptables_removeall.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter/ip6tables_init.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter/ip6tables_removeall.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter/miniupnpd_functions.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) -m 0644 -b $(SRCDIR)/miniupnpd.conf $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) -d $(DESTDIR)$(PREFIX)/etc/init.d $(INSTALL) $(SRCDIR)/linux/miniupnpd.init.d.script $(DESTDIR)$(PREFIX)/etc/init.d/miniupnpd $(INSTALL) -d $(DESTDIR)$(MANINSTALLDIR) $(INSTALL) -m 0644 $(SRCDIR)/miniupnpd.8 $(DESTDIR)$(MANINSTALLDIR) gzip -f $(DESTDIR)$(MANINSTALLDIR)/miniupnpd.8 # genuuid is using the uuidgen CLI tool which is part of libuuid # from the e2fsprogs # 'cat /proc/sys/kernel/random/uuid' could be also used miniupnpd.conf: $(SRCDIR)/miniupnpd.conf ifeq ($(TARGET_OPENWRT),) sed -e "s/^uuid=[-0-9a-f]*/uuid=`(genuuid||uuidgen||uuid) 2>/dev/null`/" $< > $@.tmp else sed -e "s/^uuid=[-0-9a-f]*/uuid=`($(STAGING_DIR_HOST)/bin/genuuid||$(STAGING_DIR_HOST)/bin/uuidgen||$(STAGING_DIR_HOST)/bin/uuid) 2>/dev/null`/" $< > $@.tmp endif mv $@.tmp $@ include $(SRCDIR)/check.mk miniupnpd: $(BASEOBJS) $(LNXOBJS) $(NETFILTEROBJS) testupnpdescgen: $(TESTUPNPDESCGENOBJS) testgetifstats: testgetifstats.o getifstats.o testupnppermissions: testupnppermissions.o upnppermissions.o testgetifaddr: testgetifaddr.o getifaddr.o testgetroute: testgetroute.o getroute.o upnputils.o testssdppktgen: testssdppktgen.o testasyncsendto: testasyncsendto.o asyncsendto.o upnputils.o \ getroute.o testportinuse: testportinuse.o portinuse.o getifaddr.o \ iptcrdr.o testminissdp: testminissdp.o minissdp.o upnputils.o upnpglobalvars.o \ asyncsendto.o getroute.o miniupnpdctl: miniupnpdctl.o config.mk config.h: $(SRCDIR)/configure $(SRCDIR)/VERSION $(SHELL) $< $(CONFIG_OPTIONS) depend: $(DEP) %.d: $(SRCDIR)/%.c $(CC) $(CPPFLAGS) $(DEPFLAGS) -o $@ $< dox: $(SRCDIR)/miniupnpd.doxyconf (cat $< ; echo "INPUT=$(SRCDIR)" ) | $(DOXYGEN) - %.o: $(SRCDIR)/%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ %.o: $(SRCDIR)/linux/%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ %.o: $(SRCDIR)/netfilter/%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ print-%: @echo "$* = $($*)" ifneq ($(MAKECMDGOALS),clean) -include $(DEP) endif miniupnpd-2.3.7/upnphttp.c010064400017500000024000001051431455107657400147600ustar00nanardstaff/* $Id: upnphttp.c,v 1.114 2024/01/15 00:13:22 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * Project : miniupnp * Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author : Thomas Bernard * Copyright (c) 2005-2024 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file included in this distribution. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef ENABLE_HTTP_DATE #include #endif #include "upnphttp.h" #include "upnpdescgen.h" #include "miniupnpdpath.h" #include "upnpsoap.h" #include "upnpevents.h" #include "upnputils.h" #if defined(RANDOMIZE_URLS) || defined(DYNAMIC_OS_VERSION) #include "upnpglobalvars.h" #endif /* RANDOMIZE_URLS */ #ifdef ENABLE_HTTPS #include #include #include static SSL_CTX *ssl_ctx = NULL; #ifndef HTTPS_CERTFILE #define HTTPS_CERTFILE "/etc/ssl/certs/ssl-cert-snakeoil.pem" #endif #ifndef HTTPS_KEYFILE #define HTTPS_KEYFILE "/etc/ssl/private/ssl-cert-snakeoil.key" #endif static void syslogsslerr(void) { unsigned long err; char buffer[256]; while((err = ERR_get_error()) != 0) { syslog(LOG_ERR, "%s", ERR_error_string(err, buffer)); } } static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { syslog(LOG_DEBUG, "verify_callback(%d, %p)", preverify_ok, ctx); return preverify_ok; } int init_ssl(void) { const SSL_METHOD *method; SSL_library_init(); SSL_load_error_strings(); #if OPENSSL_VERSION_NUMBER < 0x10100000L method = TLSv1_server_method(); #else method = TLS_server_method(); #endif if(method == NULL) { #if OPENSSL_VERSION_NUMBER < 0x10100000L syslog(LOG_ERR, "TLSv1_server_method() failed"); #else syslog(LOG_ERR, "TLS_server_method() failed"); #endif syslogsslerr(); return -1; } ssl_ctx = SSL_CTX_new(method); if(ssl_ctx == NULL) { syslog(LOG_ERR, "SSL_CTX_new() failed"); syslogsslerr(); return -1; } /* set the local certificate */ if(!SSL_CTX_use_certificate_file(ssl_ctx, HTTPS_CERTFILE, SSL_FILETYPE_PEM)) { syslog(LOG_ERR, "SSL_CTX_use_certificate_file(%s) failed", HTTPS_CERTFILE); syslogsslerr(); return -1; } /* set the private key */ if(!SSL_CTX_use_PrivateKey_file(ssl_ctx, HTTPS_KEYFILE, SSL_FILETYPE_PEM)) { syslog(LOG_ERR, "SSL_CTX_use_PrivateKey_file(%s) failed", HTTPS_KEYFILE); syslogsslerr(); return -1; } /* verify private key */ if(!SSL_CTX_check_private_key(ssl_ctx)) { syslog(LOG_ERR, "SSL_CTX_check_private_key() failed"); syslogsslerr(); return -1; } /*SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, verify_callback);*/ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, verify_callback); /*SSL_CTX_set_verify_depth(depth);*/ syslog(LOG_INFO, "using %s", SSLeay_version(SSLEAY_VERSION)); return 0; } void free_ssl(void) { /* free context */ if(ssl_ctx != NULL) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } #if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L ERR_remove_thread_state(NULL); #elif OPENSSL_VERSION_NUMBER < 0x10000000L ERR_remove_state(0); #endif ENGINE_cleanup(); CONF_modules_unload(1); ERR_free_strings(); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); } #endif /* ENABLE_HTTPS */ struct upnphttp * New_upnphttp(int s) { struct upnphttp * ret; if(s<0) return NULL; ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); if(ret == NULL) return NULL; memset(ret, 0, sizeof(struct upnphttp)); ret->socket = s; if(!set_non_blocking(s)) syslog(LOG_WARNING, "New_upnphttp::set_non_blocking(): %m"); return ret; } #ifdef ENABLE_HTTPS void InitSSL_upnphttp(struct upnphttp * h) { int r; h->ssl = SSL_new(ssl_ctx); if(h->ssl == NULL) { syslog(LOG_ERR, "SSL_new() failed"); syslogsslerr(); abort(); } if(!SSL_set_fd(h->ssl, h->socket)) { syslog(LOG_ERR, "SSL_set_fd() failed"); syslogsslerr(); abort(); } r = SSL_accept(h->ssl); /* start the handshaking */ if(r < 0) { int err; err = SSL_get_error(h->ssl, r); syslog(LOG_DEBUG, "SSL_accept() returned %d, SSL_get_error() %d", r, err); if(err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) { syslog(LOG_ERR, "SSL_accept() failed"); syslogsslerr(); abort(); } } } #endif /* ENABLE_HTTPS */ void CloseSocket_upnphttp(struct upnphttp * h) { /* SSL_shutdown() ? */ if(close(h->socket) < 0) { syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); } h->socket = -1; h->state = EToDelete; } void Delete_upnphttp(struct upnphttp * h) { if(h) { #ifdef ENABLE_HTTPS if(h->ssl) SSL_free(h->ssl); #endif if(h->socket >= 0) CloseSocket_upnphttp(h); if(h->req_buf) free(h->req_buf); if(h->res_buf) free(h->res_buf); free(h); } } /* parse HttpHeaders of the REQUEST * This function is called after the \r\n\r\n character * sequence has been found in h->req_buf */ static void ParseHttpHeaders(struct upnphttp * h) { char * line; char * colon; char * p; int n; if((h->req_buf == NULL) || (h->req_contentoff <= 0)) return; line = h->req_buf; while(line < (h->req_buf + h->req_contentoff)) { colon = line; while(*colon != ':') { if(*colon == '\r' || *colon == '\n') { colon = NULL; /* no ':' character found on the line */ break; } colon++; } if(colon) { if(strncasecmp(line, "Content-Length:", 15)==0) { p = colon; while((*p < '0' || *p > '9') && (*p != '\r') && (*p != '\n')) p++; h->req_contentlen = atoi(p); if(h->req_contentlen < 0) { syslog(LOG_WARNING, "ParseHttpHeaders() invalid Content-Length %d", h->req_contentlen); h->req_contentlen = 0; } /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen); printf(" readbufflen=%d contentoff = %d\n", h->req_buflen, h->req_contentoff);*/ } else if(strncasecmp(line, "Host:", 5)==0) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>' ') n++; h->req_HostOff = p - h->req_buf; h->req_HostLen = n; } else if(strncasecmp(line, "SOAPAction:", 11)==0) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>=' ') n++; if((p[0] == '"' && p[n-1] == '"') || (p[0] == '\'' && p[n-1] == '\'')) { p++; n -= 2; } h->req_soapActionOff = p - h->req_buf; h->req_soapActionLen = n; } else if(strncasecmp(line, "accept-language:", 16) == 0) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>=' ') n++; syslog(LOG_DEBUG, "accept-language HTTP header : '%.*s'", n, p); /* keep only the 1st accepted language */ n = 0; while(p[n]>' ' && p[n] != ',') n++; if(n >= (int)sizeof(h->accept_language)) n = (int)sizeof(h->accept_language) - 1; memcpy(h->accept_language, p, n); h->accept_language[n] = '\0'; } else if(strncasecmp(line, "expect:", 7) == 0) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>=' ') n++; if(strncasecmp(p, "100-continue", 12) == 0) { h->respflags |= FLAG_CONTINUE; syslog(LOG_DEBUG, "\"Expect: 100-Continue\" header detected"); } } else if(strncasecmp(line, "user-agent:", 11) == 0) { /* - User-Agent: Microsoft-Windows/10.0 UPnP/1.0 * - User-Agent: FDSSDP */ if(strcasestr(line + 11, "microsoft") != NULL || strstr(line + 11, "FDSSDP") != NULL) { h->respflags |= FLAG_MS_CLIENT; } } #ifdef ENABLE_EVENTS else if(strncasecmp(line, "Callback:", 9)==0) { /* The Callback can contain several urls : * If there is more than one URL, when the service sends * events, it will try these URLs in order until one * succeeds. One or more URLs each enclosed by angle * brackets ("<" and ">") */ p = colon; while(*p != '<' && *p != '\r' ) p++; n = 0; while(p[n] != '\r') n++; while(n > 0 && p[n] != '>') n--; /* found last > character */ h->req_CallbackOff = p - h->req_buf; h->req_CallbackLen = MAX(0, n + 1); } else if(strncasecmp(line, "SID:", 4)==0) { p = colon + 1; while((*p == ' ') || (*p == '\t')) p++; n = 0; while(!isspace(p[n])) n++; h->req_SIDOff = p - h->req_buf; h->req_SIDLen = n; } /* Timeout: Seconds-nnnn */ /* TIMEOUT Recommended. Requested duration until subscription expires, either number of seconds or infinite. Recommendation by a UPnP Forum working committee. Defined by UPnP vendor. Consists of the keyword "Second-" followed (without an intervening space) by either an integer or the keyword "infinite". */ else if(strncasecmp(line, "Timeout:", 8)==0) { p = colon + 1; while((*p == ' ') || (*p == '\t')) p++; if(strncasecmp(p, "Second-", 7)==0) { h->req_Timeout = atoi(p+7); } } #ifdef UPNP_STRICT else if(strncasecmp(line, "nt:", 3)==0) { p = colon + 1; while((*p == ' ') || (*p == '\t')) p++; n = 0; while(!isspace(p[n])) n++; h->req_NTOff = p - h->req_buf; h->req_NTLen = n; } #endif /* UPNP_STRICT */ #endif /* ENABLE_EVENTS */ } /* the loop below won't run off the end of the buffer * because the buffer is guaranteed to contain the \r\n\r\n * character sequence */ while(!(line[0] == '\r' && line[1] == '\n')) line++; line += 2; } } /* very minimalistic 404 error message */ static void Send404(struct upnphttp * h) { static const char body404[] = "404 Not Found" "

Not Found

The requested URL was not found" " on this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 404, "Not Found", body404, sizeof(body404) - 1); SendRespAndClose_upnphttp(h); } static void Send405(struct upnphttp * h) { static const char body405[] = "405 Method Not Allowed" "

Method Not Allowed

The HTTP Method " "is not allowed on this resource.\r\n"; h->respflags |= FLAG_HTML; BuildResp2_upnphttp(h, 405, "Method Not Allowed", body405, sizeof(body405) - 1); SendRespAndClose_upnphttp(h); } /* very minimalistic 501 error message */ static void Send501(struct upnphttp * h) { static const char body501[] = "501 Not Implemented" "

Not Implemented

The HTTP Method " "is not implemented by this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 501, "Not Implemented", body501, sizeof(body501) - 1); SendRespAndClose_upnphttp(h); } /* findendheaders() find the \r\n\r\n character sequence and * return a pointer to it. * It returns NULL if not found */ static const char * findendheaders(const char * s, int len) { while(len-->3) { if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') return s; s++; } return NULL; } #ifdef HAS_DUMMY_SERVICE static void sendDummyDesc(struct upnphttp * h) { static const char xml_desc[] = "\r\n" "" " " " 1" " 0" " " " " " " "\r\n"; BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); SendRespAndClose_upnphttp(h); } #endif /* Sends the description generated by the parameter */ static void sendXMLdesc(struct upnphttp * h, char * (f)(int *, int)) { char * desc; int len; #ifdef IGD_V2 #ifdef DEBUG if(h->respflags & FLAG_MS_CLIENT) syslog(LOG_DEBUG, "MS Client, forcing IGD v1"); #endif /* DEBUG */ desc = f(&len, GETFLAG(FORCEIGDDESCV1MASK) || (h->respflags & FLAG_MS_CLIENT)); #else desc = f(&len, 0); #endif if(!desc) { static const char error500[] = "Error 500" "Internal Server Error\r\n"; syslog(LOG_ERR, "Failed to generate XML description"); h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 500, "Internal Server Error", error500, sizeof(error500)-1); } else { BuildResp_upnphttp(h, desc, len); } SendRespAndClose_upnphttp(h); free(desc); } /* ProcessHTTPPOST_upnphttp() * executes the SOAP query if it is possible */ static void ProcessHTTPPOST_upnphttp(struct upnphttp * h) { if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { /* the request body is received */ if(h->req_soapActionOff > 0) { /* we can process the request */ syslog(LOG_INFO, "SOAPAction: %.*s", h->req_soapActionLen, h->req_buf + h->req_soapActionOff); ExecuteSoapAction(h, h->req_buf + h->req_soapActionOff, h->req_soapActionLen); } else { static const char err400str[] = "Bad request"; syslog(LOG_INFO, "No SOAPAction in HTTP headers"); h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", err400str, sizeof(err400str) - 1); SendRespAndClose_upnphttp(h); } } else if(h->respflags & FLAG_CONTINUE) { /* Sending the 100 Continue response */ if(!h->res_buf) { h->res_buf = malloc(256); h->res_buf_alloclen = 256; } h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, "%s 100 Continue\r\n\r\n", h->HttpVer); h->res_sent = 0; h->state = ESendingContinue; if(SendResp_upnphttp(h)) h->state = EWaitingForHttpContent; } else { /* waiting for remaining data */ h->state = EWaitingForHttpContent; } } #ifdef ENABLE_EVENTS /** * checkCallbackURL() * check that url is on originating IP * extract first correct URL * returns 0 if the callback header value is not valid * 1 if it is valid. */ static int checkCallbackURL(struct upnphttp * h) { char addrstr[48]; int ipv6; const char * p; unsigned int i; start_again: if(h->req_CallbackOff <= 0 || h->req_CallbackLen < 10) return 0; if(memcmp(h->req_buf + h->req_CallbackOff, "req_buf + h->req_CallbackOff + 1; goto invalid; } /* extract host from url to addrstr[] */ i = 0; p = h->req_buf + h->req_CallbackOff + 8; if(*p == '[') { p++; ipv6 = 1; while(*p != ']' && *p != '>' && i < (sizeof(addrstr)-1) && p < (h->req_buf + h->req_CallbackOff + h->req_CallbackLen)) addrstr[i++] = *(p++); } else { ipv6 = 0; while(*p != '/' && *p != ':' && *p != '>' && i < (sizeof(addrstr)-1) && p < (h->req_buf + h->req_CallbackOff + h->req_CallbackLen)) addrstr[i++] = *(p++); } addrstr[i] = '\0'; /* check addrstr */ if(ipv6) { #ifdef ENABLE_IPV6 struct in6_addr addr; if(inet_pton(AF_INET6, addrstr, &addr) <= 0) goto invalid; if(!h->ipv6 || (0!=memcmp(&addr, &(h->clientaddr_v6), sizeof(struct in6_addr)))) goto invalid; #else goto invalid; #endif } else { struct in_addr addr; if(inet_pton(AF_INET, addrstr, &addr) <= 0) goto invalid; #ifdef ENABLE_IPV6 if(h->ipv6) { if(!IN6_IS_ADDR_V4MAPPED(&(h->clientaddr_v6))) goto invalid; if(0!=memcmp(&addr, ((const char *)&(h->clientaddr_v6) + 12), 4)) goto invalid; } else { if(0!=memcmp(&addr, &(h->clientaddr), sizeof(struct in_addr))) goto invalid; } #else if(0!=memcmp(&addr, &(h->clientaddr), sizeof(struct in_addr))) goto invalid; #endif } /* select only the good callback url */ while(p < h->req_buf + h->req_CallbackOff + h->req_CallbackLen && *p != '>') p++; h->req_CallbackOff++; /* skip initial '<' */ h->req_CallbackLen = (int)(p - h->req_buf - h->req_CallbackOff); return 1; invalid: while(p < h->req_buf + h->req_CallbackOff + h->req_CallbackLen && *p != '>') p++; if(*p != '>') return 0; while(p < h->req_buf + h->req_CallbackOff + h->req_CallbackLen && *p != '<') p++; if(*p != '<') return 0; h->req_CallbackLen -= (int)(p - h->req_buf - h->req_CallbackOff); h->req_CallbackOff = (int)(p - h->req_buf); goto start_again; } static void ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) { const char * sid; syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", h->req_CallbackLen, h->req_buf + h->req_CallbackOff, h->req_Timeout); syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_buf + h->req_SIDOff); #if defined(UPNP_STRICT) && (UPNP_VERSION_MAJOR > 1) || (UPNP_VERSION_MINOR > 0) if(h->req_Timeout < 1800) { /* Second-infinite is forbidden with UDA v1.1 and later : * (UDA 1.1 : 4.1.1 Subscription) * UPnP 1.1 control points MUST NOT subscribe using keyword infinite, * UPnP 1.1 devices MUST NOT set actual subscription durations to * "infinite". The presence of infinite in a request MUST be silently * ignored by a UPnP 1.1 device (the presence of infinite is handled * by the device as if the TIMEOUT header field in a request was not * present) . The keyword infinite MUST NOT be returned by a UPnP 1.1 * device. * Also the device must return a value of minimum 1800 seconds in the * response, according to UDA 1.1 (4.1.2 SUBSCRIBE with NT and CALLBACK): * TIMEOUT * REQUIRED. Field value contains actual duration until subscription * expires. Keyword "Second-" followed by an integer (no space). * SHOULD be greater than or equal to 1800 seconds (30 minutes).*/ h->req_Timeout = 1800; /* default to 30 minutes */ } #endif /* UPNP_STRICT */ if((h->req_CallbackOff <= 0) && (h->req_SIDOff <= 0)) { /* Missing or invalid CALLBACK : 412 Precondition Failed. * If CALLBACK header is missing or does not contain a valid HTTP URL, * the publisher must respond with HTTP error 412 Precondition Failed*/ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); SendRespAndClose_upnphttp(h); } else { /* - add to the subscriber list * - respond HTTP/x.x 200 OK * - Send the initial event message */ /* Server:, SID:; Timeout: Second-(xx|infinite) */ /* Check that the callback URL is on the same IP as * the request, and not on the internet, nor on ourself (DOS attack ?) */ if(h->req_CallbackOff > 0) { #ifdef UPNP_STRICT /* SID: and Callback: are incompatible */ if(h->req_SIDOff > 0) { syslog(LOG_WARNING, "Both Callback: and SID: in SUBSCRIBE"); BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); /* "NT: upnp:event" header is mandatory */ } else if(h->req_NTOff <= 0 || h->req_NTLen != 10 || 0 != memcmp("upnp:event", h->req_buf + h->req_NTOff, 10)) { syslog(LOG_WARNING, "Invalid NT in SUBSCRIBE %.*s", h->req_NTLen, h->req_buf + h->req_NTOff); BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else #endif if(checkCallbackURL(h)) { sid = upnpevents_addSubscriber(path, h->req_buf + h->req_CallbackOff, h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if(sid) { syslog(LOG_DEBUG, "generated sid=%s", sid); h->respflags |= FLAG_SID; h->res_SID = sid; } BuildResp_upnphttp(h, 0, 0); } else { syslog(LOG_WARNING, "Invalid Callback in SUBSCRIBE %.*s", h->req_CallbackLen, h->req_buf + h->req_CallbackOff); BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } } else { /* subscription renew */ /* Invalid SID 412 Precondition Failed. If a SID does not correspond to a known, un-expired subscription, the publisher must respond with HTTP error 412 Precondition Failed. */ #ifdef UPNP_STRICT /* SID: and NT: headers are incompatibles */ if(h->req_NTOff > 0) { syslog(LOG_WARNING, "Both NT: and SID: in SUBSCRIBE"); BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); } else { #endif /* UPNP_STRICT */ sid = upnpevents_renewSubscription(h->req_buf + h->req_SIDOff, h->req_SIDLen, h->req_Timeout); if(!sid) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { h->respflags = FLAG_TIMEOUT | FLAG_SID; h->res_SID = sid; BuildResp_upnphttp(h, 0, 0); } #ifdef UPNP_STRICT } #endif /* UPNP_STRICT */ } SendRespAndClose_upnphttp(h); } } static void ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) { syslog(LOG_DEBUG, "ProcessHTTPUnSubscribe %s", path); syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_buf + h->req_SIDOff); /* Remove from the list */ #ifdef UPNP_STRICT if(h->req_SIDOff <= 0 || h->req_SIDLen == 0) { /* SID: header missing or empty */ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else if(h->req_CallbackOff > 0 || h->req_NTOff > 0) { /* no NT: or Callback: header must be present */ BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); } else #endif if(upnpevents_removeSubscriber(h->req_buf + h->req_SIDOff, h->req_SIDLen) < 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { BuildResp_upnphttp(h, 0, 0); } SendRespAndClose_upnphttp(h); } #endif /* Parse and process Http Query * called once all the HTTP headers have been received, * so it is guaranteed that h->req_buf contains the \r\n\r\n * character sequence */ static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { static const struct { const char * path; char * (* f)(int *, int); } path_desc[] = { { ROOTDESC_PATH, genRootDesc}, { WANIPC_PATH, genWANIPCn}, { WANCFG_PATH, genWANCfg}, #ifdef HAS_DUMMY_SERVICE { DUMMY_PATH, NULL}, #endif #ifdef ENABLE_L3F_SERVICE { L3F_PATH, genL3F}, #endif #ifdef ENABLE_6FC_SERVICE { WANIP6FC_PATH, gen6FC}, #endif #ifdef ENABLE_DP_SERVICE { DP_PATH, genDP}, #endif { NULL, NULL} }; char HttpCommand[16]; char HttpUrl[128]; char * HttpVer; char * p; int i; p = h->req_buf; if(!p) return; /* note : checking (*p != '\r') is enough to avoid running off the * end of the buffer, because h->req_buf is guaranteed to contain * the \r\n\r\n character sequence */ for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) HttpCommand[i] = *(p++); HttpCommand[i] = '\0'; while(*p==' ') p++; for(i = 0; i<127 && *p != ' ' && *p != '\r'; i++) HttpUrl[i] = *(p++); HttpUrl[i] = '\0'; while(*p==' ') p++; HttpVer = h->HttpVer; for(i = 0; i<15 && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; syslog(LOG_INFO, "HTTP REQUEST from %s : %s %s (%s)", h->clientaddr_str, HttpCommand, HttpUrl, HttpVer); ParseHttpHeaders(h); if(h->req_HostOff > 0 && h->req_HostLen > 0) { syslog(LOG_DEBUG, "Host: %.*s", h->req_HostLen, h->req_buf + h->req_HostOff); p = h->req_buf + h->req_HostOff; if(*p == '[') { /* IPv6 */ p++; while(p < h->req_buf + h->req_HostOff + h->req_HostLen) { if(*p == ']') break; /* TODO check *p in [0-9a-f:.] */ p++; } if(*p != ']') { syslog(LOG_NOTICE, "DNS rebinding attack suspected (Host: %.*s)", h->req_HostLen, h->req_buf + h->req_HostOff); Send404(h);/* 403 */ return; } p++; /* TODO : Check port */ } else { for(i = 0; i < h->req_HostLen; i++) { if(*p != ':' && *p != '.' && (*p > '9' || *p < '0')) { syslog(LOG_NOTICE, "DNS rebinding attack suspected (Host: %.*s)", h->req_HostLen, h->req_buf + h->req_HostOff); Send404(h);/* 403 */ return; } p++; } } } #ifdef RANDOMIZE_URLS /* first check if the URL begins with the randomized string */ if(HttpUrl[0] != '/' || memcmp(HttpUrl+1, random_url, strlen(random_url)) != 0) { Send404(h); return; } /* remove "random" from the start of the URL */ p = HttpUrl + strlen(random_url) + 1; memmove(HttpUrl, p, strlen(p) + 1); #endif /* RANDOMIZE_URLS */ if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp(h); } else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; for(i=0; path_desc[i].path; i++) { if(strcasecmp(path_desc[i].path, HttpUrl) == 0) { if(path_desc[i].f) sendXMLdesc(h, path_desc[i].f); else #ifdef HAS_DUMMY_SERVICE sendDummyDesc(h); #else continue; #endif return; } } if(0 == memcmp(HttpUrl, "/ctl/", 5)) { /* 405 Method Not Allowed * Allow: POST */ h->respflags = FLAG_ALLOW_POST; Send405(h); return; } #ifdef ENABLE_EVENTS if(0 == memcmp(HttpUrl, "/evt/", 5)) { /* 405 Method Not Allowed * Allow: SUBSCRIBE, UNSUBSCRIBE */ h->respflags = FLAG_ALLOW_SUB_UNSUB; Send405(h); return; } #endif syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); Send404(h); } #ifdef ENABLE_EVENTS else if(strcmp("SUBSCRIBE", HttpCommand) == 0) { h->req_command = ESubscribe; ProcessHTTPSubscribe_upnphttp(h, HttpUrl); } else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) { h->req_command = EUnSubscribe; ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); } #else else if(strcmp("SUBSCRIBE", HttpCommand) == 0) { syslog(LOG_NOTICE, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled"); Send501(h); } #endif else { syslog(LOG_NOTICE, "Unsupported HTTP Command %s", HttpCommand); Send501(h); } } void Process_upnphttp(struct upnphttp * h) { char * h_tmp; char buf[2048]; int n; if(!h) return; switch(h->state) { case EWaitingForHttpRequest: #ifdef ENABLE_HTTPS if(h->ssl) { n = SSL_read(h->ssl, buf, sizeof(buf)); } else { n = recv(h->socket, buf, sizeof(buf), 0); } #else n = recv(h->socket, buf, sizeof(buf), 0); #endif if(n<0) { #ifdef ENABLE_HTTPS if(h->ssl) { int err; err = SSL_get_error(h->ssl, n); if(err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) { syslog(LOG_ERR, "SSL_read() failed"); syslogsslerr(); h->state = EToDelete; } } else { #endif if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "recv (state0): %m"); h->state = EToDelete; } /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */ #ifdef ENABLE_HTTPS } #endif } else if(n==0) { #ifdef ENABLE_IPV6 if (h->ipv6) { char clientaddr_str[INET6_ADDRSTRLEN]; if(inet_ntop(AF_INET6, &(h->clientaddr_v6), clientaddr_str, INET6_ADDRSTRLEN) == NULL) strncpy(clientaddr_str, "*inet_ntop error*", sizeof(clientaddr_str)); syslog(LOG_WARNING, "HTTP Connection from %s closed unexpectedly", clientaddr_str); } else #endif { syslog(LOG_WARNING, "HTTP Connection from %s closed unexpectedly", inet_ntoa(h->clientaddr)); } h->state = EToDelete; } else { const char * endheaders; /* if 1st arg of realloc() is null, * realloc behaves the same as malloc() */ h_tmp = (char *)realloc(h->req_buf, n + h->req_buflen + 1); if (h_tmp == NULL) { syslog(LOG_WARNING, "Unable to allocate new memory for h->req_buf)"); h->state = EToDelete; } else { h->req_buf = h_tmp; memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; h->req_buf[h->req_buflen] = '\0'; } /* search for the string "\r\n\r\n" */ endheaders = findendheaders(h->req_buf, h->req_buflen); if(endheaders) { /* at this point, the request buffer (h->req_buf) * is guaranteed to contain the \r\n\r\n character sequence */ h->req_contentoff = endheaders - h->req_buf + 4; ProcessHttpQuery_upnphttp(h); } } break; case EWaitingForHttpContent: #ifdef ENABLE_HTTPS if(h->ssl) { n = SSL_read(h->ssl, buf, sizeof(buf)); } else { n = recv(h->socket, buf, sizeof(buf), 0); } #else n = recv(h->socket, buf, sizeof(buf), 0); #endif if(n<0) { #ifdef ENABLE_HTTPS if(h->ssl) { int err; err = SSL_get_error(h->ssl, n); if(err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) { syslog(LOG_ERR, "SSL_read() failed"); syslogsslerr(); h->state = EToDelete; } } else { #endif if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "recv (state1): %m"); h->state = EToDelete; } /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */ #ifdef ENABLE_HTTPS } #endif } else if(n==0) { #ifdef ENABLE_IPV6 if (h->ipv6) { char clientaddr_str[INET6_ADDRSTRLEN]; if(inet_ntop(AF_INET6, &(h->clientaddr_v6), clientaddr_str, INET6_ADDRSTRLEN) == NULL) strncpy(clientaddr_str, "*inet_ntop error*", sizeof(clientaddr_str)); syslog(LOG_WARNING, "HTTP Connection from %s closed unexpectedly", clientaddr_str); } else #endif { syslog(LOG_WARNING, "HTTP Connection from %s closed unexpectedly", inet_ntoa(h->clientaddr)); } h->state = EToDelete; } else { void * tmp = realloc(h->req_buf, n + h->req_buflen); if(!tmp) { syslog(LOG_ERR, "memory allocation error %m"); h->state = EToDelete; } else { h->req_buf = tmp; memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { ProcessHTTPPOST_upnphttp(h); } } } break; case ESendingContinue: if(SendResp_upnphttp(h)) h->state = EWaitingForHttpContent; break; case ESendingAndClosing: SendRespAndClose_upnphttp(h); break; default: syslog(LOG_WARNING, "Unexpected state: %d", h->state); } } static const char httpresphead[] = "%s %d %s\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "Content-Length: %d\r\n" "Server: " MINIUPNPD_SERVER_STRING "\r\n" "Ext:\r\n" ; /*"\r\n";*/ /* "\n" "" "" "" ""; */ /* with response code and response message * also allocate enough memory */ int BuildHeader_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, int bodylen) { int templen = sizeof(httpresphead) + 256 + bodylen; if(!h->res_buf || h->res_buf_alloclen < templen) { if(h->res_buf) free(h->res_buf); h->res_buf = (char *)malloc(templen); if(!h->res_buf) { syslog(LOG_ERR, "malloc error in BuildHeader_upnphttp()"); return -1; } h->res_buf_alloclen = templen; } h->res_sent = 0; h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, httpresphead, h->HttpVer, /* HTTP/x.x */ respcode, respmsg, (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", /* Content-Type: */ bodylen /* Content-Length: */ #ifdef DYNAMIC_OS_VERSION , os_version /* Server: */ #endif ); /* Content-Type MUST be 'text/xml; charset="utf-8"' according to UDA v1.1 */ /* Content-Type MUST be 'text/xml' according to UDA v1.0 */ /* Additional headers */ #ifdef ENABLE_HTTP_DATE { char http_date[64]; time_t t; struct tm tm; time(&t); gmtime_r(&t, &tm); /* %a and %b depend on locale */ strftime(http_date, sizeof(http_date), "%a, %d %b %Y %H:%M:%S GMT", &tm); h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Date: %s\r\n", http_date); } #endif #ifdef ENABLE_EVENTS if(h->respflags & FLAG_TIMEOUT) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Timeout: Second-"); if(h->req_Timeout) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "%d\r\n", h->req_Timeout); } else { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "infinite\r\n"); } } if(h->respflags & FLAG_SID) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "SID: %s\r\n", h->res_SID); } #endif if(h->respflags & FLAG_ALLOW_POST) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Allow: %s\r\n", "POST"); } else if(h->respflags & FLAG_ALLOW_SUB_UNSUB) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Allow: %s\r\n", "SUBSCRIBE, UNSUBSCRIBE"); } if(h->accept_language[0] != '\0') { /* defaulting to "en" */ h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, "Content-Language: %s\r\n", h->accept_language[0] == '*' ? "en" : h->accept_language); } h->res_buf[h->res_buflen++] = '\r'; h->res_buf[h->res_buflen++] = '\n'; if(h->res_buf_alloclen < (h->res_buflen + bodylen)) { char * tmp; tmp = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); if(tmp) { h->res_buf = tmp; h->res_buf_alloclen = h->res_buflen + bodylen; } else { syslog(LOG_ERR, "realloc error in BuildHeader_upnphttp()"); return -1; } } return 0; } void BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen) { int r = BuildHeader_upnphttp(h, respcode, respmsg, bodylen); if(body && (r >= 0)) { memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; } } /* responding 200 OK ! */ void BuildResp_upnphttp(struct upnphttp * h, const char * body, int bodylen) { BuildResp2_upnphttp(h, 200, "OK", body, bodylen); } int SendResp_upnphttp(struct upnphttp * h) { ssize_t n; while (h->res_sent < h->res_buflen) { #ifdef ENABLE_HTTPS if(h->ssl) { n = SSL_write(h->ssl, h->res_buf + h->res_sent, h->res_buflen - h->res_sent); } else { n = send(h->socket, h->res_buf + h->res_sent, h->res_buflen - h->res_sent, 0); } #else n = send(h->socket, h->res_buf + h->res_sent, h->res_buflen - h->res_sent, 0); #endif if(n<0) { #ifdef ENABLE_HTTPS if(h->ssl) { int err; err = SSL_get_error(h->ssl, n); if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { /* try again later */ return 0; } syslog(LOG_ERR, "SSL_write() failed"); syslogsslerr(); break; } else { #endif if(errno == EINTR) continue; /* try again immediately */ if(errno == EAGAIN || errno == EWOULDBLOCK) { /* try again later */ return 0; } syslog(LOG_ERR, "send(res_buf): %m"); break; /* avoid infinite loop */ #ifdef ENABLE_HTTPS } #endif } else if(n == 0) { syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", h->res_sent, h->res_buflen); break; } else { h->res_sent += n; } } return 1; /* finished */ } void SendRespAndClose_upnphttp(struct upnphttp * h) { if (SendResp_upnphttp(h)) CloseSocket_upnphttp(h); else h->state = ESendingAndClosing; } miniupnpd-2.3.7/miniupnpd.c010064400017500000024000002516501463564776500151170ustar00nanardstaff/* $Id: miniupnpd.c,v 1.264 2024/06/22 18:14:08 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" /* Experimental support for NFQUEUE interfaces */ #ifdef ENABLE_NFQUEUE /* apt-get install libnetfilter-queue-dev */ #include #include #if 0 #include /* Defines verdicts (NF_ACCEPT, etc) */ #endif #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(sun) #include #elif !defined(__linux__) /* for BSD's sysctl */ #include #endif #ifdef HAS_LIBCAP #include #endif #ifdef HAS_LIBCAP_NG #include #endif /* unix sockets */ #ifdef USE_MINIUPNPDCTL #include #endif #ifdef ENABLE_HTTPS #include #endif #ifdef DYNAMIC_OS_VERSION #include #endif #ifdef TOMATO #include #endif /* TOMATO */ #include "macros.h" #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "miniupnpdpath.h" #include "getifaddr.h" #include "upnpsoap.h" #include "options.h" #include "minissdp.h" #include "upnpredirect.h" #include "upnppinhole.h" #include "upnpstun.h" #include "miniupnpdtypes.h" #include "daemonize.h" #include "upnpevents.h" #include "asyncsendto.h" #ifdef ENABLE_NATPMP #include "natpmp.h" #ifdef ENABLE_PCP #include "pcpserver.h" #else #define PCP_MAX_LEN 32 #endif #endif #include "commonrdr.h" #include "upnputils.h" #ifdef USE_IFACEWATCHER #include "ifacewatcher.h" #endif #ifdef ENABLE_UPNPPINHOLE #ifdef USE_NETFILTER void init_iptpinhole(void); #endif #endif #ifdef USE_MINIUPNPDCTL struct ctlelem { int socket; LIST_ENTRY(ctlelem) entries; }; #endif /* USE_MINIUPNPDCTL */ #ifdef ENABLE_NFQUEUE /* globals */ static struct nfq_handle *nfqHandle; static struct sockaddr_in ssdp; /* prototypes */ static int nfqueue_cb( struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) ; int identify_ip_protocol (char *payload); int get_udp_dst_port (char *payload); #endif /* ENABLE_NFQUEUE */ /* variables used by signals */ static volatile sig_atomic_t quitting = 0; volatile sig_atomic_t should_send_public_address_change_notif = 0; #if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME) volatile sig_atomic_t should_rewrite_leasefile = 0; #endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */ #ifdef TOMATO #if 1 /* Tomato specific code */ static volatile sig_atomic_t gotusr2 = 0; static void sigusr2(int sig) { gotusr2 = 1; } static void tomato_save(const char *fname) { unsigned short eport; unsigned short iport; unsigned int leaseduration; unsigned int timestamp; char proto[4]; char iaddr[32]; char desc[64]; char rhost[32]; int n; FILE *f; int t; char tmp[128]; strcpy(tmp, "/etc/upnp/saveXXXXXX"); if ((t = mkstemp(tmp)) != -1) { if ((f = fdopen(t, "w")) != NULL) { n = 0; while (upnp_get_redirection_infos_by_index(n, &eport, proto, &iport, iaddr, sizeof(iaddr), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration) == 0) { timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0; fprintf(f, "%s %u %s %u [%s] %u\n", proto, eport, iaddr, iport, desc, timestamp); ++n; } fclose(f); rename(tmp, fname); } else { close(t); } unlink(tmp); } } static void tomato_load(void) { FILE *f; char s[256]; unsigned short eport; unsigned short iport; unsigned int leaseduration; unsigned int timestamp; time_t current_time; char proto[4]; char iaddr[32]; char *rhost; char *a, *b; if ((f = fopen("/etc/upnp/data", "r")) != NULL) { current_time = time(NULL); s[sizeof(s) - 1] = 0; while (fgets(s, sizeof(s) - 1, f)) { if (sscanf(s, "%3s %hu %31s %hu [%*[^]]] %u", proto, &eport, iaddr, &iport, ×tamp) >= 4) { if (((a = strchr(s, '[')) != NULL) && ((b = strrchr(a, ']')) != NULL)) { if (timestamp > 0) { if (timestamp > current_time) leaseduration = timestamp - current_time; else continue; } else { leaseduration = 0; /* default value */ } *b = 0; rhost = NULL; syslog(LOG_DEBUG, "Read redirection [%s] from file: %s port %hu to %s port %hu, timestamp: %u (%u)", a + 1, proto, eport, iaddr, iport, timestamp, leaseduration); upnp_redirect(rhost, eport, iaddr, iport, proto, a + 1, leaseduration); } } } fclose(f); } #ifdef ENABLE_NATPMP #if 0 ScanNATPMPforExpiration(); #endif #endif /* ENABLE_NATPMP */ unlink("/etc/upnp/load"); } static void tomato_delete(void) { FILE *f; char s[128]; unsigned short eport; unsigned short iport; unsigned int leaseduration; char proto[4]; char iaddr[32]; char desc[64]; char rhost[32]; int n; if ((f = fopen("/etc/upnp/delete", "r")) != NULL) { s[sizeof(s) - 1] = 0; while (fgets(s, sizeof(s) - 1, f)) { if (sscanf(s, "%3s %hu", proto, &eport) == 2) { if (proto[0] == '*') { n = upnp_get_portmapping_number_of_entries(); while (--n >= 0) { if (upnp_get_redirection_infos_by_index(n, &eport, proto, &iport, iaddr, sizeof(iaddr), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration) == 0) { upnp_delete_redirection(eport, proto); } } break; } else { upnp_delete_redirection(eport, proto); } } } fclose(f); unlink("/etc/upnp/delete"); } } static void tomato_helper(void) { struct stat st; if (stat("/etc/upnp/delete", &st) == 0) { tomato_delete(); } if (stat("/etc/upnp/load", &st) == 0) { tomato_load(); } if (stat("/etc/upnp/save", &st) == 0) { tomato_save("/etc/upnp/data"); unlink("/etc/upnp/save"); } if (stat("/etc/upnp/info", &st) == 0) { tomato_save("/etc/upnp/data.info"); unlink("/etc/upnp/info"); } } #endif /* 1 (tomato) */ #endif /* TOMATO */ static int gen_current_notify_interval(int notify_interval) { /* if possible, remove a random number of seconds between 1 and 64 */ if (notify_interval > 65) return notify_interval - 1 - (random() & 0x3f); else return notify_interval; } /* OpenAndConfHTTPSocket() : * setup the socket used to handle incoming HTTP connections. */ static int #ifdef ENABLE_IPV6 OpenAndConfHTTPSocket(unsigned short * port, int ipv6) #else OpenAndConfHTTPSocket(unsigned short * port) #endif { int s; int i = 1; #ifdef ENABLE_IPV6 struct sockaddr_in6 listenname6; struct sockaddr_in listenname4; #else struct sockaddr_in listenname; #endif socklen_t listenname_len; s = socket( #ifdef ENABLE_IPV6 ipv6 ? PF_INET6 : PF_INET, #else PF_INET, #endif SOCK_STREAM, 0); #ifdef ENABLE_IPV6 if(s < 0 && ipv6 && errno == EAFNOSUPPORT) { /* the system doesn't support IPV6 */ syslog(LOG_WARNING, "socket(PF_INET6, ...) failed with EAFNOSUPPORT, disabling IPv6"); SETFLAG(IPV6DISABLEDMASK); ipv6 = 0; /* Try again with IPv4 */ s = socket(PF_INET, SOCK_STREAM, 0); } #endif if(s < 0) { syslog(LOG_ERR, "socket(http): %m"); return -1; } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "setsockopt(http, SO_REUSEADDR): %m"); } #if 0 /* enable this to force IPV6 only for IPV6 socket. * see http://www.ietf.org/rfc/rfc3493.txt section 5.3 */ if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "setsockopt(http, IPV6_V6ONLY): %m"); } #endif if(!set_non_blocking(s)) { syslog(LOG_WARNING, "set_non_blocking(http): %m"); } #ifdef ENABLE_IPV6 if(ipv6) { memset(&listenname6, 0, sizeof(struct sockaddr_in6)); listenname6.sin6_family = AF_INET6; listenname6.sin6_port = htons(*port); listenname6.sin6_addr = ipv6_bind_addr; listenname_len = sizeof(struct sockaddr_in6); } else { memset(&listenname4, 0, sizeof(struct sockaddr_in)); listenname4.sin_family = AF_INET; listenname4.sin_port = htons(*port); listenname4.sin_addr.s_addr = htonl(INADDR_ANY); listenname_len = sizeof(struct sockaddr_in); } #else memset(&listenname, 0, sizeof(struct sockaddr_in)); listenname.sin_family = AF_INET; listenname.sin_port = htons(*port); listenname.sin_addr.s_addr = htonl(INADDR_ANY); listenname_len = sizeof(struct sockaddr_in); #endif #if defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP) /* One and only one LAN interface */ if(lan_addrs.lh_first != NULL && lan_addrs.lh_first->list.le_next == NULL && lan_addrs.lh_first->ifname[0] != '\0') { if(setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, lan_addrs.lh_first->ifname, strlen(lan_addrs.lh_first->ifname)) < 0) syslog(LOG_WARNING, "setsockopt(http, SO_BINDTODEVICE, %s): %m", lan_addrs.lh_first->ifname); } #endif /* defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP) */ #ifdef ENABLE_IPV6 if(bind(s, ipv6 ? (struct sockaddr *)&listenname6 : (struct sockaddr *)&listenname4, listenname_len) < 0) #else if(bind(s, (struct sockaddr *)&listenname, listenname_len) < 0) #endif { syslog(LOG_ERR, "bind(http): %m"); close(s); return -1; } if(listen(s, 5) < 0) { syslog(LOG_ERR, "listen(http): %m"); close(s); return -1; } if(*port == 0) { #ifdef ENABLE_IPV6 if(ipv6) { struct sockaddr_in6 sockinfo; socklen_t len = sizeof(struct sockaddr_in6); if (getsockname(s, (struct sockaddr *)&sockinfo, &len) < 0) { syslog(LOG_ERR, "getsockname(): %m"); } else { *port = ntohs(sockinfo.sin6_port); } } else { #endif /* ENABLE_IPV6 */ struct sockaddr_in sockinfo; socklen_t len = sizeof(struct sockaddr_in); if (getsockname(s, (struct sockaddr *)&sockinfo, &len) < 0) { syslog(LOG_ERR, "getsockname(): %m"); } else { *port = ntohs(sockinfo.sin_port); } #ifdef ENABLE_IPV6 } #endif /* ENABLE_IPV6 */ } return s; } static struct upnphttp * ProcessIncomingHTTP(int shttpl, const char * protocol) { int shttp; socklen_t clientnamelen; #ifdef ENABLE_IPV6 struct sockaddr_storage clientname; clientnamelen = sizeof(struct sockaddr_storage); #else struct sockaddr_in clientname; clientnamelen = sizeof(struct sockaddr_in); #endif shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); if(shttp<0) { /* ignore EAGAIN, EWOULDBLOCK, EINTR, we just try again later */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) syslog(LOG_ERR, "accept(http): %m"); } else { struct upnphttp * tmp = 0; char addr_str[64]; sockaddr_to_string((struct sockaddr *)&clientname, addr_str, sizeof(addr_str)); #ifdef DEBUG syslog(LOG_DEBUG, "%s connection from %s", protocol, addr_str); #endif /* DEBUG */ if(get_lan_for_peer((struct sockaddr *)&clientname) == NULL) { /* The peer is not a LAN ! */ syslog(LOG_WARNING, "%s peer %s is not from a LAN, closing the connection", protocol, addr_str); close(shttp); } else { /* Create a new upnphttp object and add it to * the active upnphttp object list */ tmp = New_upnphttp(shttp); if(tmp) { #ifdef ENABLE_IPV6 if(clientname.ss_family == AF_INET) { tmp->clientaddr = ((struct sockaddr_in *)&clientname)->sin_addr; } else if(clientname.ss_family == AF_INET6) { struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&clientname; if(IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr)) { memcpy(&tmp->clientaddr, &addr->sin6_addr.s6_addr[12], 4); } else { tmp->ipv6 = 1; memcpy(&tmp->clientaddr_v6, &addr->sin6_addr, sizeof(struct in6_addr)); } } #else tmp->clientaddr = clientname.sin_addr; #endif memcpy(tmp->clientaddr_str, addr_str, sizeof(tmp->clientaddr_str)); return tmp; } else { syslog(LOG_ERR, "New_upnphttp() failed"); close(shttp); } } } return NULL; } #ifdef ENABLE_NFQUEUE int identify_ip_protocol(char *payload) { return payload[9]; } /* * This function returns the destination port of the captured packet UDP */ int get_udp_dst_port(char *payload) { char *pkt_data_ptr = NULL; pkt_data_ptr = payload + sizeof(struct ip); /* Cast the UDP Header from the raw packet */ struct udphdr *udp = (struct udphdr *) pkt_data_ptr; /* get the dst port of the packet */ return(ntohs(udp->dest)); } static int OpenAndConfNFqueue(){ struct nfq_q_handle *myQueue; struct nfnl_handle *netlinkHandle; int fd = 0, e = 0; inet_pton(AF_INET, "239.255.255.250", &(ssdp.sin_addr)); /* Get a queue connection handle from the module */ if (!(nfqHandle = nfq_open())) { syslog(LOG_ERR, "Error in nfq_open(): %m"); return -1; } /* Unbind the handler from processing any IP packets Not totally sure why this is done, or if it's necessary... */ if ((e = nfq_unbind_pf(nfqHandle, AF_INET)) < 0) { syslog(LOG_ERR, "Error in nfq_unbind_pf(): %m"); return -1; } /* Bind this handler to process IP packets... */ if (nfq_bind_pf(nfqHandle, AF_INET) < 0) { syslog(LOG_ERR, "Error in nfq_bind_pf(): %m"); return -1; } /* Install a callback on queue -Q */ if (!(myQueue = nfq_create_queue(nfqHandle, nfqueue, &nfqueue_cb, NULL))) { syslog(LOG_ERR, "Error in nfq_create_queue(): %m"); return -1; } /* Turn on packet copy mode */ if (nfq_set_mode(myQueue, NFQNL_COPY_PACKET, 0xffff) < 0) { syslog(LOG_ERR, "Error setting packet copy mode (): %m"); return -1; } netlinkHandle = nfq_nfnlh(nfqHandle); fd = nfnl_fd(netlinkHandle); return fd; } static int nfqueue_cb( struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { char *pkt; struct nfqnl_msg_packet_hdr *ph; ph = nfq_get_msg_packet_hdr(nfa); if ( ph ) { int id = 0, size = 0; id = ntohl(ph->packet_id); size = nfq_get_payload(nfa, &pkt); struct ip *iph = (struct ip *) pkt; int id_protocol = identify_ip_protocol(pkt); int dport = get_udp_dst_port(pkt); int x = sizeof (struct ip) + sizeof (struct udphdr); /* packets we are interested in are UDP multicast to 239.255.255.250:1900 * and start with a data string M-SEARCH */ if ( (dport == 1900) && (id_protocol == IPPROTO_UDP) && (ssdp.sin_addr.s_addr == iph->ip_dst.s_addr) ) { /* get the index that the packet came in on */ u_int32_t idx = nfq_get_indev(nfa); int i = 0; for ( ;i < n_nfqix ; i++) { if ( nfqix[i] == idx ) { struct udphdr *udp = (struct udphdr *) (pkt + sizeof(struct ip)); char *dd = pkt + x; struct sockaddr_in sendername; sendername.sin_family = AF_INET; sendername.sin_port = udp->source; sendername.sin_addr.s_addr = iph->ip_src.s_addr; /* printf("pkt found %s\n",dd);*/ ProcessSSDPData (sudp, dd, size - x, &sendername, -1, (unsigned short) 5555); } } } nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } else { syslog(LOG_ERR,"nfq_get_msg_packet_hdr failed"); return 1; /* from nfqueue source: 0 = ok, >0 = soft error, <0 hard error */ } return 0; } static void ProcessNFQUEUE(int fd){ char buf[4096]; socklen_t len_r; struct sockaddr_in sendername; len_r = sizeof(struct sockaddr_in); int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sendername, &len_r); nfq_handle_packet(nfqHandle, buf, res); } #endif /* Functions used to communicate with miniupnpdctl */ #ifdef USE_MINIUPNPDCTL static int OpenAndConfCtlUnixSocket(const char * path) { struct sockaddr_un localun; int s; s = socket(AF_UNIX, SOCK_STREAM, 0); localun.sun_family = AF_UNIX; strncpy(localun.sun_path, path, sizeof(localun.sun_path)); if(bind(s, (struct sockaddr *)&localun, sizeof(struct sockaddr_un)) < 0) { syslog(LOG_ERR, "bind(sctl): %m"); close(s); s = -1; } else if(listen(s, 5) < 0) { syslog(LOG_ERR, "listen(sctl): %m"); close(s); s = -1; } return s; } static void write_upnphttp_details(int fd, struct upnphttp * e) { char buffer[256]; int len; write(fd, "HTTP :\n", 7); while(e) { len = snprintf(buffer, sizeof(buffer), "%d %d %s req_buf=%p(%dbytes) res_buf=%p(%dbytes alloc)\n", e->socket, e->state, e->HttpVer, e->req_buf, e->req_buflen, e->res_buf, e->res_buf_alloclen); write(fd, buffer, len); e = e->entries.le_next; } } static void write_ctlsockets_list(int fd, struct ctlelem * e) { char buffer[256]; int len; write(fd, "CTL :\n", 6); while(e) { len = snprintf(buffer, sizeof(buffer), "struct ctlelem: socket=%d\n", e->socket); write(fd, buffer, len); e = e->entries.le_next; } } #ifndef DISABLE_CONFIG_FILE static void write_option_list(int fd) { char buffer[256]; int len; unsigned int i; write(fd, "Options :\n", 10); for(i=0; i= 900s */ /* unused rules cleaning related variables : */ int clean_ruleset_threshold; /* threshold for removing unused rules */ int clean_ruleset_interval; /* (minimum) interval between checks. 0=disabled */ }; /* parselanaddr() * parse address with mask * ex: 192.168.1.1/24 or 192.168.1.1/255.255.255.0 * When MULTIPLE_EXTERNAL_IP is enabled, the IP address of the * external interface associated with the lan subnet follows. * ex : 192.168.1.1/24 81.21.41.11 * * Can also use the interface name (ie eth0) * * return value : * 0 : ok * -1 : error */ static int parselanaddr(struct lan_addr_s * lan_addr, const char * str, int debug_flag) { const char * p; unsigned int n; char tmp[16]; memset(lan_addr, 0, sizeof(struct lan_addr_s)); p = str; while(*p && *p != '/' && !isspace(*p)) p++; n = p - str; if(!isdigit(str[0]) && n < (int)sizeof(lan_addr->ifname)) { /* not starting with a digit : suppose it is an interface name */ memcpy(lan_addr->ifname, str, n); lan_addr->ifname[n] = '\0'; if(getifaddr(lan_addr->ifname, lan_addr->str, sizeof(lan_addr->str), &lan_addr->addr, &lan_addr->mask) < 0) { #ifdef ENABLE_IPV6 fprintf(stderr, "interface \"%s\" has no IPv4 address\n", str); syslog(LOG_NOTICE, "interface \"%s\" has no IPv4 address\n", str); lan_addr->str[0] = '\0'; lan_addr->addr.s_addr = htonl(0x00000000u); lan_addr->mask.s_addr = htonl(0xffffffffu); #else /* ENABLE_IPV6 */ goto parselan_error; #endif /* ENABLE_IPV6 */ } /*printf("%s => %s\n", lan_addr->ifname, lan_addr->str);*/ } else { if(n>15) goto parselan_error; memcpy(lan_addr->str, str, n); lan_addr->str[n] = '\0'; if(!inet_aton(lan_addr->str, &lan_addr->addr)) goto parselan_error; } if(!addr_is_reserved(&lan_addr->addr)) { INIT_PRINT_ERR("Error: LAN address contains public IP address : %s\n", lan_addr->str); INIT_PRINT_ERR("Public IP address can be configured via ext_ip= option\n"); INIT_PRINT_ERR("LAN address should contain private address, e.g. from 192.168. block\n"); INIT_PRINT_ERR("Listening on public IP address is a security issue\n"); return -1; } if(*p == '/') { const char * q = ++p; while(*p && isdigit(*p)) p++; if(*p=='.') { /* parse mask in /255.255.255.0 format */ while(*p && (*p=='.' || isdigit(*p))) p++; n = p - q; if(n >= sizeof(tmp)) goto parselan_error; memcpy(tmp, q, n); tmp[n] = '\0'; if(!inet_aton(tmp, &lan_addr->mask)) goto parselan_error; } else { /* it is a /24 format */ int nbits = atoi(q); if(nbits > 32 || nbits < 0) goto parselan_error; lan_addr->mask.s_addr = htonl(nbits ? (0xffffffffu << (32 - nbits)) : 0); } } else if(lan_addr->mask.s_addr == 0) { /* by default, networks are /24 */ lan_addr->mask.s_addr = htonl(0xffffff00u); } #ifdef MULTIPLE_EXTERNAL_IP /* skip spaces */ while(*p && isspace(*p)) p++; if(*p) { /* parse the exteral IP address to associate with this subnet */ n = 0; while(p[n] && !isspace(*p)) n++; if(n<=15) { memcpy(lan_addr->ext_ip_str, p, n); lan_addr->ext_ip_str[n] = '\0'; if(!inet_aton(lan_addr->ext_ip_str, &lan_addr->ext_ip_addr)) { /* error */ INIT_PRINT_ERR("Error parsing address : %s\n", lan_addr->ext_ip_str); return -1; } if(addr_is_reserved(&lan_addr->ext_ip_addr)) { /* error */ INIT_PRINT_ERR("Error: option ext_ip address contains reserved / private address : %s\n", lan_addr->ext_ip_str); return -1; } } } #else while(*p) { /* skip spaces */ while(*p && isspace(*p)) p++; if(*p) { unsigned int index; n = 0; while(p[n] && !isspace(p[n]) && n < sizeof(tmp)) { tmp[n] = p[n]; n++; } if(n >= sizeof(tmp)) { INIT_PRINT_ERR("Cannot parse '%s'\n", p); break; } tmp[n] = '\0'; index = if_nametoindex(tmp); if(index == 0) { fprintf(stderr, "Cannot get index for network interface %s\n", tmp); syslog(LOG_WARNING, "Cannot get index for network interface %s\n", tmp); } else { lan_addr->add_indexes |= (1UL << (index - 1)); } p += n; } } #endif if(lan_addr->ifname[0] != '\0') { lan_addr->index = if_nametoindex(lan_addr->ifname); if(lan_addr->index == 0) { fprintf(stderr, "Cannot get index for network interface %s\n", lan_addr->ifname); syslog(LOG_WARNING, "Cannot get index for network interface %s\n", lan_addr->ifname); } } else { #ifdef ENABLE_IPV6 INIT_PRINT_ERR("Error: please specify LAN network interface by name instead of IPv4 address : %s\n", str); return -1; #else syslog(LOG_NOTICE, "it is advised to use network interface name instead of %s", str); #endif } return 0; parselan_error: INIT_PRINT_ERR("Error parsing address/mask (or interface name) : %s\n", str); 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 full-cone NAT 1:1 with public IP address %s and firewall does not block incoming connections set by miniupnpd", ext_if_name, if_addr_str, ext_addr_str); else syslog(LOG_INFO, "STUN: ext interface %s has now public IP address %s and firewall does not block incoming connections set by miniupnpd", ext_if_name, if_addr_str); syslog(LOG_INFO, "Port forwarding is now enabled"); } else if ((init || !disable_port_forwarding) && restrictive_nat) { if (addr_is_reserved(&if_addr)) { syslog(LOG_WARNING, "STUN: ext interface %s with private IP address %s is now behind restrictive or symmetric NAT with public IP address %s which does not support port forwarding", ext_if_name, if_addr_str, ext_addr_str); syslog(LOG_WARNING, "NAT on upstream router blocks incoming connections set by miniupnpd"); syslog(LOG_WARNING, "Turn off NAT on upstream router or change it to full-cone NAT 1:1 type"); } else { syslog(LOG_WARNING, "STUN: ext interface %s has now public IP address %s but firewall filters incoming connections set by miniunnpd", ext_if_name, if_addr_str); syslog(LOG_WARNING, "Check configuration of firewall on local machine and also on upstream router"); } syslog(LOG_WARNING, "Port forwarding is now disabled"); } 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) { size_t len; len = strlen(uuidvalue_igd); memcpy(uuidvalue_wan, uuidvalue_igd, len+1); switch(uuidvalue_wan[len-1]) { case '9': uuidvalue_wan[len-1] = 'a'; break; case 'f': uuidvalue_wan[len-1] = '0'; break; default: uuidvalue_wan[len-1]++; } memcpy(uuidvalue_wcd, uuidvalue_wan, len+1); switch(uuidvalue_wcd[len-1]) { case '9': uuidvalue_wcd[len-1] = 'a'; break; case 'f': uuidvalue_wcd[len-1] = '0'; break; default: uuidvalue_wcd[len-1]++; } } /* init phase : * 1) read configuration file * 2) read command line arguments * 3) daemonize * 4) open syslog * 5) check and write pid file * 6) set startup time stamp * 7) compute presentation URL * 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) { int i; #ifndef NO_BACKGROUND_NO_PIDFILE int pid; #endif int debug_flag = 0; int verbosity_level = 0; /* for determining setlogmask() */ int openlog_option; struct in_addr addr; struct sigaction sa; /*const char * logfilename = 0;*/ const char * presurl = 0; #ifndef DISABLE_CONFIG_FILE int options_flag = 0; const char * optionsfile = DEFAULT_CONFIG; #endif /* DISABLE_CONFIG_FILE */ struct lan_addr_s * lan_addr; struct lan_addr_s * lan_addr2; /* only print usage if -h is used */ for(i=1; iport = -1; #ifdef ENABLE_HTTPS v->https_port = -1; #endif v->notify_interval = 900; /* seconds between SSDP announces */ v->clean_ruleset_threshold = 20; v->clean_ruleset_interval = 0; /* interval between ruleset check. 0=disabled */ #ifndef DISABLE_CONFIG_FILE /* read options file first since * command line arguments have final say */ if(readoptionsfile(optionsfile, debug_flag) < 0) { /* only error if file exists or using -f */ if(access(optionsfile, F_OK) == 0 || options_flag) { INIT_PRINT_ERR("Error reading configuration file %s\n", optionsfile); return 1; } } else { for(i=0; i<(int)num_options; i++) { switch(ary_options[i].id) { case UPNPEXT_IFNAME: ext_if_name = ary_options[i].value; break; #ifdef ENABLE_IPV6 case UPNPEXT_IFNAME6: ext_if_name6 = ary_options[i].value; break; #endif 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) { INIT_PRINT_ERR("malloc(sizeof(struct lan_addr_s)): %m"); return 1; } if(parselanaddr(lan_addr, ary_options[i].value, debug_flag) != 0) { INIT_PRINT_ERR("can't parse \"%s\" as a valid " #ifndef ENABLE_IPV6 "LAN address or " #endif "interface name\n", ary_options[i].value); free(lan_addr); return 1; } LIST_INSERT_HEAD(&lan_addrs, lan_addr, list); break; #ifdef ENABLE_IPV6 case UPNPIPV6_LISTENING_IP: if (inet_pton(AF_INET6, ary_options[i].value, &ipv6_bind_addr) < 1) { INIT_PRINT_ERR("can't parse \"%s\" as valid IPv6 listening address", ary_options[i].value); return 1; } break; case UPNPIPV6_DISABLE: if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(IPV6DISABLEDMASK); break; #endif /* ENABLE_IPV6 */ case UPNPPORT: v->port = atoi(ary_options[i].value); break; #ifdef ENABLE_HTTPS case UPNPHTTPSPORT: v->https_port = atoi(ary_options[i].value); break; #endif case UPNPBITRATE_UP: upstream_bitrate = strtoul(ary_options[i].value, 0, 0); break; case UPNPBITRATE_DOWN: downstream_bitrate = strtoul(ary_options[i].value, 0, 0); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION case UPNPFRIENDLY_NAME: strncpy(friendly_name, ary_options[i].value, FRIENDLY_NAME_MAX_LEN); friendly_name[FRIENDLY_NAME_MAX_LEN-1] = '\0'; break; case UPNPMANUFACTURER_NAME: strncpy(manufacturer_name, ary_options[i].value, MANUFACTURER_NAME_MAX_LEN); manufacturer_name[MANUFACTURER_NAME_MAX_LEN-1] = '\0'; break; case UPNPMANUFACTURER_URL: strncpy(manufacturer_url, ary_options[i].value, MANUFACTURER_URL_MAX_LEN); manufacturer_url[MANUFACTURER_URL_MAX_LEN-1] = '\0'; break; case UPNPMODEL_NAME: strncpy(model_name, ary_options[i].value, MODEL_NAME_MAX_LEN); model_name[MODEL_NAME_MAX_LEN-1] = '\0'; break; case UPNPMODEL_DESCRIPTION: strncpy(model_description, ary_options[i].value, MODEL_DESCRIPTION_MAX_LEN); model_description[MODEL_DESCRIPTION_MAX_LEN-1] = '\0'; break; case UPNPMODEL_URL: strncpy(model_url, ary_options[i].value, MODEL_URL_MAX_LEN); model_url[MODEL_URL_MAX_LEN-1] = '\0'; break; #endif /* ENABLE_MANUFACTURER_INFO_CONFIGURATION */ #ifdef USE_NETFILTER case UPNPTABLENAME: set_rdr_name(RDR_TABLE_NAME, ary_options[i].value); break; case UPNPNATTABLENAME: set_rdr_name(RDR_NAT_TABLE_NAME, ary_options[i].value); break; case UPNPFORWARDCHAIN: set_rdr_name(RDR_FORWARD_CHAIN_NAME, ary_options[i].value); break; case UPNPNATCHAIN: set_rdr_name(RDR_NAT_PREROUTING_CHAIN_NAME, ary_options[i].value); break; case UPNPNATPOSTCHAIN: set_rdr_name(RDR_NAT_POSTROUTING_CHAIN_NAME, ary_options[i].value); break; case UPNPNFFAMILYSPLIT: set_rdr_name(RDR_FAMILY_SPLIT, ary_options[i].value); break; #endif /* USE_NETFILTER */ case UPNPNOTIFY_INTERVAL: v->notify_interval = atoi(ary_options[i].value); break; case UPNPSYSTEM_UPTIME: if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(SYSUPTIMEMASK); /*sysuptime = 1;*/ break; #if defined(USE_PF) || defined(USE_IPF) case UPNPPACKET_LOG: if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(LOGPACKETSMASK); /*logpackets = 1;*/ break; #endif /* defined(USE_PF) || defined(USE_IPF) */ case UPNPUUID: strncpy(uuidvalue_igd+5, ary_options[i].value, strlen(uuidvalue_igd+5) + 1); complete_uuidvalues(); break; case UPNPSERIAL: strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; break; case UPNPMODEL_NUMBER: strncpy(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; break; case UPNPCLEANTHRESHOLD: v->clean_ruleset_threshold = atoi(ary_options[i].value); break; case UPNPCLEANINTERVAL: v->clean_ruleset_interval = atoi(ary_options[i].value); break; #ifdef USE_PF case UPNPANCHOR: anchor_name = ary_options[i].value; break; case UPNPQUEUE: queue = ary_options[i].value; break; case UPNPTAG: tag = ary_options[i].value; break; #endif /* USE_PF */ #ifdef ENABLE_NATPMP /* enable both NAT-PMP and PCP (if enabled) */ case UPNPENABLENATPMP: if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(ENABLENATPMPMASK); else if(atoi(ary_options[i].value)) SETFLAG(ENABLENATPMPMASK); break; #endif /* ENABLE_NATPMP */ #ifdef ENABLE_PCP case UPNPPCPMINLIFETIME: min_lifetime = atoi(ary_options[i].value); /* RFC6887 15. the minimum value SHOULD be 120 seconds */ if (min_lifetime < 120 ) { min_lifetime = 120; } break; case UPNPPCPMAXLIFETIME: max_lifetime = atoi(ary_options[i].value); /* maximum is 24 hours */ if (max_lifetime > 86400 ) { max_lifetime = 86400; } break; case UPNPPCPALLOWTHIRDPARTY: if(strcmp(ary_options[i].value, "yes") == 0) SETFLAG(PCP_ALLOWTHIRDPARTYMASK); break; #endif /* ENABLE_PCP */ #ifdef PF_ENABLE_FILTER_RULES case UPNPQUICKRULES: if(strcmp(ary_options[i].value, "no") == 0) SETFLAG(PFNOQUICKRULESMASK); break; #endif /* PF_ENABLE_FILTER_RULES */ case UPNPENABLE: if(strcmp(ary_options[i].value, "yes") != 0) CLEARFLAG(ENABLEUPNPMASK); break; case UPNPSECUREMODE: if (strcmp(ary_options[i].value, "no") == 0) CLEARFLAG(SECUREMODEMASK); break; #ifdef ENABLE_LEASEFILE case UPNPLEASEFILE: lease_file = ary_options[i].value; break; #ifdef ENABLE_UPNPPINHOLE case UPNPLEASEFILE6: lease_file6 = ary_options[i].value; break; #endif /* ENABLE_UPNPPINHOLE */ #endif /* ENABLE_LEASEFILE */ case UPNPMINISSDPDSOCKET: minissdpdsocketpath = ary_options[i].value; break; #ifdef IGD_V2 case UPNPFORCEIGDDESCV1: if (strcmp(ary_options[i].value, "yes") == 0) SETFLAG(FORCEIGDDESCV1MASK); else if (strcmp(ary_options[i].value, "no") != 0 ) { INIT_PRINT_ERR("force_igd_desc_v1 can only be yes or no\n"); return 1; } break; #endif default: INIT_PRINT_ERR("Unknown option in file %s\n", optionsfile); } } #ifdef ENABLE_PCP /* if lifetimes are inverse */ if (min_lifetime >= max_lifetime) { INIT_PRINT_ERR("Minimum lifetime (%lu) is greater than or equal to maximum lifetime (%lu).\n", min_lifetime, max_lifetime); INIT_PRINT_ERR("Check your configuration file.\n"); return 1; } #endif /* ENABLE_PCP */ if (GETFLAG(PERFORMSTUNMASK) && !ext_stun_host) { INIT_PRINT_ERR("You must specify ext_stun_host= when ext_perform_stun=yes\n"); return 1; } } #endif /* DISABLE_CONFIG_FILE */ /* command line arguments processing */ for(i=1; inotify_interval = atoi(argv[++i]); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; case 'r': if(i+1 < argc) { v->clean_ruleset_interval = atoi(argv[++i]); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; case 'u': if(i+1 < argc) { strncpy(uuidvalue_igd+5, argv[++i], strlen(uuidvalue_igd+5) + 1); complete_uuidvalues(); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION case 'z': if(i+1 < argc) { strncpy(friendly_name, argv[++i], FRIENDLY_NAME_MAX_LEN); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } friendly_name[FRIENDLY_NAME_MAX_LEN-1] = '\0'; break; #endif /* ENABLE_MANUFACTURER_INFO_CONFIGURATION */ case 's': if(i+1 < argc) { strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; break; case 'm': if(i+1 < argc) { strncpy(modelnumber, argv[++i], MODELNUMBER_MAX_LEN); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; break; #ifdef ENABLE_NATPMP case 'N': /* enable both NAT-PMP and PCP (if enabled) */ SETFLAG(ENABLENATPMPMASK); break; #endif /* ENABLE_NATPMP */ case 'U': /*sysuptime = 1;*/ SETFLAG(SYSUPTIMEMASK); break; /*case 'l': logfilename = argv[++i]; break;*/ #if defined(USE_PF) || defined(USE_IPF) case 'L': /*logpackets = 1;*/ SETFLAG(LOGPACKETSMASK); break; #endif /* defined(USE_PF) || defined(USE_IPF) */ case 'S': /* -S0 to disable secure mode, for backward compatibility * -S is ignored */ if (argv[i][2] == '0') { CLEARFLAG(SECUREMODEMASK); } else if (argv[i][2] != '\0') { INIT_PRINT_ERR("Uses -S0 to disable secure mode.\n"); goto print_usage; } break; case 'i': if(i+1 < argc) { ext_if_name = argv[++i]; } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; #ifdef ENABLE_IPV6 case 'I': if(i+1 < argc) { ext_if_name6 = argv[++i]; } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; #endif #ifdef USE_PF case 'q': if(i+1 < argc) { queue = argv[++i]; } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; case 'T': if(i+1 < argc) { tag = argv[++i]; } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; #endif /* USE_PF */ case 'p': if(i+1 < argc) { v->port = atoi(argv[++i]); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; #ifdef ENABLE_HTTPS case 'H': if(i+1 < argc) { v->https_port = atoi(argv[++i]); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; #endif /* ENABLE_HTTPS */ #ifdef ENABLE_NFQUEUE case 'Q': if(i+1list.le_next) { if (0 == strncmp(lan_addr2->str, lan_addr->str, 15)) break; } if (lan_addr2 == NULL) LIST_INSERT_HEAD(&lan_addrs, lan_addr, list); } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } #else /* #ifndef MULTIPLE_EXTERNAL_IP */ if(i+2 < argc) { char *val = calloc((strlen(argv[i+1]) + strlen(argv[i+2]) + 2), sizeof(char)); if (val == NULL) { INIT_PRINT_ERR("memory allocation error for listen address storage\n"); return 1; } sprintf(val, "%s %s", argv[i+1], argv[i+2]); lan_addr = (struct lan_addr_s *) malloc(sizeof(struct lan_addr_s)); if (lan_addr == NULL) { INIT_PRINT_ERR("malloc(sizeof(struct lan_addr_s)): %m"); free(val); return 1; } if(parselanaddr(lan_addr, val, debug_flag) != 0) { INIT_PRINT_ERR("can't parse \"%s\" as a valid LAN address or interface name\n", val); free(lan_addr); free(val); return 1; } /* check if we already have this address */ for(lan_addr2 = lan_addrs.lh_first; lan_addr2 != NULL; lan_addr2 = lan_addr2->list.le_next) { if (0 == strncmp(lan_addr2->str, lan_addr->str, 15)) break; } if (lan_addr2 == NULL) LIST_INSERT_HEAD(&lan_addrs, lan_addr, list); free(val); i+=2; } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } #endif /* #ifndef MULTIPLE_EXTERNAL_IP */ break; case 'A': if(i+1 < argc) { void * tmp; tmp = realloc(upnppermlist, sizeof(struct upnpperm) * (num_upnpperm+1)); if(tmp == NULL) { INIT_PRINT_ERR("memory allocation error for permission\n"); return 1; } else { upnppermlist = tmp; if(read_permission_line(upnppermlist + num_upnpperm, argv[++i]) >= 0) { num_upnpperm++; } else { INIT_PRINT_ERR("Permission rule parsing error :\n%s\n", argv[i]); return 1; } } } else { INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]); goto print_usage; } break; case 'f': i++; /* discarding, the config file is already read */ break; default: INIT_PRINT_ERR("Unknown option: %s\n", argv[i]); goto print_usage; } } if(!ext_if_name || !lan_addrs.lh_first) { /* bad configuration */ if(!ext_if_name) INIT_PRINT_ERR("Error: Option -i missing and ext_ifname is not set in config file\n"); if (!lan_addrs.lh_first) INIT_PRINT_ERR("Error: Option -a missing and listening_ip is not set in config file\n"); goto print_usage; } /* IPv6 ifname is defaulted to same as IPv4 */ #ifdef ENABLE_IPV6 if(!ext_if_name6) ext_if_name6 = ext_if_name; #endif if (use_ext_ip_addr && GETFLAG(PERFORMSTUNMASK)) { INIT_PRINT_ERR("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) { INIT_PRINT_ERR("Error: option ext_ip contains invalid address %s\n", use_ext_ip_addr); return 1; } if (addr_is_reserved(&addr)) { INIT_PRINT_ERR("Error: option ext_ip contains reserved / private address %s, not public routable\n", use_ext_ip_addr); return 1; } } #ifndef NO_BACKGROUND_NO_PIDFILE if(debug_flag) { pid = getpid(); } else { #ifdef USE_DAEMON if(daemon(0, 0)<0) { perror("daemon()"); } pid = getpid(); #else pid = daemonize(); #endif } #endif if(!debug_flag) { switch (verbosity_level) { case 0: /* speed things up and ignore LOG_INFO and LOG_DEBUG */ setlogmask(LOG_UPTO(LOG_NOTICE)); break; case 1: /* ignore LOG_DEBUG */ setlogmask(LOG_UPTO(LOG_INFO)); break; case 2: setlogmask(LOG_UPTO(LOG_DEBUG)); } } #ifndef NO_BACKGROUND_NO_PIDFILE if(checkforrunning(pidfilename) < 0) { syslog(LOG_ERR, "MiniUPnPd is already running. EXITING"); return 1; } #endif #ifdef TOMATO syslog(LOG_NOTICE, "version " MINIUPNPD_VERSION " started"); #endif /* TOMATO */ set_startup_time(); /* presentation url */ if(presurl) { strncpy(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); presentationurl[PRESENTATIONURL_MAX_LEN-1] = '\0'; } else { snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, "http://%s/", lan_addrs.lh_first->str); /*"http://%s:%d/", lan_addrs.lh_first->str, 80);*/ } /* set signal handler */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; if(sigaction(SIGTERM, &sa, NULL) < 0) { syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM"); return 1; } if(sigaction(SIGINT, &sa, NULL) < 0) { syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGINT"); return 1; } #ifdef TOMATO sa.sa_handler = sigusr2; sigaction(SIGUSR2, &sa, NULL); if(signal(SIGPIPE, SIG_IGN) == SIG_ERR) #else /* TOMATO */ sa.sa_handler = SIG_IGN; if(sigaction(SIGPIPE, &sa, NULL) < 0) #endif /* TOMATO */ { syslog(LOG_ERR, "Failed to ignore SIGPIPE signals"); } sa.sa_handler = sigusr1; if(sigaction(SIGUSR1, &sa, NULL) < 0) { syslog(LOG_NOTICE, "Failed to set %s handler", "SIGUSR1"); } #if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME) sa.sa_handler = sigusr2; if(sigaction(SIGUSR2, &sa, NULL) < 0) { syslog(LOG_NOTICE, "Failed to set %s handler", "SIGUSR2"); } #endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */ /* initialize random number generator */ srandom((unsigned int)time(NULL)); #ifdef RANDOMIZE_URLS snprintf(random_url, RANDOM_URL_MAX_LEN, "%08lx", random()); #endif /* RANDOMIZE_URLS */ /* initialize redirection engine (and pinholes) */ if(init_redirect() < 0) { syslog(LOG_ERR, "Failed to init redirection engine. EXITING"); return 1; } #ifdef ENABLE_UPNPPINHOLE #ifdef USE_NETFILTER init_iptpinhole(); #endif #endif #ifndef NO_BACKGROUND_NO_PIDFILE if(writepidfile(pidfilename, pid) < 0) pidfilename = NULL; #endif #ifdef ENABLE_LEASEFILE /*remove(lease_file);*/ syslog(LOG_INFO, "Reloading rules from lease file"); reload_from_lease_file(); #ifdef ENABLE_UPNPPINHOLE reload_from_lease_file6(); #endif #endif #ifdef TOMATO tomato_load(); #endif /* TOMATO */ return 0; print_usage: fprintf(stderr, "Usage:\n\t" "%s --version\n\t" "%s --help\n\t" "%s " #ifndef DISABLE_CONFIG_FILE "[-f config_file] " #endif "[-i ext_ifname] " #ifdef ENABLE_IPV6 "[-I ext_ifname6] [-4] " #endif "[-o ext_ip]\n" #ifndef MULTIPLE_EXTERNAL_IP "\t\t[-a listening_ip]" #else "\t\t[-a listening_ip ext_ip]" #endif #ifdef ENABLE_HTTPS " [-H https_port]" #endif " [-p port] [-d] [-v]" #if defined(USE_PF) || defined(USE_IPF) " [-L]" #endif " [-U] [-S0]" #ifdef ENABLE_NATPMP " [-N]" #endif "\n" /*"[-l logfile] " not functionnal */ "\t\t[-u uuid] [-s serial] [-m model_number] \n" "\t\t[-t notify_interval] " #ifndef NO_BACKGROUND_NO_PIDFILE "[-P pid_filename] " #endif #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION "[-z fiendly_name]" #endif "\n\t\t[-B down up] [-w url] [-r clean_ruleset_interval]\n" #ifdef USE_PF "\t\t[-q queue] [-T tag]\n" #endif #ifdef ENABLE_NFQUEUE "\t\t[-Q queue] [-n name]\n" #endif "\t\t[-A \"permission rule\"] [-b BOOTID]" #ifdef IGD_V2 " [-1]" #endif "\n" "\nNotes:\n\tThere can be one or several listening_ips.\n" "\tNotify interval is in seconds. Default is 900 seconds.\n" #ifndef NO_BACKGROUND_NO_PIDFILE "\tDefault pid file is '%s'.\n" #endif "\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:host[:port]\".\n" #ifdef ENABLE_IPV6 "\t-4 disable IPv6\n" #endif #if defined(USE_PF) || defined(USE_IPF) "\t-L sets packet log in pf and ipf on.\n" #endif "\t-S0 disable \"secure\" mode so clients can add mappings to other ips\n" "\t-U causes miniupnpd to report system uptime instead " "of daemon uptime.\n" #ifdef ENABLE_NATPMP #ifdef ENABLE_PCP "\t-N enables NAT-PMP and PCP functionality.\n" #else "\t-N enables NAT-PMP functionality.\n" #endif #endif "\t-B sets bitrates reported by daemon in bits per second.\n" "\t-w sets the presentation url. Default is http address on port 80\n" #ifdef USE_PF "\t-q sets the ALTQ queue in pf.\n" "\t-T sets the tag name in pf.\n" #endif #ifdef ENABLE_NFQUEUE "\t-Q sets the queue number that is used by NFQUEUE.\n" "\t-n sets the name of the interface(s) that packets will arrive on.\n" #endif "\t-A use following syntax for permission rules :\n" "\t (allow|deny) (external port range) ip/mask (internal port range)\n" "\texamples :\n" "\t \"allow 1024-65535 192.168.1.0/24 1024-65535\"\n" "\t \"deny 0-65535 0.0.0.0/0 0-65535\"\n" "\t-b sets the value of BOOTID.UPNP.ORG SSDP header\n" #ifdef IGD_V2 "\t-1 force reporting IGDv1 in rootDesc *use with care*\n" #endif "\t-v enables LOG_INFO messages, -vv LOG_DEBUG as well (default with -d)\n" "\t-h / --help prints this help and quits.\n" "", argv[0], argv[0], argv[0], #ifndef NO_BACKGROUND_NO_PIDFILE pidfilename, #endif DEFAULT_CONFIG); return 1; } /* === main === */ /* process HTTP or SSDP requests */ int main(int argc, char * * argv) { int i; int shttpl = -1; /* socket for HTTP */ #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) int shttpl_v4 = -1; /* socket for HTTP (ipv4 only) */ #endif #ifdef ENABLE_HTTPS int shttpsl = -1; /* socket for HTTPS */ #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) int shttpsl_v4 = -1; /* socket for HTTPS (ipv4 only) */ #endif #endif /* ENABLE_HTTPS */ int sudp = -1; /* IP v4 socket for receiving SSDP */ #ifdef ENABLE_IPV6 int sudpv6 = -1; /* IP v6 socket for receiving SSDP */ #endif #ifdef ENABLE_NATPMP int * snatpmp = NULL; /* also used for PCP */ #endif #if defined(ENABLE_IPV6) && defined(ENABLE_PCP) int spcp_v6 = -1; #endif #ifdef ENABLE_NFQUEUE int nfqh = -1; #endif #ifdef USE_IFACEWATCHER int sifacewatcher = -1; #endif int * snotify = NULL; int addr_count; LIST_HEAD(httplisthead, upnphttp) upnphttphead; struct upnphttp * e = 0; struct upnphttp * next; fd_set readset; /* for select() */ fd_set writeset; struct timeval timeout, timeofday, lasttimeofday = {0, 0}; int current_notify_interval; /* with random variation */ int max_fd = -1; #ifdef USE_MINIUPNPDCTL int sctl = -1; LIST_HEAD(ctlstructhead, ctlelem) ctllisthead; struct ctlelem * ectl; struct ctlelem * ectlnext; #endif struct runtime_vars v; /* variables used for the unused-rule cleanup process */ struct rule_state * rule_list = 0; struct timeval checktime = {0, 0}; struct lan_addr_s * lan_addr; #ifdef ENABLE_UPNPPINHOLE unsigned int next_pinhole_ts; #endif for(i = 0; i < argc; i++) { if(strcmp(argv[i], "version") == 0 || strcmp(argv[i], "--version") == 0) { puts("miniupnpd " MINIUPNPD_VERSION #ifdef MINIUPNPD_GIT_REF " " MINIUPNPD_GIT_REF #endif " " __DATE__ ); #ifdef USE_PF puts("using pf backend"); #endif #ifdef USE_IPF puts("using ipf backend"); #endif #ifdef USE_IPFW puts("using ipfw backend"); #endif #ifdef USE_IPTABLES puts("using netfilter(iptables) backend"); #endif #ifdef USE_NFTABLES puts("using netfilter(nftables) backend"); #endif #ifdef ENABLE_HTTPS #ifdef OPENSSL_VERSION puts(OpenSSL_version(OPENSSL_VERSION)); #else puts(SSLeay_version(SSLEAY_VERSION)); #endif #endif puts("build options:" #ifdef USE_MINIUPNPDCTL " miniupnpdctl" #endif #ifdef ENABLE_IPV6 " ipv6" #endif #ifdef UPNP_STRICT " strict" #endif #ifdef ENABLE_NATPMP " NAT-PMP" #endif #ifdef ENABLE_PCP " PCP" #ifdef PCP_PEER " PCP-PEER" #endif #ifdef PCP_FLOWP " PCP-FLOWP" #endif #ifdef PCP_SADSCP " PCP-SADSCP" #endif #endif /* ENABLE_PCP */ #ifdef ENABLE_LEASEFILE " leasefile" #endif #ifdef CHECK_PORTINUSE " check_portinuse" #endif #ifdef IGD_V2 " igdv2" #endif ); return 0; } } memset(&v, 0, sizeof(v)); if(init(argc, argv, &v) != 0) return 1; current_notify_interval = gen_current_notify_interval(v.notify_interval); #ifdef ENABLE_HTTPS if(init_ssl() < 0) return 1; #endif /* ENABLE_HTTPS */ /* count lan addrs */ addr_count = 0; for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) addr_count++; if(addr_count > 0) { #ifndef ENABLE_IPV6 snotify = calloc(addr_count, sizeof(int)); #else /* one for IPv4, one for IPv6 */ snotify = calloc(addr_count * 2, sizeof(int)); #endif } #ifdef ENABLE_NATPMP if(addr_count > 0) { snatpmp = malloc(addr_count * sizeof(int)); for(i = 0; i < addr_count; i++) snatpmp[i] = -1; } #endif LIST_INIT(&upnphttphead); #ifdef USE_MINIUPNPDCTL LIST_INIT(&ctllisthead); #endif if( #ifdef ENABLE_NATPMP !GETFLAG(ENABLENATPMPMASK) && !GETFLAG(ENABLEUPNPMASK) #else !GETFLAG(ENABLEUPNPMASK) #endif ) { syslog(LOG_ERR, "Why did you run me anyway?"); return 0; } syslog(LOG_INFO, "version " MINIUPNPD_VERSION " starting%s%sext if %s BOOTID=%u", #ifdef ENABLE_NATPMP #ifdef ENABLE_PCP GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP/PCP " : " ", #else GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP " : " ", #endif #else " ", #endif GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "", ext_if_name, upnp_bootid); #ifdef ENABLE_IPV6 if (ext_if_name6 != ext_if_name) { syslog(LOG_INFO, "specific IPv6 ext if %s", ext_if_name6); } #endif 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); disable_port_forwarding = 1; } 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"); syslog(LOG_INFO, "Or use ext_ip= / -o option to declare public IP address"); syslog(LOG_INFO, "Public IP address is required by UPnP/PCP/PMP protocols and clients do not work without it"); disable_port_forwarding = 1; } } #ifdef DYNAMIC_OS_VERSION { struct utsname utsname; if (uname(&utsname) < 0) { syslog(LOG_ERR, "uname(): %m"); os_version = strdup("unknown"); } else { os_version = strdup(utsname.release); } } #endif /* DYNAMIC_OS_VERSION */ if(GETFLAG(ENABLEUPNPMASK)) { unsigned short listen_port; listen_port = (v.port > 0) ? v.port : 0; /* open socket for HTTP connections. Listen on the 1st LAN address */ #ifdef ENABLE_IPV6 shttpl = OpenAndConfHTTPSocket(&listen_port, !GETFLAG(IPV6DISABLEDMASK)); #else /* ENABLE_IPV6 */ shttpl = OpenAndConfHTTPSocket(&listen_port); #endif /* ENABLE_IPV6 */ if(shttpl < 0) { syslog(LOG_ERR, "Failed to open socket for HTTP. EXITING"); return 1; } v.port = listen_port; syslog(LOG_NOTICE, "HTTP listening on port %d", v.port); #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) if(!GETFLAG(IPV6DISABLEDMASK)) { shttpl_v4 = OpenAndConfHTTPSocket(&listen_port, 0); if(shttpl_v4 < 0) { syslog(LOG_ERR, "Failed to open socket for HTTP on port %d (IPv4). EXITING", v.port); return 1; } } #endif /* V6SOCKETS_ARE_V6ONLY */ #ifdef ENABLE_HTTPS /* https */ listen_port = (v.https_port > 0) ? v.https_port : 0; #ifdef ENABLE_IPV6 shttpsl = OpenAndConfHTTPSocket(&listen_port, !GETFLAG(IPV6DISABLEDMASK)); #else /* ENABLE_IPV6 */ shttpsl = OpenAndConfHTTPSocket(&listen_port); #endif /* ENABLE_IPV6 */ if(shttpsl < 0) { syslog(LOG_ERR, "Failed to open socket for HTTPS. EXITING"); return 1; } v.https_port = listen_port; syslog(LOG_NOTICE, "HTTPS listening on port %d", v.https_port); #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) shttpsl_v4 = OpenAndConfHTTPSocket(&listen_port, 0); if(shttpsl_v4 < 0) { syslog(LOG_ERR, "Failed to open socket for HTTPS on port %d (IPv4). EXITING", v.https_port); return 1; } #endif /* V6SOCKETS_ARE_V6ONLY */ #endif /* ENABLE_HTTPS */ #ifdef ENABLE_IPV6 if(!GETFLAG(IPV6DISABLEDMASK)) { if(find_ipv6_addr(lan_addrs.lh_first ? lan_addrs.lh_first->ifname : NULL, ipv6_addr_for_http_with_brackets, sizeof(ipv6_addr_for_http_with_brackets)) > 0) { syslog(LOG_NOTICE, "HTTP IPv6 address given to control points : %s", ipv6_addr_for_http_with_brackets); } else { memcpy(ipv6_addr_for_http_with_brackets, "[::1]", 6); syslog(LOG_WARNING, "no HTTP IPv6 address, disabling IPv6"); SETFLAG(IPV6DISABLEDMASK); } } #endif /* ENABLE_IPV6 */ /* open socket for SSDP connections */ sudp = OpenAndConfSSDPReceiveSocket(0); if(sudp < 0) { syslog(LOG_NOTICE, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd"); if(SubmitServicesToMiniSSDPD(lan_addrs.lh_first->str, v.port) < 0) { syslog(LOG_ERR, "Failed to connect to MiniSSDPd. EXITING"); return 1; } } #ifdef ENABLE_IPV6 if(!GETFLAG(IPV6DISABLEDMASK)) { sudpv6 = OpenAndConfSSDPReceiveSocket(1); if(sudpv6 < 0) { syslog(LOG_WARNING, "Failed to open socket for receiving SSDP (IP v6)."); } } #endif /* open socket for sending notifications */ if(OpenAndConfSSDPNotifySockets(snotify) < 0) { syslog(LOG_ERR, "Failed to open sockets for sending SSDP notify " "messages. EXITING"); return 1; } #if defined(UPNP_STRICT) && defined(IGD_V2) /* WANIPConnection:2 Service p9 : * Upon startup, UPnP IGD DCP MUST broadcast an ssdp:byebye before * sending the initial ssdp:alive onto the local network. Sending an * ssdp:byebye as part of the normal start up process for a UPnP * device ensures that UPnP control points with information about the * previous device instance will safely discard state information * about the previous device instance before communicating with the * new device instance. */ if (GETFLAG(ENABLEUPNPMASK)) { #ifndef ENABLE_IPV6 if(SendSSDPGoodbye(snotify, addr_count) < 0) #else if(SendSSDPGoodbye(snotify, addr_count * 2) < 0) #endif { syslog(LOG_WARNING, "Failed to broadcast good-bye notifications"); } } #endif /* UPNP_STRICT */ #ifdef USE_IFACEWATCHER /* open socket for kernel notifications about new network interfaces */ if (sudp >= 0) { sifacewatcher = OpenAndConfInterfaceWatchSocket(); if (sifacewatcher < 0) { syslog(LOG_ERR, "Failed to open socket for receiving network interface notifications"); } } #endif } #ifdef ENABLE_NATPMP /* open socket for NAT PMP traffic */ if(GETFLAG(ENABLENATPMPMASK)) { if(OpenAndConfNATPMPSockets(snatpmp) < 0) #ifdef ENABLE_PCP { syslog(LOG_ERR, "Failed to open sockets for NAT-PMP/PCP."); } else { syslog(LOG_NOTICE, "Listening for NAT-PMP/PCP traffic on port %u", NATPMP_PORT); } #else { syslog(LOG_ERR, "Failed to open sockets for NAT PMP."); } else { syslog(LOG_NOTICE, "Listening for NAT-PMP traffic on port %u", NATPMP_PORT); } #endif } #endif #if defined(ENABLE_IPV6) && defined(ENABLE_PCP) if(!GETFLAG(IPV6DISABLEDMASK)) { spcp_v6 = OpenAndConfPCPv6Socket(); } #endif /* for miniupnpdctl */ #ifdef USE_MINIUPNPDCTL sctl = OpenAndConfCtlUnixSocket("/var/run/miniupnpd.ctl"); #endif #ifdef ENABLE_NFQUEUE if ( nfqueue != -1 && n_nfqix > 0) { nfqh = OpenAndConfNFqueue(); if(nfqh < 0) { syslog(LOG_ERR, "Failed to open fd for NFQUEUE."); return 1; } else { syslog(LOG_NOTICE, "Opened NFQUEUE %d",nfqueue); } } #endif #ifdef TOMATO tomato_helper(); #endif #ifdef ENABLE_PCP if(GETFLAG(ENABLENATPMPMASK)) { /* Send PCP startup announcements */ #ifdef ENABLE_IPV6 PCPSendUnsolicitedAnnounce(snatpmp, addr_count, spcp_v6); #else /* IPv4 only */ PCPSendUnsolicitedAnnounce(snatpmp, addr_count); #endif } #endif /* drop privileges */ #ifdef HAS_PLEDGE /* mcast ? unix ? */ if (pledge("stdio inet pf", NULL) < 0) { syslog(LOG_ERR, "pledge(): %m"); return 1; } #endif /* HAS_PLEDGE */ #ifdef HAS_LIBCAP { cap_t caps = cap_get_proc(); if (caps == NULL) { syslog(LOG_ERR, "cap_get_proc(): %m"); } else { static const cap_value_t cap_list[3] = { CAP_NET_BROADCAST, CAP_NET_ADMIN, CAP_NET_RAW }; char * txt_caps = cap_to_text(caps, NULL); if (txt_caps == NULL) { syslog(LOG_ERR, "cap_to_text(): %m"); } else { syslog(LOG_DEBUG, "capabilities %s", txt_caps); if (cap_free(txt_caps) < 0) { syslog(LOG_ERR, "cap_free(): %m"); } } if (cap_clear(caps) < 0) { syslog(LOG_ERR, "cap_clear(): %m"); } if (cap_set_flag(caps, CAP_PERMITTED, sizeof(cap_list)/sizeof(cap_list[0]), cap_list, CAP_SET) < 0) { syslog(LOG_ERR, "cap_set_flag(): %m"); } if (cap_set_flag(caps, CAP_EFFECTIVE, sizeof(cap_list)/sizeof(cap_list[0]), cap_list, CAP_SET) < 0) { syslog(LOG_ERR, "cap_set_flag(): %m"); } txt_caps = cap_to_text(caps, NULL); if (txt_caps == NULL) { syslog(LOG_ERR, "cap_to_text(): %m"); } else { syslog(LOG_DEBUG, "capabilities %s", txt_caps); if (cap_free(txt_caps) < 0) { syslog(LOG_ERR, "cap_free(): %m"); } } if (cap_set_proc(caps) < 0) { syslog(LOG_ERR, "cap_set_proc(): %m"); } if (cap_free(caps) < 0) { syslog(LOG_ERR, "cap_free(): %m"); } } } #endif /* HAS_LIBCAP */ #ifdef HAS_LIBCAP_NG capng_setpid(getpid()); capng_clear(CAPNG_SELECT_BOTH); if (capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_NET_BROADCAST, CAP_NET_ADMIN, CAP_NET_RAW, -1) < 0) { syslog(LOG_ERR, "capng_updatev() failed"); } else { if (capng_apply(CAPNG_SELECT_BOTH) < 0) { syslog(LOG_ERR, "capng_apply() failed"); } } #endif /* HAS_LIBCAP_NG */ /* main loop */ while(!quitting) { #ifdef USE_TIME_AS_BOOTID /* Correct startup_time if it was set with a RTC close to 0 */ if((upnp_bootid<60*60*24) && (time(NULL)>60*60*24)) { upnp_bootid = time(NULL); } #endif #if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME) if(should_rewrite_leasefile) { lease_file_rewrite(); should_rewrite_leasefile = 0; } #endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */ /* send public address change notifications if needed */ if(should_send_public_address_change_notif) { syslog(LOG_INFO, "should send external iface address change notification(s)"); if(GETFLAG(PERFORMSTUNMASK)) { if (update_ext_ip_addr_from_stun(0) != 0) { /* if stun succeed it updates disable_port_forwarding; * if stun failed (non-zero return value) then port forwarding would not work, so disable it */ disable_port_forwarding = 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); disable_port_forwarding = 1; } else { int reserved = addr_is_reserved(&addr); 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"); syslog(LOG_INFO, "Or use ext_ip= / -o option to declare public IP address"); syslog(LOG_INFO, "Public IP address is required by UPnP/PCP/PMP protocols and clients do not work without it"); disable_port_forwarding = 1; } else 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); disable_port_forwarding = 0; } } } #ifdef ENABLE_NATPMP if(GETFLAG(ENABLENATPMPMASK)) SendNATPMPPublicAddressChangeNotification(snatpmp, addr_count); #endif #ifdef ENABLE_EVENTS if(GETFLAG(ENABLEUPNPMASK)) { upnp_event_var_change_notify(EWanIPC); } #endif #ifdef ENABLE_PCP if(GETFLAG(ENABLENATPMPMASK)) { #ifdef ENABLE_IPV6 PCPPublicAddressChanged(snatpmp, addr_count, spcp_v6); #else /* IPv4 only */ PCPPublicAddressChanged(snatpmp, addr_count); #endif } #endif should_send_public_address_change_notif = 0; } /* Check if we need to send SSDP NOTIFY messages and do it if * needed */ if(upnp_gettimeofday(&timeofday) < 0) { syslog(LOG_ERR, "gettimeofday(): %m"); timeout.tv_sec = current_notify_interval; timeout.tv_usec = 0; } else { /* the comparaison is not very precise but who cares ? */ if(timeofday.tv_sec >= (lasttimeofday.tv_sec + current_notify_interval)) { if (GETFLAG(ENABLEUPNPMASK)) SendSSDPNotifies2(snotify, (unsigned short)v.port, #ifdef ENABLE_HTTPS (unsigned short)v.https_port, #endif v.notify_interval << 1); current_notify_interval = gen_current_notify_interval(v.notify_interval); memcpy(&lasttimeofday, &timeofday, sizeof(struct timeval)); timeout.tv_sec = current_notify_interval; timeout.tv_usec = 0; } else { timeout.tv_sec = lasttimeofday.tv_sec + current_notify_interval - timeofday.tv_sec; if(timeofday.tv_usec > lasttimeofday.tv_usec) { timeout.tv_usec = 1000000 + lasttimeofday.tv_usec - timeofday.tv_usec; timeout.tv_sec--; } else { timeout.tv_usec = lasttimeofday.tv_usec - timeofday.tv_usec; } } } /* remove unused rules */ if( v.clean_ruleset_interval && (timeofday.tv_sec >= checktime.tv_sec + v.clean_ruleset_interval)) { if(rule_list) { remove_unused_rules(rule_list); rule_list = NULL; } else { rule_list = get_upnp_rules_state_list(v.clean_ruleset_threshold); } memcpy(&checktime, &timeofday, sizeof(struct timeval)); } /* Remove expired port mappings, based on UPnP IGD LeaseDuration * or NAT-PMP lifetime) */ if(nextruletoclean_timestamp && ((unsigned int)timeofday.tv_sec >= nextruletoclean_timestamp)) { syslog(LOG_DEBUG, "cleaning expired Port Mappings"); get_upnp_rules_state_list(0); } if(nextruletoclean_timestamp && ((unsigned int)timeout.tv_sec >= (nextruletoclean_timestamp - timeofday.tv_sec))) { timeout.tv_sec = nextruletoclean_timestamp - timeofday.tv_sec; timeout.tv_usec = 0; syslog(LOG_DEBUG, "setting timeout to %u sec", (unsigned)timeout.tv_sec); } #ifdef ENABLE_UPNPPINHOLE /* Clean up expired IPv6 PinHoles */ next_pinhole_ts = 0; upnp_clean_expired_pinholes(&next_pinhole_ts); if(next_pinhole_ts && timeout.tv_sec >= (int)(next_pinhole_ts - timeofday.tv_sec)) { timeout.tv_sec = next_pinhole_ts - timeofday.tv_sec; timeout.tv_usec = 0; } #endif /* ENABLE_UPNPPINHOLE */ /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ FD_ZERO(&readset); FD_ZERO(&writeset); if (sudp >= 0) { FD_SET(sudp, &readset); max_fd = MAX( max_fd, sudp); #ifdef USE_IFACEWATCHER if (sifacewatcher >= 0) { FD_SET(sifacewatcher, &readset); max_fd = MAX(max_fd, sifacewatcher); } #endif } if (shttpl >= 0) { FD_SET(shttpl, &readset); max_fd = MAX( max_fd, shttpl); } #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) if (shttpl_v4 >= 0) { FD_SET(shttpl_v4, &readset); max_fd = MAX( max_fd, shttpl_v4); } #endif #ifdef ENABLE_HTTPS if (shttpsl >= 0) { FD_SET(shttpsl, &readset); max_fd = MAX( max_fd, shttpsl); } #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) if (shttpsl_v4 >= 0) { FD_SET(shttpsl_v4, &readset); max_fd = MAX( max_fd, shttpsl_v4); } #endif #endif /* ENABLE_HTTPS */ #ifdef ENABLE_IPV6 if (sudpv6 >= 0) { FD_SET(sudpv6, &readset); max_fd = MAX( max_fd, sudpv6); } #endif #ifdef ENABLE_NFQUEUE if (nfqh >= 0) { FD_SET(nfqh, &readset); max_fd = MAX( max_fd, nfqh); } #endif i = 0; /* active HTTP connections count */ for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if(e->socket >= 0) { if(e->state <= EWaitingForHttpContent) FD_SET(e->socket, &readset); else if(e->state == ESendingAndClosing) FD_SET(e->socket, &writeset); else continue; max_fd = MAX(max_fd, e->socket); i++; } } /* for debug */ #ifdef DEBUG if(i > 1) { syslog(LOG_DEBUG, "%d active incoming HTTP connections", i); } #endif #ifdef ENABLE_NATPMP for(i=0; i= 0) { FD_SET(snatpmp[i], &readset); max_fd = MAX( max_fd, snatpmp[i]); } } #endif #if defined(ENABLE_IPV6) && defined(ENABLE_PCP) if(spcp_v6 >= 0) { FD_SET(spcp_v6, &readset); max_fd = MAX(max_fd, spcp_v6); } #endif #ifdef USE_MINIUPNPDCTL if(sctl >= 0) { FD_SET(sctl, &readset); max_fd = MAX( max_fd, sctl); } for(ectl = ctllisthead.lh_first; ectl; ectl = ectl->entries.le_next) { if(ectl->socket >= 0) { FD_SET(ectl->socket, &readset); max_fd = MAX( max_fd, ectl->socket); } } #endif #ifdef ENABLE_EVENTS 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; #ifdef TOMATO if (gotusr2) { gotusr2 = 0; tomato_helper(); continue; } #endif /* TOMATO */ if(errno == EINTR) continue; /* interrupted by a signal, start again */ syslog(LOG_ERR, "select(all): %m"); syslog(LOG_ERR, "Failed to select open sockets. EXITING"); return 1; /* very serious cause of error */ } i = try_sendto(&writeset); if(i < 0) { syslog(LOG_ERR, "try_sendto failed to send %d packets", -i); } #ifdef USE_MINIUPNPDCTL for(ectl = ctllisthead.lh_first; ectl;) { ectlnext = ectl->entries.le_next; if((ectl->socket >= 0) && FD_ISSET(ectl->socket, &readset)) { char buf[256]; int l; l = read(ectl->socket, buf, sizeof(buf)); if(l > 0) { /*write(ectl->socket, buf, l);*/ write_command_line(ectl->socket, argc, argv); #ifndef DISABLE_CONFIG_FILE write_option_list(ectl->socket); #endif write_permlist(ectl->socket, upnppermlist, num_upnpperm); write_upnphttp_details(ectl->socket, upnphttphead.lh_first); write_ctlsockets_list(ectl->socket, ctllisthead.lh_first); write_ruleset_details(ectl->socket); #ifdef ENABLE_EVENTS write_events_details(ectl->socket); #endif /* close the socket */ close(ectl->socket); ectl->socket = -1; } else { close(ectl->socket); ectl->socket = -1; } } if(ectl->socket < 0) { LIST_REMOVE(ectl, entries); free(ectl); } ectl = ectlnext; } if((sctl >= 0) && FD_ISSET(sctl, &readset)) { int s; struct sockaddr_un clientname; struct ctlelem * tmp; socklen_t clientnamelen = sizeof(struct sockaddr_un); /*syslog(LOG_DEBUG, "sctl!");*/ s = accept(sctl, (struct sockaddr *)&clientname, &clientnamelen); syslog(LOG_DEBUG, "sctl! : '%s'", clientname.sun_path); tmp = malloc(sizeof(struct ctlelem)); if (tmp == NULL) { syslog(LOG_ERR, "Unable to allocate memory for ctlelem in main()"); close(s); } else { tmp->socket = s; LIST_INSERT_HEAD(&ctllisthead, tmp, entries); } } #endif #ifdef ENABLE_EVENTS upnpevents_processfds(&readset, &writeset); #endif #ifdef ENABLE_NATPMP /* process NAT-PMP packets */ for(i=0; i= 0) && FD_ISSET(snatpmp[i], &readset)) { unsigned char msg_buff[PCP_MAX_LEN]; struct sockaddr_in senderaddr; socklen_t senderaddrlen; int len; memset(msg_buff, 0, PCP_MAX_LEN); senderaddrlen = sizeof(senderaddr); len = ReceiveNATPMPOrPCPPacket(snatpmp[i], (struct sockaddr *)&senderaddr, &senderaddrlen, NULL, msg_buff, sizeof(msg_buff)); if (len < 1) continue; #ifdef ENABLE_PCP if (msg_buff[0]==0) { /* version equals to 0 -> means NAT-PMP */ /* Check if the packet is coming from a LAN to enforce RFC6886 : * The NAT gateway MUST NOT accept mapping requests destined to the NAT * gateway's external IP address or received on its external network * interface. Only packets received on the internal interface(s) with a * destination address matching the internal address(es) of the NAT * gateway should be allowed. */ /* TODO : move to ProcessIncomingNATPMPPacket() ? */ lan_addr = get_lan_for_peer((struct sockaddr *)&senderaddr); if(lan_addr == NULL) { char sender_str[64]; sockaddr_to_string((struct sockaddr *)&senderaddr, sender_str, sizeof(sender_str)); syslog(LOG_WARNING, "NAT-PMP packet sender %s not from a LAN, ignoring", sender_str); continue; } ProcessIncomingNATPMPPacket(snatpmp[i], msg_buff, len, &senderaddr); } else { /* everything else can be PCP */ ProcessIncomingPCPPacket(snatpmp[i], msg_buff, len, (struct sockaddr *)&senderaddr, NULL); } #else /* Check if the packet is coming from a LAN to enforce RFC6886 : * The NAT gateway MUST NOT accept mapping requests destined to the NAT * gateway's external IP address or received on its external network * interface. Only packets received on the internal interface(s) with a * destination address matching the internal address(es) of the NAT * gateway should be allowed. */ /* TODO : move to ProcessIncomingNATPMPPacket() ? */ lan_addr = get_lan_for_peer((struct sockaddr *)&senderaddr); if(lan_addr == NULL) { char sender_str[64]; sockaddr_to_string((struct sockaddr *)&senderaddr, sender_str, sizeof(sender_str)); syslog(LOG_WARNING, "NAT-PMP packet sender %s not from a LAN, ignoring", sender_str); continue; } ProcessIncomingNATPMPPacket(snatpmp[i], msg_buff, len, &senderaddr); #endif } } #endif #if defined(ENABLE_IPV6) && defined(ENABLE_PCP) /* in IPv6, only PCP is supported, not NAT-PMP */ if(spcp_v6 >= 0 && FD_ISSET(spcp_v6, &readset)) { unsigned char msg_buff[PCP_MAX_LEN]; struct sockaddr_in6 senderaddr; socklen_t senderaddrlen; struct sockaddr_in6 receiveraddr; int len; memset(msg_buff, 0, PCP_MAX_LEN); senderaddrlen = sizeof(senderaddr); len = ReceiveNATPMPOrPCPPacket(spcp_v6, (struct sockaddr *)&senderaddr, &senderaddrlen, &receiveraddr, msg_buff, sizeof(msg_buff)); if(len >= 1) ProcessIncomingPCPPacket(spcp_v6, msg_buff, len, (struct sockaddr *)&senderaddr, &receiveraddr); } #endif /* process SSDP packets */ if(sudp >= 0 && FD_ISSET(sudp, &readset)) { /*syslog(LOG_INFO, "Received UDP Packet");*/ #ifdef ENABLE_HTTPS ProcessSSDPRequest(sudp, (unsigned short)v.port, (unsigned short)v.https_port); #else ProcessSSDPRequest(sudp, (unsigned short)v.port); #endif } #ifdef ENABLE_IPV6 if(sudpv6 >= 0 && FD_ISSET(sudpv6, &readset)) { syslog(LOG_INFO, "Received UDP Packet (IPv6)"); #ifdef ENABLE_HTTPS ProcessSSDPRequest(sudpv6, (unsigned short)v.port, (unsigned short)v.https_port); #else ProcessSSDPRequest(sudpv6, (unsigned short)v.port); #endif } #endif #ifdef USE_IFACEWATCHER /* process kernel notifications */ if (sifacewatcher >= 0 && FD_ISSET(sifacewatcher, &readset)) ProcessInterfaceWatchNotify(sifacewatcher); #endif /* process active HTTP connections */ /* LIST_FOREACH macro is not available under linux */ for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if(e->socket >= 0) { if(FD_ISSET(e->socket, &readset) || FD_ISSET(e->socket, &writeset)) { Process_upnphttp(e); } } } /* process incoming HTTP connections */ if(shttpl >= 0 && FD_ISSET(shttpl, &readset)) { struct upnphttp * tmp; tmp = ProcessIncomingHTTP(shttpl, "HTTP"); if(tmp) { LIST_INSERT_HEAD(&upnphttphead, tmp, entries); } } #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) if(shttpl_v4 >= 0 && FD_ISSET(shttpl_v4, &readset)) { struct upnphttp * tmp; tmp = ProcessIncomingHTTP(shttpl_v4, "HTTP"); if(tmp) { LIST_INSERT_HEAD(&upnphttphead, tmp, entries); } } #endif #ifdef ENABLE_HTTPS if(shttpsl >= 0 && FD_ISSET(shttpsl, &readset)) { struct upnphttp * tmp; tmp = ProcessIncomingHTTP(shttpsl, "HTTPS"); if(tmp) { InitSSL_upnphttp(tmp); LIST_INSERT_HEAD(&upnphttphead, tmp, entries); } } #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) if(shttpsl_v4 >= 0 && FD_ISSET(shttpsl_v4, &readset)) { struct upnphttp * tmp; tmp = ProcessIncomingHTTP(shttpsl_v4, "HTTPS"); if(tmp) { InitSSL_upnphttp(tmp); LIST_INSERT_HEAD(&upnphttphead, tmp, entries); } } #endif #endif /* ENABLE_HTTPS */ #ifdef ENABLE_NFQUEUE /* process NFQ packets */ if(nfqh >= 0 && FD_ISSET(nfqh, &readset)) { /* syslog(LOG_INFO, "Received NFQUEUE Packet");*/ ProcessNFQUEUE(nfqh); } #endif /* delete finished HTTP connections */ for(e = upnphttphead.lh_first; e != NULL; ) { next = e->entries.le_next; if(e->state >= EToDelete) { LIST_REMOVE(e, entries); Delete_upnphttp(e); } e = next; } } /* end of main loop */ shutdown: syslog(LOG_NOTICE, "shutting down MiniUPnPd"); /* 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(); #ifdef TOMATO tomato_save("/etc/upnp/data"); #endif /* TOMATO */ #if defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME) lease_file_rewrite(); #endif /* ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */ /* close out open sockets */ while(upnphttphead.lh_first != NULL) { e = upnphttphead.lh_first; LIST_REMOVE(e, entries); Delete_upnphttp(e); } if (sudp >= 0) close(sudp); if (shttpl >= 0) close(shttpl); #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) if (shttpl_v4 >= 0) close(shttpl_v4); #endif #ifdef ENABLE_IPV6 if (sudpv6 >= 0) close(sudpv6); #endif #ifdef USE_IFACEWATCHER if(sifacewatcher >= 0) close(sifacewatcher); #endif #ifdef ENABLE_NATPMP for(i=0; i=0) { close(snatpmp[i]); snatpmp[i] = -1; } } #endif #if defined(ENABLE_IPV6) && defined(ENABLE_PCP) if(spcp_v6 >= 0) { close(spcp_v6); spcp_v6 = -1; } #endif #ifdef USE_MINIUPNPDCTL if(sctl>=0) { close(sctl); sctl = -1; if(unlink("/var/run/miniupnpd.ctl") < 0) { syslog(LOG_ERR, "unlink() %m"); } } #endif if (GETFLAG(ENABLEUPNPMASK)) { #ifndef ENABLE_IPV6 for(i = 0; i < addr_count; i++) #else for(i = 0; i < addr_count * 2; i++) #endif close(snotify[i]); } /* remove pidfile */ #ifndef NO_BACKGROUND_NO_PIDFILE if(pidfilename && (unlink(pidfilename) < 0)) { syslog(LOG_ERR, "Failed to remove pidfile %s: %m", pidfilename); } #endif /* delete lists */ while(lan_addrs.lh_first != NULL) { lan_addr = lan_addrs.lh_first; LIST_REMOVE(lan_addrs.lh_first, list); free(lan_addr); } #ifdef ENABLE_HTTPS free_ssl(); #endif #ifdef ENABLE_NATPMP free(snatpmp); #endif free(snotify); shutdown_redirect(); #ifndef DISABLE_CONFIG_FILE /* in some case shutdown_redirect() may need the option values */ freeoptions(); #endif #ifdef DYNAMIC_OS_VERSION free(os_version); #endif closelog(); return 0; } miniupnpd-2.3.7/upnphttp.h010064400017500000024000000075631411013505400147500ustar00nanardstaff/* $Id: upnphttp.h,v 1.44 2021/08/21 08:11:09 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPHTTP_H_INCLUDED #define UPNPHTTP_H_INCLUDED #include #include #include "config.h" #ifdef ENABLE_HTTPS #include #endif /* ENABLE_HTTPS */ #define UPNP_VERSION_STRING "UPnP/" UPNP_VERSION_MAJOR_STR "." UPNP_VERSION_MINOR_STR /* server: HTTP header returned in all HTTP responses : */ #define MINIUPNPD_SERVER_STRING OS_VERSION " " UPNP_VERSION_STRING " MiniUPnPd/" MINIUPNPD_VERSION /* states : 0 - waiting for data to read 1 - waiting for HTTP Post Content. ... >= 100 - to be deleted */ enum httpStates { EWaitingForHttpRequest = 0, EWaitingForHttpContent, ESendingContinue, ESendingAndClosing, EToDelete = 100 }; enum httpCommands { EUnknown = 0, EGet, EPost, ESubscribe, EUnSubscribe }; struct upnphttp { int socket; struct in_addr clientaddr; /* client address */ #ifdef ENABLE_IPV6 int ipv6; struct in6_addr clientaddr_v6; #endif /* ENABLE_IPV6 */ #ifdef ENABLE_HTTPS SSL * ssl; #endif /* ENABLE_HTTPS */ char clientaddr_str[64]; /* used for syslog() output */ enum httpStates state; char HttpVer[16]; /* request */ char * req_buf; char accept_language[8]; int req_buflen; int req_contentlen; int req_contentoff; /* header length */ enum httpCommands req_command; int req_soapActionOff; int req_soapActionLen; int req_HostOff; /* Host: header */ int req_HostLen; #ifdef ENABLE_EVENTS int req_CallbackOff; /* For SUBSCRIBE */ int req_CallbackLen; int req_Timeout; int req_SIDOff; /* For UNSUBSCRIBE */ int req_SIDLen; const char * res_SID; #ifdef UPNP_STRICT int req_NTOff; int req_NTLen; #endif #endif int respflags; /* see FLAG_* constants below */ /* response */ char * res_buf; int res_buflen; int res_sent; int res_buf_alloclen; LIST_ENTRY(upnphttp) entries; }; /* Include the "Timeout:" header in response */ #define FLAG_TIMEOUT 0x01 /* Include the "SID:" header in response */ #define FLAG_SID 0x02 /* If set, the POST request included a "Expect: 100-continue" header */ #define FLAG_CONTINUE 0x40 /* If set, the Content-Type is set to text/xml, otherwise it is text/xml */ #define FLAG_HTML 0x80 /* If set, the corresponding Allow: header is set */ #define FLAG_ALLOW_POST 0x100 #define FLAG_ALLOW_SUB_UNSUB 0x200 /* If set, the User-Agent: contains "microsoft" */ #define FLAG_MS_CLIENT 0x400 #ifdef ENABLE_HTTPS int init_ssl(void); void free_ssl(void); #endif /* ENABLE_HTTPS */ /* New_upnphttp() */ struct upnphttp * New_upnphttp(int); #ifdef ENABLE_HTTPS void InitSSL_upnphttp(struct upnphttp *); #endif /* ENABLE_HTTPS */ /* CloseSocket_upnphttp() */ void CloseSocket_upnphttp(struct upnphttp *); /* Delete_upnphttp() */ void Delete_upnphttp(struct upnphttp *); /* Process_upnphttp() */ void Process_upnphttp(struct upnphttp *); /* BuildHeader_upnphttp() * build the header for the HTTP Response * also allocate the buffer for body data * return -1 on error */ int BuildHeader_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, int bodylen); /* BuildResp_upnphttp() * fill the res_buf buffer with the complete * HTTP 200 OK response from the body passed as argument */ void BuildResp_upnphttp(struct upnphttp *, const char *, int); /* BuildResp2_upnphttp() * same but with given response code/message */ void BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen); int SendResp_upnphttp(struct upnphttp *); /* SendRespAndClose_upnphttp() */ void SendRespAndClose_upnphttp(struct upnphttp *); #endif miniupnpd-2.3.7/upnpdescgen.c010064400017500000024000001156021457060357000154030ustar00nanardstaff/* $Id: upnpdescgen.c,v 1.92 2024/03/02 10:45:27 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "config.h" #ifdef ENABLE_EVENTS #include "getifaddr.h" #include "upnpredirect.h" #endif #include "upnpdescgen.h" #include "miniupnpdpath.h" #include "upnpglobalvars.h" #include "upnpdescstrings.h" #include "upnpurns.h" #include "getconnstatus.h" #include "macros.h" /* Event magical values codes */ #define SETUPREADY_MAGICALVALUE (248) #define CONNECTIONSTATUS_MAGICALVALUE (249) #define FIREWALLENABLED_MAGICALVALUE (250) #define INBOUNDPINHOLEALLOWED_MAGICALVALUE (251) #define SYSTEMUPDATEID_MAGICALVALUE (252) #define PORTMAPPINGNUMBEROFENTRIES_MAGICALVALUE (253) #define EXTERNALIPADDRESS_MAGICALVALUE (254) #define DEFAULTCONNECTIONSERVICE_MAGICALVALUE (255) static const char * const upnptypes[] = { "string", "boolean", "ui2", "ui4", "bin.base64" }; static const char * const upnpdefaultvalues[] = { 0, "IP_Routed"/*"Unconfigured"*/, /* 1 default value for ConnectionType */ "3600", /* 2 default value for PortMappingLeaseDuration */ "Unconfigured", /* 3 default value for ConnectionStatus */ "0", /* 4 default value for RSIPAvailable */ "1", /* 5 default value for NATEnabled */ "ERROR_NONE", /* 6 default value for LastConnectionError */ }; static const char * const upnpallowedvalues[] = { 0, /* 0 */ "DSL", /* 1 */ "POTS", "Cable", "Ethernet", 0, "Up", /* 6 */ "Down", "Initializing", "Unavailable", 0, "TCP", /* 11 */ "UDP", 0, "Unconfigured", /* 14 */ "IP_Routed", "IP_Bridged", 0, "Unconfigured", /* 18 */ "Connecting", "Connected", "PendingDisconnect", "Disconnecting", "Disconnected", 0, "ERROR_NONE", /* 25 */ /* Optionals values : * ERROR_COMMAND_ABORTED * ERROR_NOT_ENABLED_FOR_INTERNET * ERROR_USER_DISCONNECT * ERROR_ISP_DISCONNECT * ERROR_IDLE_DISCONNECT * ERROR_FORCED_DISCONNECT * ERROR_NO_CARRIER * ERROR_IP_CONFIGURATION * ERROR_UNKNOWN */ 0, "", /* 27 */ 0 }; static const int upnpallowedranges[] = { 0, /* 1 PortMappingLeaseDuration */ 0, 604800, /* 3 InternalPort */ 1, 65535, /* 5 LeaseTime */ 1, 86400, /* 7 OutboundPinholeTimeout */ 100, 200, }; static const char * magicargname[] = { 0, "StartPort", /* 1 */ "EndPort", /* 2 */ "RemoteHost", /* 3 */ "RemotePort", /* 4 */ "InternalClient", /* 5 */ "InternalPort", /* 6 */ "IsWorking", /* 7 */ #ifdef ENABLE_DP_SERVICE "ProtocolType", /* 8 */ "InMessage", /* 9 */ "OutMessage", /* 10 */ "ProtocolList", /* 11 */ "RoleList", /* 12 */ #endif /* ENABLE_DP_SERVICE */ }; static const char xmlver[] = "\r\n"; static const char root_service[] = "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\""; static const char root_device[] = "root xmlns=\"urn:schemas-upnp-org:device-1-0\""; /* root Description of the UPnP Device * fixed to match UPnP_IGD_InternetGatewayDevice 1.0.pdf * Needs to be checked with UPnP-gw-InternetGatewayDevice-v2-Device.pdf * presentationURL is only "recommended" but the router doesn't appears * in "Network connections" in Windows XP if it is not present. */ static const struct XMLElt rootDesc[] = { /* 0 */ {root_device, INITHELPER(1,2)}, {"specVersion", INITHELPER(3,2)}, #if defined(ENABLE_L3F_SERVICE) || defined(HAS_DUMMY_SERVICE) || defined(ENABLE_DP_SERVICE) {"device", INITHELPER(5,13)}, #else {"device", INITHELPER(5,12)}, #endif {"/major", UPNP_VERSION_MAJOR_STR}, {"/minor", UPNP_VERSION_MINOR_STR}, /* 5 */ {"/deviceType", DEVICE_TYPE_IGD}, /* urn:schemas-upnp-org:device:InternetGatewayDevice:1 or 2 */ #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION {"/friendlyName", friendly_name/*ROOTDEV_FRIENDLYNAME*/}, /* required */ {"/manufacturer", manufacturer_name/*ROOTDEV_MANUFACTURER*/}, /* required */ /* 8 */ {"/manufacturerURL", manufacturer_url/*ROOTDEV_MANUFACTURERURL*/}, /* optional */ {"/modelDescription", model_description/*ROOTDEV_MODELDESCRIPTION*/}, /* recommended */ {"/modelName", model_name/*ROOTDEV_MODELNAME*/}, /* required */ {"/modelNumber", modelnumber}, {"/modelURL", model_url/*ROOTDEV_MODELURL*/}, #else {"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ /* 8 */ {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ {"/modelName", ROOTDEV_MODELNAME}, /* required */ {"/modelNumber", modelnumber}, {"/modelURL", ROOTDEV_MODELURL}, #endif {"/serialNumber", serialnumber}, {"/UDN", uuidvalue_igd}, /* required */ /* see if /UPC is needed. */ #ifdef ENABLE_6FC_SERVICE #define SERVICES_OFFSET 63 #else #define SERVICES_OFFSET 58 #endif #if defined(ENABLE_L3F_SERVICE) || defined(HAS_DUMMY_SERVICE) || defined(ENABLE_DP_SERVICE) /* here we dening Services for the root device : * L3F and DUMMY and DeviceProtection */ #ifdef ENABLE_L3F_SERVICE #define NSERVICES1 1 #else #define NSERVICES1 0 #endif #ifdef HAS_DUMMY_SERVICE #define NSERVICES2 1 #else #define NSERVICES2 0 #endif #ifdef ENABLE_DP_SERVICE #define NSERVICES3 1 #else #define NSERVICES3 0 #endif #define NSERVICES (NSERVICES1+NSERVICES2+NSERVICES3) {"serviceList", INITHELPER(SERVICES_OFFSET,NSERVICES)}, {"deviceList", INITHELPER(18,1)}, {"/presentationURL", presentationurl}, /* recommended */ #else {"deviceList", INITHELPER(18,1)}, {"/presentationURL", presentationurl}, /* recommended */ {0,0}, #endif /* 18 */ {"device", INITHELPER(19,13)}, /* 19 */ {"/deviceType", DEVICE_TYPE_WAN}, /* required */ /* urn:schemas-upnp-org:device:WANDevice:1 or 2 */ {"/friendlyName", WANDEV_FRIENDLYNAME}, {"/manufacturer", WANDEV_MANUFACTURER}, {"/manufacturerURL", WANDEV_MANUFACTURERURL}, {"/modelDescription" , WANDEV_MODELDESCRIPTION}, {"/modelName", WANDEV_MODELNAME}, {"/modelNumber", WANDEV_MODELNUMBER}, {"/modelURL", WANDEV_MODELURL}, {"/serialNumber", serialnumber}, {"/UDN", uuidvalue_wan}, {"/UPC", WANDEV_UPC}, /* UPC (=12 digit barcode) is optional */ /* 30 */ {"serviceList", INITHELPER(32,1)}, {"deviceList", INITHELPER(38,1)}, /* 32 */ {"service", INITHELPER(33,5)}, /* 33 */ {"/serviceType", "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"}, /*{"/serviceId", "urn:upnp-org:serviceId:WANCommonInterfaceConfig"}, */ {"/serviceId", "urn:upnp-org:serviceId:WANCommonIFC1"}, /* required */ {"/SCPDURL", WANCFG_PATH}, {"/controlURL", WANCFG_CONTROLURL}, {"/eventSubURL", WANCFG_EVENTURL}, /* 38 */ {"device", INITHELPER(39,12)}, /* 39 */ {"/deviceType", DEVICE_TYPE_WANC}, /* urn:schemas-upnp-org:device:WANConnectionDevice:1 or 2 */ {"/friendlyName", WANCDEV_FRIENDLYNAME}, {"/manufacturer", WANCDEV_MANUFACTURER}, {"/manufacturerURL", WANCDEV_MANUFACTURERURL}, {"/modelDescription", WANCDEV_MODELDESCRIPTION}, {"/modelName", WANCDEV_MODELNAME}, {"/modelNumber", WANCDEV_MODELNUMBER}, {"/modelURL", WANCDEV_MODELURL}, {"/serialNumber", serialnumber}, {"/UDN", uuidvalue_wcd}, {"/UPC", WANCDEV_UPC}, /* UPC (=12 digit Barcode) is optional */ #ifdef ENABLE_6FC_SERVICE {"serviceList", INITHELPER(51,2)}, #else {"serviceList", INITHELPER(51,1)}, #endif /* 51 */ {"service", INITHELPER(53,5)}, {"service", INITHELPER(58,5)}, /* 53 */ {"/serviceType", SERVICE_TYPE_WANIPC}, /* urn:schemas-upnp-org:service:WANIPConnection:2 for v2 */ {"/serviceId", SERVICE_ID_WANIPC}, /* urn:upnp-org:serviceId:WANIPConn1 or 2 */ {"/SCPDURL", WANIPC_PATH}, {"/controlURL", WANIPC_CONTROLURL}, {"/eventSubURL", WANIPC_EVENTURL}, #ifdef ENABLE_6FC_SERVICE /* 58 */ {"/serviceType", "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"}, {"/serviceId", "urn:upnp-org:serviceId:WANIPv6Firewall1"}, {"/SCPDURL", WANIP6FC_PATH}, {"/controlURL", WANIP6FC_CONTROLURL}, {"/eventSubURL", WANIP6FC_EVENTURL}, #endif /* 58 / 63 = SERVICES_OFFSET*/ #if defined(HAS_DUMMY_SERVICE) || defined(ENABLE_L3F_SERVICE) || defined(ENABLE_DP_SERVICE) {"service", INITHELPER(SERVICES_OFFSET+2,5)}, {"service", INITHELPER(SERVICES_OFFSET+7,5)}, #endif #ifdef HAS_DUMMY_SERVICE /* 60 / 65 = SERVICES_OFFSET+2 */ {"/serviceType", "urn:schemas-dummy-com:service:Dummy:1"}, {"/serviceId", "urn:dummy-com:serviceId:dummy1"}, {"/SCPDURL", DUMMY_PATH}, {"/controlURL", "/dummy"}, {"/eventSubURL", "/dummy"}, #endif #ifdef ENABLE_L3F_SERVICE /* 60 / 65 = SERVICES_OFFSET+2 */ {"/serviceType", "urn:schemas-upnp-org:service:Layer3Forwarding:1"}, {"/serviceId", "urn:upnp-org:serviceId:L3Forwarding1"}, {"/SCPDURL", L3F_PATH}, {"/controlURL", L3F_CONTROLURL}, /* The Layer3Forwarding service is only */ {"/eventSubURL", L3F_EVENTURL}, /* recommended, not mandatory */ #endif #ifdef ENABLE_DP_SERVICE /* InternetGatewayDevice v2 : * it is RECOMMEDED that DeviceProtection service is implemented and applied. * If DeviceProtection is not implemented and applied, it is RECOMMENDED * that control points are able to access only actions and parameters defined * as Public role. */ /* 65 / 70 = SERVICES_OFFSET+7 */ {"/serviceType", "urn:schemas-upnp-org:service:DeviceProtection:1"}, {"/serviceId", "urn:upnp-org:serviceId:DeviceProtection1"}, {"/SCPDURL", DP_PATH}, {"/controlURL", DP_CONTROLURL}, {"/eventSubURL", DP_EVENTURL}, #endif {0, 0} }; /* WANIPCn.xml */ /* see UPnP_IGD_WANIPConnection 1.0.pdf static struct XMLElt scpdWANIPCn[] = { {root_service, {INITHELPER(1,2)}}, {0, {0}} }; */ static const struct argument AddPortMappingArgs[] = { {1, 11}, /* RemoteHost */ {1, 12}, /* ExternalPort */ {1, 14}, /* PortMappingProtocol */ {1, 13}, /* InternalPort */ {1, 15}, /* InternalClient */ {1, 9}, /* PortMappingEnabled */ {1, 16}, /* PortMappingDescription */ {1, 10}, /* PortMappingLeaseDuration */ {0, 0} }; #ifdef IGD_V2 static const struct argument AddAnyPortMappingArgs[] = { {1, 11}, /* RemoteHost */ {1, 12}, /* ExternalPort */ {1, 14}, /* PortMappingProtocol */ {1, 13}, /* InternalPort */ {1, 15}, /* InternalClient */ {1, 9}, /* PortMappingEnabled */ {1, 16}, /* PortMappingDescription */ {1, 10}, /* PortMappingLeaseDuration */ {2, 12}, /* NewReservedPort / ExternalPort */ {0, 0} }; static const struct argument DeletePortMappingRangeArgs[] = { {1|(1<<2), 12}, /* NewStartPort / ExternalPort */ {1|(2<<2), 12}, /* NewEndPort / ExternalPort */ {1, 14}, /* NewProtocol / PortMappingProtocol */ {1, 18}, /* NewManage / A_ARG_TYPE_Manage */ {0, 0} }; static const struct argument GetListOfPortMappingsArgs[] = { {1|(1<<2), 12}, /* NewStartPort / ExternalPort */ {1|(2<<2), 12}, /* NewEndPort / ExternalPort */ {1, 14}, /* NewProtocol / PortMappingProtocol */ {1, 18}, /* NewManage / A_ARG_TYPE_Manage */ {1, 8}, /* NewNumberOfPorts / PortMappingNumberOfEntries */ {2, 19}, /* NewPortListing / A_ARG_TYPE_PortListing */ {0, 0} }; #endif static const struct argument GetExternalIPAddressArgs[] = { {2, 7}, {0, 0} }; static const struct argument DeletePortMappingArgs[] = { {1, 11}, /* RemoteHost */ {1, 12}, /* ExternalPort */ {1, 14}, /* PortMappingProtocol */ {0, 0} }; static const struct argument SetConnectionTypeArgs[] = { {1, 0}, {0, 0} }; static const struct argument GetConnectionTypeInfoArgs[] = { {2, 0}, {2, 1}, {0, 0} }; static const struct argument GetStatusInfoArgs[] = { {2, 2}, {2, 4}, {2, 3}, {0, 0} }; static const struct argument GetNATRSIPStatusArgs[] = { {2, 5}, {2, 6}, {0, 0} }; static const struct argument GetGenericPortMappingEntryArgs[] = { {1, 8}, {2, 11}, {2, 12}, {2, 14}, {2, 13}, {2, 15}, {2, 9}, {2, 16}, {2, 10}, {0, 0} }; static const struct argument GetSpecificPortMappingEntryArgs[] = { {1, 11}, {1, 12}, {1, 14}, {2, 13}, {2, 15}, {2, 9}, {2, 16}, {2, 10}, {0, 0} }; static const struct action WANIPCnActions[] = { {"SetConnectionType", SetConnectionTypeArgs}, /* R */ {"GetConnectionTypeInfo", GetConnectionTypeInfoArgs}, /* R */ {"RequestConnection", 0}, /* R */ /*{"RequestTermination", 0},*/ /* O */ {"ForceTermination", 0}, /* R */ /*{"SetAutoDisconnectTime", 0},*/ /* O */ /*{"SetIdleDisconnectTime", 0},*/ /* O */ /*{"SetWarnDisconnectDelay", 0}, */ /* O */ {"GetStatusInfo", GetStatusInfoArgs}, /* R */ /*GetAutoDisconnectTime*/ /* O */ /*GetIdleDisconnectTime*/ /* O */ /*GetWarnDisconnectDelay*/ /* O */ {"GetNATRSIPStatus", GetNATRSIPStatusArgs}, /* R */ {"GetGenericPortMappingEntry", GetGenericPortMappingEntryArgs}, /* R */ {"GetSpecificPortMappingEntry", GetSpecificPortMappingEntryArgs}, /* R */ {"AddPortMapping", AddPortMappingArgs}, /* R */ {"DeletePortMapping", DeletePortMappingArgs}, /* R */ {"GetExternalIPAddress", GetExternalIPAddressArgs}, /* R */ #ifdef IGD_V2 {"DeletePortMappingRange", DeletePortMappingRangeArgs}, /* R, IGD v2 */ {"GetListOfPortMappings", GetListOfPortMappingsArgs}, /* R, IGD v2 */ {"AddAnyPortMapping", AddAnyPortMappingArgs}, /* R, IGD v2 */ #endif #if 0 {"AddPortMapping", AddPortMappingArgs}, /* R */ {"GetExternalIPAddress", GetExternalIPAddressArgs}, /* R */ {"DeletePortMapping", DeletePortMappingArgs}, /* R */ {"SetConnectionType", SetConnectionTypeArgs}, /* R */ {"GetConnectionTypeInfo", GetConnectionTypeInfoArgs}, /* R */ {"RequestConnection", 0}, /* R */ {"ForceTermination", 0}, /* R */ {"GetStatusInfo", GetStatusInfoArgs}, /* R */ {"GetNATRSIPStatus", GetNATRSIPStatusArgs}, /* R */ {"GetGenericPortMappingEntry", GetGenericPortMappingEntryArgs}, /* R */ {"GetSpecificPortMappingEntry", GetSpecificPortMappingEntryArgs}, /* R */ /* added in v2 UPnP-gw-WANIPConnection-v2-Service.pdf */ #ifdef IGD_V2 {"AddAnyPortMapping", AddAnyPortMappingArgs}, {"DeletePortMappingRange", DeletePortMappingRangeArgs}, {"GetListOfPortMappings", GetListOfPortMappingsArgs}, #endif #endif {0, 0} }; /* R=Required, O=Optional */ /* ignore "warning: missing initializer" */ #pragma GCC diagnostic ignored "-Wmissing-field-initializers" /* struct stateVar : * {name, itype(&event), idefault, iallowedlist, ieventvalue} */ static const struct stateVar WANIPCnVars[] = { /* 0 */ #if 0 {"ConnectionType", 0, 0/*1*/}, /* required */ {"PossibleConnectionTypes", 0|0x80, 0, 14, 15}, #elif 0 {"ConnectionType", 0, 1, 14, 15}, /* required */ {"PossibleConnectionTypes", 0|0x80, 0, 0, 15}, #else {"ConnectionType", 0, 1, 0, 15}, /* required */ {"PossibleConnectionTypes", 0|0x80, 0, 14, 15}, #endif /* Required * Allowed values : Unconfigured / IP_Routed / IP_Bridged */ {"ConnectionStatus", 0|0x80, 3, 18, CONNECTIONSTATUS_MAGICALVALUE }, /* required */ /* Allowed Values : Unconfigured / Connecting(opt) / Connected * PendingDisconnect(opt) / Disconnecting (opt) * Disconnected */ {"Uptime", 3, 0}, /* Required */ {"LastConnectionError", 0, 6, 25}, /* required : */ /* Allowed Values : ERROR_NONE(req) / ERROR_COMMAND_ABORTED(opt) * ERROR_NOT_ENABLED_FOR_INTERNET(opt) * ERROR_USER_DISCONNECT(opt) * ERROR_ISP_DISCONNECT(opt) * ERROR_IDLE_DISCONNECT(opt) * ERROR_FORCED_DISCONNECT(opt) * ERROR_NO_CARRIER(opt) * ERROR_IP_CONFIGURATION(opt) * ERROR_UNKNOWN(opt) */ {"RSIPAvailable", 1, 4}, /* required */ {"NATEnabled", 1, 5}, /* required */ {"ExternalIPAddress", 0|0x80, 0, 0, EXTERNALIPADDRESS_MAGICALVALUE}, /* required. Default : empty string */ {"PortMappingNumberOfEntries", 2|0x80, 0, 0, PORTMAPPINGNUMBEROFENTRIES_MAGICALVALUE}, /* required >= 0 */ {"PortMappingEnabled", 1, 0}, /* Required */ /* 10 */ {"PortMappingLeaseDuration", 3, 2, 1}, /* required */ {"RemoteHost", 0, 0}, /* required. Default : empty string */ {"ExternalPort", 2, 0}, /* required */ {"InternalPort", 2, 0, 3}, /* required */ {"PortMappingProtocol", 0, 0, 11}, /* required allowedValues: TCP/UDP */ {"InternalClient", 0, 0}, /* required */ {"PortMappingDescription", 0, 0}, /* required default: empty string */ /* added in v2 UPnP-gw-WANIPConnection-v2-Service.pdf */ #ifdef IGD_V2 {"SystemUpdateID", 3|0x80, 0, 0, SYSTEMUPDATEID_MAGICALVALUE}, {"A_ARG_TYPE_Manage", 1, 0}, {"A_ARG_TYPE_PortListing", 0, 0}, #endif {0, 0} }; static const struct serviceDesc scpdWANIPCn = { WANIPCnActions, WANIPCnVars }; /* WANCfg.xml */ /* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ static const struct argument GetCommonLinkPropertiesArgs[] = { {2, 0}, {2, 1}, {2, 2}, {2, 3}, {0, 0} }; static const struct argument GetTotalBytesSentArgs[] = { {2, 4}, {0, 0} }; static const struct argument GetTotalBytesReceivedArgs[] = { {2, 5}, {0, 0} }; static const struct argument GetTotalPacketsSentArgs[] = { {2, 6}, {0, 0} }; static const struct argument GetTotalPacketsReceivedArgs[] = { {2, 7}, {0, 0} }; static const struct action WANCfgActions[] = { {"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */ {"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */ {"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */ {"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */ {"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */ {0, 0} }; /* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */ static const struct stateVar WANCfgVars[] = { {"WANAccessType", 0, 0, 1}, /* Allowed Values : DSL / POTS / Cable / Ethernet * Default value : empty string */ {"Layer1UpstreamMaxBitRate", 3, 0}, {"Layer1DownstreamMaxBitRate", 3, 0}, {"PhysicalLinkStatus", 0|0x80, 0, 6, 6}, /* allowed values : * Up / Down / Initializing (optional) / Unavailable (optionnal) * no Default value * Evented */ {"TotalBytesSent", 3, 0}, /* Optional */ {"TotalBytesReceived", 3, 0}, /* Optional */ {"TotalPacketsSent", 3, 0}, /* Optional */ {"TotalPacketsReceived", 3, 0},/* Optional */ /*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */ /*{"WANAccessProvider", 0, 0},*/ /* Optional */ {0, 0} }; static const struct serviceDesc scpdWANCfg = { WANCfgActions, WANCfgVars }; #ifdef ENABLE_L3F_SERVICE /* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */ static const struct argument SetDefaultConnectionServiceArgs[] = { {1, 0}, /* in */ {0, 0} }; static const struct argument GetDefaultConnectionServiceArgs[] = { {2, 0}, /* out */ {0, 0} }; static const struct action L3FActions[] = { {"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */ {"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */ {0, 0} }; static const struct stateVar L3FVars[] = { {"DefaultConnectionService", 0|0x80, 0, 0, DEFAULTCONNECTIONSERVICE_MAGICALVALUE}, /* Required */ {0, 0} }; static const struct serviceDesc scpdL3F = { L3FActions, L3FVars }; #endif #ifdef ENABLE_6FC_SERVICE /* see UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf */ static const struct argument GetFirewallStatusArgs[] = { {2|0x80, 0}, /* OUT : FirewallEnabled */ {2|0x80, 6}, /* OUT : InboundPinholeAllowed */ {0, 0} }; static const struct argument GetOutboundPinholeTimeoutArgs[] = { {1|0x80|(3<<2), 1}, /* RemoteHost IN A_ARG_TYPE_IPv6Address */ {1|0x80|(4<<2), 2}, /* RemotePort IN A_ARG_TYPE_Port */ {1|0x80|(5<<2), 1}, /* InternalClient IN A_ARG_TYPE_IPv6Address */ {1|0x80|(6<<2), 2}, /* InternalPort IN A_ARG_TYPE_Port */ {1|0x80, 3}, /* Protocol IN A_ARG_TYPE_Protocol */ {2|0x80, 7}, /* OutboundPinholeTimeout OUT A_ARG_TYPE_OutboundPinholeTimeout */ {0, 0} }; static const struct argument AddPinholeArgs[] = { {1|0x80|(3<<2), 1}, /* RemoteHost IN A_ARG_TYPE_IPv6Address */ {1|0x80|(4<<2), 2}, /* RemotePort IN A_ARG_TYPE_Port */ {1|0x80|(5<<2), 1}, /* InternalClient IN A_ARG_TYPE_IPv6Address */ {1|0x80|(6<<2), 2}, /* InternalPort IN A_ARG_TYPE_Port */ {1|0x80, 3}, /* Protocol IN A_ARG_TYPE_Protocol */ {1|0x80, 5}, /* LeaseTime IN A_ARG_TYPE_LeaseTime */ {2|0x80, 4}, /* UniqueID OUT A_ARG_TYPE_UniqueID */ {0, 0} }; static const struct argument UpdatePinholeArgs[] = { {1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */ {1, 5}, /* LeaseTime IN A_ARG_TYPE_LeaseTime */ {0, 0} }; static const struct argument DeletePinholeArgs[] = { {1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */ {0, 0} }; static const struct argument GetPinholePacketsArgs[] = { {1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */ {2|0x80, 9}, /* PinholePackets OUT A_ARG_TYPE_PinholePackets */ {0, 0} }; static const struct argument CheckPinholeWorkingArgs[] = { {1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */ {2|0x80|(7<<2), 8}, /* IsWorking OUT A_ARG_TYPE_Boolean */ {0, 0} }; static const struct action IPv6FCActions[] = { {"GetFirewallStatus", GetFirewallStatusArgs}, /* Req */ {"GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs}, /* Opt */ {"AddPinhole", AddPinholeArgs}, /* Req */ {"UpdatePinhole", UpdatePinholeArgs}, /* Req */ {"DeletePinhole", DeletePinholeArgs}, /* Req */ {"GetPinholePackets", GetPinholePacketsArgs}, /* Req */ {"CheckPinholeWorking", CheckPinholeWorkingArgs}, /* Opt */ {0, 0} }; static const struct stateVar IPv6FCVars[] = { {"FirewallEnabled", 1|0x80, 0, 0, FIREWALLENABLED_MAGICALVALUE}, /* Required */ {"A_ARG_TYPE_IPv6Address", 0, 0, 0, 0}, /* Required */ {"A_ARG_TYPE_Port", 2, 0, 0, 0}, /* Required */ {"A_ARG_TYPE_Protocol", 2, 0, 0, 0}, /* Required */ /* 4 */ {"A_ARG_TYPE_UniqueID", 2, 0, 0, 0}, /* Required */ {"A_ARG_TYPE_LeaseTime", 3, 0, 5, 0}, /* Required */ {"InboundPinholeAllowed", 1|0x80, 0, 0, INBOUNDPINHOLEALLOWED_MAGICALVALUE}, /* Required */ {"A_ARG_TYPE_OutboundPinholeTimeout", 3, 0, 7, 0}, /* Optional */ /* 8 */ {"A_ARG_TYPE_Boolean", 1, 0, 0, 0}, /* Optional */ {"A_ARG_TYPE_PinholePackets", 3, 0, 0, 0}, /* Required */ {0, 0} }; static const struct serviceDesc scpd6FC = { IPv6FCActions, IPv6FCVars }; #endif #ifdef ENABLE_DP_SERVICE /* UPnP-gw-DeviceProtection-v1-Service.pdf */ static const struct argument SendSetupMessageArgs[] = { {1|0x80|(8<<2), 6}, /* ProtocolType : in ProtocolType / A_ARG_TYPE_String */ {1|0x80|(9<<2), 5}, /* InMessage : in InMessage / A_ARG_TYPE_Base64 */ {2|0x80|(10<<2), 5}, /* OutMessage : out OutMessage / A_ARG_TYPE_Base64 */ {0, 0} }; static const struct argument GetSupportedProtocolsArgs[] = { {2|0x80|(11<<2), 1}, /* ProtocolList : out ProtocolList / SupportedProtocols */ {0, 0} }; static const struct argument GetAssignedRolesArgs[] = { {2|0x80|(12<<2), 6}, /* RoleList : out RoleList / A_ARG_TYPE_String */ {0, 0} }; static const struct action DPActions[] = { {"SendSetupMessage", SendSetupMessageArgs}, {"GetSupportedProtocols", GetSupportedProtocolsArgs}, {"GetAssignedRoles", GetAssignedRolesArgs}, {0, 0} }; static const struct stateVar DPVars[] = { {"SetupReady", 1|0x80, 0, 0, SETUPREADY_MAGICALVALUE}, {"SupportedProtocols", 0}, {"A_ARG_TYPE_ACL", 0}, {"A_ARG_TYPE_IdentityList", 0}, {"A_ARG_TYPE_Identity", 0}, {"A_ARG_TYPE_Base64", 4}, {"A_ARG_TYPE_String", 0}, {0, 0} }; static const struct serviceDesc scpdDP = { DPActions, DPVars }; #endif /* strcat_str() * concatenate the string and use realloc to increase the * memory buffer if needed. */ static char * strcat_str(char * str, int * len, int * tmplen, const char * s2) { int s2len; int newlen; char * p; s2len = (int)strlen(s2); if(*tmplen <= (*len + s2len)) { if(s2len < 256) newlen = *tmplen + 256; else newlen = *tmplen + s2len + 1; p = (char *)realloc(str, newlen); if(p == NULL) /* handle a failure of realloc() */ { syslog(LOG_ERR, "strcat_str: Failed to realloc %d bytes", newlen); return str; } str = p; *tmplen = newlen; } /*strcpy(str + *len, s2); */ memcpy(str + *len, s2, s2len + 1); *len += s2len; return str; } /* strcat_char() : * concatenate a character and use realloc to increase the * size of the memory buffer if needed */ static char * strcat_char(char * str, int * len, int * tmplen, char c) { char * p; if(*tmplen <= (*len + 1)) { *tmplen += 256; p = (char *)realloc(str, *tmplen); if(p == NULL) /* handle a failure of realloc() */ { syslog(LOG_ERR, "strcat_char: Failed to realloc %d bytes", *tmplen); *tmplen -= 256; return str; } str = p; } str[*len] = c; (*len)++; return str; } /* strcat_int() * concatenate the string representation of the integer. * call strcat_char() */ static char * strcat_int(char * str, int * len, int * tmplen, int i) { char buf[16]; int j; if(i < 0) { str = strcat_char(str, len, tmplen, '-'); i = -i; } else if(i == 0) { /* special case for 0 */ str = strcat_char(str, len, tmplen, '0'); return str; } j = 0; while(i && j < (int)sizeof(buf)) { buf[j++] = '0' + (i % 10); i = i / 10; } while(j > 0) { str = strcat_char(str, len, tmplen, buf[--j]); } return str; } /* iterative subroutine using a small stack * This way, the progam stack usage is kept low */ static char * genXML(char * str, int * len, int * tmplen, const struct XMLElt * p, int force_igd1) { #define GENXML_STACK_SIZE 16 unsigned short i, j; int top; const char * eltname, *s; char c; struct { unsigned short i; unsigned short j; const char * eltname; } pile[GENXML_STACK_SIZE]; /* stack */ #if !defined(IGD_V2) UNUSED(force_igd1); #endif top = -1; i = 0; /* current node */ j = 1; /* i + number of nodes*/ for(;;) { eltname = p[i].eltname; if(!eltname) return str; if(eltname[0] == '/') { /* leaf node */ if(p[i].data && p[i].data[0]) { /*printf("<%s>%s<%s>\n", eltname+1, p[i].data, eltname); */ str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname+1); str = strcat_char(str, len, tmplen, '>'); #ifdef RANDOMIZE_URLS if(p[i].data[0] == '/') { /* prepend all URL paths with a "random" value */ str = strcat_char(str, len, tmplen, '/'); str = strcat_str(str, len, tmplen, random_url); } #endif /* RANDOMIZE_URLS */ str = strcat_str(str, len, tmplen, p[i].data); #ifdef IGD_V2 /* checking a single 'u' saves us 4 strcmp() calls most of the time */ if (force_igd1 && (p[i].data[0] == 'u')) { if ((strcmp(p[i].data, DEVICE_TYPE_IGD) == 0) || (strcmp(p[i].data, DEVICE_TYPE_WAN) == 0) || (strcmp(p[i].data, DEVICE_TYPE_WANC) == 0) || (strcmp(p[i].data, SERVICE_TYPE_WANIPC) == 0) ) { str[*len - 1] = '1'; /* Change the version number to 1 */ } } #endif str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname); str = strcat_char(str, len, tmplen, '>'); } #ifdef IGD_V2 unstack: #endif for(;;) { if(top < 0) return str; i = ++(pile[top].i); j = pile[top].j; /*printf(" pile[%d]\t%d %d\n", top, i, j); */ if(i==j) { /*printf("\n", pile[top].eltname); */ str = strcat_char(str, len, tmplen, '<'); str = strcat_char(str, len, tmplen, '/'); s = pile[top].eltname; for(c = *s; c > ' '; c = *(++s)) str = strcat_char(str, len, tmplen, c); str = strcat_char(str, len, tmplen, '>'); top--; } else break; } } else { unsigned long k = (unsigned long)p[i].data; #ifdef IGD_V2 if((force_igd1 && (p[k & 0xffff].eltname[0] == '/')) && (strcmp(p[k & 0xffff].data, "urn:schemas-upnp-org:service:DeviceProtection:1") == 0 || strcmp(p[k & 0xffff].data, "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1") == 0)) { /* Skip the child element */ goto unstack; } #endif /* node with child(ren) */ /*printf("<%s>\n", eltname); */ str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname); if(memcmp(eltname, "root ", 5) == 0) { char configid_str[16]; /* add configId attribute, required by UDA 1.1 */ snprintf(configid_str, sizeof(configid_str), "\"%u\"", upnp_configid); str = strcat_str(str, len, tmplen, " configId="); str = strcat_str(str, len, tmplen, configid_str); } str = strcat_char(str, len, tmplen, '>'); i = k & 0xffff; j = i + (k >> 16); if(top < (GENXML_STACK_SIZE - 1)) { top++; /*printf(" +pile[%d]\t%d %d\n", top, i, j); */ pile[top].i = i; pile[top].j = j; pile[top].eltname = eltname; #ifdef DEBUG } else { fprintf(stderr, "*** GenXML(): stack OVERFLOW ***\n"); #endif /* DEBUG */ } } } } /* genRootDesc() : * - Generate the root description of the UPnP device. * - the len argument is used to return the length of * the returned string. * - tmp_uuid argument is used to build the uuid string */ char * genRootDesc(int * len, int force_igd1) { char * str; int tmplen; tmplen = 2048; str = (char *)malloc(tmplen); if(str == NULL) return NULL; * len = strlen(xmlver); /*strcpy(str, xmlver); */ memcpy(str, xmlver, *len + 1); str = genXML(str, len, &tmplen, rootDesc, force_igd1); str[*len] = '\0'; return str; } /* genServiceDesc() : * Generate service description with allowed methods and * related variables. */ static char * genServiceDesc(int * len, const struct serviceDesc * s, int force_igd1) { int i, j; const struct action * acts; const struct stateVar * vars; const struct argument * args; char * str; int tmplen; #if !defined(IGD_V2) UNUSED(force_igd1); #endif tmplen = 2048; str = (char *)malloc(tmplen); if(str == NULL) return NULL; /*strcpy(str, xmlver); */ *len = strlen(xmlver); memcpy(str, xmlver, *len + 1); acts = s->actionList; vars = s->serviceStateTable; str = strcat_char(str, len, &tmplen, '<'); str = strcat_str(str, len, &tmplen, root_service); str = strcat_char(str, len, &tmplen, '>'); str = strcat_str(str, len, &tmplen, "" UPNP_VERSION_MAJOR_STR "" "" UPNP_VERSION_MINOR_STR ""); i = 0; str = strcat_str(str, len, &tmplen, ""); while(acts[i].name) { #if defined(IGD_V2) /* fake a IGD v1 for Microsoft clients : * no DeletePortMappingRange, GetListOfPortMappings, AddAnyPortMapping */ if (force_igd1 && strcmp(acts[i].name, "DeletePortMappingRange") == 0) break; #endif str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, acts[i].name); str = strcat_str(str, len, &tmplen, ""); /* argument List */ args = acts[i].args; if(args) { str = strcat_str(str, len, &tmplen, ""); j = 0; while(args[j].dir) { const char * p; size_t plen; str = strcat_str(str, len, &tmplen, ""); if((args[j].dir & 0x80) == 0) { str = strcat_str(str, len, &tmplen, "New"); } p = vars[args[j].relatedVar].name; plen = strlen(p); if(args[j].dir & 0x7c) { /* use magic values ... */ str = strcat_str(str, len, &tmplen, magicargname[(args[j].dir & 0x7c) >> 2]); } else if(plen >= 11 && 0 == memcmp(p, "PortMapping", 11) && (plen < 22 || 0 != memcmp(p + 11, "Description", 11))) { if(plen >= (11+15) && 0 == memcmp(p + 11, "NumberOfEntries", 15)) { /* PortMappingNumberOfEntries */ #ifdef IGD_V2 if(0 == memcmp(acts[i].name, "GetListOfPortMappings", 22)) { str = strcat_str(str, len, &tmplen, "NumberOfPorts"); } else { str = strcat_str(str, len, &tmplen, "PortMappingIndex"); } #else str = strcat_str(str, len, &tmplen, "PortMappingIndex"); #endif } else { /* PortMappingEnabled * PortMappingLeaseDuration * PortMappingProtocol */ str = strcat_str(str, len, &tmplen, p + 11); } #ifdef IGD_V2 } else if(plen >= 11 && 0 == memcmp(p, "A_ARG_TYPE_", 11)) { str = strcat_str(str, len, &tmplen, p + 11); } else if(plen == 12 && 0 == memcmp(p, "ExternalPort", 12) && args[j].dir == 2 && 0 == memcmp(acts[i].name, "AddAnyPortMapping", 18)) { str = strcat_str(str, len, &tmplen, "ReservedPort"); #endif } else { str = strcat_str(str, len, &tmplen, p); } str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, (args[j].dir&0x03)==1?"in":"out"); str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, p); str = strcat_str(str, len, &tmplen, ""); j++; } str = strcat_str(str, len, &tmplen,""); } str = strcat_str(str, len, &tmplen, ""); /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ i++; } str = strcat_str(str, len, &tmplen, ""); i = 0; while(vars[i].name) { str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, vars[i].name); str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]); str = strcat_str(str, len, &tmplen, ""); /*if(vars[i].defaultValue) */ if(vars[i].idefault) { str = strcat_str(str, len, &tmplen, ""); /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */ str = strcat_str(str, len, &tmplen, upnpdefaultvalues[vars[i].idefault]); str = strcat_str(str, len, &tmplen, ""); } if(vars[i].iallowedlist) { if((vars[i].itype & 0x0f) == 0) { /* string */ str = strcat_str(str, len, &tmplen, ""); for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++) { str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]); str = strcat_str(str, len, &tmplen, ""); } str = strcat_str(str, len, &tmplen, ""); } else { /* ui2 and ui4 */ str = strcat_str(str, len, &tmplen, ""); str = strcat_int(str, len, &tmplen, upnpallowedranges[vars[i].iallowedlist]); str = strcat_str(str, len, &tmplen, ""); str = strcat_int(str, len, &tmplen, upnpallowedranges[vars[i].iallowedlist+1]); str = strcat_str(str, len, &tmplen, ""); } } str = strcat_str(str, len, &tmplen, ""); /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ i++; } str = strcat_str(str, len, &tmplen, ""); str[*len] = '\0'; return str; } /* genWANIPCn() : * Generate the WANIPConnection xml description */ char * genWANIPCn(int * len, int force_igd1) { return genServiceDesc(len, &scpdWANIPCn, force_igd1); } /* genWANCfg() : * Generate the WANInterfaceConfig xml description. */ char * genWANCfg(int * len, int force_igd1) { return genServiceDesc(len, &scpdWANCfg, force_igd1); } #ifdef ENABLE_L3F_SERVICE char * genL3F(int * len, int force_igd1) { return genServiceDesc(len, &scpdL3F, force_igd1); } #endif #ifdef ENABLE_6FC_SERVICE char * gen6FC(int * len, int force_igd1) { return genServiceDesc(len, &scpd6FC, force_igd1); } #endif #ifdef ENABLE_DP_SERVICE char * genDP(int * len, int force_igd1) { return genServiceDesc(len, &scpdDP, force_igd1); } #endif #ifdef ENABLE_EVENTS static char * genEventVars(int * len, const struct serviceDesc * s) { char tmp[16]; const struct stateVar * v; char * str; int tmplen; tmplen = 512; str = (char *)malloc(tmplen); if(str == NULL) return NULL; *len = 0; v = s->serviceStateTable; str = strcat_str(str, len, &tmplen, ""); while(v->name) { if(v->itype & 0x80) { str = strcat_str(str, len, &tmplen, "<"); str = strcat_str(str, len, &tmplen, v->name); str = strcat_str(str, len, &tmplen, ">"); /*printf("<%s>", v->name);*/ switch(v->ieventvalue) { case 0: break; #ifdef ENABLE_DP_SERVICE case SETUPREADY_MAGICALVALUE: /* always ready for setup */ snprintf(tmp, sizeof(tmp), "%d", 1); str = strcat_str(str, len, &tmplen, tmp); break; #endif case CONNECTIONSTATUS_MAGICALVALUE: /* or get_wan_connection_status_str(ext_if_name) */ str = strcat_str(str, len, &tmplen, upnpallowedvalues[18 + get_wan_connection_status(ext_if_name)]); break; #ifdef ENABLE_6FC_SERVICE case FIREWALLENABLED_MAGICALVALUE: /* see 2.4.2 of UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf */ snprintf(tmp, sizeof(tmp), "%d", GETFLAG(IPV6FCFWDISABLEDMASK) ? 0 : 1); str = strcat_str(str, len, &tmplen, tmp); break; case INBOUNDPINHOLEALLOWED_MAGICALVALUE: /* see 2.4.3 of UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf */ snprintf(tmp, sizeof(tmp), "%d", GETFLAG(IPV6FCINBOUNDDISALLOWEDMASK) ? 0 : 1); str = strcat_str(str, len, &tmplen, tmp); break; #endif #ifdef IGD_V2 case SYSTEMUPDATEID_MAGICALVALUE: /* Please read section 2.3.23 SystemUpdateID * of UPnP-gw-WANIPConnection-v2-Service.pdf */ snprintf(tmp, sizeof(tmp), "%d", 1/* system update id */); str = strcat_str(str, len, &tmplen, tmp); break; #endif case PORTMAPPINGNUMBEROFENTRIES_MAGICALVALUE: /* Port mapping number of entries magical value */ snprintf(tmp, sizeof(tmp), "%d", upnp_get_portmapping_number_of_entries()); str = strcat_str(str, len, &tmplen, tmp); break; case EXTERNALIPADDRESS_MAGICALVALUE: /* External ip address magical value */ 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, &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); } } break; case DEFAULTCONNECTIONSERVICE_MAGICALVALUE: /* DefaultConnectionService magical value */ str = strcat_str(str, len, &tmplen, uuidvalue_wcd); #ifdef IGD_V2 str = strcat_str(str, len, &tmplen, ":WANConnectionDevice:2,urn:upnp-org:serviceId:WANIPConn1"); #else str = strcat_str(str, len, &tmplen, ":WANConnectionDevice:1,urn:upnp-org:serviceId:WANIPConn1"); #endif break; default: str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]); } str = strcat_str(str, len, &tmplen, "name); str = strcat_str(str, len, &tmplen, ">"); /*printf("\n", v->name);*/ } v++; } str = strcat_str(str, len, &tmplen, ""); #if 0 printf("\n"); printf("\n"); printf("%d\n", tmplen); #endif str[*len] = '\0'; return str; } char * getVarsWANIPCn(int * l) { return genEventVars(l, &scpdWANIPCn); } char * getVarsWANCfg(int * l) { return genEventVars(l, &scpdWANCfg); } #ifdef ENABLE_L3F_SERVICE char * getVarsL3F(int * l) { return genEventVars(l, &scpdL3F); } #endif #ifdef ENABLE_6FC_SERVICE char * getVars6FC(int * l) { return genEventVars(l, &scpd6FC); } #endif #ifdef ENABLE_DP_SERVICE char * getVarsDP(int * l) { return genEventVars(l, &scpdDP); } #endif #endif /* ENABLE_EVENTS */ miniupnpd-2.3.7/upnpdescgen.h010064400017500000024000000046731411013505300153770ustar00nanardstaff/* $Id: upnpdescgen.h,v 1.25 2021/08/21 08:16:17 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPDESCGEN_H_INCLUDED #define UPNPDESCGEN_H_INCLUDED #include "config.h" /* for the root description * The child list reference is stored in "data" member using the * INITHELPER macro with index/nchild always in the * same order, whatever the endianness */ struct XMLElt { const char * eltname; /* begin with '/' if no child */ const char * data; /* Value */ }; /* for service description */ struct serviceDesc { const struct action * actionList; const struct stateVar * serviceStateTable; }; struct action { const char * name; const struct argument * args; }; struct argument { /* the name of the arg is obtained from the variable */ unsigned char dir; /* MSB : don't append "New" Flag, * 5 Medium bits : magic argument name index * 2 LSB : 1 = in, 2 = out */ unsigned char relatedVar; /* index of the related variable */ }; struct stateVar { const char * name; unsigned char itype; /* MSB: sendEvent flag, 7 LSB: index in upnptypes */ unsigned char idefault; /* default value */ unsigned char iallowedlist; /* index in allowed values list * or in allowed range list */ unsigned char ieventvalue; /* fixed value returned or magical values */ }; /* little endian * The code has now be tested on big endian architecture */ #define INITHELPER(i, n) ((char *)(((n)<<16)|(i))) /* char * genRootDesc(int *); * returns: NULL on error, string allocated on the heap */ char * genRootDesc(int * len, int force_igd1); /* for the two following functions */ char * genWANIPCn(int * len, int force_igd1); char * genWANCfg(int * len, int force_igd1); #ifdef ENABLE_L3F_SERVICE char * genL3F(int * len, int force_igd1); #endif #ifdef ENABLE_6FC_SERVICE char * gen6FC(int * len, int force_igd1); #endif #ifdef ENABLE_DP_SERVICE char * genDP(int * len, int force_igd1); #endif #ifdef ENABLE_EVENTS char * getVarsWANIPCn(int * len); char * getVarsWANCfg(int * len); #ifdef ENABLE_L3F_SERVICE char * getVarsL3F(int * len); #endif #ifdef ENABLE_6FC_SERVICE char * getVars6FC(int * len); #endif #ifdef ENABLE_DP_SERVICE char * getVarsDP(int * len); #endif #endif /* ENABLE_EVENTS */ #endif miniupnpd-2.3.7/miniupnpdpath.h010064400017500000024000000023511203340734000157360ustar00nanardstaff/* $Id: miniupnpdpath.h,v 1.9 2012/09/27 15:47:15 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2011 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef MINIUPNPDPATH_H_INCLUDED #define MINIUPNPDPATH_H_INCLUDED #include "config.h" /* Paths and other URLs in the miniupnpd http server */ #define ROOTDESC_PATH "/rootDesc.xml" #ifdef HAS_DUMMY_SERVICE #define DUMMY_PATH "/dummy.xml" #endif #define WANCFG_PATH "/WANCfg.xml" #define WANCFG_CONTROLURL "/ctl/CmnIfCfg" #define WANCFG_EVENTURL "/evt/CmnIfCfg" #define WANIPC_PATH "/WANIPCn.xml" #define WANIPC_CONTROLURL "/ctl/IPConn" #define WANIPC_EVENTURL "/evt/IPConn" #ifdef ENABLE_L3F_SERVICE #define L3F_PATH "/L3F.xml" #define L3F_CONTROLURL "/ctl/L3F" #define L3F_EVENTURL "/evt/L3F" #endif #ifdef ENABLE_6FC_SERVICE #define WANIP6FC_PATH "/WANIP6FC.xml" #define WANIP6FC_CONTROLURL "/ctl/IP6FCtl" #define WANIP6FC_EVENTURL "/evt/IP6FCtl" #endif #ifdef ENABLE_DP_SERVICE /* For DeviceProtection introduced in IGD v2 */ #define DP_PATH "/DP.xml" #define DP_CONTROLURL "/ctl/DP" #define DP_EVENTURL "/evt/DP" #endif #endif miniupnpd-2.3.7/testupnpdescgen.c010064400017500000024000000136021411013505300162620ustar00nanardstaff/* $Id: testupnpdescgen.c,v 1.38 2021/08/21 08:16:16 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include /* for mkdir */ #include #include #include #include "macros.h" #include "config.h" #include "upnpglobalvars.h" #include "upnpdescgen.h" #include "upnpdescstrings.h" #include "getifaddr.h" char uuidvalue_igd[] = "uuid:12345678-0000-0000-0000-000000abcd01"; char uuidvalue_wan[] = "uuid:12345678-0000-0000-0000-000000abcd02"; char uuidvalue_wcd[] = "uuid:12345678-0000-0000-0000-000000abcd03"; char serialnumber[] = "12345678"; char modelnumber[] = "1"; char presentationurl[] = "http://192.168.0.1:8080/"; /*char presentationurl[] = "";*/ #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION char friendly_name[] = OS_NAME " router"; char manufacturer_name[] = ROOTDEV_MANUFACTURER; char manufacturer_url[] = ROOTDEV_MANUFACTURERURL; char model_name[] = ROOTDEV_MODELNAME; char model_description[] = ROOTDEV_MODELDESCRIPTION; char model_url[] = ROOTDEV_MODELURL; #endif /* ENABLE_MANUFACTURER_INFO_CONFIGURATION */ #ifdef RANDOMIZE_URLS char random_url[RANDOM_URL_MAX_LEN] = "RANDOM"; #endif /* RANDOMIZE_URLS */ unsigned int upnp_configid = 666; const char * use_ext_ip_addr = NULL; const char * ext_if_name = "eth0"; int runtime_flags = 0; int getifaddr(const char * ifname, char * buf, int len, struct in_addr * addr, struct in_addr * mask) { UNUSED(ifname); UNUSED(addr); UNUSED(mask); strncpy(buf, "1.2.3.4", len); return 0; } int addr_is_reserved(struct in_addr * addr) { UNUSED(addr); return 0; } int upnp_get_portmapping_number_of_entries(void) { return 42; } int get_wan_connection_status(const char * ifname) { UNUSED(ifname); return 2; } /* To be improved */ int xml_pretty_print(const char * s, int len, FILE * f) { int n = 0, i; int elt_close = 0; int c, indent = 0; if(!s) return n; while(len > 0) { c = *(s++); len--; switch(c) { case '<': if(len>0 && *s == '/') elt_close++; else if(len>0 && *s == '?') elt_close = 1; else elt_close = 0; if(elt_close!=1) { if(elt_close > 1) indent--; fputc('\n', f); n++; for(i=indent; i>0; i--) fputc(' ', f); n += indent; } fputc(c, f); n++; break; case '>': fputc(c, f); n++; if(elt_close==1) { /*fputc('\n', f); n++; */ /* elt_close = 0; */ if(indent > 0) indent--; } else if(elt_close == 0) indent++; break; case '\n': /* remove existing LF */ break; default: fputc(c, f); n++; } } return n; } /* stupid test */ const char * str1 = "Prefix123String"; const char * str2 = "123String"; void stupid_test(void) { printf("str1:'%s' str2:'%s'\n", str1, str2); printf("str1:%p str2:%p str2-str1:%ld\n", str1, str2, (long)(str2-str1)); } /* main */ int main(int argc, char * * argv) { int force_igd1 = 0; char * rootDesc; int rootDescLen; char * s; int l; FILE * f; for(l = 1; l < argc; l++) { if(0 == strcmp(argv[l], "--help") || 0 == strcmp(argv[l], "-h")) { printf("Usage:\t%s [options]\n", argv[0]); printf("options:\n"); #ifdef IGD_V2 printf("\t--forceigdv1 Force versions of devices to be 1\n"); #else printf("\tNone\n"); #endif return 0; #ifdef IGD_V2 } else if(0 == strcmp(argv[l], "--forceigdv1")) { force_igd1 = 1; #endif } else { fprintf(stderr, "unknown option %s\n", argv[l]); } } if(mkdir("testdescs", 0777) < 0) { if(errno != EEXIST) { perror("mkdir"); } } printf("Root Description :\n"); rootDesc = genRootDesc(&rootDescLen, force_igd1); xml_pretty_print(rootDesc, rootDescLen, stdout); f = fopen("testdescs/rootdesc.xml", "w"); if(f) { xml_pretty_print(rootDesc, rootDescLen, f); fclose(f); } free(rootDesc); printf("\n-------------\n"); printf("WANIPConnection Description :\n"); s = genWANIPCn(&l, force_igd1); xml_pretty_print(s, l, stdout); f = fopen("testdescs/wanipc_scpd.xml", "w"); if(f) { xml_pretty_print(s, l, f); fclose(f); } free(s); printf("\n-------------\n"); printf("WANConfig Description :\n"); s = genWANCfg(&l, force_igd1); xml_pretty_print(s, l, stdout); f = fopen("testdescs/wanconfig_scpd.xml", "w"); if(f) { xml_pretty_print(s, l, f); fclose(f); } free(s); printf("\n-------------\n"); #ifdef ENABLE_L3F_SERVICE printf("Layer3Forwarding service :\n"); s = genL3F(&l, force_igd1); xml_pretty_print(s, l, stdout); f = fopen("testdescs/l3f_scpd.xml", "w"); if(f) { xml_pretty_print(s, l, f); fclose(f); } free(s); printf("\n-------------\n"); #endif #ifdef ENABLE_6FC_SERVICE printf("WANIPv6FirewallControl service :\n"); s = gen6FC(&l, force_igd1); xml_pretty_print(s, l, stdout); f = fopen("testdescs/wanipv6fc_scpd.xml", "w"); if(f) { xml_pretty_print(s, l, f); fclose(f); } free(s); printf("\n-------------\n"); #endif #ifdef ENABLE_DP_SERVICE printf("DeviceProtection service :\n"); s = genDP(&l, force_igd1); xml_pretty_print(s, l, stdout); f = fopen("testdescs/dp_scpd.xml", "w"); if(f) { xml_pretty_print(s, l, f); fclose(f); } free(s); printf("\n-------------\n"); #endif #ifdef ENABLE_EVENTS s = getVarsWANIPCn(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n-------------\n"); s = getVarsWANCfg(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n-------------\n"); #ifdef ENABLE_L3F_SERVICE s = getVarsL3F(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n-------------\n"); #ifdef ENABLE_6FC_SERVICE s = getVars6FC(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n-------------\n"); #endif #ifdef ENABLE_DP_SERVICE s = getVarsDP(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n-------------\n"); #endif #endif #endif /* stupid_test(); */ return 0; } miniupnpd-2.3.7/upnpsoap.c010064400017500000024000002122211455107657400147370ustar00nanardstaff/* $Id: upnpsoap.c,v 1.167 2024/01/04 02:11:14 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 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 #include #include #include #include "macros.h" #include "config.h" #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpsoap.h" #include "upnpreplyparse.h" #include "upnpredirect.h" #include "upnppermissions.h" #include "upnppinhole.h" #include "getifaddr.h" #include "getifstats.h" #include "getconnstatus.h" #include "upnpurns.h" #include "upnputils.h" /* utility function */ static int is_numeric(const char * s) { while(*s) { if(*s < '0' || *s > '9') return 0; s++; } return 1; } static void BuildSendAndCloseSoapResp(struct upnphttp * h, const char * body, int bodylen) { static const char beforebody[] = "\r\n" "" ""; static const char afterbody[] = "" "\r\n"; int r = BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1 + sizeof(afterbody) - 1 + bodylen ); if(r >= 0) { memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1); h->res_buflen += sizeof(beforebody) - 1; memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1); h->res_buflen += sizeof(afterbody) - 1; } else { BuildResp2_upnphttp(h, 500, "Internal Server Error", NULL, 0); } SendRespAndClose_upnphttp(h); } static void GetConnectionTypeInfo(struct upnphttp * h, const char * action, const char * ns) { #if 0 static const char resp[] = "" "IP_Routed" "IP_Routed" ""; #endif static const char resp[] = "" "IP_Routed" "IP_Routed" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, ns, action); BuildSendAndCloseSoapResp(h, body, bodylen); } /* maximum value for a UPNP ui4 type variable */ #define UPNP_UI4_MAX (4294967295ul) static void GetTotalBytesSent(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%lu" ""; char body[512]; int bodylen; struct ifdata data; r = getifstats(ext_if_name, &data); bodylen = snprintf(body, sizeof(body), resp, action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ #ifdef UPNP_STRICT r<0?0:(data.obytes & UPNP_UI4_MAX), action); #else /* UPNP_STRICT */ r<0?0:data.obytes, action); #endif /* UPNP_STRICT */ BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetTotalBytesReceived(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%lu" ""; char body[512]; int bodylen; struct ifdata data; r = getifstats(ext_if_name, &data); /* TotalBytesReceived * This variable represents the cumulative counter for total number of * bytes received downstream across all connection service instances on * WANDevice. The count rolls over to 0 after it reaching the maximum * value (2^32)-1. */ bodylen = snprintf(body, sizeof(body), resp, action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ #ifdef UPNP_STRICT r<0?0:(data.ibytes & UPNP_UI4_MAX), action); #else /* UPNP_STRICT */ r<0?0:data.ibytes, action); #endif /* UPNP_STRICT */ BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetTotalPacketsSent(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%lu" ""; char body[512]; int bodylen; struct ifdata data; r = getifstats(ext_if_name, &data); bodylen = snprintf(body, sizeof(body), resp, action, ns,/*"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",*/ #ifdef UPNP_STRICT r<0?0:(data.opackets & UPNP_UI4_MAX), action); #else /* UPNP_STRICT */ r<0?0:data.opackets, action); #endif /* UPNP_STRICT */ BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetTotalPacketsReceived(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%lu" ""; char body[512]; int bodylen; struct ifdata data; r = getifstats(ext_if_name, &data); bodylen = snprintf(body, sizeof(body), resp, action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ #ifdef UPNP_STRICT r<0?0:(data.ipackets & UPNP_UI4_MAX), action); #else /* UPNP_STRICT */ r<0?0:data.ipackets, action); #endif /* UPNP_STRICT */ BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetCommonLinkProperties(struct upnphttp * h, const char * action, const char * ns) { /* WANAccessType : set depending on the hardware : * DSL, POTS (plain old Telephone service), Cable, Ethernet */ static const char resp[] = "" "%s" "%lu" "%lu" "%s" ""; char body[2048]; int bodylen; struct ifdata data; const char * status = "Up"; /* Up, Down (Required), * Initializing, Unavailable (Optional) */ const char * wan_access_type = "Cable"; /* DSL, POTS, Cable, Ethernet */ char ext_ip_addr[INET_ADDRSTRLEN]; if((downstream_bitrate == 0) || (upstream_bitrate == 0)) { if(getifstats(ext_if_name, &data) >= 0) { if(downstream_bitrate == 0) downstream_bitrate = data.baudrate; if(upstream_bitrate == 0) upstream_bitrate = data.baudrate; } } if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, NULL, NULL) < 0) { status = "Down"; } bodylen = snprintf(body, sizeof(body), resp, action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ wan_access_type, upstream_bitrate, downstream_bitrate, status, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetStatusInfo(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%s" "ERROR_NONE" "%ld" ""; char body[512]; int bodylen; time_t uptime; const char * status; /* ConnectionStatus possible values : * Unconfigured, Connecting, Connected, PendingDisconnect, * Disconnecting, Disconnected */ status = get_wan_connection_status_str(ext_if_name); uptime = upnp_get_uptime(); bodylen = snprintf(body, sizeof(body), resp, action, ns, /*SERVICE_TYPE_WANIPC,*/ status, (long)uptime, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetNATRSIPStatus(struct upnphttp * h, const char * action, const char * ns) { #if 0 static const char resp[] = "" "0" "1" ""; UNUSED(action); #endif static const char resp[] = "" "0" "1" ""; char body[512]; int bodylen; /* 2.2.9. RSIPAvailable * This variable indicates if Realm-specific IP (RSIP) is available * as a feature on the InternetGatewayDevice. RSIP is being defined * in the NAT working group in the IETF to allow host-NATing using * a standard set of message exchanges. It also allows end-to-end * applications that otherwise break if NAT is introduced * (e.g. IPsec-based VPNs). * A gateway that does not support RSIP should set this variable to 0. */ bodylen = snprintf(body, sizeof(body), resp, action, ns, /*SERVICE_TYPE_WANIPC,*/ action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetExternalIPAddress(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%s" ""; char body[512]; int bodylen; char ext_ip_addr[INET_ADDRSTRLEN]; /* Does that method need to work with IPv6 ? * There is usually no NAT with IPv6 */ #ifndef MULTIPLE_EXTERNAL_IP if(use_ext_ip_addr) { strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN); ext_ip_addr[INET_ADDRSTRLEN - 1] = '\0'; } else { struct in_addr addr; 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); ext_ip_addr[0] = '\0'; } else if (addr_is_reserved(&addr)) { syslog(LOG_NOTICE, "private/reserved address %s is not suitable for external IP", ext_ip_addr); ext_ip_addr[0] = '\0'; } } #else struct lan_addr_s * lan_addr; ext_ip_addr[0] = '\0'; for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (h->clientaddr.s_addr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) { strncpy(ext_ip_addr, lan_addr->ext_ip_str, INET_ADDRSTRLEN); break; } } #endif /* WANIPConnection:2 Service 2.3.13 : * When the external IP address could not be retrieved by the gateway * (for example, because the interface is down or because there was a * failure in the last connection setup attempt), * then the ExternalIPAddress MUST be equal to the empty string. * * There is no precise requirement on how theses cases must be handled * in IGDv1 specifications, but ExternalIPAddress default value is empty * string. */ if (strcmp(ext_ip_addr, "0.0.0.0") == 0) ext_ip_addr[0] = '\0'; bodylen = snprintf(body, sizeof(body), resp, action, ns, /*SERVICE_TYPE_WANIPC,*/ ext_ip_addr, action); BuildSendAndCloseSoapResp(h, body, bodylen); } /* AddPortMapping method of WANIPConnection Service * Ignored argument : NewEnabled */ static void AddPortMapping(struct upnphttp * h, const char * action, const char * ns) { int r; /*static const char resp[] = "";*/ static const char resp[] = ""; char body[512]; int bodylen; struct NameValueParserData data; char * int_ip, * int_port, * ext_port, * protocol, * desc; char * leaseduration_str; unsigned int leaseduration; char * r_host; unsigned short iport, eport; struct hostent *hp; /* getbyhostname() */ char ** ptr; /* getbyhostname() */ struct in_addr result_ip;/*unsigned char result_ip[16];*/ /* inet_pton() */ ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); int_ip = GetValueFromNameValueList(&data, "NewInternalClient"); if (int_ip) { /* trim */ while(int_ip[0] == ' ') int_ip++; } #ifdef UPNP_STRICT if (!int_ip || int_ip[0] == '\0') { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } #endif /* IGD 2 MUST support both wildcard and specific IP address values * for RemoteHost (only the wildcard value was REQUIRED in release 1.0) */ r_host = GetValueFromNameValueList(&data, "NewRemoteHost"); #ifndef SUPPORT_REMOTEHOST #ifdef UPNP_STRICT if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*"))) { ClearNameValueList(&data); SoapError(h, 726, "RemoteHostOnlySupportsWildcard"); return; } #endif #endif #ifndef UPNP_STRICT /* if arg is empty, use client address * see https://github.com/miniupnp/miniupnp/issues/236 */ if (!int_ip || int_ip[0] == '\0') { int_ip = h->clientaddr_str; memcpy(&result_ip, &(h->clientaddr), sizeof(struct in_addr)); } else #endif /* if ip not valid assume hostname and convert */ if (inet_pton(AF_INET, int_ip, &result_ip) <= 0) { hp = gethostbyname(int_ip); if(hp && hp->h_addrtype == AF_INET) { for(ptr = hp->h_addr_list; ptr && *ptr; ptr++) { int_ip = inet_ntoa(*((struct in_addr *) *ptr)); result_ip = *((struct in_addr *) *ptr); /* TODO : deal with more than one ip per hostname */ break; } } else { syslog(LOG_ERR, "Failed to convert hostname '%s' to ip address", int_ip); ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } } /* check if NewInternalAddress is the client address */ if(GETFLAG(SECUREMODEMASK)) { if(h->clientaddr.s_addr != result_ip.s_addr) { syslog(LOG_INFO, "Client %s tried to redirect port to %s", inet_ntoa(h->clientaddr), int_ip); ClearNameValueList(&data); #ifdef IGD_V2 SoapError(h, 606, "Action not authorized"); #else SoapError(h, 718, "ConflictInMappingEntry"); #endif return; } } int_port = GetValueFromNameValueList(&data, "NewInternalPort"); ext_port = GetValueFromNameValueList(&data, "NewExternalPort"); protocol = GetValueFromNameValueList(&data, "NewProtocol"); desc = GetValueFromNameValueList(&data, "NewPortMappingDescription"); leaseduration_str = GetValueFromNameValueList(&data, "NewLeaseDuration"); if (!int_port || !ext_port || !protocol) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } eport = (unsigned short)atoi(ext_port); iport = (unsigned short)atoi(int_port); if (strcmp(ext_port, "*") == 0 || eport == 0) { ClearNameValueList(&data); SoapError(h, 716, "WildCardNotPermittedInExtPort"); return; } leaseduration = leaseduration_str ? atoi(leaseduration_str) : 0; #ifdef IGD_V2 /* PortMappingLeaseDuration can be either a value between 1 and * 604800 seconds or the zero value (for infinite lease time). * Note that an infinite lease time can be only set by out-of-band * mechanisms like WWW-administration, remote management or local * management. * If a control point uses the value 0 to indicate an infinite lease * time mapping, it is REQUIRED that gateway uses the maximum value * instead (e.g. 604800 seconds) */ if(leaseduration == 0 || leaseduration > 604800) leaseduration = 604800; #endif syslog(LOG_INFO, "%s: ext port %hu to %s:%hu protocol %s for: %s leaseduration=%u rhost=%s", action, eport, int_ip, iport, protocol, desc, leaseduration, r_host ? r_host : "NULL"); r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration); ClearNameValueList(&data); /* possible error codes for AddPortMapping : * 402 - Invalid Args * 501 - Action Failed * 606 - Action not authorized (added in IGD v2) * 715 - WildCardNotPermittedInSrcIP * 716 - WildCardNotPermittedInExtPort * 718 - ConflictInMappingEntry * 724 - SamePortValuesRequired (deprecated in IGD v2) * 725 - OnlyPermanentLeasesSupported The NAT implementation only supports permanent lease times on port mappings (deprecated in IGD v2) * 726 - RemoteHostOnlySupportsWildcard RemoteHost must be a wildcard and cannot be a specific IP address or DNS name (deprecated in IGD v2) * 727 - ExternalPortOnlySupportsWildcard ExternalPort must be a wildcard and cannot be a specific port value (deprecated in IGD v2) * 728 - NoPortMapsAvailable There are not enough free ports available to complete the mapping (added in IGD v2) * 729 - ConflictWithOtherMechanisms (added in IGD v2) * 732 - WildCardNotPermittedInIntPort (added in IGD v2) */ switch(r) { case 0: /* success */ bodylen = snprintf(body, sizeof(body), resp, action, ns/*SERVICE_TYPE_WANIPC*/); BuildSendAndCloseSoapResp(h, body, bodylen); break; case -4: #ifdef IGD_V2 SoapError(h, 729, "ConflictWithOtherMechanisms"); break; #endif /* IGD_V2 */ case -3: /* not permitted */ #ifdef IGD_V2 SoapError(h, 606, "Action not authorized"); break; #endif /* IGD_V2 */ case -2: /* already redirected */ SoapError(h, 718, "ConflictInMappingEntry"); break; default: SoapError(h, 501, "Action Failed"); } } /* AddAnyPortMapping was added in WANIPConnection v2 */ static void AddAnyPortMapping(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%hu" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * int_ip, * int_port, * ext_port, * protocol, * desc; const char * r_host; unsigned short iport, eport; const char * leaseduration_str; unsigned int leaseduration; struct hostent *hp; /* getbyhostname() */ char ** ptr; /* getbyhostname() */ struct in_addr result_ip;/*unsigned char result_ip[16];*/ /* inet_pton() */ ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); r_host = GetValueFromNameValueList(&data, "NewRemoteHost"); ext_port = GetValueFromNameValueList(&data, "NewExternalPort"); protocol = GetValueFromNameValueList(&data, "NewProtocol"); int_port = GetValueFromNameValueList(&data, "NewInternalPort"); int_ip = GetValueFromNameValueList(&data, "NewInternalClient"); /* NewEnabled */ desc = GetValueFromNameValueList(&data, "NewPortMappingDescription"); leaseduration_str = GetValueFromNameValueList(&data, "NewLeaseDuration"); leaseduration = leaseduration_str ? atoi(leaseduration_str) : 0; if(leaseduration == 0) leaseduration = 604800; if (!int_ip || !ext_port || !int_port || !protocol) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } eport = (0 == strcmp(ext_port, "*")) ? 0 : (unsigned short)atoi(ext_port); if (eport == 0) { eport = 1024 + ((random() & 0x7ffffffL) % (65536-1024)); } iport = (unsigned short)atoi(int_port); if(iport == 0 || (!is_numeric(ext_port) && 0 != strcmp(ext_port, "*"))) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } #ifndef SUPPORT_REMOTEHOST #ifdef UPNP_STRICT if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*"))) { ClearNameValueList(&data); SoapError(h, 726, "RemoteHostOnlySupportsWildcard"); return; } #endif #endif /* if ip not valid assume hostname and convert */ if (inet_pton(AF_INET, int_ip, &result_ip) <= 0) { hp = gethostbyname(int_ip); if(hp && hp->h_addrtype == AF_INET) { for(ptr = hp->h_addr_list; ptr && *ptr; ptr++) { int_ip = inet_ntoa(*((struct in_addr *) *ptr)); result_ip = *((struct in_addr *) *ptr); /* TODO : deal with more than one ip per hostname */ break; } } else { syslog(LOG_ERR, "Failed to convert hostname '%s' to ip address", int_ip); ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } } /* check if NewInternalAddress is the client address */ if(GETFLAG(SECUREMODEMASK)) { if(h->clientaddr.s_addr != result_ip.s_addr) { syslog(LOG_INFO, "Client %s tried to redirect port to %s", inet_ntoa(h->clientaddr), int_ip); ClearNameValueList(&data); SoapError(h, 606, "Action not authorized"); return; } } /* first try the port asked in request, then * try +1, -1, +2, -2, etc. */ r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration); if (r != 0 && r != -1) { unsigned short eport_below, eport_above; struct in_addr address; uint32_t allowed_eports[65536 / 32]; if(inet_aton(int_ip, &address) <= 0) { syslog(LOG_ERR, "inet_aton(%s) FAILED", int_ip); } get_permitted_ext_ports(allowed_eports, upnppermlist, num_upnpperm, address.s_addr, iport); eport_above = eport_below = eport; for(;;) { /* loop invariant * eport is equal to either eport_below or eport_above (or both) */ if (eport_below <= 1 && eport_above == 65535) { /* all possible ports tried */ r = 1; break; } if (eport_above == 65535 || (eport > eport_below && eport_below > 1)) { eport = --eport_below; } else { eport = ++eport_above; } if (!(allowed_eports[eport / 32] & ((uint32_t)1U << (eport % 32)))) continue; /* not allowed */ r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration); if (r == 0 || r == -1) { /* OK or failure : Stop */ break; } /* r : -2 / -4 already redirected or -3 permission check failed : * continue */ } } ClearNameValueList(&data); switch(r) { case 1: /* exhausted possible mappings */ SoapError(h, 728, "NoPortMapsAvailable"); break; case 0: /* success */ bodylen = snprintf(body, sizeof(body), resp, action, ns, /*SERVICE_TYPE_WANIPC,*/ eport, action); BuildSendAndCloseSoapResp(h, body, bodylen); break; case -2: /* already redirected */ SoapError(h, 718, "ConflictInMappingEntry"); break; case -3: /* not permitted */ SoapError(h, 606, "Action not authorized"); break; default: SoapError(h, 501, "Action Failed"); } } static void GetSpecificPortMappingEntry(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%u" "%s" "1" "%s" "%u" ""; char body[1024]; int bodylen; struct NameValueParserData data; const char * r_host, * ext_port, * protocol; unsigned short eport, iport; char int_ip[32]; char desc[64]; unsigned int leaseduration = 0; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); r_host = GetValueFromNameValueList(&data, "NewRemoteHost"); ext_port = GetValueFromNameValueList(&data, "NewExternalPort"); protocol = GetValueFromNameValueList(&data, "NewProtocol"); #ifdef UPNP_STRICT if(!ext_port || !protocol || !r_host) #else if(!ext_port || !protocol) #endif { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } #ifndef SUPPORT_REMOTEHOST #ifdef UPNP_STRICT if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*"))) { ClearNameValueList(&data); SoapError(h, 726, "RemoteHostOnlySupportsWildcard"); return; } #endif #endif eport = (unsigned short)atoi(ext_port); if(eport == 0) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } /* TODO : add r_host as an input parameter ... * We prevent several Port Mapping with same external port * but different remoteHost to be set up, so that is not * a priority. */ r = upnp_get_redirection_infos(eport, protocol, &iport, int_ip, sizeof(int_ip), desc, sizeof(desc), NULL, 0, &leaseduration); if(r < 0) { SoapError(h, 714, "NoSuchEntryInArray"); } else { syslog(LOG_INFO, "%s: rhost='%s' %s %s found => %s:%u desc='%s' duration=%u", action, r_host ? r_host : "NULL", ext_port, protocol, int_ip, (unsigned int)iport, desc, leaseduration); bodylen = snprintf(body, sizeof(body), resp, action, ns/*SERVICE_TYPE_WANIPC*/, (unsigned int)iport, int_ip, desc, leaseduration, action); BuildSendAndCloseSoapResp(h, body, bodylen); } ClearNameValueList(&data); } static void DeletePortMapping(struct upnphttp * h, const char * action, const char * ns) { int r; /*static const char resp[] = "" "";*/ static const char resp[] = "" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * ext_port, * protocol; unsigned short eport; #ifdef UPNP_STRICT const char * r_host; #endif /* UPNP_STRICT */ ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); ext_port = GetValueFromNameValueList(&data, "NewExternalPort"); protocol = GetValueFromNameValueList(&data, "NewProtocol"); #ifdef UPNP_STRICT r_host = GetValueFromNameValueList(&data, "NewRemoteHost"); #endif /* UPNP_STRICT */ #ifdef UPNP_STRICT if(!ext_port || !protocol || !r_host) #else if(!ext_port || !protocol) #endif /* UPNP_STRICT */ { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } #ifndef SUPPORT_REMOTEHOST #ifdef UPNP_STRICT if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*"))) { ClearNameValueList(&data); SoapError(h, 726, "RemoteHostOnlySupportsWildcard"); return; } #endif /* UPNP_STRICT */ #endif /* SUPPORT_REMOTEHOST */ eport = (unsigned short)atoi(ext_port); if(eport == 0) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } syslog(LOG_INFO, "%s: external port: %hu, protocol: %s", action, eport, protocol); /* if in secure mode, check the IP * Removing a redirection is not a security threat, * just an annoyance for the user using it. So this is not * a priority. */ if(GETFLAG(SECUREMODEMASK)) { char int_ip[32]; struct in_addr int_ip_addr; unsigned short iport; unsigned int leaseduration = 0; r = upnp_get_redirection_infos(eport, protocol, &iport, int_ip, sizeof(int_ip), NULL, 0, NULL, 0, &leaseduration); if(r >= 0) { if(inet_pton(AF_INET, int_ip, &int_ip_addr) > 0) { if(h->clientaddr.s_addr != int_ip_addr.s_addr) { #ifdef IGD_V2 SoapError(h, 606, "Action not authorized"); #else SoapError(h, 714, "NoSuchEntryInArray"); #endif ClearNameValueList(&data); return; } } } } r = upnp_delete_redirection(eport, protocol); if(r < 0) { SoapError(h, 714, "NoSuchEntryInArray"); } else { bodylen = snprintf(body, sizeof(body), resp, action, ns, action); BuildSendAndCloseSoapResp(h, body, bodylen); } ClearNameValueList(&data); } /* DeletePortMappingRange was added in IGD spec v2 */ static void DeletePortMappingRange(struct upnphttp * h, const char * action, const char * ns) { int r = -1; /*static const char resp[] = "" "";*/ static const char resp[] = "" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * protocol; const char * startport_s, * endport_s; unsigned short startport, endport; /*int manage;*/ unsigned short * port_list; unsigned int i, number = 0; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); startport_s = GetValueFromNameValueList(&data, "NewStartPort"); endport_s = GetValueFromNameValueList(&data, "NewEndPort"); protocol = GetValueFromNameValueList(&data, "NewProtocol"); /*manage = atoi(GetValueFromNameValueList(&data, "NewManage"));*/ if(startport_s == NULL || endport_s == NULL || protocol == NULL || !is_numeric(startport_s) || !is_numeric(endport_s)) { SoapError(h, 402, "Invalid Args"); ClearNameValueList(&data); return; } startport = (unsigned short)atoi(startport_s); endport = (unsigned short)atoi(endport_s); /* possible errors : 606 - Action not authorized 730 - PortMappingNotFound 733 - InconsistentParameter */ if(startport > endport) { SoapError(h, 733, "InconsistentParameter"); ClearNameValueList(&data); return; } syslog(LOG_INFO, "%s: deleting external ports: %hu-%hu, protocol: %s", action, startport, endport, protocol); port_list = upnp_get_portmappings_in_range(startport, endport, protocol, &number); if(number == 0) { SoapError(h, 730, "PortMappingNotFound"); ClearNameValueList(&data); free(port_list); return; } for(i = 0; i < number; i++) { r = upnp_delete_redirection(port_list[i], protocol); syslog(LOG_INFO, "%s: deleting external port: %hu, protocol: %s: %s", action, port_list[i], protocol, r < 0 ? "failed" : "ok"); } free(port_list); bodylen = snprintf(body, sizeof(body), resp, action, ns, action); BuildSendAndCloseSoapResp(h, body, bodylen); ClearNameValueList(&data); } static void GetGenericPortMappingEntry(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%s" "%u" "%s" "%u" "%s" "1" "%s" "%u" ""; long int index = 0; unsigned short eport, iport; const char * m_index; char * endptr; char protocol[8], iaddr[32]; char desc[64]; char rhost[40]; unsigned int leaseduration = 0; struct NameValueParserData data; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); m_index = GetValueFromNameValueList(&data, "NewPortMappingIndex"); if(!m_index) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } errno = 0; /* To distinguish success/failure after call */ index = strtol(m_index, &endptr, 10); if((errno == ERANGE && (index == LONG_MAX || index == LONG_MIN)) || (errno != 0 && index == 0) || (m_index == endptr)) { /* should condition (*endptr != '\0') be also an error ? */ if(m_index == endptr) syslog(LOG_WARNING, "%s: no digits were found in <%s>", "GetGenericPortMappingEntry", "NewPortMappingIndex"); else syslog(LOG_WARNING, "%s: strtol('%s'): %m", "GetGenericPortMappingEntry", m_index); ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } syslog(LOG_INFO, "%s: index=%d", action, (int)index); rhost[0] = '\0'; r = upnp_get_redirection_infos_by_index((int)index, &eport, protocol, &iport, iaddr, sizeof(iaddr), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration); if(r < 0) { SoapError(h, 713, "SpecifiedArrayIndexInvalid"); } else { int bodylen; char body[2048]; bodylen = snprintf(body, sizeof(body), resp, action, ns, /*SERVICE_TYPE_WANIPC,*/ rhost, (unsigned int)eport, protocol, (unsigned int)iport, iaddr, desc, leaseduration, action); BuildSendAndCloseSoapResp(h, body, bodylen); } ClearNameValueList(&data); } /* GetListOfPortMappings was added in the IGD v2 specification */ static void GetListOfPortMappings(struct upnphttp * h, const char * action, const char * ns) { static const char resp_start[] = "" "" ""; static const char list_start[] = ""; static const char list_end[] = ""; static const char entry[] = "" "%s" "%hu" "%s" "%hu" "%s" "1" "%s" "%u" ""; char * body; size_t bodyalloc; int bodylen; int r = -1; unsigned short iport; char int_ip[32]; char desc[64]; char rhost[64]; unsigned int leaseduration = 0; struct NameValueParserData data; const char * startport_s, * endport_s; unsigned short startport, endport; const char * protocol; /*int manage;*/ const char * number_s; int number; unsigned short * port_list; unsigned int i, list_size = 0; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); startport_s = GetValueFromNameValueList(&data, "NewStartPort"); endport_s = GetValueFromNameValueList(&data, "NewEndPort"); protocol = GetValueFromNameValueList(&data, "NewProtocol"); /*manage_s = GetValueFromNameValueList(&data, "NewManage");*/ number_s = GetValueFromNameValueList(&data, "NewNumberOfPorts"); if(startport_s == NULL || endport_s == NULL || protocol == NULL || number_s == NULL || !is_numeric(number_s) || !is_numeric(startport_s) || !is_numeric(endport_s)) { SoapError(h, 402, "Invalid Args"); ClearNameValueList(&data); return; } startport = (unsigned short)atoi(startport_s); endport = (unsigned short)atoi(endport_s); /*manage = atoi(manage_s);*/ number = atoi(number_s); if(number == 0) number = 1000; /* return up to 1000 mappings by default */ if(startport > endport) { SoapError(h, 733, "InconsistentParameter"); ClearNameValueList(&data); return; } /* build the PortMappingList xml document : 202.233.2.1 2345 TCP 2345 192.168.1.137 1 dooom 345 */ bodyalloc = 4096; body = malloc(bodyalloc); if(!body) { ClearNameValueList(&data); SoapError(h, 501, "Action Failed"); return; } bodylen = snprintf(body, bodyalloc, resp_start, action, ns/*SERVICE_TYPE_WANIPC*/); if(bodylen < 0) { SoapError(h, 501, "Action Failed"); free(body); return; } memcpy(body+bodylen, list_start, sizeof(list_start)); bodylen += (sizeof(list_start) - 1); port_list = upnp_get_portmappings_in_range(startport, endport, protocol, &list_size); /* loop through port mappings */ for(i = 0; number > 0 && i < list_size; i++) { /* have a margin of 1024 bytes to store the new entry */ if((unsigned int)bodylen + 1024 > bodyalloc) { char * body_sav = body; bodyalloc += 4096; body = realloc(body, bodyalloc); if(!body) { syslog(LOG_CRIT, "realloc(%p, %u) FAILED", body_sav, (unsigned)bodyalloc); ClearNameValueList(&data); SoapError(h, 501, "Action Failed"); free(body_sav); free(port_list); return; } } rhost[0] = '\0'; r = upnp_get_redirection_infos(port_list[i], protocol, &iport, int_ip, sizeof(int_ip), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration); if(r == 0) { bodylen += snprintf(body+bodylen, bodyalloc-bodylen, entry, rhost, port_list[i], protocol, iport, int_ip, desc, leaseduration); number--; } } free(port_list); port_list = NULL; if((bodylen + sizeof(list_end) + 1024) > bodyalloc) { char * body_sav = body; bodyalloc += (sizeof(list_end) + 1024); body = realloc(body, bodyalloc); if(!body) { syslog(LOG_CRIT, "realloc(%p, %u) FAILED", body_sav, (unsigned)bodyalloc); ClearNameValueList(&data); SoapError(h, 501, "Action Failed"); free(body_sav); return; } } memcpy(body+bodylen, list_end, sizeof(list_end)); bodylen += (sizeof(list_end) - 1); bodylen += snprintf(body+bodylen, bodyalloc-bodylen, resp_end, action); BuildSendAndCloseSoapResp(h, body, bodylen); free(body); ClearNameValueList(&data); } #ifdef ENABLE_L3F_SERVICE static void SetDefaultConnectionService(struct upnphttp * h, const char * action, const char * ns) { /*static const char resp[] = "" "";*/ static const char resp[] = "" ""; char body[512]; int bodylen; struct NameValueParserData data; char * p; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); p = GetValueFromNameValueList(&data, "NewDefaultConnectionService"); if(p) { /* 720 InvalidDeviceUUID * 721 InvalidServiceID * 723 InvalidConnServiceSelection */ #ifdef UPNP_STRICT char * service; service = strchr(p, ','); if(0 != memcmp(uuidvalue_wcd, p, sizeof("uuid:00000000-0000-0000-0000-000000000000") - 1)) { SoapError(h, 720, "InvalidDeviceUUID"); } else if(service == NULL || 0 != strcmp(service+1, SERVICE_ID_WANIPC)) { SoapError(h, 721, "InvalidServiceID"); } else #endif { syslog(LOG_INFO, "%s(%s) : Ignored", action, p); bodylen = snprintf(body, sizeof(body), resp, action, ns, action); BuildSendAndCloseSoapResp(h, body, bodylen); } } else { /* missing argument */ SoapError(h, 402, "Invalid Args"); } ClearNameValueList(&data); } static void GetDefaultConnectionService(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" #ifdef IGD_V2 "%s:WANConnectionDevice:2," #else "%s:WANConnectionDevice:1," #endif SERVICE_ID_WANIPC "" ""; /* example from UPnP_IGD_Layer3Forwarding 1.0.pdf : * uuid:44f5824f-c57d-418c-a131-f22b34e14111:WANConnectionDevice:1, * urn:upnp-org:serviceId:WANPPPConn1 */ char body[1024]; int bodylen; /* namespace : urn:schemas-upnp-org:service:Layer3Forwarding:1 */ bodylen = snprintf(body, sizeof(body), resp, action, ns, uuidvalue_wcd, action); BuildSendAndCloseSoapResp(h, body, bodylen); } #endif /* Added for compliance with WANIPConnection v2 */ static void SetConnectionType(struct upnphttp * h, const char * action, const char * ns) { #ifdef UPNP_STRICT const char * connection_type; #endif /* UPNP_STRICT */ struct NameValueParserData data; UNUSED(action); UNUSED(ns); ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); #ifdef UPNP_STRICT connection_type = GetValueFromNameValueList(&data, "NewConnectionType"); if(!connection_type) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } #endif /* UPNP_STRICT */ /* Unconfigured, IP_Routed, IP_Bridged */ ClearNameValueList(&data); /* always return a ReadOnly error */ SoapError(h, 731, "ReadOnly"); } /* Added for compliance with WANIPConnection v2 */ static void RequestConnection(struct upnphttp * h, const char * action, const char * ns) { UNUSED(action); UNUSED(ns); SoapError(h, 606, "Action not authorized"); } /* Added for compliance with WANIPConnection v2 */ static void ForceTermination(struct upnphttp * h, const char * action, const char * ns) { UNUSED(action); UNUSED(ns); SoapError(h, 606, "Action not authorized"); } /* If a control point calls QueryStateVariable on a state variable that is not buffered in memory within (or otherwise available from) the service, the service must return a SOAP fault with an errorCode of 404 Invalid Var. QueryStateVariable remains useful as a limited test tool but may not be part of some future versions of UPnP. */ static void QueryStateVariable(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%s" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * var_name; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */ /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/ var_name = GetValueFromNameValueList(&data, "varName"); /*syslog(LOG_INFO, "QueryStateVariable(%.40s)", var_name); */ if(!var_name) { SoapError(h, 402, "Invalid Args"); } else if(strcmp(var_name, "ConnectionStatus") == 0) { const char * status; status = get_wan_connection_status_str(ext_if_name); bodylen = snprintf(body, sizeof(body), resp, action, ns,/*"urn:schemas-upnp-org:control-1-0",*/ status, action); BuildSendAndCloseSoapResp(h, body, bodylen); } #if 0 /* not useful */ else if(strcmp(var_name, "ConnectionType") == 0) { bodylen = snprintf(body, sizeof(body), resp, "IP_Routed"); BuildSendAndCloseSoapResp(h, body, bodylen); } else if(strcmp(var_name, "LastConnectionError") == 0) { bodylen = snprintf(body, sizeof(body), resp, "ERROR_NONE"); BuildSendAndCloseSoapResp(h, body, bodylen); } #endif else if(strcmp(var_name, "PortMappingNumberOfEntries") == 0) { char strn[10]; snprintf(strn, sizeof(strn), "%i", upnp_get_portmapping_number_of_entries()); bodylen = snprintf(body, sizeof(body), resp, action, ns,/*"urn:schemas-upnp-org:control-1-0",*/ strn, action); BuildSendAndCloseSoapResp(h, body, bodylen); } else { syslog(LOG_NOTICE, "%s: Unknown: %s", action, var_name?var_name:""); SoapError(h, 404, "Invalid Var"); } ClearNameValueList(&data); } #ifdef ENABLE_6FC_SERVICE #ifndef ENABLE_IPV6 #error "ENABLE_6FC_SERVICE needs ENABLE_IPV6" #endif /* WANIPv6FirewallControl actions */ static void GetFirewallStatus(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%d" "%d" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, ns, /*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1",*/ GETFLAG(IPV6FCFWDISABLEDMASK) ? 0 : 1, GETFLAG(IPV6FCINBOUNDDISALLOWEDMASK) ? 0 : 1, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static int CheckStatus(struct upnphttp * h) { if (GETFLAG(IPV6FCFWDISABLEDMASK)) { SoapError(h, 702, "FirewallDisabled"); return 0; } else if(GETFLAG(IPV6FCINBOUNDDISALLOWEDMASK)) { SoapError(h, 703, "InboundPinholeNotAllowed"); return 0; } else return 1; } #if 0 static int connecthostport(const char * host, unsigned short port, char * result) { int s, n; char hostname[INET6_ADDRSTRLEN]; char port_str[8], ifname[8], tmp[4]; struct addrinfo *ai, *p; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); /* hints.ai_flags = AI_ADDRCONFIG; */ #ifdef AI_NUMERICSERV hints.ai_flags = AI_NUMERICSERV; #endif hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; /* AF_INET, AF_INET6 or AF_UNSPEC */ /* hints.ai_protocol = IPPROTO_TCP; */ snprintf(port_str, sizeof(port_str), "%hu", port); strcpy(hostname, host); if(!strncmp(host, "fe80", 4)) { printf("Using an linklocal address\n"); strcpy(ifname, "%"); snprintf(tmp, sizeof(tmp), "%d", linklocal_index); strcat(ifname, tmp); strcat(hostname, ifname); printf("host: %s\n", hostname); } n = getaddrinfo(hostname, port_str, &hints, &ai); if(n != 0) { fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n)); return -1; } s = -1; for(p = ai; p; p = p->ai_next) { #ifdef DEBUG char tmp_host[256]; char tmp_service[256]; printf("ai_family=%d ai_socktype=%d ai_protocol=%d ai_addrlen=%d\n ", p->ai_family, p->ai_socktype, p->ai_protocol, p->ai_addrlen); getnameinfo(p->ai_addr, p->ai_addrlen, tmp_host, sizeof(tmp_host), tmp_service, sizeof(tmp_service), NI_NUMERICHOST | NI_NUMERICSERV); printf(" host=%s service=%s\n", tmp_host, tmp_service); #endif inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)p->ai_addr)->sin6_addr), result, INET6_ADDRSTRLEN); return 0; } freeaddrinfo(ai); } #endif /* Check the security policy rights * Pinhole InternalClient address must correspond to the action sender * returns 1 if it passes. * call SoapError() and returns 0 if it fails * Side effect : if int_ip is a hostname, convert it to litteral ipv6 */ static int PinholeVerification(struct upnphttp * h, char * int_ip, unsigned short int_port) { char clientaddr_str[INET6_ADDRSTRLEN]; struct in6_addr result_ip; /* if ip not valid assume hostname and convert */ if (inet_pton(AF_INET6, int_ip, &result_ip) <= 0) { int r; struct addrinfo hints, *ai, *p; syslog(LOG_INFO, "%s: InternalClient %s is not an IPv6, assume hostname and convert", "PinholeVerification", int_ip); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; /* we may indicate which protocol IPPROTO_UDP / IPPROTO_TCP / IPPROTO_SCTP / IPPROTO_UDPLITE */ r = getaddrinfo(int_ip, NULL, &hints, &ai); if (r == 0) { int found = 0; for(p = ai; p; p = p->ai_next) { if(p->ai_family == AF_INET6) { if (!found) { result_ip = ((struct sockaddr_in6 *)p->ai_addr)->sin6_addr; if (inet_ntop(AF_INET6, &result_ip, int_ip, sizeof(struct in6_addr)) == NULL) syslog(LOG_WARNING, "%s: inet_ntop(): %m", "PinholeVerification"); syslog(LOG_INFO, "%s: InternalClient resolved as %s", "PinholeVerification", int_ip); found = 1; } else { char tmp[48]; sockaddr_to_string(p->ai_addr, tmp, sizeof(tmp)); syslog(LOG_INFO, "%s: additionnal IPv6: %s", "PinholeVerification", tmp); } } } freeaddrinfo(ai); if (!found) { syslog(LOG_NOTICE, "%s: No IPv6 address for hostname '%s'", "PinholeVerification", int_ip); SoapError(h, 402, "Invalid Args"); return -1; } } else { syslog(LOG_WARNING, "%s: Failed to convert hostname '%s' to IP address : %s", "PinholeVerification", int_ip, gai_strerror(r)); SoapError(h, 402, "Invalid Args"); return -1; } } if(inet_ntop(AF_INET6, &(h->clientaddr_v6), clientaddr_str, INET6_ADDRSTRLEN) == NULL) { syslog(LOG_ERR, "inet_ntop: %m"); strncpy(clientaddr_str, "*ERROR*", sizeof(clientaddr_str)); } if(memcmp(&h->clientaddr_v6, &result_ip, sizeof(struct in6_addr)) != 0) { syslog(LOG_INFO, "%s: Client %s tried to access pinhole for internal %s and is not authorized", "PinholeVerification", clientaddr_str, int_ip); SoapError(h, 606, "Action not authorized"); return 0; } #ifdef DEBUG else { syslog(LOG_DEBUG, "%s: sender %s == InternalClient %s", "PinholeVerification", clientaddr_str, int_ip); } #endif /* Pinhole InternalPort must be greater than or equal to 1024 */ if (int_port < 1024) { syslog(LOG_INFO, "Client %s tried to access pinhole with port < 1024 and is not authorized to do it", clientaddr_str); SoapError(h, 606, "Action not authorized"); return 0; } return 1; } static void AddPinhole(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%d" ""; char body[512]; int bodylen; struct NameValueParserData data; char * rem_host, * rem_port, * int_ip, * int_port, * protocol, * leaseTime; int uid = 0; unsigned short iport, rport; int ltime; long proto; char rem_ip[INET6_ADDRSTRLEN]; if(CheckStatus(h)==0) return; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); rem_host = GetValueFromNameValueList(&data, "RemoteHost"); rem_port = GetValueFromNameValueList(&data, "RemotePort"); int_ip = GetValueFromNameValueList(&data, "InternalClient"); int_port = GetValueFromNameValueList(&data, "InternalPort"); protocol = GetValueFromNameValueList(&data, "Protocol"); leaseTime = GetValueFromNameValueList(&data, "LeaseTime"); #ifdef UPNP_STRICT if (rem_port == NULL || rem_port[0] == '\0' || int_port == NULL || int_port[0] == '\0' ) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } #endif rport = (unsigned short)(rem_port ? atoi(rem_port) : 0); iport = (unsigned short)(int_port ? atoi(int_port) : 0); ltime = leaseTime ? atoi(leaseTime) : -1; errno = 0; proto = protocol ? strtol(protocol, NULL, 0) : -1; if(errno != 0 || proto > 65535 || proto < 0) { SoapError(h, 402, "Invalid Args"); goto clear_and_exit; } if(iport == 0) { SoapError(h, 706, "InternalPortWilcardingNotAllowed"); goto clear_and_exit; } /* In particular, [IGD2] RECOMMENDS that unauthenticated and * unauthorized control points are only allowed to invoke * this action with: * - InternalPort value greater than or equal to 1024, * - InternalClient value equals to the control point's IP address. * It is REQUIRED that InternalClient cannot be one of IPv6 * addresses used by the gateway. */ if(!int_ip || int_ip[0] == '\0' || 0 == strcmp(int_ip, "*")) { SoapError(h, 708, "WildCardNotPermittedInSrcIP"); goto clear_and_exit; } /* I guess it is useless to convert int_ip to literal ipv6 address */ if(rem_host) { /* trim */ while(isspace(rem_host[0])) rem_host++; } /* rem_host should be converted to literal ipv6 : */ if(rem_host && (rem_host[0] != '\0') && (rem_host[0] != '*')) { struct addrinfo *ai, *p; struct addrinfo hints; int err; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET6; /*hints.ai_flags = */ /* hints.ai_protocol = proto; */ err = getaddrinfo(rem_host, rem_port, &hints, &ai); if(err == 0) { /* take the 1st IPv6 address */ for(p = ai; p; p = p->ai_next) { if(p->ai_family == AF_INET6) { inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)p->ai_addr)->sin6_addr), rem_ip, sizeof(rem_ip)); syslog(LOG_INFO, "resolved '%s' to '%s'", rem_host, rem_ip); rem_host = rem_ip; break; } } freeaddrinfo(ai); } else { syslog(LOG_WARNING, "AddPinhole : getaddrinfo(%s) : %s", rem_host, gai_strerror(err)); #if 0 SoapError(h, 402, "Invalid Args"); goto clear_and_exit; #endif } } if(proto == 65535) { SoapError(h, 707, "ProtocolWilcardingNotAllowed"); goto clear_and_exit; } if(proto != IPPROTO_UDP && proto != IPPROTO_TCP #ifdef IPPROTO_UDPITE && atoi(protocol) != IPPROTO_UDPLITE #endif ) { SoapError(h, 705, "ProtocolNotSupported"); goto clear_and_exit; } if(ltime < 1 || ltime > 86400) { syslog(LOG_WARNING, "%s: LeaseTime=%d not supported, (ip=%s)", action, ltime, int_ip); SoapError(h, 402, "Invalid Args"); goto clear_and_exit; } if(PinholeVerification(h, int_ip, iport) <= 0) goto clear_and_exit; syslog(LOG_INFO, "%s: (inbound) from [%s]:%hu to [%s]:%hu with proto %ld during %d sec", action, rem_host?rem_host:"any", rport, int_ip, iport, proto, ltime); /* In cases where the RemoteHost, RemotePort, InternalPort, * InternalClient and Protocol are the same than an existing pinhole, * but LeaseTime is different, the device MUST extend the existing * pinhole's lease time and return the UniqueID of the existing pinhole. */ r = upnp_add_inboundpinhole(rem_host, rport, int_ip, iport, proto, "IGD2 pinhole", ltime, &uid); switch(r) { case 1: /* success */ bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/, uid, action); BuildSendAndCloseSoapResp(h, body, bodylen); break; case -1: /* not permitted */ SoapError(h, 701, "PinholeSpaceExhausted"); break; default: SoapError(h, 501, "Action Failed"); break; } /* 606 Action not authorized * 701 PinholeSpaceExhausted * 702 FirewallDisabled * 703 InboundPinholeNotAllowed * 705 ProtocolNotSupported * 706 InternalPortWildcardingNotAllowed * 707 ProtocolWildcardingNotAllowed * 708 WildCardNotPermittedInSrcIP */ clear_and_exit: ClearNameValueList(&data); } static void UpdatePinhole(struct upnphttp * h, const char * action, const char * ns) { #if 0 static const char resp[] = "" ""; #endif static const char resp[] = "" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * uid_str, * leaseTime; char iaddr[INET6_ADDRSTRLEN]; unsigned short iport; int ltime; int uid; int n; if(CheckStatus(h)==0) return; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); uid_str = GetValueFromNameValueList(&data, "UniqueID"); leaseTime = GetValueFromNameValueList(&data, "NewLeaseTime"); uid = uid_str ? atoi(uid_str) : -1; ltime = leaseTime ? atoi(leaseTime) : -1; ClearNameValueList(&data); if(uid < 0 || uid > 65535 || ltime <= 0 || ltime > 86400) { SoapError(h, 402, "Invalid Args"); return; } /* Check that client is not updating an pinhole * it doesn't have access to, because of its public access */ n = upnp_get_pinhole_info(uid, NULL, 0, NULL, iaddr, sizeof(iaddr), &iport, NULL, /* proto */ NULL, 0, /* desc, desclen */ NULL, NULL); if (n >= 0) { if(PinholeVerification(h, iaddr, iport) <= 0) return; } else if(n == -2) { SoapError(h, 704, "NoSuchEntry"); return; } else { SoapError(h, 501, "Action Failed"); return; } syslog(LOG_INFO, "%s: (inbound) updating lease duration to %d for pinhole with ID: %d", action, ltime, uid); n = upnp_update_inboundpinhole(uid, ltime); if(n == -1) SoapError(h, 704, "NoSuchEntry"); else if(n < 0) SoapError(h, 501, "Action Failed"); else { bodylen = snprintf(body, sizeof(body), resp, action, ns, action); BuildSendAndCloseSoapResp(h, body, bodylen); } } static void GetOutboundPinholeTimeout(struct upnphttp * h, const char * action, const char * ns) { int r; static const char resp[] = "" "%d" ""; char body[512]; int bodylen; struct NameValueParserData data; char * int_ip, * int_port, * rem_host, * rem_port, * protocol; int opt=0; /*int proto=0;*/ unsigned short iport, rport; if (GETFLAG(IPV6FCFWDISABLEDMASK)) { SoapError(h, 702, "FirewallDisabled"); return; } ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); int_ip = GetValueFromNameValueList(&data, "InternalClient"); int_port = GetValueFromNameValueList(&data, "InternalPort"); rem_host = GetValueFromNameValueList(&data, "RemoteHost"); rem_port = GetValueFromNameValueList(&data, "RemotePort"); protocol = GetValueFromNameValueList(&data, "Protocol"); if (!int_port || !rem_port || !protocol) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } rport = (unsigned short)atoi(rem_port); iport = (unsigned short)atoi(int_port); /*proto = atoi(protocol);*/ syslog(LOG_INFO, "%s: retrieving timeout for outbound pinhole from [%s]:%hu to [%s]:%hu protocol %s", action, int_ip, iport,rem_host, rport, protocol); /* TODO */ r = -1;/*upnp_check_outbound_pinhole(proto, &opt);*/ switch(r) { case 1: /* success */ bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/, opt, action); BuildSendAndCloseSoapResp(h, body, bodylen); break; case -5: /* Protocol not supported */ SoapError(h, 705, "ProtocolNotSupported"); break; default: SoapError(h, 501, "Action Failed"); } ClearNameValueList(&data); } static void DeletePinhole(struct upnphttp * h, const char * action, const char * ns) { int n; #if 0 static const char resp[] = "" ""; #endif static const char resp[] = "" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * uid_str; char iaddr[INET6_ADDRSTRLEN]; int proto; unsigned short iport; unsigned int leasetime; int uid; if(CheckStatus(h)==0) return; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); uid_str = GetValueFromNameValueList(&data, "UniqueID"); uid = uid_str ? atoi(uid_str) : -1; ClearNameValueList(&data); if(uid < 0 || uid > 65535) { SoapError(h, 402, "Invalid Args"); return; } /* Check that client is not deleting an pinhole * it doesn't have access to, because of its public access */ n = upnp_get_pinhole_info(uid, NULL, 0, NULL, iaddr, sizeof(iaddr), &iport, &proto, NULL, 0, /* desc, desclen */ &leasetime, NULL); if (n >= 0) { if(PinholeVerification(h, iaddr, iport) <= 0) return; } else if(n == -2) { SoapError(h, 704, "NoSuchEntry"); return; } else { SoapError(h, 501, "Action Failed"); return; } n = upnp_delete_inboundpinhole(uid); if(n < 0) { syslog(LOG_INFO, "%s: (inbound) failed to remove pinhole with ID: %d", action, uid); SoapError(h, 501, "Action Failed"); return; } syslog(LOG_INFO, "%s: (inbound) pinhole with ID %d successfully removed", action, uid); bodylen = snprintf(body, sizeof(body), resp, action, ns, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void CheckPinholeWorking(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%d" ""; char body[512]; int bodylen; int r; struct NameValueParserData data; const char * uid_str; int uid; char iaddr[INET6_ADDRSTRLEN]; unsigned short iport; unsigned int packets; if(CheckStatus(h)==0) return; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); uid_str = GetValueFromNameValueList(&data, "UniqueID"); uid = uid_str ? atoi(uid_str) : -1; ClearNameValueList(&data); if(uid < 0 || uid > 65535) { SoapError(h, 402, "Invalid Args"); return; } /* Check that client is not checking a pinhole * it doesn't have access to, because of its public access */ r = upnp_get_pinhole_info(uid, NULL, 0, NULL, iaddr, sizeof(iaddr), &iport, NULL, /* proto */ NULL, 0, /* desc, desclen */ NULL, &packets); if (r >= 0) { if(PinholeVerification(h, iaddr, iport) <= 0) return ; if(packets == 0) { SoapError(h, 709, "NoTrafficReceived"); return; } bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/, 1, action); BuildSendAndCloseSoapResp(h, body, bodylen); } else if(r == -2) SoapError(h, 704, "NoSuchEntry"); else SoapError(h, 501, "Action Failed"); } static void GetPinholePackets(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%u" ""; char body[512]; int bodylen; struct NameValueParserData data; const char * uid_str; int n; char iaddr[INET6_ADDRSTRLEN]; unsigned short iport; unsigned int packets = 0; int uid; int proto; unsigned int leasetime; if(CheckStatus(h)==0) return; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); uid_str = GetValueFromNameValueList(&data, "UniqueID"); uid = uid_str ? atoi(uid_str) : -1; ClearNameValueList(&data); if(uid < 0 || uid > 65535) { SoapError(h, 402, "Invalid Args"); return; } /* Check that client is not getting infos of a pinhole * it doesn't have access to, because of its public access */ n = upnp_get_pinhole_info(uid, NULL, 0, NULL, iaddr, sizeof(iaddr), &iport, &proto, NULL, 0, /* desc, desclen */ &leasetime, &packets); if (n >= 0) { if(PinholeVerification(h, iaddr, iport)<=0) return ; } #if 0 else if(r == -4 || r == -1) { SoapError(h, 704, "NoSuchEntry"); } #endif bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/, packets, action); BuildSendAndCloseSoapResp(h, body, bodylen); } #endif #ifdef ENABLE_DP_SERVICE static void SendSetupMessage(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%s" ""; char body[1024]; int bodylen; struct NameValueParserData data; const char * ProtocolType; /* string */ const char * InMessage; /* base64 */ const char * OutMessage = ""; /* base64 */ ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); ProtocolType = GetValueFromNameValueList(&data, "ProtocolType"); /* string */ InMessage = GetValueFromNameValueList(&data, "InMessage"); /* base64 */ if(ProtocolType == NULL || InMessage == NULL) { ClearNameValueList(&data); SoapError(h, 402, "Invalid Args"); return; } /*if(strcmp(ProtocolType, "DeviceProtection:1") != 0)*/ if(strcmp(ProtocolType, "WPS") != 0) { ClearNameValueList(&data); SoapError(h, 600, "Argument Value Invalid"); /* 703 ? */ return; } /* TODO : put here code for WPS */ bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:DeviceProtection:1"*/, OutMessage, action); BuildSendAndCloseSoapResp(h, body, bodylen); ClearNameValueList(&data); } static void GetSupportedProtocols(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "" ""; char body[1024]; int bodylen; const char * ProtocolList = "\n" "" "WPS" "PKCS5" ""; bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:DeviceProtection:1"*/, ProtocolList, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetAssignedRoles(struct upnphttp * h, const char * action, const char * ns) { static const char resp[] = "" "%s" ""; char body[1024]; int bodylen; const char * RoleList = "Public"; /* list of roles separated by spaces */ #ifdef ENABLE_HTTPS if(h->ssl != NULL) { /* we should get the Roles of the session (based on client certificate) */ X509 * peercert; peercert = SSL_get_peer_certificate(h->ssl); if(peercert != NULL) { RoleList = "Admin Basic"; X509_free(peercert); } } #endif bodylen = snprintf(body, sizeof(body), resp, action, ns/*"urn:schemas-upnp-org:service:DeviceProtection:1"*/, RoleList, action); BuildSendAndCloseSoapResp(h, body, bodylen); } #endif /* Windows XP as client send the following requests : * GetConnectionTypeInfo * GetNATRSIPStatus * ? GetTotalBytesSent - WANCommonInterfaceConfig * ? GetTotalBytesReceived - idem * ? GetTotalPacketsSent - idem * ? GetTotalPacketsReceived - idem * GetCommonLinkProperties - idem * GetStatusInfo - WANIPConnection * GetExternalIPAddress * QueryStateVariable / ConnectionStatus! */ static const struct { const char * methodName; void (*methodImpl)(struct upnphttp *, const char *, const char *); } soapMethods[] = { /* WANCommonInterfaceConfig */ { "QueryStateVariable", QueryStateVariable}, { "GetTotalBytesSent", GetTotalBytesSent}, { "GetTotalBytesReceived", GetTotalBytesReceived}, { "GetTotalPacketsSent", GetTotalPacketsSent}, { "GetTotalPacketsReceived", GetTotalPacketsReceived}, { "GetCommonLinkProperties", GetCommonLinkProperties}, { "GetStatusInfo", GetStatusInfo}, /* WANIPConnection */ { "GetConnectionTypeInfo", GetConnectionTypeInfo }, { "GetNATRSIPStatus", GetNATRSIPStatus}, { "GetExternalIPAddress", GetExternalIPAddress}, { "AddPortMapping", AddPortMapping}, { "DeletePortMapping", DeletePortMapping}, { "GetGenericPortMappingEntry", GetGenericPortMappingEntry}, { "GetSpecificPortMappingEntry", GetSpecificPortMappingEntry}, /* Required in WANIPConnection:2 */ { "SetConnectionType", SetConnectionType}, { "RequestConnection", RequestConnection}, { "ForceTermination", ForceTermination}, { "AddAnyPortMapping", AddAnyPortMapping}, { "DeletePortMappingRange", DeletePortMappingRange}, { "GetListOfPortMappings", GetListOfPortMappings}, #ifdef ENABLE_L3F_SERVICE /* Layer3Forwarding */ { "SetDefaultConnectionService", SetDefaultConnectionService}, { "GetDefaultConnectionService", GetDefaultConnectionService}, #endif #ifdef ENABLE_6FC_SERVICE /* WANIPv6FirewallControl */ { "GetFirewallStatus", GetFirewallStatus}, /* Required */ { "AddPinhole", AddPinhole}, /* Required */ { "UpdatePinhole", UpdatePinhole}, /* Required */ { "GetOutboundPinholeTimeout", GetOutboundPinholeTimeout}, /* Optional */ { "DeletePinhole", DeletePinhole}, /* Required */ { "CheckPinholeWorking", CheckPinholeWorking}, /* Optional */ { "GetPinholePackets", GetPinholePackets}, /* Required */ #endif #ifdef ENABLE_DP_SERVICE /* DeviceProtection */ { "SendSetupMessage", SendSetupMessage}, /* Required */ { "GetSupportedProtocols", GetSupportedProtocols}, /* Required */ { "GetAssignedRoles", GetAssignedRoles}, /* Required */ #endif { 0, 0 } }; void ExecuteSoapAction(struct upnphttp * h, const char * action, int n) { char * p; int i, len, methodlen; char namespace[256]; /* SoapAction example : * urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo */ p = memchr(action, '#', n); if(p) { for(i = 0; i < ((int)sizeof(namespace) - 1) && (action + i) < p; i++) namespace[i] = action[i]; namespace[i] = '\0'; p++; methodlen = n - (int)(p - action); if(p[methodlen-1] == '"') { methodlen--; /* remove the ending " */ } /*syslog(LOG_DEBUG, "SoapMethod: %.*s %d %d %p %p %d", methodlen, p, methodlen, n, action, p, (int)(p - action));*/ for(i = 0; soapMethods[i].methodName; i++) { len = strlen(soapMethods[i].methodName); if((len == methodlen) && memcmp(p, soapMethods[i].methodName, len) == 0) { #ifdef DEBUG syslog(LOG_DEBUG, "Remote Call of SoapMethod '%s' %s", soapMethods[i].methodName, namespace); #endif /* DEBUG */ soapMethods[i].methodImpl(h, soapMethods[i].methodName, namespace); return; } } syslog(LOG_NOTICE, "SoapMethod: Unknown: %.*s %s", methodlen, p, namespace); } else { syslog(LOG_NOTICE, "cannot parse SoapAction"); } SoapError(h, 401, "Invalid Action"); } /* Standard Errors: * * errorCode errorDescription Description * -------- ---------------- ----------- * 401 Invalid Action No action by that name at this service. * 402 Invalid Args Could be any of the following: not enough in args, * too many in args, no in arg by that name, * one or more in args are of the wrong data type. * 403 Out of Sync Out of synchronization. * 501 Action Failed May be returned in current state of service * prevents invoking that action. * 600-699 TBD Common action errors. Defined by UPnP Forum * Technical Committee. * 700-799 TBD Action-specific errors for standard actions. * Defined by UPnP Forum working committee. * 800-899 TBD Action-specific errors for non-standard actions. * Defined by UPnP vendor. */ void SoapError(struct upnphttp * h, int errCode, const char * errDesc) { static const char resp[] = "" "" "" "s:Client" "UPnPError" "" "" "%d" "%s" "" "" "" "" ""; char body[2048]; int bodylen; syslog(LOG_INFO, "Returning UPnPError %d: %s", errCode, errDesc); bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc); BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen); SendRespAndClose_upnphttp(h); } miniupnpd-2.3.7/upnpsoap.h010064400017500000024000000012171203340734000147230ustar00nanardstaff/* $Id: upnpsoap.h,v 1.10 2012/09/27 15:46:18 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPSOAP_H_INCLUDED #define UPNPSOAP_H_INCLUDED /* ExecuteSoapAction(): * this method executes the requested Soap Action */ void ExecuteSoapAction(struct upnphttp *, const char *, int); /* SoapError(): * sends a correct SOAP error with an UPNPError code and * description */ void SoapError(struct upnphttp * h, int errCode, const char * errDesc); #endif miniupnpd-2.3.7/pf/obsdrdr.h010064400017500000024000000043031366430151100151250ustar00nanardstaff/* $Id: obsdrdr.h,v 1.25 2020/05/29 21:48:57 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef OBSDRDR_H_INCLUDED #define OBSDRDR_H_INCLUDED #include "../commonrdr.h" /* add_redirect_rule2() uses DIOCCHANGERULE ioctl * proto can take the values IPPROTO_UDP or IPPROTO_TCP */ int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); /* add_filter_rule2() uses DIOCCHANGERULE ioctl * proto can take the values IPPROTO_UDP or IPPROTO_TCP */ int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc); /* get_redirect_rule() gets internal IP and port from * interface, external port and protocl */ #if 0 int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes); int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes); #endif /* delete_redirect_rule() */ int delete_redirect_rule(const char * ifname, unsigned short eport, int proto); /* delete_redirect_and_filter_rules() */ 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); int clear_filter_rules(void); int clear_nat_rules(void); #endif #endif miniupnpd-2.3.7/pf/obsdrdr.c010064400017500000024000001330401463564776700151520ustar00nanardstaff/* $Id: obsdrdr.c,v 1.104 2024/06/22 16:48:54 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ /* * pf rules created (with ext_if = xl1) * - OpenBSD up to version 4.6 : * rdr pass on xl1 inet proto udp from any to any port = 54321 \ * keep state label "test label" -> 192.168.0.42 port 12345 * or a rdr rule + a pass rule : * rdr quick on xl1 inet proto udp from any to any port = 54321 \ * keep state label "test label" -> 192.168.0.42 port 12345 * pass in quick on xl1 inet proto udp from any to 192.168.0.42 port = 12345 \ * flags S/SA keep state label "test label" * * - OpenBSD starting from version 4.7 * match in on xl1 inet proto udp from any to any port 54321 \ * label "test label" rdr-to 192.168.0.42 port 12345 * or * pass in quick on xl1 inet proto udp from any to any port 54321 \ * label "test label" rdr-to 192.168.0.42 port 12345 * * * * Macros/#defines : * - PF_ENABLE_FILTER_RULES * If set, two rules are created : rdr + pass. Else a rdr/pass rule * is created. * - USE_IFNAME_IN_RULES * If set the interface name is set in the rule. * - PFRULE_INOUT_COUNTS * Must be set with OpenBSD version 3.8 and up, FreeBSD 7.0+, DragonFly 2.8+ * and OS X with pf. * - PFRULE_HAS_RTABLEID * Must be set with OpenBSD version 4.0 and up. * - PF_NEWSTYLE * Must be set with OpenBSD version 4.7 and up. FreeBSD/pfSense is old style. * - USE_LIBPFCTL * libpfctl was introduced in FreeBSD 14. Starting with FreeBSD 15, * several ioctl calls (such as DIOCGETRULE) are removed, so libpfctl has * to be used. */ #include "config.h" #include #include #include #include #include #include #include #ifdef __DragonFly__ #include #else #ifdef __APPLE__ #define PRIVATE 1 #endif #include #endif #include #include #include #include #include #include #include #ifdef USE_LIBPFCTL #include #endif #include "../macros.h" #include "obsdrdr.h" #include "../upnpglobalvars.h" #include "../getifaddr.h" #include "rtickets.h" #ifndef USE_PF #error "USE_PF macro is undefined, check consistency between config.h and Makefile" #else /* list to keep timestamps for port mappings having a lease duration */ struct timestamp_entry { struct timestamp_entry * next; unsigned int timestamp; unsigned short eport; short protocol; }; static struct timestamp_entry * timestamp_list = NULL; static unsigned int get_timestamp(unsigned short eport, int proto) { struct timestamp_entry * e; e = timestamp_list; while(e) { if(e->eport == eport && e->protocol == (short)proto) return e->timestamp; e = e->next; } return 0; } static void remove_timestamp_entry(unsigned short eport, int proto) { struct timestamp_entry * e; struct timestamp_entry * * p; p = ×tamp_list; e = *p; while(e) { if(e->eport == eport && e->protocol == (short)proto) { /* remove the entry */ *p = e->next; free(e); return; } p = &(e->next); e = *p; } } static void add_timestamp_entry(unsigned short eport, int proto, unsigned timestamp) { struct timestamp_entry * tmp; tmp = malloc(sizeof(struct timestamp_entry)); if(tmp) { tmp->next = timestamp_list; tmp->timestamp = timestamp; tmp->eport = eport; tmp->protocol = (short)proto; timestamp_list = tmp; } else { syslog(LOG_ERR, "add_timestamp_entry() malloc(%lu) error", sizeof(struct timestamp_entry)); } } /* /dev/pf when opened */ extern int dev; /* global also used in pfpinhole.c */ int dev = -1; /* shutdown_redirect() : * close the /dev/pf device */ void shutdown_redirect(void) { if(close(dev)<0) syslog(LOG_ERR, "close(\"/dev/pf\"): %m"); dev = -1; } /* open the device */ int init_redirect(void) { #ifdef USE_LIBPFCTL struct pfctl_status *status; #else struct pf_status status; #endif if(dev>=0) shutdown_redirect(); dev = open("/dev/pf", O_RDWR); if(dev<0) { syslog(LOG_ERR, "open(\"/dev/pf\"): %m"); return -1; } #ifdef USE_LIBPFCTL status = pfctl_get_status(dev); if (status == NULL) { syslog(LOG_ERR, "pfctl_get_status: %m"); return -1; } if(!status->running) { pfctl_free_status(status); syslog(LOG_ERR, "pf is disabled"); return -1; } pfctl_free_status(status); #else if(ioctl(dev, DIOCGETSTATUS, &status)<0) { syslog(LOG_ERR, "DIOCGETSTATUS: %m"); return -1; } if(!status.running) { syslog(LOG_ERR, "pf is disabled"); return -1; } #endif return 0; } #if TEST /* for debug */ int clear_redirect_rules(void) { struct pfioc_trans io; struct pfioc_trans_e ioe; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&ioe, 0, sizeof(ioe)); io.size = 1; io.esize = sizeof(ioe); io.array = &ioe; #ifndef PF_NEWSTYLE ioe.rs_num = PF_RULESET_RDR; #else ioe.type = PF_TRANS_RULESET; #endif strlcpy(ioe.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCXBEGIN, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m"); goto error; } if(ioctl(dev, DIOCXCOMMIT, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m"); goto error; } return 0; error: return -1; } int clear_filter_rules(void) { #ifndef PF_ENABLE_FILTER_RULES return 0; #else struct pfioc_trans io; struct pfioc_trans_e ioe; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&ioe, 0, sizeof(ioe)); io.size = 1; io.esize = sizeof(ioe); io.array = &ioe; #ifndef PF_NEWSTYLE ioe.rs_num = PF_RULESET_FILTER; #else /* ? */ ioe.type = PF_TRANS_RULESET; #endif strlcpy(ioe.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCXBEGIN, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m"); goto error; } if(ioctl(dev, DIOCXCOMMIT, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m"); goto error; } return 0; error: return -1; #endif } int clear_nat_rules(void) { struct pfioc_trans io; struct pfioc_trans_e ioe; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&ioe, 0, sizeof(ioe)); io.size = 1; io.esize = sizeof(ioe); io.array = &ioe; #ifndef PF_NEWSTYLE ioe.rs_num = PF_RULESET_NAT; #else /* ? */ ioe.type = PF_TRANS_RULESET; #endif strlcpy(ioe.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCXBEGIN, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m"); goto error; } if(ioctl(dev, DIOCXCOMMIT, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m"); goto error; } return 0; error: return -1; } #endif #ifdef ENABLE_PORT_TRIGGERING /* add_nat_rule() * nat on re0 inet proto udp from 192.168.1.49 port 3074 to any -> (re0) port 3148 * is ext_if, (re0) is ext_ip * should be created when a port mapping (3148 => 192.168.1.49:3074 UDP) is created. * for symetric NAT / UPnP IGD NAT Port Triggering */ int add_nat_rule(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc) { int r; struct pfioc_rule pcr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; struct pf_pooladdr *a; #endif const char * extaddr; char extaddr_buf[INET_ADDRSTRLEN]; struct in_addr wan_addr; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } if(getifaddr(ifname, extaddr_buf, INET_ADDRSTRLEN, &wan_addr, NULL) < 0) { syslog(LOG_WARNING, "failed to get address for interface %s", ifname); if(use_ext_ip_addr && use_ext_ip_addr[0] != '\0') { extaddr = use_ext_ip_addr; } else { return -1; /* no address to use => failure */ } } else { if (addr_is_reserved(&wan_addr)) { syslog(LOG_DEBUG, "WAN IP is reserved, it will be used for NAT"); extaddr = extaddr_buf; } else if (use_ext_ip_addr && use_ext_ip_addr[0] != '\0') { extaddr = use_ext_ip_addr; } else { extaddr = extaddr_buf; } } syslog(LOG_DEBUG, "use external ip %s", extaddr); r = 0; memset(&pcr, 0, sizeof(pcr)); strlcpy(pcr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m"); r = -1; } else { pcr.pool_ticket = pp.ticket; #else { pcr.rule.nat.addr.type = PF_ADDR_ADDRMASK; pcr.rule.rdr.addr.type = PF_ADDR_NONE; #endif /*pcr.rule.src.addr.type = PF_ADDR_NONE;*/ pcr.rule.src.addr.type = PF_ADDR_ADDRMASK; pcr.rule.dst.addr.type = PF_ADDR_ADDRMASK; #ifndef PF_NEWSTYLE pcr.rule.action = PF_NAT; #else pcr.rule.action = PF_PASS; /* or PF_MATCH as we dont expect outbound packets to be blocked */ pcr.rule.direction = PF_OUT; #endif pcr.rule.af = AF_INET; #ifdef USE_IFNAME_IN_RULES if(ifname) strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); #endif pcr.rule.proto = proto; pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0; /*logpackets;*/ #ifdef PFRULE_HAS_RTABLEID pcr.rule.rtableid = -1; /* first appeared in OpenBSD 4.0 */ #endif #ifdef PFRULE_HAS_ONRDOMAIN pcr.rule.onrdomain = -1; /* first appeared in OpenBSD 5.0 */ #endif pcr.rule.quick = 1; pcr.rule.keep_state = PF_STATE_NORMAL; if(tag) strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE); strlcpy(pcr.rule.label, desc, PF_RULE_LABEL_SIZE); #ifdef PFVAR_NEW_STYLE inet_pton(AF_INET, iaddr, &pcr.rule.src.addr.v.a.addr.v4addr.s_addr); pcr.rule.src.addr.v.a.mask.v4addr.s_addr = htonl(INADDR_NONE); #else inet_pton(AF_INET, iaddr, &pcr.rule.src.addr.v.a.addr.v4.s_addr); pcr.rule.src.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); #endif #ifdef __APPLE__ pcr.rule.src.xport.range.op = PF_OP_EQ; pcr.rule.src.xport.range.port[0] = htons(iport); pcr.rule.src.xport.range.port[1] = htons(iport); #else pcr.rule.src.port_op = PF_OP_EQ; pcr.rule.src.port[0] = htons(iport); pcr.rule.src.port[1] = htons(iport); #endif if(rhost && rhost[0] != '\0' && rhost[0] != '*') { #ifdef PFVAR_NEW_STYLE inet_pton(AF_INET, rhost, &pcr.rule.dst.addr.v.a.addr.v4addr.s_addr); pcr.rule.dst.addr.v.a.mask.v4addr.s_addr = htonl(INADDR_NONE); #else inet_pton(AF_INET, rhost, &pcr.rule.dst.addr.v.a.addr.v4.s_addr); pcr.rule.dst.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); #endif } #ifdef __APPLE__ pcr.rule.dst.xport.range.op = PF_OP_NONE; #else pcr.rule.dst.port_op = PF_OP_NONE; #endif /* -> xxx.xxx.xxx.xxx port 1234 */ #ifndef PF_NEWSTYLE pcr.rule.rpool.proxy_port[0] = eport; pcr.rule.rpool.proxy_port[1] = eport; TAILQ_INIT(&pcr.rule.rpool.list); a = calloc(1, sizeof(struct pf_pooladdr)); #ifdef PFVAR_NEW_STYLE inet_pton(AF_INET, extaddr, &a->addr.v.a.addr.v4addr.s_addr); a->addr.v.a.mask.v4addr.s_addr = htonl(INADDR_NONE); #else inet_pton(AF_INET, extaddr, &a->addr.v.a.addr.v4.s_addr); a->addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); #endif TAILQ_INSERT_TAIL(&pcr.rule.rpool.list, a, entries); memcpy(&pp.addr, a, sizeof(struct pf_pooladdr)); if(ioctl(dev, DIOCADDADDR, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCADDADDR, ...): %m"); r = -1; } else { #else pcr.rule.nat.proxy_port[0] = eport; pcr.rule.nat.proxy_port[1] = eport; inet_pton(AF_INET, extaddr, &pcr.rule.nat.addr.v.a.addr.v4.s_addr); pcr.rule.nat.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); { #endif /* PF_NEWSTYLE */ pcr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); r = -1; } else { pcr.action = PF_CHANGE_ADD_TAIL; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); r = -1; } } } #ifndef PF_NEWSTYLE free(a); #endif } return r; } /* * returns: 0 : OK * -1 : ERROR * -2 : Rule not found */ static int delete_nat_rule(const char * ifname, unsigned short iport, int proto, in_addr_t iaddr) { int i, n, r; unsigned int tnum; #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; struct pfctl_rule rule; #define RULE (rule) char anchor_call[MAXPATHLEN] = ""; #else struct pfioc_rule pr; #define RULE (pr.rule) #endif UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } #ifdef USE_LIBPFCTL if(pfctl_get_rules_info(dev, &ri, PF_PASS, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); return -1; } n = ri.nr; #ifdef PF_RELEASETICKETS tnum = ri.ticket; #endif /* PF_RELEASETICKETS */ #else /* USE_LIBPFCTL */ memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE RULE.action = PF_NAT; #else RULE.action = PF_PASS; /* or PF_MATCH as we dont expect outbound packets to be blocked */ RULE.direction = PF_OUT; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ #endif /* USE_LIBPFCTL */ r = -2; /* not found */ for(i=0; iaddr.v.a.addr.v4addr.s_addr); a->addr.v.a.mask.v4addr.s_addr = htonl(INADDR_NONE); #else inet_pton(AF_INET, iaddr, &a->addr.v.a.addr.v4.s_addr); a->addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); #endif TAILQ_INSERT_TAIL(&pcr.rule.rpool.list, a, entries); memcpy(&pp.addr, a, sizeof(struct pf_pooladdr)); if(ioctl(dev, DIOCADDADDR, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCADDADDR, ...): %m"); r = -1; } else { #else pcr.rule.rdr.proxy_port[0] = iport; pcr.rule.rdr.proxy_port[1] = iport; inet_pton(AF_INET, iaddr, &pcr.rule.rdr.addr.v.a.addr.v4.s_addr); pcr.rule.rdr.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); if(1) { #endif pcr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); r = -1; } else { pcr.action = PF_CHANGE_ADD_TAIL; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); r = -1; } } } #ifndef PF_NEWSTYLE free(a); #endif } if(r == 0 && timestamp > 0) add_timestamp_entry(eport, proto, timestamp); #ifdef ENABLE_PORT_TRIGGERING if(r == 0 && proto == IPPROTO_UDP) { add_nat_rule(ifname, rhost, eport, iaddr, iport, proto, desc); /* TODO check error */ } #endif return r; } /* thanks to Seth Mos for this function */ int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { #ifndef PF_ENABLE_FILTER_RULES UNUSED(ifname); UNUSED(rhost); UNUSED(iaddr); UNUSED(eport); UNUSED(iport); UNUSED(proto); UNUSED(desc); return 0; #else int r; struct pfioc_rule pcr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif #ifndef USE_IFNAME_IN_RULES UNUSED(ifname); #endif UNUSED(eport); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } r = 0; memset(&pcr, 0, sizeof(pcr)); strlcpy(pcr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m"); r = -1; } else { pcr.pool_ticket = pp.ticket; #else if(1) { #endif pcr.rule.dst.port_op = PF_OP_EQ; pcr.rule.dst.port[0] = htons(iport); pcr.rule.direction = PF_IN; pcr.rule.action = PF_PASS; pcr.rule.af = AF_INET; #ifdef USE_IFNAME_IN_RULES if(ifname) strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); #endif pcr.rule.proto = proto; pcr.rule.quick = (GETFLAG(PFNOQUICKRULESMASK))?0:1; pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0; /*logpackets;*/ /* see the discussion on the forum : * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=638 */ pcr.rule.flags = TH_SYN; pcr.rule.flagset = (TH_SYN|TH_ACK); #ifdef PFRULE_HAS_RTABLEID pcr.rule.rtableid = -1; /* first appeared in OpenBSD 4.0 */ #endif #ifdef PFRULE_HAS_ONRDOMAIN pcr.rule.onrdomain = -1; /* first appeared in OpenBSD 5.0 */ #endif pcr.rule.keep_state = 1; strlcpy(pcr.rule.label, desc, PF_RULE_LABEL_SIZE); if(queue) strlcpy(pcr.rule.qname, queue, PF_QNAME_SIZE); if(tag) strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE); if(rhost && rhost[0] != '\0' && rhost[0] != '*') { inet_pton(AF_INET, rhost, &pcr.rule.src.addr.v.a.addr.v4.s_addr); pcr.rule.src.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); } /* we want any - iaddr port = # keep state label */ inet_pton(AF_INET, iaddr, &pcr.rule.dst.addr.v.a.addr.v4.s_addr); pcr.rule.dst.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); #ifndef PF_NEWSTYLE pcr.rule.rpool.proxy_port[0] = iport; pcr.rule.rpool.proxy_port[1] = iport; TAILQ_INIT(&pcr.rule.rpool.list); #endif if(1) { pcr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); r = -1; } else { pcr.action = PF_CHANGE_ADD_TAIL; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); r = -1; } } } } return r; #endif } /* get_redirect_rule_count() * return value : -1 for error or the number of rdr rules */ int get_redirect_rule_count(const char * ifname) { #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; #else struct pfioc_rule pr; #endif UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } #ifdef USE_LIBPFCTL if (pfctl_get_rules_info(dev, &ri, PF_RDR, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); return -1; } return ri.nr; #else memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE RULE.action = PF_RDR; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } release_ticket(dev, pr.ticket); return pr.nr; #endif } /* get_redirect_rule() * return value : 0 success (found) * -1 = error * -2 = rule not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int i, n, r; unsigned int tnum; #undef RULE #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; struct pfctl_rule rule; #define RULE (rule) char anchor_call[MAXPATHLEN] = ""; #else /* USE_LIBPFCTL */ struct pfioc_rule pr; #define RULE (pr.rule) #endif /* USE_LIBPFCTL */ #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } #ifdef USE_LIBPFCTL if(pfctl_get_rules_info(dev, &ri, PF_RDR, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); return -1; } n = ri.nr; #ifdef PF_RELEASETICKETS tnum = ri.ticket; #endif /* PF_RELEASETICKETS */ #else /* USE_LIBPFCTL */ memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE RULE.action = PF_RDR; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ #endif /* USE_LIBPFCTL */ r = -2; for(i=0; i 0) { #ifdef PFVAR_NEW_STYLE if (RULE.src.addr.v.a.addr.v4addr.s_addr == 0) #else if (RULE.src.addr.v.a.addr.v4.s_addr == 0) #endif { rhost[0] = '\0'; /* empty string */ } else { #ifdef PFVAR_NEW_STYLE inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4addr.s_addr, rhost, rhostlen); #else inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4.s_addr, rhost, rhostlen); #endif } } if(timestamp) *timestamp = get_timestamp(eport, proto); r = 0; break; } } release_ticket(dev, tnum); return r; } #define priv_delete_redirect_rule(ifname, eport, proto, iport, \ iaddr, rhost, rhostlen) \ priv_delete_redirect_rule_check_desc(ifname, eport, proto, iport, \ iaddr, rhost, rhostlen, 0, NULL) /* if check_desc is true, only delete the rule if the description differs. * returns : -2 : rule not found * -1 : error * 0 : rule deleted * 1 : rule untouched */ static int priv_delete_redirect_rule_check_desc(const char * ifname, unsigned short eport, int proto, unsigned short * iport, in_addr_t * iaddr, char * rhost, int rhostlen, int check_desc, const char * desc) { int i, n, r; unsigned int tnum; struct pfioc_rule pr; #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; struct pfctl_rule rule; #define RULE (rule) char anchor_call[MAXPATHLEN] = ""; #else /* USE_LIBPFCTL */ #define RULE (pr.rule) #endif /* USE_LIBPFCTL */ UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE RULE.action = PF_RDR; pr.rule.action = PF_RDR; /* used with USE_LIBPFCTL for the DIOCCHANGERULE calls */ #endif #ifdef USE_LIBPFCTL if (pfctl_get_rules_info(dev, &ri, PF_RDR, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); return -1; } pr.ticket = ri.ticket; n = ri.nr; #else /* USE_LIBPFCTL */ if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ #endif /* USE_LIBPFCTL */ r = -2; for(i=0; i 0) { #ifdef PFVAR_NEW_STYLE if (RULE.src.addr.v.a.addr.v4addr.s_addr == 0) #else if (RULE.src.addr.v.a.addr.v4.s_addr == 0) #endif rhost[0] = '\0'; /* empty string */ else #ifdef PFVAR_NEW_STYLE inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4addr.s_addr, rhost, rhostlen); #else inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4.s_addr, rhost, rhostlen); #endif } if(check_desc) { #ifdef USE_LIBPFCTL if((desc == NULL && RULE.label[0][0] == '\0') || (desc && 0 == strcmp(desc, RULE.label[0]))) { #else /* USE_LIBPFCTL */ if((desc == NULL && RULE.label[0] == '\0') || (desc && 0 == strcmp(desc, RULE.label))) { #endif /* USE_LIBPFCTL */ r = 1; break; } } pr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); r = -1; break; } pr.action = PF_CHANGE_REMOVE; pr.nr = i; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m"); r = -1; break; } remove_timestamp_entry(eport, proto); r = 0; break; } } if (r == -2) syslog(LOG_NOTICE, "could not find redirect rule to delete eport=%hu", eport); release_ticket(dev, tnum); return r; } int delete_redirect_rule(const char * ifname, unsigned short eport, int proto) { return priv_delete_redirect_rule(ifname, eport, proto, NULL, NULL, NULL, 0); } /* returns: 0 : OK * -1 : Error * -2 : rule not found */ static int priv_delete_filter_rule(const char * ifname, unsigned short iport, int proto, in_addr_t iaddr) { #ifndef PF_ENABLE_FILTER_RULES UNUSED(ifname); UNUSED(iport); UNUSED(proto); UNUSED(iaddr); return 0; #else int i, n, r; unsigned int tnum; struct pfioc_rule pr; UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); RULE.action = PF_PASS; if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ r = -2; for(i=0; i= n) goto error; #ifdef USE_LIBPFCTL if(pfctl_get_rule(dev, index, ri.ticket, anchor_name, PF_RDR, &rule, anchor_call) < 0) { syslog(LOG_ERR, "pfctl_get_rule: %m"); goto error; } #else /* USE_LIBPFCTL */ pr.nr = index; if(ioctl(dev, DIOCGETRULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); goto error; } #endif /* USE_LIBPFCTL */ *proto = RULE.proto; #ifdef __APPLE__ *eport = ntohs(RULE.dst.xport.range.port[0]); #else *eport = ntohs(RULE.dst.port[0]); #endif #ifndef PF_NEWSTYLE *iport = RULE.rpool.proxy_port[0]; #else *iport = RULE.rdr.proxy_port[0]; #endif if(ifname) strlcpy(ifname, RULE.ifname, IFNAMSIZ); if(desc) #ifdef USE_LIBPFCTL strlcpy(desc, RULE.label[0], desclen); #else /* USE_LIBPFCTL */ strlcpy(desc, RULE.label, desclen); #endif /* USE_LIBPFCTL */ #ifdef PFRULE_INOUT_COUNTS if(packets) *packets = RULE.packets[0] + RULE.packets[1]; if(bytes) *bytes = RULE.bytes[0] + RULE.bytes[1]; #else if(packets) *packets = RULE.packets; if(bytes) *bytes = RULE.bytes; #endif #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); pp.r_action = PF_RDR; pp.r_num = index; #ifdef USE_LIBPFCTL pp.ticket = ri.ticket; #else /* USE_LIBPFCTL */ pp.ticket = pr.ticket; #endif /* USE_LIBPFCTL */ if(ioctl(dev, DIOCGETADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETADDRS, ...): %m"); goto error; } if(pp.nr != 1) { syslog(LOG_NOTICE, "No address associated with pf rule"); goto error; } pp.nr = 0; /* first */ if(ioctl(dev, DIOCGETADDR, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETADDR, ...): %m"); goto error; } #ifdef PFVAR_NEW_STYLE inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4addr.s_addr, iaddr, iaddrlen); #else inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr, iaddr, iaddrlen); #endif #else inet_ntop(AF_INET, &RULE.rdr.addr.v.a.addr.v4.s_addr, iaddr, iaddrlen); #endif if(rhost && rhostlen > 0) { #ifdef PFVAR_NEW_STYLE if (RULE.src.addr.v.a.addr.v4addr.s_addr == 0) #else if (RULE.src.addr.v.a.addr.v4.s_addr == 0) #endif { rhost[0] = '\0'; /* empty string */ } else { #ifdef PFVAR_NEW_STYLE inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4addr.s_addr, rhost, rhostlen); #else inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4.s_addr, rhost, rhostlen); #endif } } if(timestamp) *timestamp = get_timestamp(*eport, *proto); r = 0; error: release_ticket(dev, tnum); return r; } /* return an (malloc'ed) array of "external" port for which there is * a port mapping. number is the size of the array */ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number) { unsigned short * array; unsigned int capacity, tnum; int i, n; unsigned short eport; #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; struct pfctl_rule rule; #define RULE (rule) #else /* USE_LIBPFCTL */ struct pfioc_rule pr; #define RULE (pr.rule) #endif *number = 0; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return NULL; } capacity = 128; array = calloc(capacity, sizeof(unsigned short)); if(!array) { syslog(LOG_ERR, "get_portmappings_in_range() : calloc error"); return NULL; } #ifdef USE_LIBPFCTL if (pfctl_get_rules_info(dev, &ri, PF_RDR, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); free(array); return NULL; } n = ri.nr; #else /* USE_LIBPFCTL */ memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE RULE.action = PF_RDR; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); free(array); return NULL; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ #endif /* USE_LIBPFCTL */ for(i=0; i= capacity) { /* need to increase the capacity of the array */ unsigned short * tmp; capacity += 128; tmp = realloc(array, sizeof(unsigned short)*capacity); if(!tmp) { syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%lu) error", sizeof(unsigned short)*capacity); *number = 0; free(array); release_ticket(dev, tnum); return NULL; } array = tmp; } array[*number] = eport; (*number)++; } } release_ticket(dev, tnum); return array; } /* update the port mapping internal port, description and timestamp * returns: 0 : OK * -1 : Error */ 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; if (inet_ntop(AF_INET, &iaddr, iaddr_str, sizeof(iaddr_str)) == NULL) { syslog(LOG_ERR, "inet_ntop(AF_INET, ...): %m"); return -1; } 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 description 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]; int r; r = priv_delete_redirect_rule_check_desc(ifname, eport, proto, &iport, &iaddr, rhost, sizeof(rhost), 1, desc); if(r < 0) return -1; if(r == 1) { /* only change timestamp */ remove_timestamp_entry(eport, proto); add_timestamp_entry(eport, proto, timestamp); return 0; } if (priv_delete_filter_rule(ifname, iport, proto, iaddr) < 0) return -1; if (inet_ntop(AF_INET, &iaddr, iaddr_str, sizeof(iaddr_str)) == NULL) { syslog(LOG_ERR, "inet_ntop(AF_INET, ...): %m"); return -1; } 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 list_rules(void) { char buf[32]; char buf2[32]; int i, n; unsigned int tnum; #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; struct pfctl_rule rule; #define RULE (rule) char anchor_call[MAXPATHLEN] = ""; #else /* USE_LIBPFCTL */ struct pfioc_rule pr; #define RULE (pr.rule) #endif /* USE_LIBPFCTL */ #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif if(dev<0) { perror("pf dev not open"); return ; } #ifdef USE_LIBPFCTL if (pfctl_get_rules_info(dev, &ri, PF_RDR, anchor_name) < 0) perror("pfctl_get_rules_info"); printf("ticket = %d, nr = %d\n", ri.ticket, ri.nr); n = ri.nr; #else /* USE_LIBPFCTL */ memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); RULE.action = PF_RDR; if(ioctl(dev, DIOCGETRULES, &pr) < 0) perror("DIOCGETRULES"); printf("ticket = %d, nr = %d\n", pr.ticket, pr.nr); n = pr.nr; #endif /* USE_LIBPFCTL */ for(i=0; i %s %d:%d proto %d keep_state=%d action=%d\n", RULE.ifname, inet_ntop(AF_INET, &RULE.src.addr.v.a.addr.v4.s_addr, buf, 32), (int)ntohs(RULE.dst.port[0]), (int)ntohs(RULE.dst.port[1]), inet_ntop(AF_INET, &RULE.dst.addr.v.a.addr.v4.s_addr, buf2, 32), #ifndef PF_NEWSTYLE (int)RULE.rpool.proxy_port[0], (int)RULE.rpool.proxy_port[1], #else (int)RULE.rdr.proxy_port[0], (int)RULE.rdr.proxy_port[1], #endif (int)RULE.proto, (int)RULE.keep_state, (int)RULE.action); #ifdef USE_LIBPFCTL printf(" description: \"%s\"\n", RULE.label[0]); #else /* USE_LIBPFCTL */ printf(" description: \"%s\"\n", RULE.label); #endif /* USE_LIBPFCTL */ #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); pp.r_action = PF_RDR; pp.r_num = i; #ifdef USE_LIBPFCTL pp.ticket = ri.ticket; #else pp.ticket = pr.ticket; #endif if(ioctl(dev, DIOCGETADDRS, &pp) < 0) perror("DIOCGETADDRS"); printf(" nb pool addr = %d ticket=%d\n", pp.nr, pp.ticket); /*if(ioctl(dev, DIOCGETRULE, &pr) < 0) perror("DIOCGETRULE"); */ pp.nr = 0; /* first */ if(ioctl(dev, DIOCGETADDR, &pp) < 0) perror("DIOCGETADDR"); /* addr.v.a.addr.v4.s_addr */ printf(" %s\n", inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr, buf, 32)); #else printf(" rule_flag=%08x action=%d direction=%d log=%d logif=%d " "quick=%d ifnot=%d af=%d type=%d code=%d rdr.port_op=%d rdr.opts=%d\n", RULE.rule_flag, RULE.action, RULE.direction, RULE.log, RULE.logif, RULE.quick, RULE.ifnot, RULE.af, RULE.type, RULE.code, RULE.rdr.port_op, RULE.rdr.opts); printf(" %s\n", inet_ntop(AF_INET, &RULE.rdr.addr.v.a.addr.v4.s_addr, buf, 32)); #endif } release_ticket(dev, tnum); } #endif /* TEST */ #endif /* USE_PF */ miniupnpd-2.3.7/pf/testobsdrdr.c010064400017500000024000000074541463564776700160630ustar00nanardstaff/* $Id: testobsdrdr.c,v 1.32 2024/06/22 16:48:54 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 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 "obsdrdr.h" /*int logpackets = 1;*/ int runtime_flags = 0; const char * tag = 0; const char * anchor_name = "miniupnpd"; const char * queue = NULL; const char * use_ext_ip_addr = "42.42.42.42"; void list_rules(void); void list_eports_tcp(void) { unsigned short * port_list; unsigned int number = 0; unsigned int i; port_list = get_portmappings_in_range(0, 65535, IPPROTO_TCP, &number); printf("%u ports redirected (TCP) :", number); for(i = 0; i < number; i++) { printf(" %hu", port_list[i]); } printf("\n"); free(port_list); } void test_index(void) { char ifname[16/*IFNAMSIZ*/]; char iaddr[32]; char desc[64]; char rhost[32]; unsigned short iport = 0; unsigned short eport = 0; int proto = 0; unsigned int timestamp; ifname[0] = '\0'; iaddr[0] = '\0'; rhost[0] = '\0'; if(get_redirect_rule_by_index(0, ifname, &eport, iaddr, sizeof(iaddr), &iport, &proto, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, 0, 0) < 0) { printf("get.._by_index : no rule\n"); } else { printf("%s %u -> %s:%u proto %d\n", ifname, (unsigned int)eport, iaddr, (unsigned int)iport, proto); printf("description: \"%s\"\n", desc); } } int main(int argc, char * * argv) { char buf[32]; char desc[64]; char rhost[64]; /*char rhost[32];*/ unsigned short iport; unsigned int timestamp; u_int64_t packets = 0; u_int64_t bytes = 0; int clear = 0; if(argc > 1) { if(0 == strcmp(argv[1], "--clear") || 0 == strcmp(argv[1], "-c")) clear = 1; } openlog("testobsdrdr", LOG_PERROR, LOG_USER); if(init_redirect() < 0) { fprintf(stderr, "init_redirect() failed\n"); return 1; } #if 0 add_redirect_rule("ep0", 12123, "192.168.1.23", 1234); add_redirect_rule2("ep0", 12155, "192.168.1.155", 1255, IPPROTO_TCP); #endif if(add_redirect_rule2("ep0", NULL/*"8.8.8.8"*/, 12123, "192.168.1.125", 1234, IPPROTO_UDP, "test description", 0) < 0) printf("add_redirect_rule2() #3 failed\n"); use_ext_ip_addr = NULL; if(add_redirect_rule2("em0", NULL, 12123, "127.1.2.3", 1234, IPPROTO_TCP, "test description tcp", 0) < 0) printf("add_redirect_rule2() #4 failed\n"); if(add_filter_rule2("em0", NULL, "127.1.2.3", 12123, 1234, IPPROTO_TCP, "test description tcp") < 0) printf("add_filter_rule2() #1 failed\n"); list_rules(); list_eports_tcp(); if(get_redirect_rule("xl1", 4662, IPPROTO_TCP, buf, sizeof(buf), &iport, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) < 0) printf("get_redirect_rule() failed\n"); else { printf("\n%s:%d '%s' packets=%" PRIu64 " bytes=%" PRIu64 "\n", buf, (int)iport, desc, packets, bytes); } /* if(delete_redirect_rule("ep0", 12123, IPPROTO_UDP) < 0) printf("delete_redirect_rule() failed\n"); */ if(delete_redirect_and_filter_rules("ep0", 12123, IPPROTO_UDP) < 0) printf("delete_redirect_and_filter_rules() failed\n"); if(delete_redirect_rule("ep0", 12123, IPPROTO_UDP) < 0) printf("delete_redirect_rule() failed\n"); if(delete_redirect_and_filter_rules("em0", 12123, IPPROTO_TCP) < 0) printf("delete_redirect_and_filter_rules() failed\n"); test_index(); if(clear) { clear_redirect_rules(); clear_filter_rules(); clear_nat_rules(); } /*list_rules();*/ return 0; } miniupnpd-2.3.7/pf/Makefile010064400017500000024000000016331463564776700150110ustar00nanardstaff# $Id: Makefile,v 1.8 2024/06/22 16:48:54 nanard Exp $ # made for GNU Make (and BSD make) # only for testing purposes. # Please follow instructions in INSTALL file for building miniUPnPd CFLAGS = -Wall -g -DTEST CFLAGS += -Wextra CFLAGS += -I.. -I../build EXECUTABLES = testobsdrdr testpfpinhole # to test libpfctl CFLAGS += -I/usr/local/include/ LDFLAGS += -L/usr/local/lib LIBS += -lpfctl all: $(EXECUTABLES) clean: rm -f *.o $(EXECUTABLES) testobsdrdr: testobsdrdr.o obsdrdr.o getifaddr.o $(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ $> testpfpinhole: testpfpinhole.o obsdrdr.o pfpinhole.o \ getifaddr.o upnputils.o getroute.o $(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ $> obsdrdr.o: obsdrdr.c obsdrdr.h pfpinhole.o: pfpinhole.c pfpinhole.h testobsdrdr.o: testobsdrdr.c obsdrdr.h testpfpinhole.o: testpfpinhole.c pfpinhole.h getifaddr.o: ../getifaddr.c upnputils.o: ../upnputils.c getroute.o: ../bsd/getroute.c miniupnpd-2.3.7/bsd/Makefile010064400017500000024000000006621156543264100151330ustar00nanardstaff# $Id: Makefile,v 1.2 2011/05/20 09:34:25 nanard Exp $ # made for GNU Make CFLAGS = -Wall -g EXECUTABLES = testgetifstats testifacewatcher all: $(EXECUTABLES) clean: rm -f *.o $(EXECUTABLES) testobsdrdr.o: testobsdrdr.c obsdrdr.h testgetifstats: testgetifstats.o getifstats.o $(CC) $(CFLAGS) -o $@ $> -lkvm testifacewatcher: testifacewatcher.o ifacewatcher.o upnputils.o $(CC) $(CFLAGS) -o $@ $> upnputils.o: ../upnputils.c miniupnpd-2.3.7/bsd/getifstats.c010064400017500000024000000033731365610016700160140ustar00nanardstaff/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * author: Gleb Smirnoff * (c) 2006 Ryan Wagoner * (c) 2014 Gleb Smirnoff * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #ifdef ENABLE_GETIFSTATS_CACHING #include #endif #include "../getifstats.h" #include "config.h" int getifstats(const char *ifname, struct ifdata *data) { static struct ifaddrs *ifap, *ifa; #ifdef ENABLE_GETIFSTATS_CACHING static time_t cache_timestamp; time_t current_time; #endif if(!data) return -1; data->baudrate = 4200000; data->opackets = 0; data->ipackets = 0; data->obytes = 0; data->ibytes = 0; if(!ifname || ifname[0]=='\0') return -1; #ifdef ENABLE_GETIFSTATS_CACHING current_time = upnp_time(); if (ifap != NULL && current_time < cache_timestamp + GETIFSTATS_CACHING_DURATION) goto copy; #endif if (ifap != NULL) { freeifaddrs(ifap); ifap = NULL; } if (getifaddrs(&ifap) != 0) { syslog (LOG_ERR, "getifstats() : getifaddrs(): %s", strerror(errno)); return (-1); } for (ifa = ifap; ifa; ifa = ifa->ifa_next) if (ifa->ifa_addr->sa_family == AF_LINK && strcmp(ifa->ifa_name, ifname) == 0) { #ifdef ENABLE_GETIFSTATS_CACHING cache_timestamp = current_time; copy: #endif #define IFA_STAT(s) (((struct if_data *)ifa->ifa_data)->ifi_ ## s) data->opackets = IFA_STAT(opackets); data->ipackets = IFA_STAT(ipackets); data->obytes = IFA_STAT(obytes); data->ibytes = IFA_STAT(ibytes); data->baudrate = IFA_STAT(baudrate); return (0); } return (-1); } miniupnpd-2.3.7/bsd/testgetifstats.c010064400017500000024000000013311172522177200167050ustar00nanardstaff/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include "../getifstats.h" int main(int argc, char * * argv) { int r; struct ifdata data; printf("usage: %s if_name\n", argv[0]); if(argc<2) return -1; r = getifstats(argv[1], &data); if(r<0) printf("getifstats() failed\n"); else { printf("ipackets = %10lu opackets = %10lu\n", data.ipackets, data.opackets); printf("ibytes = %10lu obytes = %10lu\n", data.ibytes, data.obytes); printf("baudrate = %10lu\n", data.baudrate); } return 0; } miniupnpd-2.3.7/solaris/getifstats.c010064400017500000024000000043351302473124100167070ustar00nanardstaff/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * author: Ryan Wagoner and Thomas Bernard * (c) 2007 Darren Reed * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include "../getifstats.h" int getifstats(const char * ifname, struct ifdata * data) { char buffer[64], *s; kstat_named_t *kn; kstat_ctl_t *kc; int instance; kstat_t *ksp; if (data == NULL) goto error; if (ifname == NULL || *ifname == '\0') goto error; s = (char *)ifname + strlen(ifname); s--; while ((s > ifname) && isdigit(*s)) s--; s++; instance = atoi(s); strlcpy(buffer, ifname, MIN(s - ifname + 1, 64)); kc = kstat_open(); if (kc != NULL) { ksp = kstat_lookup(kc, buffer, instance, (char *)ifname); if (ksp && (kstat_read(kc, ksp, NULL) != -1)) { /* found the right interface */ if (sizeof(long) == 8) { kn = kstat_data_lookup(ksp, "rbytes64"); if (kn != NULL) { data->ibytes = kn->value.i64; } kn = kstat_data_lookup(ksp, "ipackets64"); if (kn != NULL) { data->ipackets = kn->value.i64; } kn = kstat_data_lookup(ksp, "obytes64"); if (kn != NULL) { data->obytes = kn->value.i64; } kn = kstat_data_lookup(ksp, "opackets64"); if (kn != NULL) { data->opackets = kn->value.i64; } } else { kn = kstat_data_lookup(ksp, "rbytes"); if (kn != NULL) { data->ibytes = kn->value.i32; } kn = kstat_data_lookup(ksp, "ipackets"); if (kn != NULL) { data->ipackets = kn->value.i32; } kn = kstat_data_lookup(ksp, "obytes"); if (kn != NULL) { data->obytes = kn->value.i32; } kn = kstat_data_lookup(ksp, "opackets"); if (kn != NULL) { data->ipackets = kn->value.i32; } } kn = kstat_data_lookup(ksp, "ifspeed"); if (kn != NULL) { data->baudrate = kn->value.i32; } kstat_close(kc); return 0; /* ok */ } syslog(LOG_ERR, "kstat_lookup/read() failed: %m"); kstat_close(kc); return -1; } else { syslog(LOG_ERR, "kstat_open() failed: %m"); } error: return -1; /* not found or error */ } miniupnpd-2.3.7/getifstats.h010064400017500000024000000012231203340734000152300ustar00nanardstaff/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2008 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef GETIFSTATS_H_INCLUDED #define GETIFSTATS_H_INCLUDED struct ifdata { unsigned long opackets; unsigned long ipackets; unsigned long obytes; unsigned long ibytes; unsigned long baudrate; }; /* getifstats() * Fill the ifdata structure with statistics for network interface ifname. * Return 0 in case of success, -1 for bad arguments or any error */ int getifstats(const char * ifname, struct ifdata * data); #endif miniupnpd-2.3.7/upnpredirect.h010064400017500000024000000073111327254414700156000ustar00nanardstaff/* $Id: upnpredirect.h,v 1.37 2018/05/03 08:27:41 nanard Exp $ */ /* 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 */ #ifndef UPNPREDIRECT_H_INCLUDED #define UPNPREDIRECT_H_INCLUDED /* for u_int64_t */ #include #include "config.h" #ifdef ENABLE_LEASEFILE int reload_from_lease_file(void); #ifdef LEASEFILE_USE_REMAINING_TIME void lease_file_rewrite(void); #endif #endif /* upnp_redirect() * calls OS/fw dependent implementation of the redirection. * protocol should be the string "TCP" or "UDP" * returns: 0 on success * -1 failed to redirect * -2 already redirected * -3 permission check failed */ int upnp_redirect(const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, const char * protocol, const char * desc, unsigned int leaseduration); /* upnp_redirect_internal() * same as upnp_redirect() without any check */ int upnp_redirect_internal(const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); /* upnp_get_redirection_infos() * returns : 0 on success * -1 failed to get the port mapping entry or no entry exists */ int upnp_get_redirection_infos(unsigned short eport, const char * protocol, unsigned short * iport, char * iaddr, int iaddrlen, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * leaseduration); /* upnp_get_redirection_infos_by_index() * returns : 0 on success * -1 failed to get the port mapping or index out of range */ int upnp_get_redirection_infos_by_index(int index, unsigned short * eport, char * protocol, unsigned short * iport, char * iaddr, int iaddrlen, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * leaseduration); /* upnp_delete_redirection() * returns: 0 on success * -1 on failure*/ int upnp_delete_redirection(unsigned short eport, const char * protocol); /* _upnp_delete_redir() * same as above */ int _upnp_delete_redir(unsigned short eport, int proto); /* Periodic cleanup functions */ struct rule_state { u_int64_t packets; u_int64_t bytes; struct rule_state * next; unsigned short eport; unsigned char proto; unsigned char to_remove; }; /* return a linked list of all rules * or an empty list if there are not enough * As a "side effect", delete rules which are expired */ struct rule_state * get_upnp_rules_state_list(int max_rules_number_target); /* return the number of port mapping entries */ int upnp_get_portmapping_number_of_entries(void); /* remove_unused_rules() : * also free the list */ void remove_unused_rules(struct rule_state * list); /* upnp_get_portmappings_in_range() * return a list of all "external" ports for which a port * mapping exists */ unsigned short * upnp_get_portmappings_in_range(unsigned short startport, unsigned short endport, const char * protocol, unsigned int * number); /* stuff for responding to miniupnpdctl */ #ifdef USE_MINIUPNPDCTL void write_ruleset_details(int s); #endif #endif miniupnpd-2.3.7/upnpredirect.c010064400017500000024000000525521457642071700156060ustar00nanardstaff/* $Id: upnpredirect.c,v 1.100 2024/03/19 23:34:58 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 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 #include #include "macros.h" #include "config.h" #include "upnpredirect.h" #include "upnpglobalvars.h" #include "upnpevents.h" #include "portinuse.h" #include "upnputils.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 USE_MINIUPNPDCTL #include #include #endif #ifdef ENABLE_LEASEFILE #include #endif /* from */ #ifndef PRIu64 #define PRIu64 "llu" #endif /* proto_atoi() * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */ static int proto_atoi(const char * protocol) { int proto = IPPROTO_TCP; if(strcasecmp(protocol, "UDP") == 0) proto = IPPROTO_UDP; #ifdef IPPROTO_UDPLITE else if(strcasecmp(protocol, "UDPLITE") == 0) proto = IPPROTO_UDPLITE; #endif /* IPPROTO_UDPLITE */ return proto; } /* proto_itoa() * convert IPPROTO_UDP, IPPROTO_UDP, etc. to "UDP", "TCP" */ static const char * proto_itoa(int proto) { const char * protocol; switch(proto) { case IPPROTO_UDP: protocol = "UDP"; break; case IPPROTO_TCP: protocol = "TCP"; break; #ifdef IPPROTO_UDPLITE case IPPROTO_UDPLITE: protocol = "UDPLITE"; break; #endif /* IPPROTO_UDPLITE */ default: protocol = "*UNKNOWN*"; } return protocol; } #ifdef ENABLE_LEASEFILE static int lease_file_add(unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { FILE * fd; if (lease_file == NULL) return 0; fd = fopen( lease_file, "a"); if (fd==NULL) { syslog(LOG_ERR, "could not open lease file: %s", lease_file); return -1; } /* convert our time to unix time * if LEASEFILE_USE_REMAINING_TIME is defined, only the remaining time is stored */ if (timestamp != 0) { timestamp -= upnp_time(); #ifndef LEASEFILE_USE_REMAINING_TIME timestamp += time(NULL); #endif } fprintf(fd, "%s:%hu:%s:%hu:%u:%s\n", proto_itoa(proto), eport, iaddr, iport, timestamp, desc); fclose(fd); return 0; } static int lease_file_remove(unsigned short eport, int proto) { FILE* fd, *fdt; int tmp; char buf[512]; char str[32]; char tmpfilename[128]; int str_size, buf_size; if (lease_file == NULL) return 0; if (strlen( lease_file) + 7 > sizeof(tmpfilename)) { syslog(LOG_ERR, "Lease filename is too long"); return -1; } snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file); fd = fopen( lease_file, "r"); if (fd==NULL) { return 0; } snprintf( str, sizeof(str), "%s:%u", proto_itoa(proto), eport); str_size = strlen(str); tmp = mkstemp(tmpfilename); if (tmp==-1) { fclose(fd); syslog(LOG_ERR, "could not open temporary lease file"); return -1; } fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fdt = fdopen(tmp, "a"); buf[sizeof(buf)-1] = 0; while( fgets(buf, sizeof(buf)-1, fd) != NULL) { buf_size = strlen(buf); if (buf_size < str_size || strncmp(str, buf, str_size)!=0) { fwrite(buf, buf_size, 1, fdt); } } fclose(fdt); fclose(fd); if (rename(tmpfilename, lease_file) < 0) { syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file); remove(tmpfilename); } return 0; } /* reload_from_lease_file() * read lease_file and add the rules contained */ int reload_from_lease_file() { FILE * fd; char * p; unsigned short eport, iport; char * proto; char * iaddr; char * desc; char * rhost; unsigned int leaseduration; unsigned int timestamp; time_t current_time; #ifndef LEASEFILE_USE_REMAINING_TIME time_t current_unix_time; #endif char line[128]; int r; if(!lease_file) return -1; fd = fopen( lease_file, "r"); if (fd==NULL) { syslog(LOG_ERR, "could not open lease file: %s", lease_file); return -1; } if(unlink(lease_file) < 0) { syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file); } current_time = upnp_time(); #ifndef LEASEFILE_USE_REMAINING_TIME current_unix_time = time(NULL); #endif while(fgets(line, sizeof(line), fd)) { syslog(LOG_DEBUG, "parsing lease file line '%s'", line); proto = line; p = strchr(line, ':'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; iaddr = strchr(p, ':'); if(!iaddr) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(iaddr++) = '\0'; eport = (unsigned short)atoi(p); p = strchr(iaddr, ':'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; iport = (unsigned short)atoi(p); p = strchr(p, ':'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; desc = strchr(p, ':'); if(!desc) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(desc++) = '\0'; /*timestamp = (unsigned int)atoi(p);*/ timestamp = (unsigned int)strtoul(p, NULL, 10); /* trim description */ while(isspace(*desc)) desc++; p = desc; while(*(p+1)) p++; while(isspace(*p) && (p > desc)) *(p--) = '\0'; if(timestamp > 0) { #ifdef LEASEFILE_USE_REMAINING_TIME leaseduration = timestamp; timestamp += current_time; /* convert to our time */ #else if(timestamp <= (unsigned int)current_unix_time) { syslog(LOG_NOTICE, "already expired lease in lease file"); continue; } else { leaseduration = timestamp - current_unix_time; timestamp = leaseduration + current_time; /* convert to our time */ } #endif } else { leaseduration = 0; /* default value */ } rhost = NULL; r = upnp_redirect(rhost, eport, iaddr, iport, proto, desc, leaseduration); if(r == -1) { syslog(LOG_ERR, "Failed to redirect %hu -> %s:%hu protocol %s", eport, iaddr, iport, proto); } else if(r == -2) { /* Add the redirection again to the lease file */ lease_file_add(eport, iaddr, iport, proto_atoi(proto), desc, timestamp); } } fclose(fd); return 0; } #ifdef LEASEFILE_USE_REMAINING_TIME void lease_file_rewrite(void) { int index; unsigned short eport, iport; int proto; char iaddr[32]; char desc[64]; char rhost[40]; unsigned int timestamp; if (lease_file == NULL) return; remove(lease_file); for(index = 0; ; index++) { if(get_redirect_rule_by_index(index, 0/*ifname*/, &eport, iaddr, sizeof(iaddr), &iport, &proto, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, 0, 0) < 0) break; if(lease_file_add(eport, iaddr, iport, proto, desc, timestamp) < 0) break; } } #endif #endif /* upnp_redirect() * calls OS/fw dependent implementation of the redirection. * protocol should be the string "TCP" or "UDP" * returns: 0 on success * -1 failed to redirect * -2 already redirected * -3 permission check failed * -4 already redirected (other mechanism) */ int upnp_redirect(const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, const char * protocol, const char * desc, unsigned int leaseduration) { int proto, r; char iaddr_old[32]; char rhost_old[32]; unsigned short iport_old; struct in_addr address; unsigned int timestamp; proto = proto_atoi(protocol); if(inet_aton(iaddr, &address) <= 0) { syslog(LOG_ERR, "inet_aton(%s) FAILED", iaddr); return -1; } if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, address, iport, desc)) { syslog(LOG_INFO, "redirection permission check failed for " "%hu->%s:%hu %s %s", eport, iaddr, iport, protocol, desc); return -3; } if (desc == NULL) desc = ""; /* assume empty description */ /* 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, rhost_old, sizeof(rhost_old), ×tamp, 0, 0); if(r == 0) { 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) ? upnp_time() + leaseduration : 0; if(iport != iport_old) { r = update_portmapping(ext_if_name, eport, proto, iport, desc, timestamp); } else { r = update_portmapping_desc_timestamp(ext_if_name, eport, proto, desc, timestamp); } #ifdef ENABLE_LEASEFILE if(r == 0) { lease_file_remove(eport, proto); lease_file_add(eport, iaddr, iport, proto, desc, timestamp); } #endif /* ENABLE_LEASEFILE */ return r; } else { 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 } else if (port_in_use(ext_if_name, eport, proto, iaddr, iport) > 0) { syslog(LOG_INFO, "port %hu protocol %s already in use", eport, protocol); return -4; #endif /* CHECK_PORTINUSE */ } else { timestamp = (leaseduration > 0) ? upnp_time() + leaseduration : 0; syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s", eport, iaddr, iport, protocol, desc); return upnp_redirect_internal(rhost, eport, iaddr, iport, proto, desc, timestamp); } } int upnp_redirect_internal(const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { /*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; } #ifdef ENABLE_LEASEFILE lease_file_add( eport, iaddr, iport, proto, desc, timestamp); #endif /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s", iaddr, iport, protocol, desc);*/ if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) { /* clean up the redirect rule */ #if !defined(__linux__) delete_redirect_rule(ext_if_name, eport, proto); #endif return -1; } if(timestamp > 0) { if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp)) nextruletoclean_timestamp = timestamp; } #ifdef ENABLE_EVENTS /* the number of port mappings changed, we must * inform the subscribers */ upnp_event_var_change_notify(EWanIPC); #endif return 0; } /* Firewall independent code which call the FW dependent code. */ int upnp_get_redirection_infos(unsigned short eport, const char * protocol, unsigned short * iport, char * iaddr, int iaddrlen, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * leaseduration) { int r; unsigned int timestamp; time_t current_time; if(desc && (desclen > 0)) desc[0] = '\0'; if(rhost && (rhostlen > 0)) rhost[0] = '\0'; r = get_redirect_rule(ext_if_name, eport, proto_atoi(protocol), iaddr, iaddrlen, iport, desc, desclen, rhost, rhostlen, ×tamp, 0, 0); if(r == 0 && timestamp > 0 && timestamp > (unsigned int)(current_time = upnp_time())) { *leaseduration = timestamp - current_time; } else { *leaseduration = 0; } return r; } int upnp_get_redirection_infos_by_index(int index, unsigned short * eport, char * protocol, unsigned short * iport, char * iaddr, int iaddrlen, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * leaseduration) { /*char ifname[IFNAMSIZ];*/ int proto = 0; unsigned int timestamp; time_t current_time; if(desc && (desclen > 0)) desc[0] = '\0'; if(rhost && (rhostlen > 0)) rhost[0] = '\0'; if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen, iport, &proto, desc, desclen, rhost, rhostlen, ×tamp, 0, 0) < 0) return -1; else { current_time = upnp_time(); *leaseduration = (timestamp > (unsigned int)current_time) ? (timestamp - current_time) : 0; if(proto == IPPROTO_TCP) memcpy(protocol, "TCP", 4); #ifdef IPPROTO_UDPLITE else if(proto == IPPROTO_UDPLITE) memcpy(protocol, "UDPLITE", 8); #endif /* IPPROTO_UDPLITE */ else memcpy(protocol, "UDP", 4); return 0; } } /* called from natpmp.c too */ int _upnp_delete_redir(unsigned short eport, int proto) { int r; #if defined(__linux__) r = delete_redirect_and_filter_rules(eport, proto); #elif defined(USE_PF) r = delete_redirect_and_filter_rules(ext_if_name, eport, proto); #else r = delete_redirect_rule(ext_if_name, eport, proto); delete_filter_rule(ext_if_name, eport, proto); #endif #ifdef ENABLE_LEASEFILE lease_file_remove( eport, proto); #endif #ifdef ENABLE_EVENTS upnp_event_var_change_notify(EWanIPC); #endif return r; } int upnp_delete_redirection(unsigned short eport, const char * protocol) { syslog(LOG_INFO, "removing redirect rule port %hu %s", eport, protocol); return _upnp_delete_redir(eport, proto_atoi(protocol)); } /* upnp_get_portmapping_number_of_entries() */ int upnp_get_portmapping_number_of_entries(void) { #if defined(USE_PF) || defined(USE_NFTABLES) return get_redirect_rule_count(ext_if_name); #else int n = 0, r = 0; unsigned short eport, iport; char protocol[8], iaddr[32], desc[64], rhost[32]; unsigned int leaseduration; do { protocol[0] = '\0'; iaddr[0] = '\0'; desc[0] = '\0'; r = upnp_get_redirection_infos_by_index(n, &eport, protocol, &iport, iaddr, sizeof(iaddr), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration); n++; } while(r==0); return (n-1); #endif } /* functions used to remove unused rules * As a side effect, delete expired rules (based on LeaseDuration) */ struct rule_state * get_upnp_rules_state_list(int max_rules_number_target) { /*char ifname[IFNAMSIZ];*/ int proto; unsigned short iport; unsigned int timestamp; struct rule_state * tmp; struct rule_state * list = 0; struct rule_state * * p; int i = 0; time_t current_time; /*ifname[0] = '\0';*/ tmp = malloc(sizeof(struct rule_state)); if(!tmp) return 0; current_time = upnp_time(); nextruletoclean_timestamp = 0; while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0, &iport, &proto, 0, 0, 0,0, ×tamp, &tmp->packets, &tmp->bytes) >= 0) { tmp->to_remove = 0; if(timestamp > 0) { /* need to remove this port mapping ? */ if(timestamp <= (unsigned int)current_time) tmp->to_remove = 1; else if((nextruletoclean_timestamp <= (unsigned int)current_time) || (timestamp < nextruletoclean_timestamp)) nextruletoclean_timestamp = timestamp; } tmp->proto = (short)proto; /* add tmp to list */ tmp->next = list; list = tmp; /* prepare next iteration */ i++; tmp = malloc(sizeof(struct rule_state)); if(!tmp) break; } #ifdef PCP_PEER i=0; while(get_peer_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0, &iport, &proto, 0, 0, 0,0,0, ×tamp, &tmp->packets, &tmp->bytes) >= 0) { tmp->to_remove = 0; if(timestamp > 0) { /* need to remove this port mapping ? */ if(timestamp <= (unsigned int)current_time) tmp->to_remove = 1; else if((nextruletoclean_timestamp <= (unsigned int)current_time) || (timestamp < nextruletoclean_timestamp)) nextruletoclean_timestamp = timestamp; } tmp->proto = (short)proto; /* add tmp to list */ tmp->next = list; list = tmp; /* prepare next iteration */ i++; tmp = malloc(sizeof(struct rule_state)); if(!tmp) break; } #endif free(tmp); /* remove the redirections that need to be removed */ for(p = &list, tmp = list; tmp; tmp = *p) { if(tmp->to_remove) { syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired", tmp->eport, proto_itoa(tmp->proto)); _upnp_delete_redir(tmp->eport, tmp->proto); *p = tmp->next; free(tmp); i--; } else { p = &(tmp->next); } } /* return empty list if not enough redirections */ if(i<=max_rules_number_target) while(list) { tmp = list; list = tmp->next; free(tmp); } /* return list */ return list; } void remove_unused_rules(struct rule_state * list) { char ifname[IFNAMSIZ]; unsigned short iport; struct rule_state * tmp; u_int64_t packets; u_int64_t bytes; unsigned int timestamp; int n = 0; while(list) { /* remove the rule if no traffic has used it */ if(get_redirect_rule(ifname, list->eport, list->proto, 0, 0, &iport, 0, 0, 0, 0, ×tamp, &packets, &bytes) >= 0) { if(packets == list->packets && bytes == list->bytes) { syslog(LOG_DEBUG, "removing unused mapping %hu %s : still " "%" PRIu64 "packets %" PRIu64 "bytes", list->eport, proto_itoa(list->proto), packets, bytes); _upnp_delete_redir(list->eport, list->proto); n++; } } tmp = list; list = tmp->next; free(tmp); } if(n>0) syslog(LOG_NOTICE, "removed %d unused rules", n); } /* upnp_get_portmappings_in_range() * return a list of all "external" ports for which a port * mapping exists */ unsigned short * upnp_get_portmappings_in_range(unsigned short startport, unsigned short endport, const char * protocol, unsigned int * number) { int proto; proto = proto_atoi(protocol); if(!number) return NULL; return get_portmappings_in_range(startport, endport, proto, number); } /* stuff for miniupnpdctl */ #ifdef USE_MINIUPNPDCTL void write_ruleset_details(int s) { int proto = 0; unsigned short eport, iport; char desc[64]; char iaddr[32]; char rhost[32]; unsigned int timestamp; u_int64_t packets; u_int64_t bytes; int i = 0; char buffer[256]; int n; write(s, "Ruleset :\n", 10); while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr), &iport, &proto, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) >= 0) { n = snprintf(buffer, sizeof(buffer), "%2d %s %s:%hu->%s:%hu " "'%s' %u %" PRIu64 " %" PRIu64 "\n", /*"'%s' %llu %llu\n",*/ i, proto_itoa(proto), rhost, eport, iaddr, iport, desc, timestamp, packets, bytes); write(s, buffer, n); i++; } } #endif miniupnpd-2.3.7/getifaddr.c010064400017500000024000000213551463115324100150130ustar00nanardstaff/* $Id: getifaddr.c,v 1.30 2024/06/04 23:05:28 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 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 #if defined(sun) #include #endif #include "config.h" #include "getifaddr.h" #if defined(USE_GETIFADDRS) || defined(ENABLE_IPV6) || defined(ENABLE_PCP) #include #endif int getifaddr(const char * ifname, char * buf, int len, struct in_addr * addr, struct in_addr * mask) { #ifndef USE_GETIFADDRS /* use ioctl SIOCGIFADDR. Works only for ip v4 */ /* SIOCGIFADDR struct ifreq * */ int s; struct ifreq ifr; int ifrlen; struct sockaddr_in * ifaddr; ifrlen = sizeof(ifr); if(!ifname || ifname[0]=='\0') return -1; s = socket(PF_INET, SOCK_DGRAM, 0); if(s < 0) { syslog(LOG_ERR, "socket(PF_INET, SOCK_DGRAM): %m"); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1] = '\0'; if(ioctl(s, SIOCGIFFLAGS, &ifr, &ifrlen) < 0) { syslog(LOG_DEBUG, "ioctl(s, SIOCGIFFLAGS, ...): %m"); close(s); return -1; } if ((ifr.ifr_flags & IFF_UP) == 0) { syslog(LOG_DEBUG, "network interface %s is down", ifname); close(s); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1] = '\0'; if(ioctl(s, SIOCGIFADDR, &ifr, &ifrlen) < 0) { syslog(LOG_ERR, "ioctl(s, SIOCGIFADDR, ...): %m"); close(s); return -1; } ifaddr = (struct sockaddr_in *)&ifr.ifr_addr; if(addr) *addr = ifaddr->sin_addr; if(buf) { if(!inet_ntop(AF_INET, &ifaddr->sin_addr, buf, len)) { syslog(LOG_ERR, "inet_ntop(): %m"); close(s); return -1; } } if(mask) { strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1] = '\0'; if(ioctl(s, SIOCGIFNETMASK, &ifr, &ifrlen) < 0) { syslog(LOG_ERR, "ioctl(s, SIOCGIFNETMASK, ...): %m"); close(s); return -1; } #ifdef ifr_netmask *mask = ((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr; #else *mask = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr; #endif } close(s); #else /* ifndef USE_GETIFADDRS */ /* Works for all address families (both ip v4 and ip v6) */ struct ifaddrs * ifap; struct ifaddrs * ife; struct ifaddrs * candidate = NULL; if(!ifname || ifname[0]=='\0') return -1; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife; ife = ife->ifa_next) { /* skip other interfaces if one was specified */ if(ifname && (0 != strcmp(ifname, ife->ifa_name))) continue; if(ife->ifa_addr == NULL) continue; switch(ife->ifa_addr->sa_family) { case AF_INET: /* only consider the address if it is the 1st candidate or if it is not privante AND the current candidate is a private address. So we return a private address only if there is no public address on this interface */ if(!candidate || (addr_is_reserved(&((struct sockaddr_in *)candidate->ifa_addr)->sin_addr) && !addr_is_reserved(&((struct sockaddr_in *)ife->ifa_addr)->sin_addr))) candidate = ife; break; /* case AF_INET6: inet_ntop(ife->ifa_addr->sa_family, &((struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr, buf, len); */ } } if(candidate) { if(buf) { inet_ntop(candidate->ifa_addr->sa_family, &((struct sockaddr_in *)candidate->ifa_addr)->sin_addr, buf, len); } if(addr) *addr = ((struct sockaddr_in *)candidate->ifa_addr)->sin_addr; if(mask) *mask = ((struct sockaddr_in *)candidate->ifa_netmask)->sin_addr; } else { syslog(LOG_WARNING, "no AF_INET address found for %s", ifname); freeifaddrs(ifap); return -1; } freeifaddrs(ifap); #endif return 0; } #ifdef ENABLE_PCP int getifaddr_in6(const char * ifname, int af, struct in6_addr * addr) { #if defined(ENABLE_IPV6) || defined(USE_GETIFADDRS) struct ifaddrs * ifap; struct ifaddrs * ife; #ifdef ENABLE_IPV6 const struct sockaddr_in6 * tmpaddr; #endif /* ENABLE_IPV6 */ int found = 0; if(!ifname || ifname[0]=='\0') return -1; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife && !found; ife = ife->ifa_next) { /* skip other interfaces if one was specified */ if(ifname && (0 != strcmp(ifname, ife->ifa_name))) continue; if(ife->ifa_addr == NULL) continue; if (ife->ifa_addr->sa_family != af) continue; switch(ife->ifa_addr->sa_family) { case AF_INET: /* IPv4-mapped IPv6 address ::ffff:1.2.3.4 */ memset(addr->s6_addr, 0, 10); addr->s6_addr[10] = 0xff; addr->s6_addr[11] = 0xff; memcpy(addr->s6_addr + 12, &(((struct sockaddr_in *)ife->ifa_addr)->sin_addr.s_addr), 4); found = 1; break; #ifdef ENABLE_IPV6 case AF_INET6: tmpaddr = (const struct sockaddr_in6 *)ife->ifa_addr; if(!IN6_IS_ADDR_LOOPBACK(&tmpaddr->sin6_addr) && !IN6_IS_ADDR_LINKLOCAL(&tmpaddr->sin6_addr)) { memcpy(addr->s6_addr, &tmpaddr->sin6_addr, 16); found = 1; } break; #endif /* ENABLE_IPV6 */ } } freeifaddrs(ifap); return (found ? 0 : -1); #else /* defined(ENABLE_IPV6) || defined(USE_GETIFADDRS) */ /* IPv4 only */ struct in_addr addr4; if(af != AF_INET) return -1; if(getifaddr(ifname, NULL, 0, &addr4, NULL) < 0) return -1; /* IPv4-mapped IPv6 address ::ffff:1.2.3.4 */ memset(addr->s6_addr, 0, 10); addr->s6_addr[10] = 0xff; addr->s6_addr[11] = 0xff; memcpy(addr->s6_addr + 12, &addr4.s_addr, 4); return 0; #endif } #endif /* ENABLE_PCP */ #ifdef ENABLE_IPV6 int find_ipv6_addr(const char * ifname, char * dst, int n) { struct ifaddrs * ifap; struct ifaddrs * ife; const struct sockaddr_in6 * addr; char buf[64]; int r = 0; if(!dst) return -1; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife; ife = ife->ifa_next) { /* skip other interfaces if one was specified */ if(ifname && (0 != strcmp(ifname, ife->ifa_name))) continue; if(ife->ifa_addr == NULL) continue; if(ife->ifa_addr->sa_family == AF_INET6) { addr = (const struct sockaddr_in6 *)ife->ifa_addr; if(!IN6_IS_ADDR_LOOPBACK(&addr->sin6_addr) && !IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) /* RFC4193 "Unique Local IPv6 Unicast Addresses" only if no * other address found */ && (r == 0 || (addr->sin6_addr.s6_addr[0] & 0xfe) != 0xfc)) { inet_ntop(ife->ifa_addr->sa_family, &addr->sin6_addr, buf, sizeof(buf)); /* add brackets */ snprintf(dst, n, "[%s]", buf); r = 1; } } } freeifaddrs(ifap); return r; } #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(24) }, /* 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; } miniupnpd-2.3.7/getifaddr.h010064400017500000024000000020541331765521100150170ustar00nanardstaff/* $Id: getifaddr.h,v 1.11 2018/07/06 11:47:29 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef GETIFADDR_H_INCLUDED #define GETIFADDR_H_INCLUDED struct in_addr; struct in6_addr; /* getifaddr() * take a network interface name and write the * ip v4 address as text in the buffer * returns: 0 success, -1 failure */ int getifaddr(const char * ifname, char * buf, int len, struct in_addr * addr, struct in_addr * mask); int getifaddr_in6(const char * ifname, int af, struct in6_addr* addr); /* find a non link local IP v6 address for the interface. * if ifname is NULL, look for all interfaces */ 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 miniupnpd-2.3.7/daemonize.c010064400017500000024000000041351326716305100150360ustar00nanardstaff/* $Id: daemonize.c,v 1.14 2018/04/22 19:36:58 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 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 "daemonize.h" #include "config.h" #ifndef USE_DAEMON int daemonize(void) { int pid, i; switch(fork()) { /* fork error */ case -1: perror("fork()"); exit(1); /* child process */ case 0: /* obtain a new process group */ if( (pid = setsid()) < 0) { perror("setsid()"); exit(1); } /* close all descriptors */ for (i=getdtablesize();i>=0;--i) close(i); i = open("/dev/null", O_RDWR); /* open stdin */ dup(i); /* stdout */ dup(i); /* stderr */ umask(027); chdir("/"); /* chdir to /tmp ? */ return pid; /* parent process */ default: exit(0); } } #endif int writepidfile(const char * fname, int pid) { char pidstring[16]; int pidstringlen; int pidfile; if(!fname || fname[0] == '\0') return -1; if( (pidfile = open(fname, O_WRONLY|O_CREAT, 0644)) < 0) { syslog(LOG_ERR, "Unable to open pidfile for writing %s: %m", fname); return -1; } pidstringlen = snprintf(pidstring, sizeof(pidstring), "%d\n", pid); if(pidstringlen <= 0) { syslog(LOG_ERR, "Unable to write to pidfile %s: snprintf(): FAILED", fname); close(pidfile); return -1; } else { if(write(pidfile, pidstring, pidstringlen) < 0) syslog(LOG_ERR, "Unable to write to pidfile %s: %m", fname); } close(pidfile); return 0; } int checkforrunning(const char * fname) { char buffer[64]; int pidfile; pid_t pid; if(!fname || fname[0] == '\0') return -1; if( (pidfile = open(fname, O_RDONLY)) < 0) return 0; memset(buffer, 0, 64); if(read(pidfile, buffer, 63)) { if( (pid = atol(buffer)) > 0) { if(!kill(pid, 0)) { close(pidfile); return -2; } } } close(pidfile); return 0; } miniupnpd-2.3.7/daemonize.h010064400017500000024000000015151203340734000150320ustar00nanardstaff/* $Id: daemonize.h,v 1.8 2012/09/27 16:00:44 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef DAEMONIZE_H_INCLUDED #define DAEMONIZE_H_INCLUDED #include "config.h" #ifndef USE_DAEMON /* daemonize() * "fork" to background, detach from terminal, etc... * returns: pid of the daemon, exits upon failure */ int daemonize(void); #endif /* writepidfile() * write the pid to a file */ int writepidfile(const char * fname, int pid); /* checkforrunning() * check for another instance running * returns: 0 only instance * -1 invalid filename * -2 another instance running */ int checkforrunning(const char * fname); #endif miniupnpd-2.3.7/upnpglobalvars.h010064400017500000024000000107501411013505300161140ustar00nanardstaff/* $Id: upnpglobalvars.h,v 1.52 2021/08/21 08:15:00 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPGLOBALVARS_H_INCLUDED #define UPNPGLOBALVARS_H_INCLUDED #include #include "upnppermissions.h" #include "miniupnpdtypes.h" #include "config.h" /* name of the network interface used to access internet */ extern const char * ext_if_name; #ifdef ENABLE_IPV6 /* name of the network interface used to access internet - for IPv6*/ extern const char * ext_if_name6; #endif /* 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; #ifdef ENABLE_UPNPPINHOLE extern const char * lease_file6; #endif #endif /* forced ip address to use for this interface * 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; /* statup time */ extern time_t startup_time; #if defined(ENABLE_NATPMP) || defined(ENABLE_PCP) /* origin for "epoch time" sent into NATPMP and PCP responses */ extern time_t epoch_origin; #endif /* defined(ENABLE_NATPMP) || defined(ENABLE_PCP) */ extern unsigned long int min_lifetime; extern unsigned long int max_lifetime; /* runtime boolean flags */ extern int runtime_flags; #define LOGPACKETSMASK 0x0001 #define SYSUPTIMEMASK 0x0002 #ifdef ENABLE_NATPMP #define ENABLENATPMPMASK 0x0004 #endif #define CHECKCLIENTIPMASK 0x0008 #define SECUREMODEMASK 0x0010 #define ENABLEUPNPMASK 0x0020 #ifdef PF_ENABLE_FILTER_RULES #define PFNOQUICKRULESMASK 0x0040 #endif #ifdef ENABLE_IPV6 #define IPV6DISABLEDMASK 0x0080 #endif #ifdef ENABLE_6FC_SERVICE #define IPV6FCFWDISABLEDMASK 0x0100 #define IPV6FCINBOUNDDISALLOWEDMASK 0x0200 #endif #ifdef ENABLE_PCP #define PCP_ALLOWTHIRDPARTYMASK 0x0400 #endif #ifdef IGD_V2 #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 extern const char * pidfilename; extern char uuidvalue_igd[]; /* uuid of root device (IGD) */ extern char uuidvalue_wan[]; /* uuid of WAN Device */ extern char uuidvalue_wcd[]; /* uuid of WAN Connection Device */ #define SERIALNUMBER_MAX_LEN (10) extern char serialnumber[]; #define MODELNUMBER_MAX_LEN (48) extern char modelnumber[]; #define PRESENTATIONURL_MAX_LEN (64) extern char presentationurl[]; #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION #define FRIENDLY_NAME_MAX_LEN (64) extern char friendly_name[]; #define MANUFACTURER_NAME_MAX_LEN (64) extern char manufacturer_name[]; #define MANUFACTURER_URL_MAX_LEN (64) extern char manufacturer_url[]; #define MODEL_NAME_MAX_LEN (64) extern char model_name[]; #define MODEL_DESCRIPTION_MAX_LEN (64) extern char model_description[]; #define MODEL_URL_MAX_LEN (64) extern char model_url[]; #endif /* UPnP permission rules : */ extern struct upnpperm * upnppermlist; extern unsigned int num_upnpperm; #ifdef PCP_SADSCP extern struct dscp_values* dscp_values_list; extern unsigned int num_dscp_values; #endif /* For automatic removal of expired rules (with LeaseDuration) */ extern unsigned int nextruletoclean_timestamp; #ifdef USE_PF extern const char * anchor_name; /* queue and tag for PF rules */ extern const char * queue; extern const char * tag; #endif #ifdef ENABLE_NFQUEUE extern int nfqueue; extern int n_nfqix; extern unsigned nfqix[]; #endif /* lan addresses to listen to SSDP traffic */ extern struct lan_addr_list lan_addrs; #ifdef ENABLE_IPV6 /* ipv6 address used for HTTP */ extern char ipv6_addr_for_http_with_brackets[64]; /* address used to bind local services */ extern struct in6_addr ipv6_bind_addr; #endif /* ENABLE_IPV6 */ extern const char * minissdpdsocketpath; /* BOOTID.UPNP.ORG and CONFIGID.UPNP.ORG */ extern unsigned int upnp_bootid; extern unsigned int upnp_configid; #ifdef RANDOMIZE_URLS #define RANDOM_URL_MAX_LEN (16) extern char random_url[]; #endif /* RANDOMIZE_URLS */ #ifdef DYNAMIC_OS_VERSION extern char * os_version; #endif #endif /* UPNPGLOBALVARS_H_INCLUDED */ miniupnpd-2.3.7/upnpglobalvars.c010064400017500000024000000127321411013505300161110ustar00nanardstaff/* $Id: upnpglobalvars.c,v 1.48 2021/08/21 08:15:00 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include "config.h" #include "upnpglobalvars.h" #include "upnpdescstrings.h" /* network interface for internet */ const char * ext_if_name = 0; #ifdef ENABLE_IPV6 /* network interface for internet - IPv6 */ const char * ext_if_name6 = 0; #endif /* 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; #ifdef ENABLE_UPNPPINHOLE const char* lease_file6 = 0; #endif #endif /* forced ip address to use for this interface * 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; /* startup time */ time_t startup_time = 0; #if defined(ENABLE_NATPMP) || defined(ENABLE_PCP) /* origin for "epoch time" sent into NATPMP and PCP responses */ time_t epoch_origin = 0; #endif /* defined(ENABLE_NATPMP) || defined(ENABLE_PCP) */ #ifdef ENABLE_PCP /* for PCP */ unsigned long int min_lifetime = 120; unsigned long int max_lifetime = 86400; #endif int runtime_flags = 0; const char * pidfilename = "/var/run/miniupnpd.pid"; char uuidvalue_igd[] = "uuid:00000000-0000-0000-0000-000000000000"; char uuidvalue_wan[] = "uuid:00000000-0000-0000-0000-000000000000"; char uuidvalue_wcd[] = "uuid:00000000-0000-0000-0000-000000000000"; char serialnumber[SERIALNUMBER_MAX_LEN] = "00000000"; char modelnumber[MODELNUMBER_MAX_LEN] = "1"; /* presentation url : * http://nnn.nnn.nnn.nnn:ppppp/ => max 30 bytes including terminating 0 */ char presentationurl[PRESENTATIONURL_MAX_LEN]; #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION /* friendly name for root devices in XML description */ char friendly_name[FRIENDLY_NAME_MAX_LEN] = OS_NAME " router"; /* manufacturer name for root devices in XML description */ char manufacturer_name[MANUFACTURER_NAME_MAX_LEN] = ROOTDEV_MANUFACTURER; /* manufacturer url for root devices in XML description */ char manufacturer_url[MANUFACTURER_URL_MAX_LEN] = ROOTDEV_MANUFACTURERURL; /* model name for root devices in XML description */ char model_name[MODEL_NAME_MAX_LEN] = ROOTDEV_MODELNAME; /* model description for root devices in XML description */ char model_description[MODEL_DESCRIPTION_MAX_LEN] = ROOTDEV_MODELDESCRIPTION; /* model url for root devices in XML description */ char model_url[MODEL_URL_MAX_LEN] = ROOTDEV_MODELURL; #endif /* UPnP permission rules : */ struct upnpperm * upnppermlist = 0; unsigned int num_upnpperm = 0; #ifdef PCP_SADSCP struct dscp_values* dscp_values_list = 0; unsigned int num_dscp_values = 0; #endif /*PCP_SADSCP*/ /* For automatic removal of expired rules (with LeaseDuration) */ unsigned int nextruletoclean_timestamp = 0; #ifdef USE_PF /* "rdr-anchor miniupnpd" or/and "anchor miniupnpd" in pf.conf */ const char * anchor_name = "miniupnpd"; const char * queue = 0; const char * tag = 0; #endif #ifdef ENABLE_NFQUEUE int nfqueue = -1; int n_nfqix = 0; unsigned nfqix[MAX_LAN_ADDR]; #endif /* ENABLE_NFQUEUE */ struct lan_addr_list lan_addrs; #ifdef ENABLE_IPV6 /* ipv6 address used for HTTP */ char ipv6_addr_for_http_with_brackets[64]; /* address used to bind local services */ struct in6_addr ipv6_bind_addr; #endif /* Path of the Unix socket used to communicate with MiniSSDPd */ const char * minissdpdsocketpath = "/var/run/minissdpd.sock"; /* BOOTID.UPNP.ORG and CONFIGID.UPNP.ORG */ /* See UPnP Device Architecture v1.1 section 1.2 Advertisement : * The field value of the BOOTID.UPNP.ORG header field MUST be increased * each time a device (re)joins the network and sends an initial announce * (a "reboot" in UPnP terms), or adds a UPnP-enabled interface. * Unless the device explicitly announces a change in the BOOTID.UPNP.ORG * field value using an SSDP message, as long as the device remains * continuously available in the network, the same BOOTID.UPNP.ORG field * value MUST be used in all repeat announcements, search responses, * update messages and eventually bye-bye messages. */ unsigned int upnp_bootid = 1; /* BOOTID.UPNP.ORG */ /* The field value of the CONFIGID.UPNP.ORG header field identifies the * current set of device and service descriptions; control points can * parse this header field to detect whether they need to send new * description query messages. */ /* UPnP 1.1 devices MAY freely assign configid numbers from 0 to * 16777215 (2^24-1). Higher numbers are reserved for future use, and * can be assigned by the Technical Committee. The configuration of a * root device consists of the following information: the DDD of the * root device and all its embedded devices, and the SCPDs of all the * contained services. If any part of the configuration changes, the * CONFIGID.UPNP.ORG field value MUST be changed. * DDD = Device Description Document * SCPD = Service Control Protocol Description */ unsigned int upnp_configid = 1337; /* CONFIGID.UPNP.ORG */ #ifdef RANDOMIZE_URLS char random_url[RANDOM_URL_MAX_LEN] = "random"; #endif /* RANDOMIZE_URLS */ #ifdef DYNAMIC_OS_VERSION char * os_version = NULL; #endif /* DYNAMIC_OS_VERSION */ miniupnpd-2.3.7/LICENSE010064400017500000024000000027671454540261500137370ustar00nanardstaffBSD 3-Clause License Copyright (c) 2005-2024, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. miniupnpd-2.3.7/README010064400017500000024000000024641454540266600136120ustar00nanardstaffMiniUPnP project (c) 2006-2024 Thomas Bernard webpage: http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ github: https://github.com/miniupnp/miniupnp freecode: http://freecode.com/projects/miniupnp contact: miniupnp@free.fr This directory contain the miniUPnP daemon software, aka miniUPnPd. This software is subject to the conditions detailed in the LICENSE file provided with this distribution. The miniUPnP daemon is an UPnP IGD (internet gateway device) which provide NAT traversal services to any UPnP enabled client on the network. See http://www.upnp.org/ for more details on UPnP. During the year 2011, support for IGD v2 has been added. In 2012, IGD v2 WANIPv6FirewallControl has been implemented. IGD2 is still not enabled by default because of interoperability issues. In addition to UPnP IGD, miniUPnPd supports NAT-PMP and PCP : See information about NAT Port Mapping Protocol (NAT-PMP) here : http://miniupnp.free.fr/nat-pmp.html NAT-PMP is the precursor of Port Control Protocol (PCP, RFC 6887). In 2013, support for PCP has been added too. Read the INSTALL file for instructions to compile, install and configure miniupnpd on your system. Report bugs to miniupnp@free.fr or on the web forum https://miniupnp.tuxfamily.org/forum/ or using https://github.com/miniupnp/miniupnp/issues Thomas Bernard miniupnpd-2.3.7/INSTALL010064400017500000024000000210351463564776400137700ustar00nanardstaffMiniUPnP project. (c) 2006-2024 Thomas Bernard Homepage : http://miniupnp.free.fr/ Mirror: https://miniupnp.tuxfamily.org/ github: https://github.com/miniupnp/miniupnp If you find some outdated information in this documentation, please fix or report the issue on https://github.com/miniupnp/miniupnp or the web forum https://miniupnp.tuxfamily.org/ ================================ *BSD/pf ================================= To Build and Install : - first use ./configure For more details about options : > ./configure -h then edit config.h to fine tune to your preferences. - use BSD make to compile. - add "rdr-anchor miniupnpd" or/and "anchor miniupnpd" lines to /etc/pf.conf (Since OpenBSD 4.7, rdr-anchor lines are no longer used and should be removed, leaving only the anchor lines). - some FreeBSD users reported that it is also necessary for them to explicitly allow udp traffic on 239.0.0.0/8 by adding the two following lines to /etc/pf.conf : pass out on $int_if from any to 239.0.0.0/8 keep state pass in on $int_if from any to 239.0.0.0/8 keep state - don't forget to " pfctl -f /etc/pf.conf " - you can check your modifications are taken into account with "pfctl -s nat" and "pfctl -s rule". Look for the "rdr-anchor miniupnpd" (if applicable) and/or "anchor miniupnpd" lines. - OpenBSD users may need to add a multicast_host= line to /etc/rc.conf.local see $man 8 netstart - install as root using : # make install or # PREFIX=/usr/local make install - run as root : The daemon needs rights to modify pf rules. =========================== *BSD,*Solaris/ipf ============================= configure tries to detect wether ipf or pf should be used. If it fails, you can use ./configure --firewall=ipf Installation steps are allmost the same as with pf. *Solaris users would be interested in reading informations from : http://blogs.sun.com/avalon/category/IPFilter ============================= Mac OS X/ipfw =============================== - To enable non standard compilation options, > ./configure -h - use either 'bsdmake -f Makefile.bsd' (if available) or 'make' to build ============================== Mac OS X/pf ================================ Starting with Mac OS X 10.7 Lion, pf replaced ipfw as the OS X firewall. also bsdmake is not available anymore. Make sure you have installed the Xcode commande line tools (from the Xcode Preferences menu or using 'xcode-select --install' command) You need to download xnu sources : https://opensource.apple.com/tarballs/xnu/ - If version of xnu >= 4570, > ./configure Then edit config.h, adding line "#define PFVAR_NEW_STYLE" to it. > INCLUDES="-I.../xnu/bsd -I.../xnu/libkern" make ============================ Linux/netfilter ============================== To Build and install : - make sure you have libiptc available on your system : if you are using debian, "apt-get install iptables-dev" Some versions of the iptables-dev package don't include the necessary files : read "how to get libiptc with its headers on debian" below. In anycase, libiptc is available in iptables sources packages from http://netfilter.org - edit and run netfilter/iptables_init.sh shell script. This script must allways be run before the daemon to set up initial rules and chains. - Build and edit the config.h file > ./configure > vi config.h - Build the daemon > make If not using iptables from your system, > ./configure --iptablespath=/path/to/iptables-1.4.1 > make - install as root using : > make install - A miniupnpd script should be installed to /etc/init.d and the configuration files to /etc/miniupnpd - anytime, you can use the netfilter/iptables_flush.sh script to flush all rules added by the daemon. - after killing the daemon, you can get back to iptables initial state by running the netfilter/iptables_removeall.sh script. Don't forget to edit the script to your convenience. NOTE: a /etc/init.d/miniupnpd script will be installed. If it suits you, you can use is with start, stop or restart argument. # /etc/init.d/miniupnpd restart How to get libiptc with its headers on debian : (Note: that should be useless now that netfilter/tiny_nf_nat.h is included) - Use apt-get to get sources : > apt-get source iptables you should then have an iptables-x.x.x/ directory. - configure and compile : > cd iptables-x.x.x/ > ./configure --enable-static > make - it is now possible to compile miniupnpd using the following command : > ./configure --iptablespath=/path/to/iptables-x.x.x > make ======================== Linux/netfilter nftables ========================= install the required development libraries. For debian : > apt-get install libnftnl-dev libmnl-dev To build : > ./configure --firewall=nftables > make see : https://miniupnp.tuxfamily.org/forum/viewtopic.php?p=4370 https://github.com/miniupnp/miniupnp/pull/114 =========================== Configuration ============================= The configuration file is either installed to /etc/miniupnpd.conf (BSD) or /etc/miniupnpd/miniupnpd.conf (Linux) Almost all options are also available through command line switches. A basic configuration would set : ext_ifname : WAN network interface (interface connected to the internet) listening_ip : LAN network interface (network where to supply NAT traversal) enable_pcp_pmp=yes enable_upnp=yes and the permission rules (see below). Historically, LAN had to be specified by IP/mask, such as listening_ip=192.168.0.1/24 but if you compiled with IPv6 support, you need to specify an interface name : listening_ip=eth0 The current code assumes there is only one IPv4 address assigned to LAN interfaces. That is not the case with some CARP setup, there is then a risk the wrong mask would be picked. You can force the mask when using interface names : listening_ip=eth0/24 Some users want to use miniupnpd on a NAT router which is connected to the internet through another NAT router (NAT behind NAT). This kind of setup is strongly discouraged. miniupnpd will refuses to launch when detecting a private (RFC1918) "WAN" IP address. The ext_ip option sould be added : ext_ifname=eth1 # "WAN" network interface, whose IP could be 192.168.1.22 ext_ip=80.1.2.3 # Real public IP address listening_ip=eth0 Please note that miniupnpd doesn't redirect any port on the other NAT router. UPNP requests forwarding is not implemented. It is however possible to use STUN. See the ext_perform_stun / ext_stun_host / ext_stun_port options. it is also possible to set a different interface for IPv6 WAN ext_ifname=eth0 ext_ifname6=sit0 miniupnpd supports some kind of security check for allowing or disallowing redirection to be made. The UPnP permission rules are read from the miniupnpd.conf configuration file. When a new redirection is requested, permission rules are evaluated in top-down order and the first permission rule matched gives the response : redirection allowed or denied. If no rule is matching, the redirection is allowed, so it is a good practice to have a "catch all" deny permission rule at the end of your permission ruleset. Sample permission ruleset : allow 4662-4672 192.168.1.34/32 4662-4672 deny 0-65535 192.168.1.34/32 0-65535 allow 1024-65535 192.168.1.0/24 1024-65535 deny 0-65535 0.0.0.0/0 0-65535 With this ruleset, redirections are allowed only for host on the subnet 192.168.1.0/255.255.255.0 for the ports 1024 or above. There is an exception for the host 192.168.1.34 for which only redirections from/to port 4662 to 4672 are allowed. You can generate the uuid for your UPnP device with the uuidgen available under linux. The following following OpenBSD package is also providing a "uuid" tool : http://www.openbsd.org/4.0_packages/i386/uuid-1.5.0.tgz-long.html An web based uuid generator is also available : http://kruithof.xs4all.nl/uuid/uuidgen On linux systems, one could also use the command 'cat /proc/sys/kernel/random/uuid' to generate an uuid. More simple, use the genuuid makefile target : > make genuuid This target is needed by the "install" target, so it is done automatically during install. To stop the daemon use : # kill `cat /var/run/miniupnpd.pid` or if your linux system use /etc/init.d/ # /etc/init.d/miniupnpd stop * Signals : miniupnpd handles the following signals : SIGUSR1: Send public IP address change notification SIGUSR2: Handle special actions in Tomato Firmware version Or rewrite the lease_file SIGINT: Close gracefully SIGTERM: Close gracefully SIGPIPE: Ignore There is code to detect change in network interfaces bsd/ifacewatcher.c and linux/ifacewatcher.c, but if that code doesn't work for you, you may want to send SIGUSR1 to miniupnpd if your public IP address changed. miniupnpd-2.3.7/Changelog.txt010064400017500000024000001210261463565007000153500ustar00nanardstaff$Id: Changelog.txt,v 1.517 2024/06/22 16:48:51 nanard Exp $ VERSION 2.3.7 : released on 2024/06/22 2024/06/17: FreeBSD: use libpfctl (FreeBSD 15+ or --libpfctl) 2024/06/09: default NOTIFY interval of 900s minus a random value 2024/04/29: ipv6: prefer globally routable addresses VERSION 2.3.6 : released on 2024/03/20 2024/03/05: NFTables: rewrite rule parsing code to fix source/dest address parsing. 2024/03/04: NFTables: add upnp_nftables_family_split option to use NFPROTO_IPV4/IPV6 instead of the generic NFPROTO_INET VERSION 2.3.5 : released on 2024/03/02 2024/02/25: fix IGDv2 WANIPConnection XML service description for AddAnyPortMapping out argument is NewReservedPort, not NewExternalPort 2024/01/17: default path for miniupnpd.conf is /etc/miniupnpd/miniupnpd.conf under linux 2024/01/15: fix SUBSCRIBE timeout compliance (should be >= 1800 seconds) PCP: fix enforcing min_lifetime minimum of 120secs CACHE-CONTROL: max-age=1800 in M-SEARCH response 2024/01/07: pf: fix for OpenBSD 7.4 VERSION 2.3.4 : released on 2024/01/04 2024/01/04: Fix parsing of action string in ExecuteSoapAction() 2023/06/29: Disable DeviceProtection service by default 2023/06/23: detect FDSSDP as a Microsoft client 2023/05/27: option enable_natpmp => enable_pcp_pmp add a short list of build-time options in --version output enable secure mode by default VERSION 2.3.3 : released on 2023/02/17 2023/02/04: Fix PinholeVerification() 2023/01/28 Fix updating of existing pinhole leasetime by AddPinhole VERSION 2.3.2 : released on 2023/01/20 2023/01/15: Fix NFTables again (RULE_HANDLE using more than 32 bits) 2022/10/21: Add option to match rules description with regex VERSION 2.3.1 : released on 2022/10/16 2022/10/10: NFTables : fix for Big Endian. iport was always read as 0 2022/08/06: Fixes DeletePCPMap() for "IPv6 pinholes" with netfilter_nftables backend 2022/06/02: Fixes "Port Triggering" with OpenBSD 4.7+ 2022/05/18: improve configure script for cross builds 2022/02/19: prefer non-reserved over reserved addresses in getifaddr() pf: use external IP for NAT in double NAT setups miniupnpd.init.d.script: support nftables VERSION 2.3.0 : released on 2022/01/23 2021/12/01: NFTables : use scripts to create table and chains (see https://github.com/miniupnp/miniupnp/pull/584) 2021/11/17: NFTables : use the nat chain for inet rules (Sven Auhagen) (see https://github.com/miniupnp/miniupnp/pull/562) VERSION 2.2.3 : released on 2021/08/21 2021/08/18: lease file for IPv6 pinholes 2021/08/11: Detect Microsoft User-Agent: and force IGDv1 in that case 2021/06/18: Fails on config parsing and init errors. print errors during init to both syslog and stderr. 2021/05/22: dynamically retrieve `uname -r` VERSION 2.2.2 : released on 2021/05/13 2021/04/22: Add SO_REUSEPORT option for SSDP receive sockets 2021/03/31: GetExternalIPAddress returns empty string when the External IP address can not be retrieved. 2021/02/26: iptables_removeall.sh: fix the cleanup of PREROUTING mangle chain VERSION 2.2.1 : released on 2020/12/20 (only minor build corrections) VERSION 2.2.0 : released on 2020/10/31 2020/10/30: OpenBSD: portinuse.c compatible with OpenBSD 5.5+ 2020/10/26: Improve AddAnyPortMapping port selection 2020/10/22: netfilter_nft: fix rule-cache update when using the same chain name in both tables. 2020/06/20: -v/-vv to output more log (LOG_INFO / LOG_DEBUG) AddAnyPortMapping() tries all possible ports 2020/06/10: improvements and bug fixes in netfilter_nft/nftnlrdr_misc.c 2020/06/05: fix handling of ipv4 M-SEARCH received on ipv6 sockets numerous fixes in linux/nftables implementation 2020/06/03: configure --disable-fork to disable going to background improve upnp_get_portmapping_number_of_entries() 2020/05/30: "IGD2 Port Triggering" pf implementation (adding a nat rule) 2020/05/07: Linux: use libcap or libcap-ng to drop unnecessary capabilities 2020/05/04: Move configuration detection from Makefiles to configure (was genconfig.sh) update linux Makefiles 2020/04/29: fix for bridges 2020/04/21: Remove FW API detecting code from Makefile (BSD). generate bsdmake.inc netfilter: addmasqueraderule() even if internal/external ports are the same improve error and debug log in upnpstun.c 2020/04/20: Fix "IGD2 Port Triggering" in update_portmapping() 2020/04/12: pf: disabled setting dst address in rule by default 2020/04/09: Option to disable IPv6 at runtime : -4 / ipv6_disable=yes 2020/03/29: Fix FreeBSD build 2019/12/18: Fix PCPSendUnsolicitedAnnounce() when IPv6 is not available 2019/10/05: Use OpenSSL TLS_server_method() instead of TLSv1_server_method() Add --version commandline option 2019/10/03: Use OpenBSD pledge() 2019/10/02: Working NFTables implementation thanks to Paul Chambers 2019/09/24: Distinguish between iptables and nftables in genconfig.sh 2019/09/01: fix of nftables scripts : https://github.com/miniupnp/miniupnp/pull/395 2019/08/24: Small fixes in netfilter code 2019/06/25: Update netfilter nftables code 2019/05/21: Allow to use two different network interfaces for IPv4 and IPv6 internet 2019/05/02: Fix ssdp notify on unrelated interfaces 2019/04/09: Fix buffer over-read in upnpevents.c with urls in the form http://ip:port 2019/04/05: Fix memory leak in upnpreplyparse.c with NewPortListing element 2019/03/22: netfilter: miniupnpd_functions.sh parsing fix. fix postrouting chain init. use iptables -I instead of -A to add rules 2019/03/09: check NULL protocol arg in AddAnyPortMapping 2019/03/07: Update portinuse code to reflect changes made in FreeBSD 12.0 2019/02/12: linux/getifstats.c: use custom strtoul() implementation to roll over after 2^32-1 2019/02/03: netfilter: fix build with linux kernel 5.0 2018/12/18: upnp_redirect(): accept NULL desc argument (avoid DOS in AddPortMapping) upnp_event_prepare(): check the return value of snprintf() 2018/09/07: Fix PCP Public address announcement 2018/07/06: STUN support VERSION 2.1 : released on 2018/05/08 2018/05/02: option to store remaining time in leasefile 2018/04/12: pf: set dst address in rule if use_ext_ip_addr is set 2018/04/06: Add options for netfilter scripts 2018/03/13: Use monotonic clock for timeouts, etc. 2018/02/22: Add option force_igd_desc_v1 to force devices and services versions to 1 in IGD v2 mode 2017/12/12: Fix a few buffer overrun in SSDP and SOAP parsing 2017/11/02: PCP : reset epoch after address change 2017/05/26: merge https://github.com/miniupnp/miniupnp/tree/randomize_url branch 2017/05/24: get SSDP packet receiving interface index and use it to check if the packet is from a LAN 2017/03/13: default to client address for AddPortMapping when is empty pass ext_if_name to add_pinhole() 2016/12/23: Fix UDA-1.2.10 Man header empty or invalid 2016/12/16: Do not try to open IPv6 sockets once it is disabled 2016/12/01: Fix "AddPinhole Twice" test 2016/11/11: fixes build for Solaris/SunOS 2016/07/23: fixes build error on DragonFly BSD VERSION 2.0 : released on 2016/04/19 2016/04/18: linux/netfilter: fix compile time detection of iptables version >= 1.4.3 2016/03/08: linux/netfilter: do not add MASQUERADE rule if ports are equals 2016/02/19: set IPv6 Hop limit to 10 fix HOST: header of event notifications in IPv6 be more compliant on 64bit machines : ui4 in [0;2^32-1] 2016/02/16: minor changes to follow UDA 1.1 more closely. more argument checking in Soap methods. 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 2016/01/28: renamed iptables chain MINIUPNPD-PCP-PEER to MINIUPNPD-POSTROUTING implemented "IGD2 Port Triggering" with netfilter/iptables 2016/01/18: fix pcpserver.c CreatePCPMap_FW() : check pinhole before adding 2015/12/16: improve syslog message for incoming HTTP requests 2015/12/13: --disable-pppconn to disable WanPPPConnection more fixes in DeviceProtection service 2015/12/12: add commandline option to genconfig.sh to set UPnP (UDA) version advertise correct service and device versions when IGDv2 is enabled fix action arguments for DeviceProtection service fix event subscription renewal (include SID in response) 2015/11/16: Fix bsd/getroute.c get_src_for_route_to() when args are NULL 2015/11/02: use LOG_INFO instead of LOG_ERR for PCP PEER and MAP success 2015/10/30: fix : properly call find_ipv6_addr() with the 1st LAN interface use name server from query in SOAP responses (continued) 2015/10/24: move SSDP_PACKET_MAX_LEN definition to config.h. also set default to 1024. 2015/09/22: cleanup UPNP_VERSION macro / add UPNP_VERSION_MAJOR, UPNP_VERSION_MINOR Don't use packed structs anymore to read/write PCP messages 2015/09/15: use name server from query in SOAP responses 2015/09/14: Randomize URLs to avoid http://www.filet-o-firewall.com/ https://github.com/filetofirewall/fof (specific branch, merged later, see above) 2015/08/25: better bind socket to right interface(s), using struct ip_mreqn, SO_BINDTODEVICE 2015/04/30: Adding linux/nftables support 2015/04/26: Remove dependency to libnfnetlink fix typos in miniupnpd.conf 2015/03/09: fix get_portmappings_in_range() for linux/netfilter 2015/03/07: don't die when IPv6 is enabled and interface has no IPv4 address 2015/02/10: IP wildcard for AddPinhole() is empty string 2014/12/10: Checking Host: HTTP request header to prevent DNS rebinding attack configurable BOOTID.UPNP.ORG SSDP header use time for BOOTID.UPNP.ORG value 2014/12/09: fix upnp_add_inboundpinhole() : check inet_pton() return fix upnp_redirect() : check inet_aton() return fix potential memory corruption in upnpsoap.c/GetListOfPortMappings() fix buffer overrun in ParseHttpHeaders() if Content-Length doesn't contain any digit ! check if BuildHeader_upnphttp() failed to allocate memory Credits goes to Stephen Röttger of the Google Security Team for identifying the vulnerabilities 2014/12/04: check "sysctl -n net.ipv6.bindv6only" for linux 2014/11/28: fixes ExecuteSoapAction if SoapAction value is not enclosed into double quotes 2014/11/07: sockaddr_to_string() includes scope in IPv6 addresses VERSION 1.9 : released on 2014/10/27 2014/10/23: Properly implements NAT-PMP mapping removal according to RCF6886 2014/10/22: Discard NAT-PMP packets coming from the WAN Send SSDP announces to IPv6 link-local, site-local and global multicast addresses 2014/10/21: small modifications to compile with exotic C libraries 2014/10/14: add comments in miniupnpd.conf regarding security 2014/09/25: DeletePortMapping now checks for client IP in Securemode 2014/06/xx: Various fixes : e->ipv6.flags |= IP6T_F_PROTO; (netfilter) fix natpmp.c byte order conversion add small delay before SSDP response to prevent flooding 2014/05/22: Add ipv6_bind_address (option "ipv6_listening_ip") disable IPv6 when socket(PF_INTET6, ...) errors with EAFNOSUPPORT Add IPV6 multicast membership only on selected "LAN" interfaces 2014/05/20: be more strict when parsing LAN addresses / interface names 2014/05/19: set source address for IPV6 packets sendto_schedule2() etc. 2014/05/15: Fix deletePortMappingRange() 2014/04/21: Fix PCP when request contain 0 IPv4 external address Remove pointer casting in natpmp.c 2014/04/15: rewrite iptables_*.sh scripts 2014/04/12: Add FreeBSD support for CHECK_PORTINUSE Add PCP support for CHECK_PORTINUSE 2014/04/09: Add HTTPS support and skeleton of DeviceProtection implementation 2014/03/24: start work to enable IPv6 PCP operations 2014/03/14: reject renewal of subscribtion that already timeouted Support for multiple URL in Callback: header (SUBSCRIBE) 2014/03/13: fix getifaddr_in6() (used for PCP) implement permissions with PCP Map fix upnp_event_notify_connect() when ENABLE_IPV6 is set 2014/03/10: Enable PCP by default. Work in IPv6 on system where PF_INET6 are restricted to IPv6 only change ipv6_enabled/ipv6fc_inbound_pinhole_allowed/ipv6fc_firewall_enabled global vars to flags in runtime_flags 2014/03/09: IPv6 support in testgetifaddr 2014/03/07: NAT-PMP search an allowed eport instead of returning an error if the original eport is not allowed. 2014/03/06: Fix add_filter_rule2() for pf. 2014/02/28: log message when shutting down natpmp : avoid hang when all external ports in use 2014/02/25: add implementation of scheduled sendto (asyncsendto) in order to retry failed sendto() calls or schedule sending of packets 2014/02/24: Defaulting to SSDP_RESPOND_SAME_VERSION 2014/02/11: Fix PCP Map renewal 2014/02/06: possibility to disable ipv6 at runtime 2014/02/03: PCP : Add support for ANNOUNCE requests minixml now handle XML comments 2013/12/16: Attempt to compile with OS X/pf 2013/12/13: Make all manufacturer info configurable thanks to Leo Moll Merge PCP support (see https://github.com/miniupnp/miniupnp) 2013/06/13: Have 3 UUID for the 3 devices (IGD, WAN Device, WAN Connection Device) 2013/06/06: update upnpreplyparse to allow larger values (128 chars instead of 64) 2013/06/05: check Service ID in SetDefaultConnectionService method Don't advertise WANPPPConnection in UPNP_STRICT mode 2013/05/29: Remove namespace from variable name elements in Events "propertyset" to comply with UPNP DeviceArchitecture v1.1. 2013/05/20: Adding support for IP Filter version 5.x 2013/05/16: refuses non integer values 2013/05/14: Update upnpreplyparse to take into account "empty" elements 2013/05/03: Use pkg-config under linux to find libiptc. Thanks to Olivier Langlois 2013/04/29: Add warning message when using IPv4 address for listening_ip with IPv6 enabled 2013/04/27: Uses ifr_addr if ifr_netmask is not defined in struct ifreq 2013/04/26: Correctly handle truncated snprintf() in SSDP code 2013/04/24: to avoid build race conditions, genconfig.sh now uses a temporary file 2013/04/20: use scope in get_lan_for_peer() for IPv6 addresses 2013/03/23: autodetect LAN interface netmask instead of defaulting to /24 2013/02/11: Use $(DESTDIR) in Makefile.linux. see https://github.com/miniupnp/miniupnp/issues/26 2013/02/07: Add DATE: header in SSDP packets Fix SSDP packets sent with uuid as ST: header to conform to UDA ignore SSDP packets missing the MX: header in UPNP_STRICT mode Added Ext: header to HTTP responses to conform to UDA Refactored SendSSDPNotifies() and SendSSDPGoodbye() and add missing ssdp:alive and ssdp:byebye with NT uuid value. VERSION 1.8 : released on 2013/02/06 2013/02/06: Check source address of incomining HTTP connections and SSDP packets in order to filter out WAN SSDP and HTTP traffic. Implement get_src_for_route_to() for *BSD fix 2 potential memory leaks in GetListOfPortMappings() 2013/01/29: upnphttp.c: Fix and comment the findendheaders() function upnphttp.c: remove strchr() call in ParseHttpHeaders() add comments to explain how buffer is checked before calls to ParseHttpHeaders() 2013/01/27: upnphttp.c: ParseHttpHeaders() now checks atoi() return 2012/12/11: More return value check for malloc() and realloc() 2012/10/23: minor modifications to linux/getroute.c and testgetroute.c 2012/10/04: updated DEFAULTCONNECTIONSERVICE_MAGICALVALUE for IGDv2 increased default buffer size for HTTP response More argument check for SOAP actions in UPNP_STRICT mode Better error checking after connect() in upnpevent 2012/10/03: Fix atoi() on null pointer in upnpsoap.c properly set service/device version in SSDP messages fix newSubscriber() for IP6FirewallControl and DeviceProtection services Enforce compliance for SUBSCRIBE messages (UPNP_STRICT mode) Enforce compliance for UNSUBSCRIBE messages (UPNP_STRICT mode) Ignore "-Wmissing-field-initializers" in upnpdescgen.c check size of h->res_buf before building HTTP response ENABLE_HTTP_DATE : add a Date: header to all HTTP responses 2012/09/27: Fixes with DISABLE_CONFIG_FILE and UPNP_STRICT UPC must be a 12 decimal digit code SetDefaultConnectionService() checks its argumnents in UPNP_STRICT mode Support for Accept-Language/Content-Language HTTP headers Content-Type is now text/xml; charset="utf-8" to conform with UDA v1.1 Support Expect: 100-continue for POST HTTP requests Manage services/devices versions in minissdp.c Rename all include guards to not clash with C99. (7.1.3 Reserved identifiers) 2012/09/20: Cleaning code in ipfw (Jardel Weyrich) 2012/09/18: Fixing a bug in clean_pinhole_list() under linux/netfilter 2012/09/15: Adding an informational message at startup 2012/08/24: Moved man page to section 8. miniupnpd.1 => miniupnpd.8 Added install of miniupnpd.8 man page in Makefile.linux 2012/08/10: improved SubmitServicesToMiniSSDPD() function fiability 2012/07/17: Add -A command line option to add permission rules 2012/07/14: Add -z command line option to change friendly name (thanks to Shawn Fisher) 2012/07/10: Detect port in use - patch by David Kerr 2012/06/29: added DISABLE_CONFIG_FILE in options.h to disable miniupnpd.conf parsing Add command line parsing for clean_ruleset_interval option 2012/06/28: Only activate -L option for PF and IPF -a option takes two arguments with MULTIPLE_EXTERNAL_IP defined 2012/06/23: in UPNP_STRICT mode, the literal IPv6 address in "location:" of SSDP messages is the source address used to send the message 2012/06/08: Disable -ansi CFLAGS in Makefile.linux because recent iptables headers make use of typeof keyword which is a GCC extension. 2012/05/31: Improvements in autodetecting firewall under (Free)BSD 2012/05/28: Cleanup HTTP request handling. Answer 405 when relevant VERSION 1.7 : released the 2012/05/28 2012/05/28: clean linux/ifacewatcher.c set natpmp socket non blocking 2012/05/24: More solaris fixes 2012/05/21: Clean signal handling 2012/05/08: Clean expired IPv6 pinholes correctly. and also with linux/netfilter. 2012/05/07: Finalizing netfilter version of get_pinhole_info() 2012/05/01: Move IPv6FirewallControl related code from upnpredirect.c to upnppinhole.c Add netfilter implementation for delete_pinhole()/update_pinhole()/get_pinhole_info() 2012/04/30: Clean up settings of CFLAGS in Makefile's Remove Warnings caused by signed/unsigned integer comparaisons Also fix a couple of integer/pointer comparaisons. Add UNUSED(arg) macro to remove unused argument warning. Fix error handling in upnpevents.c (was causing segfault on Solaris !) 2012/04/26: Started to implement add_pinhole() for netfilter (linux) 2012/04/25: Fixed a bug in upnphttp that happened when POST is received in several recv() calls and realloc() is called so the buffer used is moved. 2012/04/23: Implement CheckPinholeWorking GetPinholePackets. WANIPv6FirewallControl UpdatePinhole still to be done. And also netfilter/ipf/ipfw versions 2012/04/20: Enough WANIPv6FirewallControl is implemented on pf so that AddPinhole() and DeletePinhole() works ! 2012/04/19: First working experiment of IPv6 "pinhole" with pf 2012/04/15: More C++ => ANSI C comments to compile with -ansi option Add command line arguments to genconfig.sh config script. 2012/04/12: Set TTL on SSDP Notify sockets (IPv4). TTL is set to 2 (recommendation from UPnP Device Architecture v1.1) 2012/04/06: Implementing IPv6 support : Send SSDP NOTIFY ssdp:alive and ssdp:goodbye messages in IPv6. Use UPnP/1.1 in SERVER: string as required in UPnP Device architecture 1.1. Allow LAN interface to be given as interface names, instead of interface IP addresses. It will allow IPv6 operations. fix linux/getifstats.c when bitrate is unknown 2012/03/31: Only remove pidfile if one was written in the first place. 2012/03/19: Fix ipfilter support (thanks dhowland https://github.com/dhowland) 2012/03/14: Changes to miniupnpd.init.d.script by Shawn Landden 2012/03/05: fixed reload_from_lease_file(). 2012/02/15: Change parselanaddr() function to allow 192.168.1.1/255.255.255.0 in configuration file. Change read_permission_line() to allow 192.168.1.1/255.255.255.0 in permission line (in configuration file). 2012/02/12: More syntax checks in upnppermissions.c 2012/02/11: Fix ipfw/Mac OS X specific source files to compile ok with -ansi flag 2012/02/09: Make HTTP listen socket non blocking (so accept() can't block) Make SSDP receive sockets non blocking use sockaddr_to_string() in SendSSDPAnnonce2 to handle IPv6 addresses 2012/02/06: Make HTTP (SOAP) sockets non blocking. 2012/02/05: Compile ok with -ansi flag. Save a few bytes in options.c using a string repository, instead of a fixed size buffer for each option value. 2012/02/04: Added friendly_name= option to config file 2012/02/03: Anchor name (PF) is now configurable through the config file with anchor= Added test of presence of /lib/libip4tc.so and /lib/libip6tc.so files in Makefile.linux in order to add -lip4tc and -lip6tc to LIBS accordingly. 2012/02/01: always handle EAGAIN, EWOULDBLOCK and EINTR after recv()/recvfrom() calls 2012/01/20: Always #include before #include (for OpenBSD) .onrdomain field was added in pf with OpenBSD 5.0. Add PFRULE_HAS_ONRDOMAIN 2012/01/02: Fixing netfilter/iptables_*.sh scripts for new ifconfig output format. getifaddr.c: added additional checks on structure returned by getifaddrs() Fixing Mac OS X makefile for installation 2011/11/18: avoid infinite loop in SendResp_upnphttp() in case of error Replaced SendResp_upnphttp() + CloseSocket_upnphttp() by SendRespAndClose_upnphttp() Tomato specifics in genconfig.sh 2011/07/30: netfilter : Added a tiny_nf_nat.h file to compile with iptables installed headers. include xtables.h instead of iptables.h VERSION 1.6 : released the 2011/07/25 2011/07/25: Update doc for version 1.6 2011/07/15: Fixing code with MULTIPLE_EXTERNAL_IP defined. 2011/06/27: IPv6 support for UPnP events. Security checks in UPnP events. 2011/06/22: Remote host for GetListOfPortMappings Remote host support for ipfw (tested on Mac OS X) 2011/06/20: support for iptables-1.4.11.1 2011/06/18: Remote host support for pf version 2011/06/04: Supporting RemoteHost (mandatory in IGD v2) 2011/06/03: Enabling events by default 2011/06/01: Fixing Timeout missing in SUBSCRIBE renewal responses (thanks to Pranesh Kulkarni) Added comments about changes between IGD v1 and IGD v2 2011/05/28: Description and leaseduration kept in ipfw version of the code. Fixing ipfw code after testing under Mac OS X 10.6.7 (darwin 10.7.0) 2011/05/27: Finishing and testing LeaseDuration support under OpenBSD. Changing NAT-PMP port mapping lifetime support to match lease duration support. NAT-PMP address change announce broadcasted to both port 5350 and 5351 to be compatible with client following the version of NAT PMP specification from 2008 or earlier. writepidfile() Overwrite file if already existing 2011/05/26: fix in linux/getifstats.c. See http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=2212 Implementation of LeaseDuration support. 2011/05/23: added get_wan_connection_status_str() 2011/05/20: adding ifacewatcher thanks to Alexey Osipov GET /DP.xml is now available. The description has to be completed. 2011/05/19: Add getconnstatus.c/.h. Don't always have ConnectionStatus to "Connected" Events for WANIPv6FirewallControll 2011/05/16: patches for gentoo linux. generation of the DeviceProtection service description. 2011/05/15: Making the SSDP receiving socket work in IPv6 ! 2011/05/14: Support for HTTP in both IPv6 and IPv4. IPv6 for SSDP receiving socket. 2011/05/13: add new options in genconfig.sh (IGD_V2, ENABLE_DP_SERVICE) add global vars ipv6fc_firewall_enabled and ipv6fc_inbound_pinhole_allowed have MACROS for magical values in upnpdescgen.c, add eventing vars for WanIPv6FirewallControl. applied 0001-Cosmetic-changes.patch(see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=764) applied 0002-Remove-lan-addresses-limit-by-changing-storage-type-.patch replaced some of the urn:schemas-upnp-org:device:* literal strings by macros. adding some support for IP v6. #define ENABLE_IPV6 added -fno-strict-aliasing to compile options. 2011/05/09: updating upnp descriptions for IGDv2 2011/05/07: Adding WANIPv6FirewallContro to upnp description 2011/04/30: adding a UPNP_STRICT config macro. Use it now for checking RemoteHost. ENABLE_6FC_SERVICE : add the implementations of WANIPv6FirewallControl actions 2011/04/11: preparing getifaddr() for IP v6 preparing SSDP stuff for IP v6. Trying to conform to UDA v1.1 2011/03/09: Some modifications thanks to Daniel Dickinson to improve OpenWrt build. Fixed some warnings. 2011/03/03: Added code to generate devices/services descriptions for IGD v2 (to be continued) 2011/03/02: improved netfilter/delete_redirect_and_filter_rules() in order to remove the right filter rule, even if it has another index than the nat rule. 2011/03/01: clean up an fixes to make netfilter/testiptcrdr compile 2011/02/21: Make "Makefile" work under Mac OS X with bsdmake. added get_portmappings_in_range() in ipfwrdr.c 2011/02/07: added get_portmappings_in_range() / upnp_get_portmappings_in_range() 2011/02/06: Implementation of GetListOfPortMappings 2011/01/27: Reverting "fixes" done in linux/iptables code the 2010/09/27. see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=741 2011/01/04: added MINIUPNPD_VERSION in config.h. Taken from VERSION file. VERSION 1.5 : released the 2011/01/01 2011/01/01: Started to implement some of the new methods from WANIPConnection v2 2010/09/27: Some fixes in the linux/iptables code when miniupnpd_nat_chain <> miniupnpd_forward_chain 2010/09/21: Patch to support nfqueue thanks to Colin McFarlane 2010/08/07: Update Mac OS X / ipfw stuff from Jardel Weyrich Fix in Makefile.linux for x86_64 2010/05/06: Bugfix un CleanNATPMPRules() : see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=640 2010/03/14: Fixing natpmp sockets. 2010/03/08: Fix Makefile.linux to compile properly under Mandriva/rh/Fedora with Iptables >= 1.4.3 Workaround for bad uptime when started with a bad time set. 2010/03/07: Tried to make a OpenBSD version 4.7 compatible code... still some issues. 2010/03/06: updates to testobsdrdr 2010/03/03: -lip4tc in Makefile.linux. 2010/02/15: some more error handling in set_startup_time() silencing some warnings 2010/01/14: Open Several sockets for NAT-PMP to make sure the source address of NAT-PMP replies is right. see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=609 2009/12/31: miniupnpdctl now output command line arguments. added a -h option to get help. improved help. 2009/12/22: using PRIu64 format to printf u_int64_t Fixing calls to get_redirect_rule_by_index() : ifname should be initialized. Add header lines to miniupnpdctl output 2009/11/06: implementing sending of ip address change notification when receiving the signal SIGUSR1 VERSION 1.4 : released the 2009/10/30 2009/10/10: Integrate IPfilter patch from Roy Marples. Fix Netfilter code for old netfilter : see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=584 trim the description string in reload_from_lease_file() 2009/09/21: Fixing unclosed raw sockets bug with netfilter code. 2009/09/04: Fixes in ipf code thanks to Roy Marples Enable DragonFly BSD Support thanks to Roy Marples. Allow packager to define default location of config file via CFLAGS Respect $DESTDIR when installing 2009/08/20: Adding some support for MacOS X and IPFW SO_REUSEADDR in minissdp.c for SSDP listening socket 2009/06/05: unlink lease file in reload_from_lease_file() 2009/05/16: Fixed a buffer overflow in ProcessSSDPRequest() 2009/05/11: improving genconfig.sh for NetBSD : detecting use of pf or ipf VERSION 1.3 : 2009/04/17: working support for iptables >= 1.4.3 2009/04/13: work to support iptables-1.4.3 and up 2009/04/10: fix in upnpevents_removeSubscriber() 2009/02/14: added reload_from_lease_file() 2009/02/13: Changes in upnpdescgen.c to allow to remove empty elements strcasecmp instead of strcmp on path comparaisons to allow bugged clients to work 2009/01/29: Some minor changes to Makefile improving Makefile.linux in order to build with iptables not properly installed on the system. 2009/01/23: Fixing upnpevents thanks to Justin Maggard 2008/10/15: getifstats() return -1 when supplied with bad arguments 2008/10/11: Fixed NAT-PMP response when IP not allocated to external interface 2008/10/09: adding testgetifaddr Reporting Unconnected status when the "external interface" has no IP address assigned. Also added some comments VERSION 1.2 : 2008/10/07: updating docs 2008/10/06: MiniUPnPd is now able to use MiniSSDPd to manage SSDP M-SEARCH answering 2008/10/03: You can now let miniupnpd choose itself the HTTP port used. 2008/10/01: Improvements in genconfig.sh for detecting ipf or pf (under FreeBSD) and improve debian/ubuntu stuff. custom chain name patch from : http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=493 2008/08/24: added USE_IFNAME_IN_RULES macro that can be disabled in order to remove interface name from rules. 2008/07/10: Fixed compilation without ENABLE_L3F_SERVICE 2008/04/27: correct UNSUBSCRIBE processing 2008/04/25(bis): changed iptables_removeall.sh and iptables_init.sh in order to remove IP from the rules VERSION 1.1 : 2008/04/25: Eventing is allmost completly implemented 2008/04/24: Correct event handling ? 2008/04/08: enabling tag in PF rules. quick can be set off. 2008/03/13: implementing event notify 2008/03/11: fixing a command line parsing error 2008/03/09: optimisations in upnpsoap.c 2008/03/08: optimizing upnpsoap.c for size 2008/03/06: Worked on the Eventing : generating XML event notifications Send initial notification after subscribe Improved pretty print of testupnpdescgen Reduced Memory usage of upnpdescgen fixed a small bug in the description 2008/03/03: Fixed miniupnpd.c for compiling without natpmp support fixed presentationURL not there with L3F fixing lease file creation/modification 2008/02/25: Rewrite of Send501() and Send404() More work on events genconfig.sh autodetects pf/ipf 2008/02/24: Started to implement UPnP Events. do NOT use it at the moment ! 2008/02/21: Added support for the Layer3Forwarding Service added init_redirect() and shutdown_redirect() functions 2008/02/20: Removed Ext: HTTP header when useless enabled the dummy service by default to please windows XP ! 2008/02/07: upnp_enable patch by Nikos Mavrogiannopoulos. lease_file patch by Nikos Mavrogiannopoulos. 2008/01/29: some changes to Makefile.openwrt use daemon() - daemonize() is still available for systems lacking daemon() VERSION 1.0 : 2008/01/27: moved lan_addr to upnpglobalvars.h/.c Adding experimental multiple external IP support. 2008/01/22: removed dummy service from description to improve compatibility with emule client Add "secure mode". put runtime flags in the same variable 2008/01/14: Fixed a bug in options.c for the parsing of empty lines. 2008/01/03: Fixed CleanExpiredNATPMP() 2008/01/02: Adding a queue parameter for setting ALTQ in pf 2007/12/27: improving some stuff with the PF_ENABLE_FILTER_RULE. 2007/12/22: Adding a runtime option to enable/disable NAT-PMP 2007/12/20: Added a cache in linux getifstats(). Please enable by editing config.h 2007/12/14: Updating an existing NAT-PMP mapping now works 2007/12/13: NAT-PMP code now remove expired mappings TCP/UDP where swapped in NAT-PMP code 2007/12/04: Adding details to the error message for sendto(udp_notify) 2007/11/27: pf code doesn't generate filter rules by default anymore. The #ifdef PF_ENABLE_FILTER_RULES must be uncommented in config.h. 2007/11/02: moved some of the prototypes common to all firewalls to commonrdr.h Added functionalities to NAT-PMP 2007/11/01: Debugged NAT-PMP code 2007/10/28: Cleaning and improving NAT-PMP code 2007/10/25: improved the NAT-PMP experimental support updated README and INSTALL files 2007/10/24: Adding support for NAT-PMP (from apple !) 2007/10/11: Checking the commandline for errors. 2007/10/08: Improved the BSD/Solaris Makefile Merging last code from Darren Reed. Solaris/IPF should work now ! added a man page. 2007/10/07: Adding Darren Reed code for ipf. 2007/10/06: Adding SunOS support thanks to Darren Reed. Reorganizing os/firewall dependent code thanks to Darren Reed. 2007/09/27: linux make install support PREFIX variable 2007/09/25: reorganizing LAN sockets/address to improve multi LAN support. SSDP announces are sent to all configured networks. SSDP responses are "customized" by subnetwork. 2007/09/24: prototype code to remove unused rules miniupnpdctl now display current rules synchronised add_filter_rule2() prototype between pf and netfilter code. 2007/09/19: Correctly filling the Cache-control header in SSDP packets 2007/08/28: update PFRULE_INOUT_COUNTS detection for FreeBSD 2007/08/27: update version in genconfig.sh do not error when a duplicate redirection is requested. 2007/07/16: really fixed the compilation bug with linux>=2.6.22 2007/07/04: fixed an error in options.c that prevented to use packet_log option 2007/07/03: improved genconfig.sh fixed a compilation bug with linux>=2.6.22 2007/06/22: added PFRULE_INOUT_COUNTS macro to enable separate in/out packet and bytes counts in pf for OpenBSD >= 3.8 2007/06/15: removed a possible racecondition in writepidfile() 2007/06/12: improved genconfig.sh : no more "echo -e", use lsb_release when available 2007/06/11: get_redirect_rule*() functions now return some statistics about rule usage (bytes and packets) 2007/06/07: Fixed the get_redirect_desc() in the linux/netfilter code 2007/06/05: Clean up init code in miniupnpd.c Added a syslog message in SoapError() 2007/06/04: Now store redirection descriptions in the linux/netfilter code 2007/05/21: Answers to SSDP M-SEARCH requests with ST: ssdp:all added make install to Makefile.linux 2007/05/10: Fixed a bug int the DeletePortMapping linux/netfilter implementation It was allways the 1st rule that was deleted. 2007/04/26: Fixed config.h.openwrt 2007/04/16: added something in the INSTALL file about the FreeBSD send(udp_notify) problem fix (allowing 239.0.0.0/8 explicitely in pf.conf) 2007/03/30: added setsockopt(s, SOL_SOCKET, SO_BROADCAST ...) for broadcasting socket 2007/03/17: Fixed filter rule under linux : it was using wrong port ! thanks to Wesley W. Terpstra 2007/03/01: Moved some of the SSDP code from miniupnpd.c to minissdp.c 2007/02/28: creating miniupnpdctl 2007/02/26: use LOG_MINIUPNPD macro for openlog() simplify miniupndShutdown() 2007/02/09: improved genconfig.h Added stuff to change the pf rule "rdr" to "rdr pass" 2007/02/07: Corrected Bytes per seconds to bits per second. Ryan cleaned up comments and typos. Ryan cleaned up daemonize stuff. Ryan added possibility to configure model number and serial number 2007/01/30: ryan improved the robustness of most UPnP Soap methods I added a target in the Makefiles to properly generate an uuid using command line tools. Improved configuration file parsing. 2007/01/29: Adding uuid option in miniupnpd.conf 2007/01/27: Added upnppermissions stuff : adding some security to UPnP ! fixed XML description thanks to Ryan Wagoner improved QueryStateVariable thanks to Ryan Wagoner 2007/01/22: use getifaddr() for each GetExtenalIPAddress() Call. We can change the ip during execution without pb 2007/01/17: Lots of code cleanup 2007/01/12: Fixed a nasty bug in the linux/netfilter version of get_filter_rule() 2007/01/11: Improved the handling of the miniupnpd.conf file. added -f option to choose which config file to read. 2007/01/10: Fixed potential bugs with ClearNameValueList() 2007/01/08: All by Ryan Wagoner : - coding style and comments cleanup - using now option file miniupnpd.conf 2007/01/03: changed "xx active incoming HTTP connections" msg 2007/01/02: Patch from Ryan Wagoner : - no need to open sockets if we can't set the error handlers - format the usage so it fits nicely on a standard size terminal - fix up log_err message so they have the same format and you know what they are related to - use same "white space" style throughout - on shutdown no need to continue if opening socket or setsockopt fails 2006/12/14: reduce amount of log lines (keeping the same information) 2006/12/07: Fixed Makefiles fixed typos in logs version 1.0-RC1 released 2006/12/02: moved strings from upnpdescgen.c to upnpdescstrings.h for easier modification Server: HTTP header now comes from a #define added a compilation-time generated config.h 2006/11/30: minixml updated. should have no impact Added support for presentationURL with -w switch implemented getifstats() for linux. Added testgetifstats program improved error handling in getifstats() BSD 2006/11/26: no need to have miniupnpc sources to compile miniupnpd. Makefile.openwrt updated Closing sockets on exit thanks to Ryan Wagoner 2006/11/23: now handling signal SIGINT setting HTTP socket with REUSEADDR thanks to Ryan Wagoner daemon now tested on a Linksys WRT54G device running OpenWrt ! 2006/11/21: disabling rtableid in pf code. 2006/11/22: Also responds on M-SEARCH with the uuid 2006/11/20: gaining some space in upnpsoap.c 2006/11/19: Cleaning up code to comply with ANSI C89 2006/11/17: Linux version now deleting both nat and accept rules implemented -U option under Linux 2006/11/16: implemented delete_redirect_rule() for linux returning error 714 in DeletePortMapping() when needed 2006/11/12: The linux/netfilter version should now WORK ! fix in the writepidfile() function. open with a mode ! 2006/11/10: fixing the XML description generation for big endian machines working on the linux/netfilter port 2006/11/09: improved a lot the handling of HTTP error cases 2006/11/08: Tried to make the Makefile compatible with both BSDmake and GNUmake. It was hard because of $^ and $< 2006/11/07: Makefile compatible with BSD make make install target. getifstats.c compatible with both OpenBSD and FreeBSD. 2006/11/06: added getifstats.c for openBSD. May not work under FreeBSD ? now reports bytes/packets sent/received reporting bitrates possibility to report system uptime 2006/10/29: added a -L option to enable loggin (is off by default now). 2006/10/28: Patch by Ryan Wagoner to correct the XML description (was NewUpTime instead of NewUptime) and implement uptime. Trying to fix the memory leak. Added some comments added a -d option for debugging purpose Tnaks to valgrind (under linux!) I removed a small memory access error. 2006/10/27: Thanks to a patch sent by Michael van Tellingen, miniupnpd is now ignoring NOTIFY packets sent by other devices and is writing is own pid to /var/run/miniupnpd.pid 2006/10/23: Allways set sendEvents="no" in XML description (was causing pb with winXP as SUBSCRIBE is not implemented) 2006/10/22: added translation from hostname to IP in the AddPortMapping() method Thanks to Ryan Wagoner. 2006/10/18: Added an INSTALL file 2006/10/13: Added the possibility to change the notify interval 2006/09/29: Improved compliance of the XML Descriptions pretty print for testupnpdescgen 2006/09/25: improved the Error 404 response. Better serviceType and serviceId for dummy service... 2006/09/24: updating the XML description generator 2006/09/18: Thanks to Rick Richard, support for SSDP "alive" and "byebye" notifications was added. The -u options was also added. The SSDP response are now improved. The -o option is now working (to force a specific external IP address). The Soap Methods errors are correctly responded (401 Invalid Action) 2006/09/09: Added code to handle filter rules. Thanks to Seth Mos (pfsense.com) storing the descriptions in the label of the rule 2006/09/02: improved the generation of the XML descriptions. I still need to add allowed values to variables. 2006/07/29: filtering SSDP requests and responding with same ST: field 2006/07/25: Added a dummy description for the WANDevice 2006/07/20: Command line arguments processing Added possibility to listen internally on several interfaces miniupnpd-2.3.7/linux/getifstats.c010064400017500000024000000103561365604170400164030ustar00nanardstaff/* $Id: getifstats.c,v 1.16 2020/05/10 17:51:00 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include "config.h" #include "../getifstats.h" #ifdef GET_WIRELESS_STATS #include #include #include #include #endif /* GET_WIRELESS_STATS */ /* that is the answer */ #define BAUDRATE_DEFAULT 4200000 /* this custom implementation of strtoul() rollover * this is needed by the UPnP standard */ static unsigned long my_strtoul(const char * p, char ** endptr, int base) { unsigned long value; if (base == 0) { /* autodetect base : * 0xnnnnn is hexadecimal * 0nnnnnn is octal * everything else is decimal */ if (*p == '0') { p++; if (*p == 'x') { p++; base = 16; } else { base = 8; } } else { base = 10; } } for (value = 0; *p >= '0'; p++) { value *= (unsigned long)base; if (*p <= '9') { if (base < 10 && *p >= ('0' + base)) break; value += (unsigned long)(*p - '0'); /* 0-9 */ } else if (base <= 10 || *p < 'A') { break; } else if (*p < ('A' + base - 10)) { value += (unsigned long)(*p - 'A' + 10); /* A-F */ } else if (*p >= 'a' && *p < ('a' + base - 10)) { value += (unsigned long)(*p - 'a' + 10); /* a-f */ } else { break; } } if (endptr != NULL) *endptr = (char *)p; return value; } int getifstats(const char * ifname, struct ifdata * data) { FILE *f; char line[512]; char * p; int i; int r = -1; char fname[64]; #ifdef ENABLE_GETIFSTATS_CACHING static time_t cache_timestamp = 0; static struct ifdata cache_data; time_t current_time; #endif /* ENABLE_GETIFSTATS_CACHING */ if(!data) return -1; data->baudrate = BAUDRATE_DEFAULT; data->opackets = 0; data->ipackets = 0; data->obytes = 0; data->ibytes = 0; if(!ifname || ifname[0]=='\0') return -1; #ifdef ENABLE_GETIFSTATS_CACHING current_time = upnp_time(); if(current_time == ((time_t)-1)) { syslog(LOG_ERR, "getifstats() : time() error : %m"); } else { if(current_time < cache_timestamp + GETIFSTATS_CACHING_DURATION) { /* return cached data */ memcpy(data, &cache_data, sizeof(struct ifdata)); return 0; } } #endif /* ENABLE_GETIFSTATS_CACHING */ f = fopen("/proc/net/dev", "r"); if(!f) { syslog(LOG_ERR, "getifstats() : cannot open /proc/net/dev : %m"); return -1; } /* discard the two header lines */ if(!fgets(line, sizeof(line), f) || !fgets(line, sizeof(line), f)) { syslog(LOG_ERR, "getifstats() : error reading /proc/net/dev : %m"); } while(fgets(line, sizeof(line), f)) { p = line; while(*p==' ') p++; i = 0; while(ifname[i] == *p) { p++; i++; } /* TODO : how to handle aliases ? */ if(ifname[i] || *p != ':') continue; p++; while(*p==' ') p++; data->ibytes = my_strtoul(p, &p, 0); while(*p==' ') p++; data->ipackets = my_strtoul(p, &p, 0); /* skip 6 columns */ for(i=6; i>0 && *p!='\0'; i--) { while(*p==' ') p++; while(*p!=' ' && *p) p++; } while(*p==' ') p++; data->obytes = my_strtoul(p, &p, 0); while(*p==' ') p++; data->opackets = my_strtoul(p, &p, 0); r = 0; break; } fclose(f); /* get interface speed */ snprintf(fname, sizeof(fname), "/sys/class/net/%s/speed", ifname); f = fopen(fname, "r"); if(f) { if(fgets(line, sizeof(line), f)) { i = atoi(line); /* 65535 means unknown */ if(i > 0 && i < 65535) data->baudrate = 1000000*i; } fclose(f); } else { syslog(LOG_INFO, "cannot read %s file : %m", fname); } #ifdef GET_WIRELESS_STATS if(data->baudrate == BAUDRATE_DEFAULT) { struct iwreq iwr; int s; s = socket(AF_INET, SOCK_DGRAM, 0); if(s >= 0) { strncpy(iwr.ifr_name, ifname, IFNAMSIZ); if(ioctl(s, SIOCGIWRATE, &iwr) >= 0) { data->baudrate = iwr.u.bitrate.value; } close(s); } } #endif /* GET_WIRELESS_STATS */ #ifdef ENABLE_GETIFSTATS_CACHING if(r==0 && current_time!=((time_t)-1)) { /* cache the new data */ cache_timestamp = current_time; memcpy(&cache_data, data, sizeof(struct ifdata)); } #endif /* ENABLE_GETIFSTATS_CACHING */ return r; } miniupnpd-2.3.7/netfilter/iptcrdr.c010064400017500000024000001602711417311724300165310ustar00nanardstaff/* $Id: iptcrdr.c,v 1.68 2021/11/09 17:54:25 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2020 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 #include #include #include #include "config.h" #ifdef IPTABLES_143 /* IPTABLES API version >= 1.4.3 */ /* added in order to compile on gentoo : * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=2183 */ #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) #define __must_be_array(a) \ BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0]))) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr)) #define LIST_POISON2 ((void *) 0x00200200 ) #if 0 #include #else #include "tiny_nf_nat.h" #endif #define ip_nat_multi_range nf_nat_multi_range #define ip_nat_range nf_nat_range #define IPTC_HANDLE struct iptc_handle * #else /* IPTABLES API version < 1.4.3 */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) #include #else #if 0 #include #else #include "tiny_nf_nat.h" #endif #endif #define IPTC_HANDLE iptc_handle_t #endif /* IPT_ALIGN was renamed XT_ALIGN in iptables-1.4.11 */ #ifndef IPT_ALIGN #define IPT_ALIGN XT_ALIGN #endif #include "../macros.h" #include "iptcrdr.h" #include "../upnpglobalvars.h" /* chain names to use in the nat and filter tables. */ /* iptables -t nat -N MINIUPNPD * iptables -t nat -A PREROUTING -i -j MINIUPNPD */ static const char * miniupnpd_nat_chain = "MINIUPNPD"; /* iptables -t nat -N MINIUPNPD-POSTROUTING * iptables -t nat -A POSTROUTING -o -j MINIUPNPD-POSTROUTING */ static const char * miniupnpd_nat_postrouting_chain = "MINIUPNPD-POSTROUTING"; /* iptables -t filter -N MINIUPNPD * iptables -t filter -A FORWARD -i ! -o -j MINIUPNPD */ static const char * miniupnpd_forward_chain = "MINIUPNPD"; /** * used by the core to override default chain names if specified in config file * @param param which string to set * @param string the new name to use. Do not dispose after setting (i.e. use strdup if not static). * @return 0 if successful */ int set_rdr_name(rdr_name_type param, const char *string) { if (string == NULL || strlen(string) > 30 || string[0] == '\0') { syslog(LOG_ERR, "%s(): invalid string argument '%s'", "set_rdr_name", string); return -1; } switch (param) { case RDR_NAT_PREROUTING_CHAIN_NAME: miniupnpd_nat_chain = string; break; case RDR_NAT_POSTROUTING_CHAIN_NAME: miniupnpd_nat_postrouting_chain = string; break; case RDR_FORWARD_CHAIN_NAME: miniupnpd_forward_chain = string; break; default: syslog(LOG_ERR, "%s(): tried to set invalid string parameter: %d", "set_rdr_name", param); return -2; } return 0; } /* local functions declarations */ static int addnatrule(int proto, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost); static int add_filter_rule(int proto, const char * rhost, const char * iaddr, unsigned short iport); #ifdef ENABLE_PORT_TRIGGERING static int addmasqueraderule(int proto, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost/*, const char * extif*/); #endif /* ENABLE_PORT_TRIGGERING */ static int addpeernatrule(int proto, const char * eaddr, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost, unsigned short rport); static int addpeerdscprule(int proto, unsigned char dscp, const char * iaddr, unsigned short iport, const char * rhost, unsigned short rport); /* dummy init and shutdown functions * Only test iptc_init() */ int init_redirect(void) { IPTC_HANDLE h; h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "iptc_init() failed : %s", iptc_strerror(errno)); return -1; } else { #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif } return 0; } void shutdown_redirect(void) { return; } /* convert an ip address to string */ static int snprintip(char * dst, size_t size, uint32_t ip) { return snprintf(dst, size, "%u.%u.%u.%u", ip >> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); } /* netfilter cannot store redirection descriptions, so we use our * own structure to store them */ struct rdr_desc { struct rdr_desc * next; unsigned int timestamp; unsigned short eport; short proto; char str[]; }; /* pointer to the chained list where descriptions are stored */ static struct rdr_desc * rdr_desc_list = 0; /* add a description to the list of redirection descriptions */ static void add_redirect_desc(unsigned short eport, int proto, const char * desc, unsigned int timestamp) { struct rdr_desc * p; size_t l; /* set a default description if none given */ if(!desc) desc = "miniupnpd"; l = strlen(desc) + 1; p = malloc(sizeof(struct rdr_desc) + l); if(p) { p->next = rdr_desc_list; p->timestamp = timestamp; p->eport = eport; p->proto = (short)proto; memcpy(p->str, desc, l); rdr_desc_list = p; } } /* delete a description from the list */ static void del_redirect_desc(unsigned short eport, int proto) { struct rdr_desc * p, * last; p = rdr_desc_list; last = 0; while(p) { if(p->eport == eport && p->proto == proto) { if(!last) rdr_desc_list = p->next; else last->next = p->next; free(p); return; } last = p; p = p->next; } } /* go through the list to find the description */ static void get_redirect_desc(unsigned short eport, int proto, char * desc, int desclen, unsigned int * timestamp) { struct rdr_desc * p; for(p = rdr_desc_list; p; p = p->next) { if(p->eport == eport && p->proto == (short)proto) { if(desc) strncpy(desc, p->str, desclen); if(timestamp) *timestamp = p->timestamp; return; } } /* if no description was found, return miniupnpd as default */ if(desc) strncpy(desc, "miniupnpd", desclen); if(timestamp) *timestamp = 0; } /* add_redirect_rule2() */ int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { int r; UNUSED(ifname); r = addnatrule(proto, eport, iaddr, iport, rhost); if(r >= 0) { add_redirect_desc(eport, proto, desc, timestamp); #ifdef ENABLE_PORT_TRIGGERING /* we now always setup SNAT to support bidirectional mapping * we cannot expect that iport == eport on all the firewall. */ /* TODO : check if this should be done only with UDP */ r = addmasqueraderule(proto, eport, iaddr, iport, rhost/*, ifname*/); if(r < 0) { syslog(LOG_NOTICE, "add_redirect_rule2(): addmasqueraderule returned %d", r); } #endif /* ENABLE_PORT_TRIGGERING */ } return r; } /* add_peer_redirect_rule2() */ int add_peer_redirect_rule2(const char * ifname, const char * rhost, unsigned short rport, const char * eaddr, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { int r; UNUSED(ifname); r = addpeernatrule(proto, eaddr, eport, iaddr, iport, rhost, rport); if(r >= 0) add_redirect_desc(eport, proto, desc, timestamp); return r; } int add_peer_dscp_rule2(const char * ifname, const char * rhost, unsigned short rport, unsigned char dscp, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { int r; UNUSED(ifname); UNUSED(desc); UNUSED(timestamp); r = addpeerdscprule(proto, dscp, iaddr, iport, rhost, rport); /* if(r >= 0) add_redirect_desc(dscp, proto, desc, timestamp); */ return r; } int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { UNUSED(ifname); UNUSED(eport); UNUSED(desc); return add_filter_rule(proto, rhost, iaddr, iport); } /* get_redirect_rule() * returns -1 if the rule is not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { return get_nat_redirect_rule(miniupnpd_nat_chain, ifname, eport, proto, iaddr, iaddrlen, iport, desc, desclen, rhost, rhostlen, timestamp, packets, bytes); } int get_nat_redirect_rule(const char * nat_chain_name, const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int r = -1; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const struct ipt_entry_match *match; UNUSED(ifname); h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() failed : %s", "get_nat_redirect_rule", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(nat_chain_name, h)) { syslog(LOG_ERR, "chain %s not found", nat_chain_name); } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(nat_chain_name, h); e; e = iptc_next_rule(e, h)) #else for(e = iptc_first_rule(nat_chain_name, &h); e; e = iptc_next_rule(e, &h)) #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; } target = (void *)e + e->target_offset; /* target = ipt_get_target(e); */ mr = (const struct ip_nat_multi_range *)&target->data[0]; snprintip(iaddr, iaddrlen, ntohl(mr->range[0].min_ip)); *iport = ntohs(mr->range[0].min.all); get_redirect_desc(eport, proto, desc, desclen, timestamp); if(packets) *packets = e->counters.pcnt; if(bytes) *bytes = e->counters.bcnt; /* rhost */ if(e->ip.src.s_addr && rhost) { snprintip(rhost, rhostlen, ntohl(e->ip.src.s_addr)); } r = 0; break; } } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return r; } /* get_redirect_rule_by_index() * return -1 when the rule was not found */ int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int r = -1; int i = 0; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const struct ipt_entry_match *match; UNUSED(ifname); h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() failed : %s", "get_redirect_rule_by_index", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_nat_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain); } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_chain, h); e; e = iptc_next_rule(e, h)) #else for(e = iptc_first_rule(miniupnpd_nat_chain, &h); e; e = iptc_next_rule(e, &h)) #endif { if(i==index) { *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; *eport = info->dpts[0]; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; *eport = info->dpts[0]; } target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; snprintip(iaddr, iaddrlen, ntohl(mr->range[0].min_ip)); *iport = ntohs(mr->range[0].min.all); get_redirect_desc(*eport, *proto, desc, desclen, timestamp); if(packets) *packets = e->counters.pcnt; if(bytes) *bytes = e->counters.bcnt; /* rhost */ if(rhost && rhostlen > 0) { if(e->ip.src.s_addr) { snprintip(rhost, rhostlen, ntohl(e->ip.src.s_addr)); } else { rhost[0] = '\0'; } } r = 0; break; } i++; } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return r; } /* get_peer_rule_by_index() * return -1 when the rule was not found */ int get_peer_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned short * rport, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int r = -1; int i = 0; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const struct ipt_entry_match *match; UNUSED(ifname); h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() failed : %s", "get_peer_rule_by_index", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_nat_postrouting_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_postrouting_chain); } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, h); e; e = iptc_next_rule(e, h)) #else for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, &h); e; e = iptc_next_rule(e, &h)) #endif { if(i==index) { *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 (rport) *rport = info->dpts[0]; if (iport) *iport = info->spts[0]; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; if (rport) *rport = info->dpts[0]; if (iport) *iport = info->spts[0]; } target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; *eport = ntohs(mr->range[0].min.all); get_redirect_desc(*eport, *proto, desc, desclen, timestamp); if(packets) *packets = e->counters.pcnt; if(bytes) *bytes = e->counters.bcnt; /* rhost */ if(rhost && rhostlen > 0) { if(e->ip.dst.s_addr) { snprintip(rhost, rhostlen, ntohl(e->ip.dst.s_addr)); } else { rhost[0] = '\0'; } } if(iaddr && iaddrlen > 0) { if(e->ip.src.s_addr) { snprintip(iaddr, iaddrlen, ntohl(e->ip.src.s_addr)); } else { rhost[0] = '\0'; } } r = 0; break; } i++; } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return r; } /* delete_rule_and_commit() : * subfunction used in delete_redirect_and_filter_rules() * always call iptc_free(h) */ static int delete_rule_and_commit(unsigned int index, IPTC_HANDLE h, const char * miniupnpd_chain, const char * logcaller) { int r = 0; #ifdef IPTABLES_143 if(!iptc_delete_num_entry(miniupnpd_chain, index, h)) #else if(!iptc_delete_num_entry(miniupnpd_chain, index, &h)) #endif { syslog(LOG_ERR, "%s() : iptc_delete_num_entry(): %s\n", logcaller, 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\n", logcaller, iptc_strerror(errno)); r = -1; } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif 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 delete_redirect_and_filter_rules(unsigned short eport, int proto) { int r = -1, r2 = -1; unsigned index = 0; unsigned i = 0; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const struct ipt_entry_match *match; unsigned short iport = 0; uint32_t iaddr = 0; h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() failed : %s", "delete_redirect_and_filter_rules", 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); } 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; } /* get the index, the internal address and the internal port * of the rule */ index = i; target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; iaddr = mr->range[0].min_ip; iport = ntohs(mr->range[0].min.all); r = 0; break; } } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif if(r == 0) { syslog(LOG_INFO, "Trying to delete nat rule at index %u", index); /* Now delete both rules */ /* first delete the nat rule */ h = iptc_init("nat"); if(h) { r = delete_rule_and_commit(index, h, miniupnpd_nat_chain, "delete_redirect_rule"); } if((r == 0) && (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(iport != info->dpts[0]) continue; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; if(iport != info->dpts[0]) continue; } if(iaddr != e->ip.dst.s_addr) 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; } } /* in case the filter rule has not been found, delete_rule_and_commit() is not called * so we neet to free h */ if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif } } /*delete PEER rule*/ if((h = iptc_init("nat"))) { i = 0; /* we must find the right index for the filter rule */ #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, h); e; e = iptc_next_rule(e, h), i++) #else for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, &h); e; e = iptc_next_rule(e, &h), i++) #endif { if(proto==e->ip.proto) { target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; syslog(LOG_DEBUG, "postrouting rule #%u: %s %s %hu", i, target->u.user.name, inet_ntoa(e->ip.src), ntohs(mr->range[0].min.all)); /* target->u.user.name SNAT / MASQUERADE */ if (eport != ntohs(mr->range[0].min.all)) { continue; } iaddr = e->ip.src.s_addr; 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; iport = info->spts[0]; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; iport = info->spts[0]; } index = i; syslog(LOG_INFO, "Trying to delete peer rule at index %u", index); r2 = delete_rule_and_commit(index, h, miniupnpd_nat_postrouting_chain, "delete_peer_rule"); h = NULL; break; } } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif /*delete DSCP rule*/ if((r2==0)&&(h = iptc_init("mangle"))) { i = 0; index = -1; /* we must find the right index for the filter rule */ #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; /*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(iport != info->spts[0]) continue; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; if(iport != info->spts[0]) continue; } if(iaddr != e->ip.src.s_addr) continue; index = i; syslog(LOG_INFO, "Trying to delete dscp rule at index %u", index); r2 = delete_rule_and_commit(index, h, miniupnpd_nat_chain, "delete_dscp_rule"); h = NULL; break; } } if (h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif } del_redirect_desc(eport, proto); return r*r2; } /* ==================================== */ /* TODO : add the -m state --state NEW,ESTABLISHED,RELATED * only for the filter rule */ static struct ipt_entry_match * get_tcp_match(unsigned short dport, unsigned short sport) { struct ipt_entry_match *match; struct ipt_tcp * tcpinfo; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + IPT_ALIGN(sizeof(struct ipt_tcp)); match = calloc(1, size); match->u.match_size = size; strncpy(match->u.user.name, "tcp", sizeof(match->u.user.name)); tcpinfo = (struct ipt_tcp *)match->data; if (sport == 0) { tcpinfo->spts[0] = 0; /* all source ports */ tcpinfo->spts[1] = 0xFFFF; } else { tcpinfo->spts[0] = sport; /* specified source port */ tcpinfo->spts[1] = sport; } if (dport == 0) { tcpinfo->dpts[0] = 0; /* all destination ports */ tcpinfo->dpts[1] = 0xFFFF; } else { tcpinfo->dpts[0] = dport; /* specified destination port */ tcpinfo->dpts[1] = dport; } return match; } static struct ipt_entry_match * get_udp_match(unsigned short dport, unsigned short sport) { struct ipt_entry_match *match; struct ipt_udp * udpinfo; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + IPT_ALIGN(sizeof(struct ipt_udp)); match = calloc(1, size); match->u.match_size = size; strncpy(match->u.user.name, "udp", sizeof(match->u.user.name)); udpinfo = (struct ipt_udp *)match->data; if (sport == 0) { udpinfo->spts[0] = 0; /* all source ports */ udpinfo->spts[1] = 0xFFFF; } else { udpinfo->spts[0] = sport; /* specified source port */ udpinfo->spts[1] = sport; } if (dport == 0) { udpinfo->dpts[0] = 0; /* all destination ports */ udpinfo->dpts[1] = 0xFFFF; } else { udpinfo->dpts[0] = dport; /* specified destination port */ udpinfo->dpts[1] = dport; } return match; } static struct ipt_entry_target * get_dnat_target(const char * daddr, unsigned short dport) { struct ipt_entry_target * target; struct ip_nat_multi_range * mr; struct ip_nat_range * range; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(struct ip_nat_multi_range)); target = calloc(1, size); target->u.target_size = size; strncpy(target->u.user.name, "DNAT", sizeof(target->u.user.name)); /* one ip_nat_range already included in ip_nat_multi_range */ mr = (struct ip_nat_multi_range *)&target->data[0]; mr->rangesize = 1; range = &mr->range[0]; range->min_ip = range->max_ip = inet_addr(daddr); range->flags |= IP_NAT_RANGE_MAP_IPS; range->min.all = range->max.all = htons(dport); range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED; return target; } static struct ipt_entry_target * get_snat_target(const char * saddr, unsigned short sport) { struct ipt_entry_target * target; struct ip_nat_multi_range * mr; struct ip_nat_range * range; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(struct ip_nat_multi_range)); target = calloc(1, size); target->u.target_size = size; strncpy(target->u.user.name, "SNAT", sizeof(target->u.user.name)); /* one ip_nat_range already included in ip_nat_multi_range */ mr = (struct ip_nat_multi_range *)&target->data[0]; mr->rangesize = 1; range = &mr->range[0]; range->min_ip = range->max_ip = inet_addr(saddr); range->flags |= IP_NAT_RANGE_MAP_IPS; range->min.all = range->max.all = htons(sport); range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED; return target; } static struct ipt_entry_target * get_dscp_target(unsigned char dscp) { struct ipt_entry_target * target; struct xt_DSCP_info * di; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(struct xt_DSCP_info)); target = calloc(1, size); target->u.target_size = size; strncpy(target->u.user.name, "DSCP", sizeof(target->u.user.name)); /* one ip_nat_range already included in ip_nat_multi_range */ di = (struct xt_DSCP_info *)&target->data[0]; di->dscp=dscp; return target; } #ifdef ENABLE_PORT_TRIGGERING static struct ipt_entry_target * get_masquerade_target(unsigned short port) { struct ipt_entry_target * target; struct ip_nat_multi_range * mr; struct ip_nat_range * range; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(struct ip_nat_multi_range)); target = calloc(1, size); target->u.target_size = size; strncpy(target->u.user.name, "MASQUERADE", sizeof(target->u.user.name)); /* one ip_nat_range already included in ip_nat_multi_range */ mr = (struct ip_nat_multi_range *)&target->data[0]; mr->rangesize = 1; range = &mr->range[0]; range->min.tcp.port = range->max.tcp.port = htons(port); /*range->min.all = range->max.all = htons(port);*/ range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED; return target; } #endif /* ENABLE_PORT_TRIGGERING */ /* iptc_init_verify_and_append() * return 0 on success, -1 on failure */ static int iptc_init_verify_and_append(const char * table, const char * miniupnpd_chain, struct ipt_entry * e, const char * logcaller) { IPTC_HANDLE h; h = iptc_init(table); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() error : %s\n", logcaller, iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_chain, h)) { syslog(LOG_ERR, "%s() : chain %s not found", logcaller, miniupnpd_chain); if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } /* iptc_insert_entry(miniupnpd_chain, e, n, h/&h) could also be used */ #ifdef IPTABLES_143 if(!iptc_append_entry(miniupnpd_chain, e, h)) #else if(!iptc_append_entry(miniupnpd_chain, e, &h)) #endif { syslog(LOG_ERR, "%s() : iptc_append_entry() error : %s\n", logcaller, iptc_strerror(errno)); if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } #ifdef IPTABLES_143 if(!iptc_commit(h)) #else if(!iptc_commit(&h)) #endif { syslog(LOG_ERR, "%s() : iptc_commit() error : %s\n", logcaller, iptc_strerror(errno)); if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return 0; } /* add nat rule * iptables -t nat -A MINIUPNPD -p [-s ] --dport -j DNAT --to : * */ static int addnatrule(int proto, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost) { int r = 0; struct ipt_entry * e; struct ipt_entry * tmp; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); if(!e) { syslog(LOG_ERR, "%s: calloc(%d) error", "addnatrule", (int)sizeof(struct ipt_entry)); return -1; } e->ip.proto = proto; if(proto == IPPROTO_TCP) { match = get_tcp_match(eport, 0); } else { match = get_udp_match(eport, 0); } #ifdef NFC_UNKNOWN e->nfcache = NFC_UNKNOWN; #endif target = get_dnat_target(iaddr, iport); #ifdef NFC_IP_DST_PT e->nfcache |= NFC_IP_DST_PT; #endif tmp = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); if(!tmp) { syslog(LOG_ERR, "%s: realloc(%d) error", "addnatrule", (int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size)); free(e); free(match); free(target); return -1; } e = tmp; memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; /* remote host */ if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) { e->ip.src.s_addr = inet_addr(rhost); e->ip.smsk.s_addr = INADDR_NONE; } r = iptc_init_verify_and_append("nat", miniupnpd_nat_chain, e, "addnatrule"); free(target); free(match); free(e); return r; } /* for "Port Triggering" * Section 2.5.16 figure 2.2 in UPnP-gw-WANIPConnection-v2-Service.pdf * * When a control point creates a port forwarding rule with AddPortMapping() * action for inbound traffic , this rule MUST also be applied when NAT port * triggering occurs for outbound traffic. * * iptables -t nat -A MINIUPNPD-POSTROUTING {-o } -s * -p [-d ] --sport -j MASQUERADE --to-ports */ #ifdef ENABLE_PORT_TRIGGERING static int addmasqueraderule(int proto, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost/*, const char * extif*/) { int r = 0; struct ipt_entry * e; struct ipt_entry * tmp; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); if(!e) { syslog(LOG_ERR, "%s: calloc(%d) error", "addmasqueraderule", (int)sizeof(struct ipt_entry)); return -1; } e->ip.proto = proto; if(proto == IPPROTO_TCP) { match = get_tcp_match(0, iport); } else { match = get_udp_match(0, iport); } #ifdef NFC_UNKNOWN e->nfcache = NFC_UNKNOWN; #endif target = get_masquerade_target(eport); #ifdef NFC_IP_DST_PT e->nfcache |= NFC_IP_DST_PT; #endif tmp = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); if(!tmp) { syslog(LOG_ERR, "%s: realloc(%d) error", "addmasqueraderule", (int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size)); free(e); free(match); free(target); return -1; } e = tmp; memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; #if 0 /* do not add outiface (-o) to rule, as the MINIUPNPD-POSTROUTING chain * should already have matched it */ if(extif != NULL) { strncpy(e->ip.outiface, extif, sizeof(e->ip.outiface)); memset(e->ip.outiface_mask, 0xff, strlen(e->ip.outiface) + 1);/* Include nul-terminator in match */ } #endif /* internal host */ if(iaddr && (iaddr[0] != '\0') && (0 != strcmp(iaddr, "*"))) { e->ip.src.s_addr = inet_addr(iaddr); e->ip.smsk.s_addr = INADDR_NONE; } /* remote host */ if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) { e->ip.dst.s_addr = inet_addr(rhost); e->ip.dmsk.s_addr = INADDR_NONE; } r = iptc_init_verify_and_append("nat", miniupnpd_nat_postrouting_chain, e, "addmasqueraderule"); free(target); free(match); free(e); return r; } #endif /* ENABLE_PORT_TRIGGERING */ /* called by add_peer_redirect_rule2() * * iptables -t nat -A MINIUPNPD-POSTROUTING -s -d * -p --sport --dport -j SNAT * --to-source : */ static int addpeernatrule(int proto, const char * eaddr, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost, unsigned short rport) { int r = 0; struct ipt_entry * e; struct ipt_entry * tmp; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); if(!e) { syslog(LOG_ERR, "%s: calloc(%d) error", "addpeernatrule", (int)sizeof(struct ipt_entry)); return -1; } e->ip.proto = proto; /* TODO: Fill port matches and SNAT */ if(proto == IPPROTO_TCP) { match = get_tcp_match(rport, iport); } else { match = get_udp_match(rport, iport); } #ifdef NFC_UNKNOWN e->nfcache = NFC_UNKNOWN; #endif target = get_snat_target(eaddr, eport); #ifdef NFC_IP_DST_PT e->nfcache |= NFC_IP_DST_PT; #endif #ifdef NFC_IP_SRC_PT e->nfcache |= NFC_IP_SRC_PT; #endif tmp = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); if(!tmp) { syslog(LOG_ERR, "%s: realloc(%d) error", "addpeernatrule", (int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size)); free(e); free(match); free(target); return -1; } e = tmp; memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; /* internal host */ if(iaddr && (iaddr[0] != '\0') && (0 != strcmp(iaddr, "*"))) { e->ip.src.s_addr = inet_addr(iaddr); e->ip.smsk.s_addr = INADDR_NONE; } /* remote host */ if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) { e->ip.dst.s_addr = inet_addr(rhost); e->ip.dmsk.s_addr = INADDR_NONE; } r = iptc_init_verify_and_append("nat", miniupnpd_nat_postrouting_chain, e, "addpeernatrule"); free(target); free(match); free(e); return r; } /* called by add_peer_dscp_rule2() * iptables -t mangle -A MINIUPNPD -s -d * -p --sport --dport -j DSCP * --set-dscp 0xXXXX */ static int addpeerdscprule(int proto, unsigned char dscp, const char * iaddr, unsigned short iport, const char * rhost, unsigned short rport) { int r = 0; struct ipt_entry * e; struct ipt_entry * tmp; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); if(!e) { syslog(LOG_ERR, "%s: calloc(%d) error", "addpeerdscprule", (int)sizeof(struct ipt_entry)); return -1; } e->ip.proto = proto; /* TODO: Fill port matches and SNAT */ if(proto == IPPROTO_TCP) { match = get_tcp_match(rport, iport); } else { match = get_udp_match(rport, iport); } #ifdef NFC_UNKNOWN e->nfcache = NFC_UNKNOWN; #endif target = get_dscp_target(dscp); #ifdef NFC_IP_DST_PT e->nfcache |= NFC_IP_DST_PT; #endif #ifdef NFC_IP_SRC_PT e->nfcache |= NFC_IP_SRC_PT; #endif tmp = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); if(!tmp) { syslog(LOG_ERR, "%s: realloc(%d) error", "addpeerdscprule", (int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size)); free(e); free(match); free(target); return -1; } e = tmp; memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; /* internal host */ if(iaddr && (iaddr[0] != '\0') && (0 != strcmp(iaddr, "*"))) { e->ip.src.s_addr = inet_addr(iaddr); e->ip.smsk.s_addr = INADDR_NONE; } /* remote host */ if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) { e->ip.dst.s_addr = inet_addr(rhost); e->ip.dmsk.s_addr = INADDR_NONE; } r = iptc_init_verify_and_append("mangle", miniupnpd_nat_chain, e, "addpeerDSCPrule"); free(target); free(match); free(e); return r; } /* ================================= */ static struct ipt_entry_target * get_accept_target(void) { struct ipt_entry_target * target = NULL; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(int)); target = calloc(1, size); target->u.user.target_size = size; strncpy(target->u.user.name, "ACCEPT", sizeof(target->u.user.name)); return target; } /* add_filter_rule() * iptables -t filter -A MINIUPNPD [-s ] -p -d --dport -j ACCEPT */ static int add_filter_rule(int proto, const char * rhost, const char * iaddr, unsigned short iport) { int r = 0; struct ipt_entry * e; struct ipt_entry * tmp; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); if(!e) { syslog(LOG_ERR, "%s: calloc(%d) error", "add_filter_rule", (int)sizeof(struct ipt_entry)); return -1; } e->ip.proto = proto; if(proto == IPPROTO_TCP) { match = get_tcp_match(iport,0); } else { match = get_udp_match(iport,0); } e->ip.dst.s_addr = inet_addr(iaddr); e->ip.dmsk.s_addr = INADDR_NONE; #ifdef NFC_UNKNOWN e->nfcache = NFC_UNKNOWN; #endif target = get_accept_target(); #ifdef NFC_IP_DST_PT e->nfcache |= NFC_IP_DST_PT; #endif tmp = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); if(!tmp) { syslog(LOG_ERR, "%s: realloc(%d) error", "add_filter_rule", (int)(sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size)); free(e); free(match); free(target); return -1; } e = tmp; memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; /* remote host */ if(rhost && (rhost[0] != '\0') && (0 != strcmp(rhost, "*"))) { e->ip.src.s_addr = inet_addr(rhost); e->ip.smsk.s_addr = INADDR_NONE; } r = iptc_init_verify_and_append("filter", miniupnpd_forward_chain, e, "add_filter_rule"); free(target); free(match); free(e); return r; } /* return an (malloc'ed) array of "external" port for which there is * a port mapping. number is the size of the array */ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number) { unsigned short * array; unsigned int capacity; unsigned short eport; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_match *match; *number = 0; capacity = 128; array = calloc(capacity, sizeof(unsigned short)); if(!array) { syslog(LOG_ERR, "%s() : calloc error", "get_portmappings_in_range"); return NULL; } h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() failed : %s", "get_portmappings_in_range", iptc_strerror(errno)); free(array); return NULL; } if(!iptc_is_chain(miniupnpd_nat_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain); free(array); array = NULL; } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_chain, h); e; e = iptc_next_rule(e, h)) #else for(e = iptc_first_rule(miniupnpd_nat_chain, &h); e; e = iptc_next_rule(e, &h)) #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; eport = info->dpts[0]; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; eport = info->dpts[0]; } if(startport <= eport && eport <= endport) { if(*number >= capacity) { unsigned short * tmp; /* need to increase the capacity of the array */ capacity += 128; if (capacity <= *number) capacity = *number + 1; tmp = realloc(array, sizeof(unsigned short)*capacity); if(!tmp) { syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%u) error", (unsigned)sizeof(unsigned short)*capacity); *number = 0; free(array); array = NULL; break; } array = tmp; } array[*number] = eport; (*number)++; } } } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif 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; 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 = 0; 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); target = (void *)new_e + new_e->target_offset; if (target->u.user.name[0] == '\0' && iptc_get_target(e, h)) { strncpy(target->u.user.name, iptc_get_target(e, h), sizeof(target->u.user.name)); } } 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; #ifdef ENABLE_PORT_TRIGGERING /* update snat rule */ h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "%s() : iptc_init() failed : %s", "update_portmapping", iptc_strerror(errno)); goto skip; } i = 0; found = 0; if(!iptc_is_chain(miniupnpd_nat_postrouting_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_postrouting_chain); } else { /* we must find the right index for the filter rule */ #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, h); e; e = iptc_next_rule(e, h), i++) #else for(e = iptc_first_rule(miniupnpd_nat_postrouting_chain, &h); e; e = iptc_next_rule(e, &h), i++) #endif { if(proto==e->ip.proto) { target = (void *)e + e->target_offset; mr = (struct ip_nat_multi_range *)&target->data[0]; syslog(LOG_DEBUG, "postrouting rule #%u: %s %s %hu", i, target->u.user.name, inet_ntoa(e->ip.src), ntohs(mr->range[0].min.all)); /* target->u.user.name SNAT / MASQUERADE */ if (eport != ntohs(mr->range[0].min.all)) { continue; } 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->spts[0]) continue; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; if(old_iport != info->spts[0]) continue; } if (iaddr != e->ip.src.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) goto skip; syslog(LOG_INFO, "Trying to update snat 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->spts[0] = info->spts[1] = iport; } else { struct ipt_udp * info; info = (struct ipt_udp *)match->data; info->spts[0] = info->spts[1] = iport; } r = update_rule_and_commit("nat", miniupnpd_nat_postrouting_chain, index, new_e); free(new_e); new_e = NULL; if(r < 0) syslog(LOG_INFO, "Trying to update snat rule at index %u fail!", index); skip: #endif /* ENABLE_PORT_TRIGGERING */ return update_portmapping_desc_timestamp(ifname, eport, proto, desc, timestamp); } /* ================================ */ #ifdef DEBUG static int print_match(const struct ipt_entry_match *match) { printf("match %s :\n", match->u.user.name); if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) { struct ipt_tcp * tcpinfo; tcpinfo = (struct ipt_tcp *)match->data; printf(" srcport = %hu:%hu dstport = %hu:%hu\n", tcpinfo->spts[0], tcpinfo->spts[1], tcpinfo->dpts[0], tcpinfo->dpts[1]); } else if(0 == strncmp(match->u.user.name, "udp", IPT_FUNCTION_MAXNAMELEN)) { struct ipt_udp * udpinfo; udpinfo = (struct ipt_udp *)match->data; printf(" srcport = %hu:%hu dstport = %hu:%hu\n", udpinfo->spts[0], udpinfo->spts[1], udpinfo->dpts[0], udpinfo->dpts[1]); } return 0; } static void print_iface(const char * iface, const unsigned char * mask, int invert) { unsigned i; if(mask[0] == 0) return; if(invert) printf("! "); for(i=0; i> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); } /* for debug */ /* read the "filter" and "nat" tables */ int list_redirect_rule(const char * ifname) { IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const char * target_str; char addr[16], mask[16]; unsigned int index; (void)ifname; h = iptc_init("nat"); if(!h) { printf("iptc_init() error : %s\n", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_nat_chain, h)) { printf("chain %s not found\n", miniupnpd_nat_chain); #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } index = 0; #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_chain, h); e; e = iptc_next_rule(e, h)) { target_str = iptc_get_target(e, h); #else for(e = iptc_first_rule(miniupnpd_nat_chain, &h); e; e = iptc_next_rule(e, &h)) { target_str = iptc_get_target(e, &h); #endif printf("=== rule #%u ===\n", index); inet_ntop(AF_INET, &e->ip.src, addr, sizeof(addr)); inet_ntop(AF_INET, &e->ip.smsk, mask, sizeof(mask)); printf("src = %s%s/%s\t", (e->ip.invflags & IPT_INV_SRCIP)?"! ":"", /*inet_ntoa(e->ip.src), inet_ntoa(e->ip.smsk)*/ addr, mask); inet_ntop(AF_INET, &e->ip.dst, addr, sizeof(addr)); inet_ntop(AF_INET, &e->ip.dmsk, mask, sizeof(mask)); printf("dst = %s%s/%s\n", (e->ip.invflags & IPT_INV_DSTIP)?"! ":"", /*inet_ntoa(e->ip.dst), inet_ntoa(e->ip.dmsk)*/ addr, mask); /*printf("in_if = %s out_if = %s\n", e->ip.iniface, e->ip.outiface);*/ printf("in_if = "); print_iface(e->ip.iniface, e->ip.iniface_mask, e->ip.invflags & IPT_INV_VIA_IN); printf("\tout_if = "); print_iface(e->ip.outiface, e->ip.outiface_mask, e->ip.invflags & IPT_INV_VIA_OUT); printf("\n"); printf("ip.proto = %s%d\n", (e->ip.invflags & IPT_INV_PROTO)?"! ":"", e->ip.proto); /* display matches stuff */ if(e->target_offset) { IPT_MATCH_ITERATE(e, print_match); /*printf("\n");*/ } printf("target = %s :\n", target_str); target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; printf(" ips "); printip(ntohl(mr->range[0].min_ip)); printf(" "); printip(ntohl(mr->range[0].max_ip)); printf("\n ports %hu %hu\n", ntohs(mr->range[0].min.all), ntohs(mr->range[0].max.all)); printf(" flags = %x\n", mr->range[0].flags); index++; } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif printf("======\n"); return 0; } #endif miniupnpd-2.3.7/netfilter/iptcrdr.h010064400017500000024000000050771331765521100165410ustar00nanardstaff/* $Id: iptcrdr.h,v 1.22 2018/07/06 12:00:10 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef IPTCRDR_H_INCLUDED #define IPTCRDR_H_INCLUDED #include "../commonrdr.h" int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int add_peer_redirect_rule2(const char * ifname, const char * rhost, unsigned short rport, const char * eaddr, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc); 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, unsigned char dscp, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int get_nat_ext_addr(struct sockaddr* src, struct sockaddr *dst, uint8_t proto, struct sockaddr* ret_ext); int get_peer_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned short * rport, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); int get_nat_redirect_rule(const char * nat_chain_name, const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); /* for debug */ int list_redirect_rule(const char * ifname); #endif miniupnpd-2.3.7/netfilter/iptables_display.sh010075500017500000024000000007201326165065400206020ustar00nanardstaff#! /bin/sh # $Id: iptables_display.sh,v 1.8 2018/04/06 10:17:09 nanard Exp $ . $(dirname "$0")/miniupnpd_functions.sh #display all chains relative to miniupnpd $IPTABLES -v -n -t nat -L PREROUTING $IPTABLES -v -n -t nat -L $CHAIN $IPTABLES -v -n -t nat -L POSTROUTING $IPTABLES -v -n -t nat -L $CHAIN-POSTROUTING $IPTABLES -v -n -t mangle -L PREROUTING $IPTABLES -v -n -t mangle -L $CHAIN $IPTABLES -v -n -t filter -L FORWARD $IPTABLES -v -n -t filter -L $CHAIN miniupnpd-2.3.7/netfilter/iptables_display_miniupnpd.sh010075500017500000024000000004621326165065400226700ustar00nanardstaff#! /bin/sh # $Id: iptables_display_miniupnpd.sh,v 1.2 2018/04/06 09:21:11 nanard Exp $ . $(dirname "$0")/miniupnpd_functions.sh #display miniupnpd chains $IPTABLES -v -n -t nat -L $CHAIN $IPTABLES -v -n -t nat -L $CHAIN-POSTROUTING $IPTABLES -v -n -t mangle -L $CHAIN $IPTABLES -v -n -t filter -L $CHAIN miniupnpd-2.3.7/netfilter/iptables_flush.sh010075500017500000024000000004301326165065400202540ustar00nanardstaff#! /bin/sh # $Id: iptables_flush.sh,v 1.7 2018/04/06 10:17:09 nanard Exp $ . $(dirname "$0")/miniupnpd_functions.sh #flush all rules owned by miniupnpd $IPTABLES -t nat -F $CHAIN $IPTABLES -t nat -F $CHAIN-POSTROUTING $IPTABLES -t filter -F $CHAIN $IPTABLES -t mangle -F $CHAIN miniupnpd-2.3.7/netfilter/iptables_init.sh010075500017500000024000000045771345115742000201100ustar00nanardstaff#! /bin/sh # $Id: iptables_init.sh,v 1.12 2019/04/03 16:25:55 nanard Exp $ # Improved Miniupnpd iptables init script. # Checks for state of filter before doing anything.. EXT=1 . $(dirname "$0")/miniupnpd_functions.sh # -I inserts the rule at the head of the chain, # -A appends the rule at the end of the chain ADDCMD=-I #ADDCMD=-A # MINIUPNPD chain for nat if [ "$NDIRTY" = "${CHAIN}Chain" ]; then echo "Nat table dirty; Cleaning..." elif [ "$NDIRTY" = "Chain" ]; then echo "Dirty NAT chain but no reference..? Fixing..." #$IPTABLES -t nat $ADDCMD PREROUTING -d $EXTIP -i $EXTIF -j $CHAIN $IPTABLES -t nat $ADDCMD PREROUTING -i $EXTIF -j $CHAIN else echo "NAT table clean..initalizing.." $IPTABLES -t nat -N $CHAIN #$IPTABLES -t nat $ADDCMD PREROUTING -d $EXTIP -i $EXTIF -j $CHAIN $IPTABLES -t nat $ADDCMD PREROUTING -i $EXTIF -j $CHAIN fi if [ "$CLEAN" = "yes" ]; then $IPTABLES -t nat -F $CHAIN fi # MINIUPNPD chain for mangle if [ "$MDIRTY" = "${CHAIN}Chain" ]; then echo "Mangle table dirty; Cleaning..." elif [ "$MDIRTY" = "Chain" ]; then echo "Dirty Mangle chain but no reference..? Fixing..." $IPTABLES -t mangle $ADDCMD PREROUTING -i $EXTIF -j $CHAIN else echo "Mangle table clean..initializing..." $IPTABLES -t mangle -N $CHAIN $IPTABLES -t mangle $ADDCMD PREROUTING -i $EXTIF -j $CHAIN fi if [ "$CLEAN" = "yes" ]; then $IPTABLES -t mangle -F $CHAIN fi # MINIUPNPD chain for filter if [ "$FDIRTY" = "${CHAIN}Chain" ]; then echo "Filter table dirty; Cleaning..." elif [ "$FDIRTY" = "Chain" ]; then echo "Dirty filter chain but no reference..? Fixing..." $IPTABLES -t filter $ADDCMD FORWARD -i $EXTIF ! -o $EXTIF -j $CHAIN else echo "Filter table clean..initalizing.." $IPTABLES -t filter -N MINIUPNPD $IPTABLES -t filter $ADDCMD FORWARD -i $EXTIF ! -o $EXTIF -j $CHAIN fi if [ "$CLEAN" = "yes" ]; then $IPTABLES -t filter -F $CHAIN fi # MINIUPNPD-POSTROUTING chain (for nat) if [ "$NPDIRTY" = "${CHAIN}-POSTROUTINGChain" ]; then echo "Postrouting Nat table dirty; Cleaning..." elif [ "$NPDIRTY" = "Chain" ]; then echo "Dirty POSTROUTING NAT chain but no reference..? Fixing..." $IPTABLES -t nat $ADDCMD POSTROUTING -o $EXTIF -j $CHAIN-POSTROUTING else echo "POSTROUTING NAT table clean..initalizing.." $IPTABLES -t nat -N $CHAIN-POSTROUTING $IPTABLES -t nat $ADDCMD POSTROUTING -o $EXTIF -j $CHAIN-POSTROUTING fi if [ "$CLEAN" = "yes" ]; then $IPTABLES -t nat -F $CHAIN-POSTROUTING fi miniupnpd-2.3.7/netfilter/iptables_removeall.sh010075500017500000024000000026531404660123000211160ustar00nanardstaff#! /bin/sh # $Id: iptables_removeall.sh,v 1.12 2021/05/11 21:55:36 nanard Exp $ EXT=1 . $(dirname "$0")/miniupnpd_functions.sh #removing the MINIUPNPD chain for nat if [ "$NDIRTY" = "${CHAIN}Chain" ]; then $IPTABLES -t nat -F $CHAIN #$IPTABLES -t nat -D PREROUTING -d $EXTIP -i $EXTIF -j $CHAIN $IPTABLES -t nat -D PREROUTING -i $EXTIF -j $CHAIN $IPTABLES -t nat -X $CHAIN elif [ "$NDIRTY" = "Chain" ]; then $IPTABLES -t nat -F $CHAIN $IPTABLES -t nat -X $CHAIN fi #removing the MINIUPNPD chain for mangle if [ "$MDIRTY" = "${CHAIN}Chain" ]; then $IPTABLES -t mangle -F $CHAIN $IPTABLES -t mangle -D PREROUTING -i $EXTIF -j $CHAIN $IPTABLES -t mangle -X $CHAIN elif [ "$MDIRTY" = "Chain" ]; then $IPTABLES -t mangle -F $CHAIN $IPTABLES -t mangle -X $CHAIN fi #removing the MINIUPNPD chain for filter if [ "$FDIRTY" = "${CHAIN}Chain" ]; then $IPTABLES -t filter -F $CHAIN $IPTABLES -t filter -D FORWARD -i $EXTIF ! -o $EXTIF -j $CHAIN $IPTABLES -t filter -X $CHAIN elif [ "$FDIRTY" = "Chain" ]; then $IPTABLES -t filter -F $CHAIN $IPTABLES -t filter -X $CHAIN fi #removing the MINIUPNPD-POSTROUTING chain for nat if [ "$NPDIRTY" = "${CHAIN}-POSTROUTINGChain" ]; then $IPTABLES -t nat -F $CHAIN-POSTROUTING $IPTABLES -t nat -D POSTROUTING -o $EXTIF -j $CHAIN-POSTROUTING $IPTABLES -t nat -X $CHAIN-POSTROUTING elif [ "$NPDIRTY" = "Chain" ]; then $IPTABLES -t nat -F $CHAIN-POSTROUTING $IPTABLES -t nat -X $CHAIN-POSTROUTING fi miniupnpd-2.3.7/netfilter/miniupnpd_functions.sh010075500017500000024000000025131345115742200213430ustar00nanardstaff#! /bin/sh # $Id: miniupnpd_functions.sh,v 1.3 2019/04/03 16:25:55 nanard Exp $ IP=$(which ip) || { echo "Can't find ip" >&2 exit 1 } if [ -z "$IPV6" ]; then IPTABLES=$(which iptables) || { echo "Can't find iptables" >&2 exit 1 } IP="$IP -4" else IPTABLES=$(which ip6tables) || { echo "Can't find ip6tables" >&2 exit 1 } IP="$IP -6" fi IPTABLES="$IPTABLES -w" CHAIN=MINIUPNPD CLEAN= while getopts ":c:i:f" opt; do case $opt in c) CHAIN=$OPTARG ;; i) EXTIF=$OPTARG ;; f) CLEAN=yes ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; :) echo "Option -$OPTARG requires an argument." >&2 exit 1 ;; esac done if [ -n "$EXT" ]; then if [ -z "$EXTIF" ]; then EXTIF=$(LC_ALL=C $IP route | grep 'default' | sed -e 's/.*dev[[:space:]]*//' -e 's/[[:space:]].*//') || { echo "Can't find default interface" >&2 exit 1 } fi #if [ -z "$IPV6" ]; then # EXTIP=$(LC_ALL=C $IP addr show $EXTIF | awk '/inet/ { print $2 }' | cut -d "/" -f 1) #fi fi FDIRTY=$(LC_ALL=C $IPTABLES -t filter -L -n | awk "/$CHAIN / {printf \$1}") if [ -z "$IPV6" ]; then NDIRTY=$(LC_ALL=C $IPTABLES -t nat -L -n | awk "/$CHAIN / {printf \$1}") MDIRTY=$(LC_ALL=C $IPTABLES -t mangle -L -n | awk "/$CHAIN / {printf \$1}") NPDIRTY=$(LC_ALL=C $IPTABLES -t nat -L -n | awk "/$CHAIN-POSTROUTING / {printf \$1}") fi miniupnpd-2.3.7/netfilter/Makefile010064400017500000024000000052601364006264700163570ustar00nanardstaff# $Id: Makefile,v 1.9 2020/03/22 22:47:19 nanard Exp $ CFLAGS?=-Wall -g -D_GNU_SOURCE -DDEBUG -Wstrict-prototypes -Wdeclaration-after-statement CC = gcc #LIBS = -liptc LIBS = -lip4tc ARCH := $(shell uname -m | grep -q "x86_64" && echo 64) ifdef IPTABLESPATH CFLAGS := $(CFLAGS) -I$(IPTABLESPATH)/include/ LDFLAGS := $(LDFLAGS) -L$(IPTABLESPATH)/libiptc/ # get iptables version and set IPTABLES_143 macro if needed IPTABLESVERSION := $(shell grep "\#define VERSION" $(IPTABLESPATH)/config.h | tr -d \" |cut -d" " -f3 ) IPTABLESVERSION1 := $(shell echo $(IPTABLESVERSION) | cut -d. -f1 ) IPTABLESVERSION2 := $(shell echo $(IPTABLESVERSION) | cut -d. -f2 ) IPTABLESVERSION3 := $(shell echo $(IPTABLESVERSION) | cut -d. -f3 ) # test if iptables version >= 1.4.3 TEST := $(shell [ \( \( $(IPTABLESVERSION1) -ge 1 \) -a \( $(IPTABLESVERSION2) -ge 4 \) \) -a \( $(IPTABLESVERSION3) -ge 3 \) ] && echo 1 ) ifeq ($(TEST), 1) CFLAGS := $(CFLAGS) -DIPTABLES_143 # the following sucks, but works LIBS = $(IPTABLESPATH)/libiptc/.libs/libip4tc.o #LIBS = $(IPTABLESPATH)/libiptc/.libs/libiptc.a else LIBS = $(IPTABLESPATH)/libiptc/libiptc.a endif else # check for system-wide iptables files. Test if iptables version >= 1.4.3 #TEST := $(shell test -f /usr/include/iptables/internal.h && grep -q "\#define IPTABLES_VERSION" /usr/include/iptables/internal.h && echo 1) TEST := $(shell test -f /usr/include/xtables.h && grep -q "XTABLES_VERSION_CODE" /usr/include/xtables.h && echo 1) ifeq ($(TEST), 1) CFLAGS := $(CFLAGS) -DIPTABLES_143 LIBS = -liptc TEST_LIB := $(shell test -f /usr/lib$(ARCH)/libiptc.a && echo 1) ifeq ($(TEST_LIB), 1) LIBS = -liptc /usr/lib$(ARCH)/libiptc.a endif endif endif LIBS += /lib/libip4tc.so /lib/libip6tc.so all: iptcrdr.o testiptcrdr iptpinhole.o \ testiptcrdr_peer testiptcrdr_dscp test_nfct_get # testiptpinhole clean: $(RM) *.o testiptcrdr testiptpinhole testiptcrdr_peer test_nfct_get \ testiptcrdr_dscp testiptcrdr: testiptcrdr.o upnpglobalvars.o $(LIBS) testiptcrdr_peer: testiptcrdr_peer.o upnpglobalvars.o $(LIBS) testiptcrdr_dscp: testiptcrdr_dscp.o upnpglobalvars.o $(LIBS) testiptpinhole: testiptpinhole.o iptpinhole.o upnpglobalvars.o $(LIBS) test_nfct_get: test_nfct_get.o test_nfct_get.o -lmnl -lnetfilter_conntrack test_nfct_get.o: test_nfct_get.c testiptcrdr_peer.o: testiptcrdr_peer.c testiptcrdr_dscp.o: testiptcrdr_dscp.c iptcrdr.o: iptcrdr.c iptcrdr.h iptpinhole.o: iptpinhole.c iptpinhole.h upnpglobalvars.o: ../upnpglobalvars.c ../upnpglobalvars.h $(CC) -c -o $@ $< #depends testiptcrdr.o: testiptcrdr.c iptcrdr.c testiptcrdr_dscp.o: testiptcrdr_dscp.c iptcrdr.c testiptcrdr_peer.o: testiptcrdr_peer.c iptcrdr.c test_nfct_get.o: test_nfct_get.c nfct_get.c miniupnpd-2.3.7/netfilter/testiptcrdr.c010064400017500000024000000050041265735071000174240ustar00nanardstaff/* $Id: testiptcrdr.c,v 1.21 2016/02/12 12:35:50 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "iptcrdr.c" /*#include "../commonrdr.h"*/ #ifndef PRIu64 #define PRIu64 "llu" #endif int main(int argc, char ** argv) { unsigned short eport, iport; const char * iaddr; int r; int proto = IPPROTO_TCP; printf("Usage %s [TCP/UDP]\n", argv[0]); if(argc<4) return -1; openlog("testiptcrdr", LOG_PERROR|LOG_CONS, LOG_LOCAL0); if(init_redirect() < 0) return -1; eport = (unsigned short)atoi(argv[1]); iaddr = argv[2]; iport = (unsigned short)atoi(argv[3]); if(argc >= 4) { if(strcasecmp(argv[4], "udp") == 0) proto = IPPROTO_UDP; } printf("trying to redirect port %hu to %s:%hu proto %d\n", eport, iaddr, iport, proto); if(addnatrule(proto, eport, iaddr, iport, NULL) < 0) return -1; r = addmasqueraderule(proto, eport, iaddr, iport, NULL); syslog(LOG_DEBUG, "addmasqueraderule() returned %d", r); if(add_filter_rule(proto, NULL, iaddr, iport) < 0) return -1; #if 0 /* TEST */ if(proto == IPPROTO_UDP) { if(addpeernatrule(proto, "8.8.8.8"/*eaddr*/, eport, iaddr, iport, NULL, 0) < 0) 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; char addr[16]; int proto2; char desc[256]; char rhost[256]; unsigned int timestamp; u_int64_t packets, bytes; desc[0] = '\0'; if(get_redirect_rule_by_index(0, "", &p1, addr, sizeof(addr), &p2, &proto2, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) < 0) { printf("rule not found\n"); } else { printf("redirected port %hu to %s:%hu proto %d packets=%" PRIu64 " bytes=%" PRIu64 " ts=%u desc='%s'\n", p1, addr, p2, proto2, packets, bytes, timestamp, desc); } } printf("trying to list nat rules :\n"); list_redirect_rule(NULL); printf("deleting\n"); delete_redirect_and_filter_rules(eport, proto); shutdown_redirect(); return 0; } miniupnpd-2.3.7/minixml.c010064400017500000024000000132321321374133400145330ustar00nanardstaff/* $Id: minixml.c,v 1.12 2017/12/12 11:17:40 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * minixml.c : the minimum size a xml parser can be ! */ /* Project : miniupnp * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author : Thomas Bernard Copyright (c) 2005-2017, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "minixml.h" /* parseatt : used to parse the argument list * return 0 (false) in case of success and -1 (true) if the end * of the xmlbuffer is reached. */ static int parseatt(struct xmlparser * p) { const char * attname; int attnamelen; const char * attvalue; int attvaluelen; while(p->xml < p->xmlend) { if(*p->xml=='/' || *p->xml=='>') return 0; if( !IS_WHITE_SPACE(*p->xml) ) { char sep; attname = p->xml; attnamelen = 0; while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) { attnamelen++; p->xml++; if(p->xml >= p->xmlend) return -1; } while(*(p->xml++) != '=') { if(p->xml >= p->xmlend) return -1; } while(IS_WHITE_SPACE(*p->xml)) { p->xml++; if(p->xml >= p->xmlend) return -1; } sep = *p->xml; if(sep=='\'' || sep=='\"') { p->xml++; if(p->xml >= p->xmlend) return -1; attvalue = p->xml; attvaluelen = 0; while(*p->xml != sep) { attvaluelen++; p->xml++; if(p->xml >= p->xmlend) return -1; } } else { attvalue = p->xml; attvaluelen = 0; while( !IS_WHITE_SPACE(*p->xml) && *p->xml != '>' && *p->xml != '/') { attvaluelen++; p->xml++; if(p->xml >= p->xmlend) return -1; } } /*printf("%.*s='%.*s'\n", attnamelen, attname, attvaluelen, attvalue);*/ if(p->attfunc) p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); } p->xml++; } return -1; } /* parseelt parse the xml stream and * call the callback functions when needed... */ static void parseelt(struct xmlparser * p) { int i; const char * elementname; while(p->xml < (p->xmlend - 1)) { if((p->xml + 4) <= p->xmlend && (0 == memcmp(p->xml, "", 3) != 0); p->xml += 3; } else if((p->xml)[0]=='<' && (p->xml)[1]!='?') { i = 0; elementname = ++p->xml; while( !IS_WHITE_SPACE(*p->xml) && (*p->xml!='>') && (*p->xml!='/') ) { i++; p->xml++; if (p->xml >= p->xmlend) return; /* to ignore namespace : */ if(*p->xml==':') { i = 0; elementname = ++p->xml; } } if(i>0) { if(p->starteltfunc) p->starteltfunc(p->data, elementname, i); if(parseatt(p)) return; if(*p->xml!='/') { const char * data; i = 0; data = ++p->xml; if (p->xml >= p->xmlend) return; while( IS_WHITE_SPACE(*p->xml) ) { i++; p->xml++; if (p->xml >= p->xmlend) return; } /* CDATA are at least 9 + 3 characters long : */ if((p->xmlend >= (p->xml + (9 + 3))) && (memcmp(p->xml, "xml += 9; data = p->xml; i = 0; while(memcmp(p->xml, "]]>", 3) != 0) { i++; p->xml++; if ((p->xml + 3) >= p->xmlend) return; } if(i>0 && p->datafunc) p->datafunc(p->data, data, i); while(*p->xml!='<') { p->xml++; if (p->xml >= p->xmlend) return; } } else { while(*p->xml!='<') { i++; p->xml++; if ((p->xml + 1) >= p->xmlend) return; } if(i>0 && p->datafunc && *(p->xml + 1) == '/') p->datafunc(p->data, data, i); } } } else if(*p->xml == '/') { i = 0; elementname = ++p->xml; if (p->xml >= p->xmlend) return; while((*p->xml != '>')) { i++; p->xml++; if (p->xml >= p->xmlend) return; } if(p->endeltfunc) p->endeltfunc(p->data, elementname, i); p->xml++; } } else { p->xml++; } } } /* the parser must be initialized before calling this function */ void parsexml(struct xmlparser * parser) { parser->xml = parser->xmlstart; parser->xmlend = parser->xmlstart + parser->xmlsize; parseelt(parser); } miniupnpd-2.3.7/minixml.h010064400017500000024000000022331343001453300145320ustar00nanardstaff/* $Id: minixml.h,v 1.8 2019/02/10 12:29:25 nanard Exp $ */ /* minimal xml parser * * Project : miniupnp * Website : http://miniupnp.free.fr/ * Author : Thomas Bernard * Copyright (c) 2005 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ #ifndef MINIXML_H_INCLUDED #define MINIXML_H_INCLUDED #define IS_WHITE_SPACE(c) ((c)==' ' || (c)=='\t' || (c)=='\r' || (c)=='\n') /* if a callback function pointer is set to NULL, * the function is not called */ struct xmlparser { const char *xmlstart; const char *xmlend; const char *xml; /* pointer to current character */ int xmlsize; void * data; void (*starteltfunc) (void *, const char *, int); void (*endeltfunc) (void *, const char *, int); void (*datafunc) (void *, const char *, int); void (*attfunc) (void *, const char *, int, const char *, int); }; /* parsexml() * the xmlparser structure must be initialized before the call * the following structure members have to be initialized : * xmlstart, xmlsize, data, *func * xml is for internal usage, xmlend is computed automatically */ void parsexml(struct xmlparser *); #endif miniupnpd-2.3.7/upnpreplyparse.c010064400017500000024000000106551345264716300161670ustar00nanardstaff/* $Id: upnpreplyparse.c,v 1.21 2019/04/08 13:30:51 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2019 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include "upnpreplyparse.h" #include "minixml.h" static void NameValueParserStartElt(void * d, const char * name, int l) { struct NameValueParserData * data = (struct NameValueParserData *)d; data->topelt = 1; if(l>63) l = 63; memcpy(data->curelt, name, l); data->curelt[l] = '\0'; data->cdata = NULL; data->cdatalen = 0; } static void NameValueParserEndElt(void * d, const char * name, int namelen) { struct NameValueParserData * data = (struct NameValueParserData *)d; struct NameValue * nv; (void)name; (void)namelen; if(!data->topelt) return; if(strcmp(data->curelt, "NewPortListing") != 0) { int l; /* standard case. Limited to n chars strings */ l = data->cdatalen; nv = malloc(sizeof(struct NameValue)); if(nv == NULL) { /* malloc error */ #ifdef DEBUG fprintf(stderr, "%s: error allocating memory", "NameValueParserEndElt"); #endif /* DEBUG */ return; } if(l>=(int)sizeof(nv->value)) l = sizeof(nv->value) - 1; strncpy(nv->name, data->curelt, 64); nv->name[63] = '\0'; if(data->cdata != NULL) { memcpy(nv->value, data->cdata, l); nv->value[l] = '\0'; } else { nv->value[0] = '\0'; } nv->l_next = data->l_head; /* insert in list */ data->l_head = nv; } data->cdata = NULL; data->cdatalen = 0; data->topelt = 0; } static void NameValueParserGetData(void * d, const char * datas, int l) { struct NameValueParserData * data = (struct NameValueParserData *)d; if(strcmp(data->curelt, "NewPortListing") == 0) { /* specific case for NewPortListing which is a XML Document */ free(data->portListing); data->portListing = malloc(l + 1); if(!data->portListing) { /* malloc error */ #ifdef DEBUG fprintf(stderr, "%s: error allocating memory", "NameValueParserGetData"); #endif /* DEBUG */ return; } memcpy(data->portListing, datas, l); data->portListing[l] = '\0'; data->portListingLength = l; } else { /* standard case. */ data->cdata = datas; data->cdatalen = l; } } void ParseNameValue(const char * buffer, int bufsize, struct NameValueParserData * data) { struct xmlparser parser; memset(data, 0, sizeof(struct NameValueParserData)); /* init xmlparser object */ parser.xmlstart = buffer; parser.xmlsize = bufsize; parser.data = data; parser.starteltfunc = NameValueParserStartElt; parser.endeltfunc = NameValueParserEndElt; parser.datafunc = NameValueParserGetData; parser.attfunc = 0; parsexml(&parser); } void ClearNameValueList(struct NameValueParserData * pdata) { struct NameValue * nv; if(pdata->portListing) { free(pdata->portListing); pdata->portListing = NULL; pdata->portListingLength = 0; } while((nv = pdata->l_head) != NULL) { pdata->l_head = nv->l_next; free(nv); } } char * GetValueFromNameValueList(struct NameValueParserData * pdata, const char * Name) { struct NameValue * nv; char * p = NULL; for(nv = pdata->l_head; (nv != NULL) && (p == NULL); nv = nv->l_next) { if(strcmp(nv->name, Name) == 0) p = nv->value; } return p; } #if 0 /* useless now that minixml ignores namespaces by itself */ char * GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, const char * Name) { struct NameValue * nv; char * p = NULL; char * pname; for(nv = pdata->head.lh_first; (nv != NULL) && (p == NULL); nv = nv->entries.le_next) { pname = strrchr(nv->name, ':'); if(pname) pname++; else pname = nv->name; if(strcmp(pname, Name)==0) p = nv->value; } return p; } #endif /* debug all-in-one function * do parsing then display to stdout */ #ifdef DEBUG void DisplayNameValueList(char * buffer, int bufsize) { struct NameValueParserData pdata; struct NameValue * nv; ParseNameValue(buffer, bufsize, &pdata); for(nv = pdata.l_head; nv != NULL; nv = nv->l_next) { printf("%s = %s\n", nv->name, nv->value); } ClearNameValueList(&pdata); } #endif /* DEBUG */ miniupnpd-2.3.7/upnpreplyparse.h010064400017500000024000000025211242347260200161540ustar00nanardstaff/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2013 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPREPLYPARSE_H_INCLUDED #define UPNPREPLYPARSE_H_INCLUDED #ifdef __cplusplus extern "C" { #endif struct NameValue { struct NameValue * l_next; char name[64]; char value[128]; }; struct NameValueParserData { struct NameValue * l_head; char curelt[64]; char * portListing; int portListingLength; int topelt; const char * cdata; int cdatalen; }; /* ParseNameValue() */ void ParseNameValue(const char * buffer, int bufsize, struct NameValueParserData * data); /* ClearNameValueList() */ void ClearNameValueList(struct NameValueParserData * pdata); /* GetValueFromNameValueList() */ char * GetValueFromNameValueList(struct NameValueParserData * pdata, const char * Name); #if 0 /* GetValueFromNameValueListIgnoreNS() */ char * GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, const char * Name); #endif /* DisplayNameValueList() */ #ifdef DEBUG void DisplayNameValueList(char * buffer, int bufsize); #endif #ifdef __cplusplus } #endif #endif miniupnpd-2.3.7/testgetifstats.c010064400017500000024000000020601177217435300161410ustar00nanardstaff/* $Id: testgetifstats.c,v 1.6 2012/06/22 16:11:58 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include "getifstats.h" #if defined(__sun) /* solaris 10 does not define LOG_PERROR */ #define LOG_PERROR 0 #endif int main(int argc, char **argv) { int r; struct ifdata data; if(argc<2) { fprintf(stderr, "usage : %s \n", argv[0]); return 1; } openlog("testgetifstats", LOG_CONS|LOG_PERROR, LOG_USER); memset(&data, 0, sizeof(data)); r = getifstats(argv[1], &data); printf("getifstats() returned %d\n", r); printf("stats for interface %s :\n", argv[1]); printf("bitrate = %lu\n", data.baudrate); printf(" input packets : %9lu\t input bytes : %9lu\n", data.ipackets, data.ibytes); printf("output packets : %9lu\toutput bytes : %9lu\n", data.opackets, data.obytes); return 0; } miniupnpd-2.3.7/upnpdescstrings.h010064400017500000024000000031621457060357000163250ustar00nanardstaff/* $Id: upnpdescstrings.h,v 1.12 2024/03/02 10:50:42 nanard Exp $ */ /* miniupnp project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 Thomas Bernard * This software is subject to the coditions detailed in * the LICENCE file provided within the distribution */ #ifndef UPNPDESCSTRINGS_H_INCLUDED #define UPNPDESCSTRINGS_H_INCLUDED #include "config.h" /* strings used in the root device xml description */ #define ROOTDEV_FRIENDLYNAME OS_NAME " router" #define ROOTDEV_MANUFACTURER OS_NAME #define ROOTDEV_MANUFACTURERURL OS_URL #define ROOTDEV_MODELNAME OS_NAME " router" #define ROOTDEV_MODELDESCRIPTION OS_NAME " with MiniUPnPd version " MINIUPNPD_VERSION " router" #define ROOTDEV_MODELURL OS_URL #define WANDEV_FRIENDLYNAME "WANDevice" #define WANDEV_MANUFACTURER "MiniUPnP" #define WANDEV_MANUFACTURERURL "https://miniupnp.tuxfamily.org/" #define WANDEV_MODELNAME "MiniUPnPd" #define WANDEV_MODELDESCRIPTION "MiniUPnP daemon version " MINIUPNPD_VERSION #define WANDEV_MODELNUMBER MINIUPNPD_DATE #define WANDEV_MODELURL "https://miniupnp.tuxfamily.org/" #define WANDEV_UPC "000000000000" /* UPC is 12 digit (barcode) */ #define WANCDEV_FRIENDLYNAME "WANConnectionDevice" #define WANCDEV_MANUFACTURER WANDEV_MANUFACTURER #define WANCDEV_MANUFACTURERURL WANDEV_MANUFACTURERURL #define WANCDEV_MODELNAME "MiniUPnPd" #define WANCDEV_MODELDESCRIPTION "MiniUPnP daemon version " MINIUPNPD_VERSION #define WANCDEV_MODELNUMBER MINIUPNPD_DATE #define WANCDEV_MODELURL "https://miniupnp.tuxfamily.org/" #define WANCDEV_UPC "000000000000" /* UPC is 12 digit (barcode) */ #endif miniupnpd-2.3.7/configure010075500017500000024000001016201463564776500146460ustar00nanardstaff#! /bin/sh # $Id: configure,v 1.130 2024/06/22 19:20:10 nanard Exp $ # vim: tabstop=4 shiftwidth=4 noexpandtab # # miniupnp daemon # http://miniupnp.free.fr or https://miniupnp.tuxfamily.org/ # (c) 2006-2024 Thomas Bernard # This software is subject to the conditions detailed in the # LICENCE file provided within the distribution # default to UPnP Device Architecture (UDA) v1.1 # some control points do not like UDA v2.0 UPNP_VERSION_MAJOR=1 UPNP_VERSION_MINOR=1 # input environment variables : # IPV6, IGD2, STRICT, DEBUG, LEASFILE, VENDORCFG, PCP_PEER, # PORTINUSE, REGEX, DISABLEPPPCONN, FW, IPTABLESPATH, TARGET_OPENWRT, # PKG_CONFIG, NO_BACKGROUND_NO_PIDFILE, DYNAMIC_OS_VERSION # OS_NAME, OS_VERSION, OS_MACHINE, V6SOCKETS_ARE_V6ONLY # USE_LIBPFCTL if [ -z "$DYNAMIC_OS_VERSION" ] ; then DYNAMIC_OS_VERSION=1 fi for argv; do case "$argv" in --ipv6) IPV6=1 ;; --igd2) IGD2=1 ;; --strict) STRICT=1 ;; --debug) DEBUG=1 ;; --leasefile) LEASEFILE=1 ;; --vendorcfg) VENDORCFG=1 ;; --pcp-peer) PCP_PEER=1 ;; --portinuse) PORTINUSE=1 ;; --regex) REGEX=1 ;; --uda-version=*) UPNP_VERSION=$(echo $argv | cut -d= -f2) UPNP_VERSION_MAJOR=$(echo $UPNP_VERSION | cut -s -d. -f1) UPNP_VERSION_MINOR=$(echo $UPNP_VERSION | cut -s -d. -f2) echo "Setting UPnP version major=$UPNP_VERSION_MAJOR minor=$UPNP_VERSION_MINOR" if [ -z "$UPNP_VERSION_MAJOR" ] || [ -z "$UPNP_VERSION_MINOR" ] ; then echo "UPnP Version invalid in option $argv" exit 1 fi ;; --disable-pppconn) DISABLEPPPCONN=1 ;; --disable-fork) NO_BACKGROUND_NO_PIDFILE=1 ;; --firewall=*) FW=$(echo $argv | cut -d= -f2) ;; --libpfctl) USE_LIBPFCTL=1 ;; --iptablespath=*) IPTABLESPATH=$(echo $argv | cut -d= -f2) ;; --getifaddrs) GETIFADDRS=1 ;; --v6sockets-v6only) V6SOCKETS_ARE_V6ONLY=1 ;; --host-os=*) OS_NAME=$(echo $argv | cut -d= -f2) ;; --host-os-version=*) OS_VERSION=$(echo $argv | cut -d= -f2) ;; --host-machine=*) OS_MACHINE=$(echo $argv | cut -d= -f2) ;; --help|-h) echo "Usage: $0 [options]" echo " --help Show this help" echo " --ipv6 Enable IPv6" echo " --igd2 Build an IGDv2 instead of an IGDv1" echo " --strict Stricter compliance with UPnP specifications" echo " --debug #define DEBUG 1" echo " --leasefile Enable lease file" echo " --vendorcfg Enable configuration of manufacturer info" echo " --pcp-peer Enable PCP PEER operation" echo " --portinuse Enable port in use check" echo " --regex Enable description regex filter" echo " --uda-version=x.x Set advertised UPnP version (default to ${UPNP_VERSION_MAJOR}.${UPNP_VERSION_MINOR})" echo " --disable-pppconn Disable WANPPPConnection" echo " --firewall= Force firewall type (nftables, iptables, pf, ipf, ipfw)" echo " --libpfctl Use libpfctl" echo " --iptablespath=/path Use a specific version of iptables" echo " --disable-fork Do not go to background and do not write pid file" echo " --getifaddrs Force use getifaddrs() to obtain interface addresses" echo " --v6sockets-v6only v6 sockets don't do v4, ie sysctl net.inet6.ip6.v6only=1" echo " --host-os= For cross build. result of uname -s on the host machine" echo " --host-os-version=x.x For cross build. result of uname -r on the host machine" echo " --host-machine= For cross build. result of uname -m on the host machine" exit 1 ;; *) echo "Option not recognized: $argv" echo "Use -h option to display help" exit 1 ;; esac done echo $* > .configure.cache BASEDIR=`dirname "$0"` RM="rm -f" MV="mv" CONFIGFILE=`mktemp tmp.config.h.XXXXXXXXXX` CONFIGFILE_FINAL="config.h" CONFIGMACRO="CONFIG_H_INCLUDED" if [ -z "$PKG_CONFIG" ] ; then PKG_CONFIG=`command -v pkg-config` fi MINIUPNPD_DATE=`date +"%Y%m%d"` if [ -n "$SOURCE_DATE_EPOCH" ]; then if date --version 2>&1 | grep -q GNU; then MINIUPNPD_DATE=`date --utc --date="@$SOURCE_DATE_EPOCH" +"%Y%m%d"` else MINIUPNPD_DATE=`TZ=UTC date -j -f %s $SOURCE_DATE_EPOCH +%Y%m%d` fi fi # Facility to syslog LOG_MINIUPNPD="LOG_DAEMON" # Makefile to use MAKEFILE= if [ -z "$OS_NAME" ] ; then # detecting the OS name and version OS_NAME=`uname -s` OS_VERSION=`uname -r` OS_MACHINE=`uname -m` # pfSense special case if [ -f /etc/platform ]; then if [ `cat /etc/platform` = "pfSense" ]; then OS_NAME=pfSense OS_VERSION=`cat /etc/version` fi fi # OpenWrt special case if [ -f ./os.openwrt ]; then OS_NAME=OpenWrt OS_VERSION=$(cat ./os.openwrt) fi # AstLinux special case if [ -f ./os.astlinux ]; then OS_NAME=AstLinux OS_VERSION=$(cat ./os.astlinux) fi # Tomato USB special case if [ -f ../shared/tomato_version ]; then OS_NAME=Tomato TOMATO_VER=`cat ../shared/tomato_version | cut -d' ' -f2,3` OS_VERSION="Tomato $TOMATO_VER" fi # OpenEmbedded special case if [ -f ./os.openembedded ]; then OS_NAME=OpenEmbedded OS_VERSION=$(cat ./os.openembedded) fi else CROSSBUILD=1 if [ -z "$OS_VERSION" ] || [ -z "$OS_MACHINE" ] ; then echo "OS_NAME set to \"$OS_NAME\"." echo "Please also set OS_VERSION/--host-os-version and OS_MACHINE/--host-machine" exit 1 fi fi ${RM} ${CONFIGFILE} echo "/* MiniUPnP Project" >> ${CONFIGFILE} echo " * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/" >> ${CONFIGFILE} echo " * (c) 2006-2024 Thomas Bernard" >> ${CONFIGFILE} echo " * Generated by $0 on `date`" >> ${CONFIGFILE} echo " * `uname -a`" >> ${CONFIGFILE} if [ -z "$*" ] ; then echo " * using no command line option */" >> ${CONFIGFILE} else echo " * using command line options $* */" >> ${CONFIGFILE} fi echo "#ifndef $CONFIGMACRO" >> ${CONFIGFILE} echo "#define $CONFIGMACRO" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "#include " >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "#define MINIUPNPD_VERSION \"`cat ${BASEDIR}/VERSION`\"" >> ${CONFIGFILE} echo "#define MINIUPNPD_DATE \"$MINIUPNPD_DATE\"" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} if [ -n "$DEBUG" ] ; then echo "#define DEBUG 1" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} fi cat >> ${CONFIGFILE} <> ${CONFIGFILE} cat >> ${CONFIGFILE} <> ${CONFIGFILE} # OS Specific stuff case $OS_NAME in OpenBSD) MAKEFILE=Makefile.bsd MAJORVER=`echo $OS_VERSION | cut -d. -f1` MINORVER=`echo $OS_VERSION | cut -d. -f2` #echo "OpenBSD majorversion=$MAJORVER minorversion=$MINORVER" # The pledge() system call first appeared in OpenBSD 5.9. if [ \( $MAJORVER -ge 6 \) -o \( $MAJORVER -eq 5 -a $MINORVER -ge 9 \) ]; then # as of writing (OpenBSD 6.7) DIOCGETRULES is not included in the # operations allowed by the "pf" pledge. echo "/*#define HAS_PLEDGE*/" >> ${CONFIGFILE} fi # rtableid was introduced in OpenBSD 4.0 if [ $MAJORVER -ge 4 ]; then echo "#define PFRULE_HAS_RTABLEID" >> ${CONFIGFILE} fi # from the 3.8 version, packets and bytes counters are double : in/out if [ \( $MAJORVER -ge 4 \) -o \( $MAJORVER -eq 3 -a $MINORVER -ge 8 \) ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi # from the 4.7 version, new pf if [ \( $MAJORVER -ge 5 \) -o \( $MAJORVER -eq 4 -a $MINORVER -ge 7 \) ]; then echo "#define PF_NEWSTYLE" >> ${CONFIGFILE} fi # onrdomain was introduced in OpenBSD 5.0 if [ $MAJORVER -ge 5 ]; then echo "#define PFRULE_HAS_ONRDOMAIN" >> ${CONFIGFILE} fi # before OpenBSD 5.5 inpt_queue was CIRCLEQ if [ $MAJORVER -lt 5 ] || [ $MAJORVER -eq 5 -a $MINORVER -lt 5 ]; then echo "#define INPT_QUEUE_IS_CIRCLEQ" >> ${CONFIGFILE} fi FW=pf echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} OS_URL=https://www.openbsd.org/ # net.inet6.ip6.v6only has been removed in recent OpenBSD versions # Default to 1 in that case if [ "$CROSSBUILD" != "1" ] && [ -z "$V6SOCKETS_ARE_V6ONLY" ] ; then if sysctl net.inet6.ip6 | grep net.inet6.ip6.v6only ; then V6SOCKETS_ARE_V6ONLY=`sysctl -n net.inet6.ip6.v6only` else V6SOCKETS_ARE_V6ONLY=1 fi fi ;; FreeBSD | GNU/kFreeBSD) MAKEFILE=Makefile.bsd if [ "$CROSSBUILD" != "1" ] ; then VER=`grep '#define __FreeBSD_version' /usr/include/sys/param.h | awk '{print $3}'` if [ $VER -ge 700049 ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi if [ $VER -ge 1500000 ]; then USE_LIBPFCTL=1 fi else VER=`echo $OS_VERSION | cut -d. -f1` if [ $VER -ge 7 ]; then echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} fi if [ $VER -ge 15 ]; then USE_LIBPFCTL=1 fi fi HAVE_IP_MREQN=1 # new way to see which one to use PF or IPF. # see https://miniupnp.tuxfamily.org/forum/viewtopic.php?p=957 if [ "$CROSSBUILD" != "1" ] && [ -z $FW ] && [ -f /etc/rc.subr ] && [ -f /etc/defaults/rc.conf ] ; then # source file with handy subroutines like checkyesno . /etc/rc.subr # source config file so we can probe vars . /etc/defaults/rc.conf if [ -f /etc/rc.conf ] ; then . /etc/rc.conf fi if checkyesno ipfilter_enable; then echo "Using ipf" FW=ipf elif checkyesno pf_enable; then echo "Using pf" FW=pf elif checkyesno firewall_enable; then echo "Using ifpw" FW=ipfw fi fi if [ -z $FW ] ; then echo "Could not detect usage of ipf, pf, ipfw. Compiling for pf by default" FW=pf fi if [ "$FW" = "ipfw" ] ; then echo "!!! ipfw is known to not work with FreeBSD, please contribute !!!" echo "!!! see https://github.com/miniupnp/miniupnp/issues/596 !!!" fi echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} OS_URL=https://www.freebsd.org/ if [ "$CROSSBUILD" != "1" ] && [ -z "$V6SOCKETS_ARE_V6ONLY" ] ; then V6SOCKETS_ARE_V6ONLY=`sysctl -n net.inet6.ip6.v6only` fi ;; pfSense) MAKEFILE=Makefile.bsd # we need to detect if PFRULE_INOUT_COUNTS macro is needed FW=pf echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} OS_URL=https://www.pfsense.com/ if [ "$CROSSBUILD" != "1" ] && [ -z "$V6SOCKETS_ARE_V6ONLY" ] ; then V6SOCKETS_ARE_V6ONLY=`sysctl -n net.inet6.ip6.v6only` fi ;; NetBSD) MAKEFILE=Makefile.bsd if [ "$CROSSBUILD" != "1" ] && [ -z $FW ] && [ -f /etc/rc.subr ] && [ -f /etc/rc.conf ] ; then # source file with handy subroutines like checkyesno . /etc/rc.subr # source config file so we can probe vars . /etc/rc.conf if checkyesno pf; then FW=pf elif checkyesno ipfilter; then FW=ipf fi fi if [ -z $FW ] ; then echo "Could not detect ipf nor pf, defaulting to pf." FW=pf fi echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} OS_URL=https://www.netbsd.org/ ;; DragonFly) MAKEFILE=Makefile.bsd if [ "$CROSSBUILD" != "1" ] && [ -z $FW ] && [ -f /etc/rc.subr ] && [ -f /etc/rc.conf ] ; then # source file with handy subroutines like checkyesno . /etc/rc.subr # source config file so we can probe vars . /etc/rc.conf if checkyesno pf; then FW=pf elif checkyesno ipfilter; then FW=ipf fi fi if [ -z $FW ] ; then echo "Could not detect ipf nor pf, defaulting to pf." FW=pf fi echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} # PFRULE_INOUT_COUNTS should be set for DragonFly > 2.8 # version detection is not yet added to this script. echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} # net.inet6.ip6.v6only has been on by default for many years # and this sysctl node has been removed V6SOCKETS_ARE_V6ONLY=1 OS_URL=https://www.dragonflybsd.org/ ;; SunOS) MAKEFILE=Makefile.bsd echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} FW=ipf echo "#define LOG_PERROR 0" >> ${CONFIGFILE} echo "#define SOLARIS_KSTATS 1" >> ${CONFIGFILE} # solaris 10 does not define u_int64_t ? # but it does define uint64_t echo "typedef uint64_t u_int64_t;" >> ${CONFIGFILE} OS_URL=https://www.oracle.com/solaris/ ;; Linux) OS_URL=https://www.kernel.org/ KERNVERA=`echo $OS_VERSION | awk -F. '{print $1}'` KERNVERB=`echo $OS_VERSION | awk -F. '{print $2}'` KERNVERC=`echo $OS_VERSION | awk -F. '{print $3}'` KERNVERD=`echo $OS_VERSION | awk -F. '{print $4}'` #echo "$KERNVERA.$KERNVERB.$KERNVERC.$KERNVERD" # from the 2.4 version, struct ip_mreqn instead of struct ip_mreq if [ \( $KERNVERA -ge 3 \) -o \( $KERNVERA -eq 2 -a $KERNVERB -ge 4 \) ]; then HAVE_IP_MREQN=1 fi if [ "$CROSSBUILD" != "1" ] ; then # Debian GNU/Linux special case if [ -f /etc/debian_version ]; then OS_NAME=Debian OS_VERSION=`cat /etc/debian_version` OS_URL=https://www.debian.org/ fi # same thing for Gentoo linux if [ -f /etc/gentoo-release ]; then OS_NAME=Gentoo OS_VERSION=`cat /etc/gentoo-release` OS_URL=https://www.gentoo.org/ fi # ClearOS special case if [ -f /etc/clearos-release ]; then OS_NAME=ClearOS OS_VERSION=`grep ^base_version /etc/product | awk '{ print $3 }'` OS_URL=https://www.clearos.com/ fi # use lsb_release (Linux Standard Base) when available LSB_RELEASE=`command -v lsb_release` if [ 0 -eq $? ]; then OS_NAME=`${LSB_RELEASE} -i -s` OS_VERSION=`${LSB_RELEASE} -r -s` case $OS_NAME in Chimera) OS_URL=https://chimera-linux.org/ OS_VERSION=`uname -r` ;; Debian) OS_URL=https://www.debian.org/ OS_VERSION=`${LSB_RELEASE} -c -s` ;; Ubuntu) OS_URL=https://ubuntu.com/ OS_VERSION=`${LSB_RELEASE} -c -s` ;; Gentoo) OS_URL=https://www.gentoo.org/ ;; arch) OS_URL=https://archlinux.org/ OS_VERSION=`uname -r` ;; esac fi fi echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} if [ "$CROSSBUILD" != "1" ] ; then if [ -z ${FW} ]; then # test the current environment to determine which to use # Would be better to check for actual presence of nftable rules, but that requires root privileges if [ -x "$(command -v nft)" ]; then FW=nftables else FW=iptables fi fi if [ -z "$V6SOCKETS_ARE_V6ONLY" ] ; then V6SOCKETS_ARE_V6ONLY=`$(find /sbin /bin /usr/sbin /usr/bin -name sysctl) -n net.ipv6.bindv6only` fi fi ;; OpenWrt) OS_URL=https://www.openwrt.org/ echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} GETIFADDRS=1 ;; OpenEmbedded) OS_URL=https://www.openembedded.org/ echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} FW=iptables ;; AstLinux) OS_URL=https://www.astlinux-project.org/ echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} FW=iptables ;; Tomato) OS_NAME=UPnP OS_URL=http://tomatousb.org/ echo "" >> ${CONFIGFILE} echo "#ifndef TOMATO" >> ${CONFIGFILE} echo "#define TOMATO" >> ${CONFIGFILE} echo "#endif" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "#ifdef LINUX26" >> ${CONFIGFILE} echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} echo "#endif" >> ${CONFIGFILE} echo "#ifdef TCONFIG_IPV6" >> ${CONFIGFILE} echo "#define ENABLE_IPV6" >> ${CONFIGFILE} echo "#endif" >> ${CONFIGFILE} FW=iptables ;; Darwin) MAKEFILE=Makefile.macosx MAJORVER=`echo $OS_VERSION | cut -d. -f1` echo "#define USE_IFACEWATCHER 1" >> ${CONFIGFILE} # OS X switched to pf since 10.7 Lion (Darwin 11.0) if [ $MAJORVER -ge 11 ] ; then FW=pf echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} else FW=ipfw fi OS_URL=https://developer.apple.com/macos/ ;; *) echo "Unknown OS : $OS_NAME" echo "Supported OS_NAME / --host-os values : " # find all the cases in this "case $OS_NAME in" statement : awk '/# OS Specific stuff/{if(b>0){b=0}else{b=NR}} (b>0&&NR>b){print}' "$0" | grep '^ [^\(]*)$' | grep -v '*)' | tr ')|' " \n" | tr -d '\t ' | sort | tr "\n" " " ; echo "" echo "Please contact the author at https://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/." exit 1 ;; esac case $FW in pf) echo "#define USE_PF 1" >> ${CONFIGFILE} if [ "$USE_LIBPFCTL" = "1" ] ; then echo "#define USE_LIBPFCTL 1" >> ${CONFIGFILE} fi ;; ipf) echo "#define USE_IPF 1" >> ${CONFIGFILE} ;; ipfw) echo "#define USE_IPFW 1" >> ${CONFIGFILE} ;; iptables) MAKEFILE=Makefile.linux echo "#define USE_NETFILTER 1" >> ${CONFIGFILE} echo "#define USE_IPTABLES 1" >> ${CONFIGFILE} echo "# generated by $0 on `date`" > config.mk echo "SRCDIR = ${BASEDIR}" >> config.mk echo "CPPFLAGS += -I." >> config.mk if [ "$PKG_CONFIG" ] ; then if ${PKG_CONFIG} --exists libiptc ; then IPTABLESVERSION=`${PKG_CONFIG} --modversion libiptc` echo "detected libiptc version $IPTABLESVERSION" echo "# detected libiptc version $IPTABLESVERSION" >> config.mk echo "IPTABLES_PCFILE_FOUND = 1" >> config.mk IPTVER1=`echo $IPTABLESVERSION | cut -d. -f1` IPTVER2=`echo $IPTABLESVERSION | cut -d. -f2` IPTVER3=`echo $IPTABLESVERSION | cut -d. -f3` if [ $IPTVER1 -gt 1 ] || \ [ \( $IPTVER1 -eq 1 \) -a \( \( $IPTVER2 -gt 4 \) \ -o \( \( $IPTVER2 -eq 4 \) -a \( $IPTVER3 -ge 3 \) \) \) ] ; then IPTABLES_143=1 fi echo "CFLAGS += `${PKG_CONFIG} --cflags libiptc`" >> config.mk echo "LDLIBS += `${PKG_CONFIG} --static --libs-only-l libiptc`" >> config.mk echo "LDFLAGS += `${PKG_CONFIG} --libs-only-L --libs-only-other libiptc`" >> config.mk else echo "Warning: no libiptc pkg-config found" fi if ${PKG_CONFIG} --atleast-version=1.0.2 libnetfilter_conntrack \ && ${PKG_CONFIG} --atleast-version=1.0.3 libmnl ; then echo "CPPFLAGS += -DUSE_NFCT" >> config.mk echo "LDLIBS += `${PKG_CONFIG} --static --libs-only-l libmnl libnetfilter_conntrack`" >> config.mk fi fi if [ "$IPTABLESPATH" ] ; then echo "CPPFLAGS += -I${IPTABLESPATH}/include/" >> config.mk echo "LDFLAGS += -L${IPTABLESPATH}/libiptc/" >> config.mk # to test : change the following test to [ "$OS_NAME" != "OpenWrt" ] if [ -z "$TARGET_OPENWRT" ] ; then IPTABLESVERSION=`grep "\#define VERSION" ${IPTABLESPATH}/config.h | tr -d \" |cut -d" " -f3` echo "detected libiptc version $IPTABLESVERSION" echo "# detected libiptc version $IPTABLESVERSION" >> config.mk IPTVER1=`echo $IPTABLESVERSION | cut -d. -f1` IPTVER2=`echo $IPTABLESVERSION | cut -d. -f2` IPTVER3=`echo $IPTABLESVERSION | cut -d. -f3` if [ $IPTVER1 -gt 1 ] || \ [ \( $IPTVER1 -eq 1 \) -a \( \( $IPTVER2 -gt 4 \) \ -o \( \( $IPTVER2 -eq 4 \) -a \( $IPTVER3 -ge 3 \) \) \) ] ; then IPTABLES_143=1 fi if [ "$IPTABLES_143" = "1" ] ; then echo "LDLIBS += ${IPTABLESPATH}/libiptc/.libs/libip4tc.o" >> config.mk else echo "LDLIBS += ${IPTABLESPATH}/libiptc/libiptc.a" >> config.mk fi else # OpenWrt # check for system-wide iptables files. Test if iptables version >= 1.4.3 # the following test has to be verified : if test -f /usr/include/iptables/internal.h && \ grep -q "\#define IPTABLES_VERSION" /usr/include/iptables/internal.h ; then IPTABLES_143=1 echo "LDLIBS += -liptc" >> config.mk fi arch=`echo $OS_MACHINE | grep -q x86_64 && echo 64` if test -f /usr/lib${arch}/libiptc.a ; then echo "LDLIBS += -liptc /usr/lib${arch}/libiptc.a" >> config.mk fi fi elif [ -z "${PKG_CONFIG}" ] ; then # IPTABLESPATH not defined and no pkg-config if test -f /usr/include/xtables.h && \ grep -q "XTABLES_VERSION_CODE" /usr/include/xtables.h ; then IPTABLES_143=1 echo "LDLIBS += -liptc" >> config.mk if test -f /lib/libip4tc.so ; then echo "LDLIBS += -lip4tc" >> config.mk fi if test -f /lib/libip6tc.so ; then echo "LDLIBS += -lip6tc" >> config.mk fi fi fi echo "/* when IPTABLES_143 is defined, miniupnpd uses the new API" >> ${CONFIGFILE} echo " * from libiptc 1.4.3+ */ " >> ${CONFIGFILE} if [ "$IPTABLES_143" = "1" ] ; then echo "#define IPTABLES_143" >> ${CONFIGFILE} else echo "#undef IPTABLES_143" >> ${CONFIGFILE} fi ;; nftables) MAKEFILE=Makefile.linux_nft echo "#define USE_NETFILTER 1" >> ${CONFIGFILE} echo "#define USE_NFTABLES 1" >> ${CONFIGFILE} echo "# generated by $0 on `date`" > config.mk echo "SRCDIR = ${BASEDIR}" >> config.mk echo "CPPFLAGS += -I." >> config.mk ;; *) echo "Unknown Firewall/packet filtering software [$FW]" echo "Please contact the author at http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/." exit 1 ;; esac if [ "$FW" = "iptables" ] || [ "$FW" = "nftables" ] ; then # linux if [ "$PKG_CONFIG" ] ; then if ${PKG_CONFIG} --exists libcap-ng ; then echo "detected libcap-ng `${PKG_CONFIG} --modversion libcap-ng`" echo "CFLAGS += `${PKG_CONFIG} --cflags libcap-ng`" >> config.mk echo "LDLIBS += `${PKG_CONFIG} --libs-only-l libcap-ng`" >> config.mk echo "LDFLAGS += `${PKG_CONFIG} --libs-only-L --libs-only-other libcap-ng`" >> config.mk echo "#define HAS_LIBCAP_NG" >> ${CONFIGFILE} elif ${PKG_CONFIG} --exists libcap ; then echo "detected libcap `${PKG_CONFIG} --modversion libcap`" echo "CFLAGS += `${PKG_CONFIG} --cflags libcap`" >> config.mk echo "LDLIBS += `${PKG_CONFIG} --libs-only-l libcap`" >> config.mk echo "LDFLAGS += `${PKG_CONFIG} --libs-only-L --libs-only-other libcap`" >> config.mk echo "#define HAS_LIBCAP" >> ${CONFIGFILE} fi fi fi if [ "$MAKEFILE" = "Makefile.linux" ] || [ "$MAKEFILE" = "Makefile.linux_nft" ] || [ "$MAKEFILE" = "Makefile.macosx" ] ; then DEFAULT_CONFIG=/etc/miniupnpd/miniupnpd.conf else DEFAULT_CONFIG=/etc/miniupnpd.conf fi if [ "$MAKEFILE" = "Makefile.bsd" ] || [ "$OS_NAME" = "Darwin" ] || [ "$OS_NAME" = "SunOS" ] ; then echo "FWNAME = $FW" > bsdmake.inc echo "SRCDIR = ${BASEDIR}" >> bsdmake.inc echo "CPPFLAGS += -I." >> bsdmake.inc if [ "$USE_LIBPFCTL" = "1" ] ; then echo "CPPFLAGS += -I/usr/local/include/" >> bsdmake.inc echo "LDFLAGS += -L/usr/local/lib" >> bsdmake.inc echo "LIBS += -lpfctl" >> bsdmake.inc fi fi if [ "$MAKEFILE" ] ; then cp "${BASEDIR}/${MAKEFILE}" Makefile && echo "${BASEDIR}/${MAKEFILE} -> Makefile" fi # UUID API case $OS_NAME in OpenWrt) echo "#define LIB_UUID" >> ${CONFIGFILE} ;; *) if grep uuid_create /usr/include/uuid.h > /dev/null 2>&1 ; then echo "#define BSD_UUID" >> ${CONFIGFILE} fi if grep uuid_generate /usr/include/uuid/uuid.h > /dev/null 2>&1 ; then echo "#define LIB_UUID" >> ${CONFIGFILE} fi ;; esac # set V6SOCKETS_ARE_V6ONLY to 0 if it was not set above if [ -z "$V6SOCKETS_ARE_V6ONLY" ] ; then V6SOCKETS_ARE_V6ONLY=0 fi echo "Configuring compilation for [$OS_NAME] [$OS_VERSION] [$OS_MACHINE] with [$FW] firewall software." echo "Please edit config.h for more compilation options." # define SUPPORT_REMOTEHOST if the FW related code really supports setting # a RemoteHost if [ \( "$FW" = "nftables" \) -o \( "$FW" = "iptables" \) -o \( "$FW" = "pf" \) -o \( "$FW" = "ipfw" \) ] ; then echo "#define SUPPORT_REMOTEHOST" >> ${CONFIGFILE} fi echo "/* Enable IGD2 \"Port Triggering\" as defined in Section 2.5.16" >> ${CONFIGFILE} echo " * figure 2.2 in UPnP-gw-WANIPConnection-v2-Service.pdf */" >> ${CONFIGFILE} echo "#define ENABLE_PORT_TRIGGERING" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} if [ $DYNAMIC_OS_VERSION -ne 0 ] ; then OS_VERSION="%s" echo "#define DYNAMIC_OS_VERSION 1" >> ${CONFIGFILE} fi echo "#define OS_NAME \"$OS_NAME\"" >> ${CONFIGFILE} echo "#define OS_VERSION \"$OS_NAME/$OS_VERSION\"" >> ${CONFIGFILE} echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* syslog facility to be used by miniupnpd */" >> ${CONFIGFILE} echo "#define LOG_MINIUPNPD ${LOG_MINIUPNPD}" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to allow miniupnpd to be" >> ${CONFIGFILE} echo " * controlled by miniupnpdctl */" >> ${CONFIGFILE} echo "/*#define USE_MINIUPNPDCTL*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Comment the following line to disable NAT-PMP operations */" >> ${CONFIGFILE} echo "#define ENABLE_NATPMP" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Comment the following line to disable PCP operations */" >> ${CONFIGFILE} echo "#define ENABLE_PCP" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "#ifdef ENABLE_PCP" >> ${CONFIGFILE} if [ -n "$PCP_PEER" ]; then echo "/* Comment the following line to disable PCP PEER operation */" >> ${CONFIGFILE} echo "#define PCP_PEER" >> ${CONFIGFILE} else echo "/* Uncomment the following line to enable PCP PEER operation */" >> ${CONFIGFILE} echo "/*#define PCP_PEER*/" >> ${CONFIGFILE} fi echo "#ifdef PCP_PEER" >> ${CONFIGFILE} echo "/*#define PCP_FLOWP*/" >> ${CONFIGFILE} echo "#endif /*PCP_PEER*/" >> ${CONFIGFILE} echo "/*#define PCP_SADSCP*/" >> ${CONFIGFILE} echo "#endif /*ENABLE_PCP*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable generation of" >> ${CONFIGFILE} echo " * filter rules with pf */" >> ${CONFIGFILE} echo "/*#define PF_ENABLE_FILTER_RULES*/">> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to set dst address in rdr rules with pf." >> ${CONFIGFILE} echo " * It is disabled by default because of" >> ${CONFIGFILE} echo " * https://github.com/miniupnp/miniupnp/issues/433 */" >> ${CONFIGFILE} echo "/*#define PF_SET_DST_ADDR*/">> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable caching of results of" >> ${CONFIGFILE} echo " * the getifstats() function */" >> ${CONFIGFILE} echo "/*#define ENABLE_GETIFSTATS_CACHING*/" >> ${CONFIGFILE} echo "/* The cache duration is indicated in seconds */" >> ${CONFIGFILE} echo "#define GETIFSTATS_CACHING_DURATION 2" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable multiple external ip support */" >> ${CONFIGFILE} echo "/* note : That is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */" >> ${CONFIGFILE} echo "/* Dynamic external ip adresses are not supported when this option is enabled." >> ${CONFIGFILE} echo " * Also note that you would need to configure your .conf file accordingly. */" >> ${CONFIGFILE} echo "/*#define MULTIPLE_EXTERNAL_IP*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Comment the following line to use home made daemonize() func instead" >> ${CONFIGFILE} echo " * of BSD daemon() */" >> ${CONFIGFILE} echo "#define USE_DAEMON" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable lease file support */" >> ${CONFIGFILE} if [ -n "$LEASEFILE" ] ; then echo "#define ENABLE_LEASEFILE" >> ${CONFIGFILE} else echo "/*#define ENABLE_LEASEFILE*/" >> ${CONFIGFILE} fi echo "/* Uncomment the following line to store remaining time in lease file */" >> ${CONFIGFILE} echo "/*#define LEASEFILE_USE_REMAINING_TIME*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable port in use check */" >> ${CONFIGFILE} if [ -n "$PORTINUSE" ]; then echo "#define CHECK_PORTINUSE" >> ${CONFIGFILE} else echo "/*#define CHECK_PORTINUSE*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable description regex filter */" >> ${CONFIGFILE} if [ -n "$REGEX" ]; then echo "#define ENABLE_REGEX" >> ${CONFIGFILE} else echo "/*#define ENABLE_REGEX*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} echo "/* Define one or none of the two following macros in order to make some" >> ${CONFIGFILE} echo " * clients happy. It will change the XML Root Description of the IGD." >> ${CONFIGFILE} echo " * Enabling the Layer3Forwarding Service seems to be the more compatible" >> ${CONFIGFILE} echo " * option. */" >> ${CONFIGFILE} echo "/*#define HAS_DUMMY_SERVICE*/" >> ${CONFIGFILE} echo "#define ENABLE_L3F_SERVICE" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* define ADVERTISE_WANPPPCONN to allow buggy Control Point to use" >> ${CONFIGFILE} echo " * WANPPPConnection instead of WANIPConnection. */" >> ${CONFIGFILE} if [ -n "$STRICT" ] || [ -n "$DISABLEPPPCONN" ] ; then echo "/*#define ADVERTISE_WANPPPCONN*/" >> ${CONFIGFILE} else echo "#define ADVERTISE_WANPPPCONN" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} echo "/* Enable IP v6 support */" >> ${CONFIGFILE} if [ -n "$IPV6" ]; then echo "#define ENABLE_IPV6" >> ${CONFIGFILE} else echo "/*#define ENABLE_IPV6*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} echo "/* Define V6SOCKETS_ARE_V6ONLY if AF_INET6 sockets are restricted" >> ${CONFIGFILE} echo " * to IPv6 communications only. */" >> ${CONFIGFILE} if [ $V6SOCKETS_ARE_V6ONLY -eq 1 ] ; then echo "#define V6SOCKETS_ARE_V6ONLY" >> ${CONFIGFILE} else echo "/*#define V6SOCKETS_ARE_V6ONLY*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} if [ -n "$HAVE_IP_MREQN" ]; then echo "#define HAVE_IP_MREQN" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} fi echo "/* Enable the support of IGD v2 specification." >> ${CONFIGFILE} echo " * This is not fully tested yet and can cause incompatibilities with some" >> ${CONFIGFILE} echo " * control points, so enable with care. */" >> ${CONFIGFILE} if [ -n "$IGD2" ]; then echo "#define IGD_V2" >> ${CONFIGFILE} else echo "/*#define IGD_V2*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} echo "#ifdef IGD_V2" >> ${CONFIGFILE} echo "/* Enable DeviceProtection service (IGDv2) - incomplete implementation */" >> ${CONFIGFILE} echo "/*#define ENABLE_DP_SERVICE*/" >> ${CONFIGFILE} echo "/*#define ENABLE_HTTPS*/" >> ${CONFIGFILE} echo "/*#define HTTPS_CERTFILE \"/path/to/certificate.pem\"*/" >> ${CONFIGFILE} echo "/*#define HTTPS_KEYFILE \"/path/to/private.key\"*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Enable WANIPv6FirewallControl service (IGDv2). needs IPv6 */" >> ${CONFIGFILE} echo "#ifdef ENABLE_IPV6" >> ${CONFIGFILE} echo "#define ENABLE_6FC_SERVICE" >> ${CONFIGFILE} echo "#endif /* ENABLE_IPV6 */" >> ${CONFIGFILE} echo "#endif /* IGD_V2 */" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* UPnP Events support. Working well enough to be enabled by default." >> ${CONFIGFILE} echo " * It can be disabled to save a few bytes. */" >> ${CONFIGFILE} echo "#define ENABLE_EVENTS" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* include interface name in pf and ipf rules */" >> ${CONFIGFILE} echo "#define USE_IFNAME_IN_RULES" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Experimental NFQUEUE support. */" >> ${CONFIGFILE} echo "/*#define ENABLE_NFQUEUE*/" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Enable to make MiniUPnPd more strict about UPnP conformance" >> ${CONFIGFILE} echo " * and the messages it receives from control points */" >> ${CONFIGFILE} if [ -n "$STRICT" ] ; then echo "#define UPNP_STRICT" >> ${CONFIGFILE} else echo "/*#define UPNP_STRICT*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} echo "/* If SSDP_RESPOND_SAME_VERSION is defined, the M-SEARCH response" >> ${CONFIGFILE} echo " * include the same device version as was contained in the search" >> ${CONFIGFILE} echo " * request. It conforms to UPnP DA v1.1 */" >> ${CONFIGFILE} echo "#define SSDP_RESPOND_SAME_VERSION" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Add the optional Date: header in all HTTP responses */" >> ${CONFIGFILE} if [ -n "$STRICT" ] ; then echo "#define ENABLE_HTTP_DATE" >> ${CONFIGFILE} else echo "/*#define ENABLE_HTTP_DATE*/" >> ${CONFIGFILE} 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} echo "#define DEFAULT_CONFIG \"${DEFAULT_CONFIG}\"" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE} echo "/* Uncomment the following line to configure all manufacturer infos through miniupnpd.conf */" >> ${CONFIGFILE} if [ -n "$VENDORCFG" ] ; then echo "#define ENABLE_MANUFACTURER_INFO_CONFIGURATION" >> ${CONFIGFILE} else echo "/*#define ENABLE_MANUFACTURER_INFO_CONFIGURATION*/" >> ${CONFIGFILE} fi echo "" >> ${CONFIGFILE} cat >> ${CONFIGFILE} <> ${CONFIGFILE} <> ${CONFIGFILE} <> ${CONFIGFILE} if [ -n "$NO_BACKGROUND_NO_PIDFILE" ] && [ $NO_BACKGROUND_NO_PIDFILE -eq 1 ] ; then echo "#define NO_BACKGROUND_NO_PIDFILE" >> ${CONFIGFILE} else echo "/*#define NO_BACKGROUND_NO_PIDFILE*/" >> ${CONFIGFILE} fi echo "/* Whether to use getifaddrs() to determine interface addresses */" >> ${CONFIGFILE} if [ -n "$GETIFADDRS" ] && [ $GETIFADDRS -eq 1 ] ; then echo "#define USE_GETIFADDRS" >> ${CONFIGFILE} else echo "/*#define USE_GETIFADDRS*/" >> ${CONFIGFILE} fi echo "#endif /* ${CONFIGMACRO} */" >> ${CONFIGFILE} ${MV} ${CONFIGFILE} ${CONFIGFILE_FINAL} exit 0 miniupnpd-2.3.7/options.h010064400017500000024000000061061463564776600146070ustar00nanardstaff/* $Id: options.h,v 1.36 2024/06/22 18:13:33 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * author: Ryan Wagoner * (c) 2006-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef OPTIONS_H_INCLUDED #define OPTIONS_H_INCLUDED #include "config.h" #ifndef DISABLE_CONFIG_FILE /* enum of option available in the miniupnpd.conf */ enum upnpconfigoptions { UPNP_INVALID = 0, UPNPEXT_IFNAME = 1, /* ext_ifname */ #ifdef ENABLE_IPV6 UPNPEXT_IFNAME6, /* ext_ifname6 */ #endif 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 */ UPNPIPV6_DISABLE, /* ipv6_disable */ #endif /* ENABLE_IPV6 */ UPNPPORT, /* "port" / "http_port" */ #ifdef ENABLE_HTTPS UPNPHTTPSPORT, /* "https_port" */ #endif UPNPBITRATE_UP, /* "bitrate_up" */ UPNPBITRATE_DOWN, /* "bitrate_down" */ UPNPPRESENTATIONURL, /* presentation_url */ #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION UPNPFRIENDLY_NAME, /* "friendly_name" */ UPNPMANUFACTURER_NAME, /* "manufacturer_name" */ UPNPMANUFACTURER_URL, /* "manufacturer_url" */ UPNPMODEL_NAME, /* "model_name" */ UPNPMODEL_DESCRIPTION, /* "model_description" */ UPNPMODEL_URL, /* "model_url" */ #endif UPNPNOTIFY_INTERVAL, /* notify_interval */ UPNPSYSTEM_UPTIME, /* "system_uptime" */ UPNPPACKET_LOG, /* "packet_log" */ UPNPUUID, /* uuid */ UPNPSERIAL, /* serial */ UPNPMODEL_NUMBER, /* model_number */ UPNPCLEANTHRESHOLD, /* clean_ruleset_threshold */ UPNPCLEANINTERVAL, /* clean_ruleset_interval */ UPNPENABLENATPMP, /* enable_pcp_pmp */ UPNPPCPMINLIFETIME, /* minimum lifetime for PCP mapping */ UPNPPCPMAXLIFETIME, /* maximum lifetime for PCP mapping */ UPNPPCPALLOWTHIRDPARTY, /* allow third-party requests */ #ifdef USE_NETFILTER UPNPTABLENAME, UPNPNATTABLENAME, UPNPFORWARDCHAIN, UPNPNATCHAIN, UPNPNATPOSTCHAIN, UPNPNFFAMILYSPLIT, #endif #ifdef USE_PF UPNPANCHOR, /* anchor */ UPNPQUEUE, /* queue */ UPNPTAG, /* tag */ #endif #ifdef PF_ENABLE_FILTER_RULES UPNPQUICKRULES, /* quickrules */ #endif UPNPSECUREMODE, /* secure_mode */ #ifdef ENABLE_LEASEFILE UPNPLEASEFILE, /* lease_file */ #ifdef ENABLE_UPNPPINHOLE UPNPLEASEFILE6, /* lease_file v6 */ #endif #endif UPNPMINISSDPDSOCKET, /* minissdpdsocket */ #ifdef IGD_V2 UPNPFORCEIGDDESCV1, #endif UPNPENABLE /* enable_upnp */ }; /* readoptionsfile() * parse and store the option file values * returns: 0 success, -1 failure */ int readoptionsfile(const char * fname, int debug_flag); /* freeoptions() * frees memory allocated to option values */ void freeoptions(void); struct option { enum upnpconfigoptions id; const char * value; }; extern struct option * ary_options; extern unsigned int num_options; #endif /* DISABLE_CONFIG_FILE */ #endif /* OPTIONS_H_INCLUDED */ miniupnpd-2.3.7/options.c010064400017500000024000000204531457641723400145700ustar00nanardstaff/* $Id: options.c,v 1.45 2024/03/11 23:17:56 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * author: Ryan Wagoner * (c) 2006-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include "config.h" #include "options.h" #include "upnppermissions.h" #ifdef PCP_SADSCP #include "pcplearndscp.h" #endif /* PCP_SADSPC */ #include "upnpglobalvars.h" #include "macros.h" #ifndef DISABLE_CONFIG_FILE struct option * ary_options = NULL; static char * string_repo = NULL; unsigned int num_options = 0; static const struct { enum upnpconfigoptions id; const char * name; } optionids[] = { { UPNPEXT_IFNAME, "ext_ifname" }, #ifdef ENABLE_IPV6 { UPNPEXT_IFNAME6, "ext_ifname6" }, #endif { 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" }, { UPNPIPV6_DISABLE, "ipv6_disable" }, #endif /* ENABLE_IPV6 */ { UPNPPORT, "port" }, { UPNPPORT, "http_port" }, /* "port" and "http_port" are synonims */ #ifdef ENABLE_HTTPS { UPNPHTTPSPORT, "https_port" }, #endif /* ENABLE_HTTPS */ { UPNPBITRATE_UP, "bitrate_up" }, { UPNPBITRATE_DOWN, "bitrate_down" }, { UPNPPRESENTATIONURL, "presentation_url" }, #ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION { UPNPFRIENDLY_NAME, "friendly_name" }, { UPNPMANUFACTURER_NAME, "manufacturer_name" }, { UPNPMANUFACTURER_URL, "manufacturer_url" }, { UPNPMODEL_NAME, "model_name" }, { UPNPMODEL_DESCRIPTION, "model_description" }, { UPNPMODEL_URL, "model_url" }, #endif { UPNPNOTIFY_INTERVAL, "notify_interval" }, { UPNPSYSTEM_UPTIME, "system_uptime" }, { UPNPPACKET_LOG, "packet_log" }, { UPNPUUID, "uuid"}, { UPNPSERIAL, "serial"}, { UPNPMODEL_NUMBER, "model_number"}, { UPNPCLEANTHRESHOLD, "clean_ruleset_threshold"}, { UPNPCLEANINTERVAL, "clean_ruleset_interval"}, #ifdef USE_NETFILTER { UPNPTABLENAME, "upnp_table_name"}, { UPNPNATTABLENAME, "upnp_nat_table_name"}, { UPNPFORWARDCHAIN, "upnp_forward_chain"}, { UPNPNATCHAIN, "upnp_nat_chain"}, { UPNPNATPOSTCHAIN, "upnp_nat_postrouting_chain"}, { UPNPNFFAMILYSPLIT, "upnp_nftables_family_split"}, #endif #ifdef ENABLE_NATPMP /* both NAT-PMP and PCP (when PCP is enabled at compile time) */ { UPNPENABLENATPMP, "enable_natpmp"}, { UPNPENABLENATPMP, "enable_pcp_pmp"}, #endif #ifdef ENABLE_PCP { UPNPPCPMINLIFETIME, "min_lifetime"}, { UPNPPCPMAXLIFETIME, "max_lifetime"}, { UPNPPCPALLOWTHIRDPARTY, "pcp_allow_thirdparty"}, #endif { UPNPENABLE, "enable_upnp"}, #ifdef USE_PF { UPNPANCHOR, "anchor"}, { UPNPQUEUE, "queue"}, { UPNPTAG, "tag"}, #endif #ifdef PF_ENABLE_FILTER_RULES { UPNPQUICKRULES, "quickrules" }, #endif #ifdef ENABLE_LEASEFILE { UPNPLEASEFILE, "lease_file"}, #ifdef ENABLE_UPNPPINHOLE { UPNPLEASEFILE6, "lease_file6"}, #endif #endif #ifdef IGD_V2 { UPNPFORCEIGDDESCV1, "force_igd_desc_v1"}, #endif { UPNPMINISSDPDSOCKET, "minissdpdsocket"}, { UPNPSECUREMODE, "secure_mode"} }; int readoptionsfile(const char * fname, int debug_flag) { FILE *hfile = NULL; char buffer[1024]; char *equals; char *name; char *value; char *t; int linenum = 0; unsigned int i; enum upnpconfigoptions id; size_t string_repo_len = 0; size_t len; void *tmp; if(!fname || (fname[0] == '\0')) return -1; memset(buffer, 0, sizeof(buffer)); #ifdef DEBUG printf("Reading configuration from file %s\n", fname); #endif if(!(hfile = fopen(fname, "r"))) return -1; if(ary_options != NULL) { free(ary_options); num_options = 0; } while(fgets(buffer, sizeof(buffer), hfile)) { linenum++; t = strchr(buffer, '\n'); if(t) { *t = '\0'; t--; /* remove spaces at the end of the line */ while((t >= buffer) && isspace(*t)) { *t = '\0'; t--; } } /* skip leading whitespaces */ name = buffer; while(isspace(*name)) name++; /* check for comments or empty lines */ if(name[0] == '#' || name[0] == '\0') continue; len = strlen(name); /* length of the whole line excluding leading * and ending white spaces */ /* check for UPnP permissions rule */ if((len > 6) && (0 == memcmp(name, "allow", 5) || 0 == memcmp(name, "deny", 4))) { tmp = realloc(upnppermlist, sizeof(struct upnpperm) * (num_upnpperm+1)); if(tmp == NULL) { INIT_PRINT_ERR("memory allocation error. Permission line in file %s line %d\n", fname, linenum); return -1; } else { upnppermlist = tmp; /* parse the rule */ if(read_permission_line(upnppermlist + num_upnpperm, name) >= 0) { num_upnpperm++; } else { INIT_PRINT_ERR("parsing error file %s line %d : %s\n", fname, linenum, name); return -1; } } continue; } #ifdef PCP_SADSCP /* check for DSCP values configuration */ if((len > 15) && 0 == memcmp(name, "set_learn_dscp", sizeof("set_learn_dscp")-1) ) { tmp = realloc(dscp_values_list, sizeof(struct dscp_values) * (num_dscp_values+1)); if(tmp == NULL) { INIT_PRINT_ERR("memory allocation error. DSCP line in file %s line %d\n", fname, linenum); return -1; } else { dscp_values_list = tmp; /* parse the rule */ if(read_learn_dscp_line(dscp_values_list + num_dscp_values, name, debug_flag) >= 0) { num_dscp_values++; } else { INIT_PRINT_ERR("parsing error file %s line %d : %s\n", fname, linenum, name); return -1; } } continue; } #endif /* PCP_SADSCP */ if(!(equals = strchr(name, '='))) { INIT_PRINT_ERR("parsing error file %s line %d : %s\n", fname, linenum, name); return -1; } /* remove ending whitespaces */ for(t=equals-1; t>name && isspace(*t); t--) *t = '\0'; *equals = '\0'; value = equals+1; /* skip leading whitespaces */ while(isspace(*value)) value++; id = UPNP_INVALID; for(i=0; i- or if there is only # one port in the range. # IP/mask format must be nnn.nnn.nnn.nnn/nn # Regex support must be enabled at build time : ./configure --regex # It is advised to only allow redirection of ports >= 1024 # and end the rule set with "deny 0-65535 0.0.0.0/0 0-65535" # The following default ruleset allows specific LAN side IP addresses # to request only ephemeral ports. It is recommended that users # modify the IP ranges to match their own internal networks, and # also consider implementing network-specific restrictions # CAUTION: failure to enforce any rules may permit insecure requests to be made! allow 1024-65535 192.168.0.0/24 1024-65535 # disallow requests whose description string matches the given regex # deny 1024-65535 192.168.1.0/24 1024-65535 "My evil app ver [[:digit:]]*" allow 1024-65535 192.168.1.0/24 1024-65535 allow 1024-65535 192.168.0.0/23 22 allow 12345 192.168.7.113/32 54321 deny 0-65535 0.0.0.0/0 0-65535 miniupnpd-2.3.7/upnppermissions.h010064400017500000024000000041451437357067200163610ustar00nanardstaff/* $Id: upnppermissions.h,v 1.14 2023/02/11 23:02:17 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2023 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPPERMISSIONS_H_INCLUDED #define UPNPPERMISSIONS_H_INCLUDED #include #include #include #include "config.h" #ifdef ENABLE_REGEX #include #endif /* UPnP permission rule samples: * allow 1024-65535 192.168.3.0/24 1024-65535 * deny 0-65535 192.168.1.125/32 0-65535 */ struct upnpperm { enum {UPNPPERM_ALLOW=1, UPNPPERM_DENY=2 } type; /* is it an allow or deny permission rule ? */ u_short eport_min, eport_max; /* external port range */ struct in_addr address, mask; /* ip/mask */ u_short iport_min, iport_max; /* internal port range */ char * re; #ifdef ENABLE_REGEX regex_t regex; /* matching regex */ #endif }; /* read_permission_line() * returns: 0 line read okay * -1 error reading line * * line sample : * allow 1024-65535 192.168.3.0/24 1024-65535 * allow 22 192.168.4.33/32 22 * deny 0-65535 0.0.0.0/0 0-65535 */ int read_permission_line(struct upnpperm * perm, char * p); void free_permission_line(struct upnpperm * perm); /* check_upnp_rule_against_permissions() * returns: 0 if the upnp rule should be rejected, * 1 if it could be accepted */ int check_upnp_rule_against_permissions(const struct upnpperm * permary, int n_perms, u_short eport, struct in_addr address, u_short iport, const char * desc); /** * Build an array of all allowed external ports (for the address and internal port) */ void get_permitted_ext_ports(uint32_t * allowed, const struct upnpperm * permary, int n_perms, in_addr_t addr, u_short iport); #ifdef USE_MINIUPNPDCTL void write_permlist(int fd, const struct upnpperm * permary, int nperms); #endif #endif miniupnpd-2.3.7/upnppermissions.c010064400017500000024000000256771455107677400163730ustar00nanardstaff/* $Id: upnppermissions.c,v 1.23 2023/06/11 23:08:09 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2023 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 #ifdef ENABLE_REGEX #include #endif #include "config.h" #include "macros.h" #include "upnppermissions.h" static int isodigit(char c) { return '0' <= c && c >= '7'; } static char hex2chr(char c) { if(c >= 'a') return c - 'a'; if(c >= 'A') return c - 'A'; return c - '0'; } static char unescape_char(const char * s, int * seqlen) { char c; int len; if(s[0] != '\\') { c = s[0]; len = 1; } else { s++; c = s[0]; len = 2; switch(s[0]) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; /* no need: escape the char itself case '\\': c = '\\'; break; case '\'': c = '\''; break; case '"': c = '"'; break; case '?': c = '?'; break; */ case 'x': if(isxdigit(s[1]) && isxdigit(s[2])) { c = (hex2chr(s[1]) << 4) + hex2chr(s[2]); len = 4; } break; default: if(isodigit(s[1]) && isodigit(s[2]) && isodigit(s[3])) { c = (hex2chr(s[0]) << 6) + (hex2chr(s[1]) << 3) + hex2chr(s[2]); len = 4; } } } if(seqlen) *seqlen = len; return c; } /* get_next_token(s, &token, raw) * put the unquoted/unescaped token in token and returns * a pointer to the begining of the next token * Do not unescape if raw is true */ static char * get_next_token(const char * s, char ** token, int raw) { char deli; const char * end; /* skip any whitespace */ for(; isspace(*s); s++) if(*s == '\0' || *s == '\n') { if(token) *token = NULL; return (char *) s; } /* find the start */ if(*s == '"' || *s == '\'') { deli = *s; s++; } else deli = 0; /* find the end */ end = s; for(; *end != '\0' && *end != '\n' && (deli ? *end != deli : !isspace(*end)); end++) if(*end == '\\') { end++; if(*end == '\0') break; } /* save the token */ if(token) { unsigned int token_len; unsigned int i; token_len = end - s; *token = strndup(s, token_len); if(!*token) return NULL; for(i = 0; (*token)[i] != '\0'; i++) { int sequence_len; if((*token)[i] != '\\') continue; if(raw && deli && (*token)[i + 1] != deli) continue; (*token)[i] = unescape_char(*token + i, &sequence_len); memmove(*token + i + 1, *token + i + sequence_len, token_len - i - sequence_len); } if (i == 0) { /* behavior of realloc(p, 0) is implementation-defined, so better set it to NULL. * https://github.com/miniupnp/miniupnp/issues/652#issuecomment-1518922139 */ free(*token); *token = NULL; } else { char * tmp = realloc(*token, i); if (tmp != NULL) *token = tmp; else syslog(LOG_ERR, "%s: failed to reallocate to %u bytes", "get_next_token()", i); } } /* return the beginning of the next token */ if(deli && *end == deli) end++; while(isspace(*end)) end++; return (char *) end; } /* read_permission_line() * parse the a permission line which format is : * (deny|allow) [0-9]+(-[0-9]+) ip/mask [0-9]+(-[0-9]+) regex * ip/mask is either 192.168.1.1/24 or 192.168.1.1/255.255.255.0 */ int read_permission_line(struct upnpperm * perm, char * p) { char * q; int n_bits; int i; /* zero memory : see https://github.com/miniupnp/miniupnp/issues/652 */ memset(perm, 0, sizeof(struct upnpperm)); /* first token: (allow|deny) */ while(isspace(*p)) p++; if(0 == memcmp(p, "allow", 5)) { perm->type = UPNPPERM_ALLOW; p += 5; } else if(0 == memcmp(p, "deny", 4)) { perm->type = UPNPPERM_DENY; p += 4; } else { return -1; } while(isspace(*p)) p++; /* second token: eport or eport_min-eport_max */ if(!isdigit(*p)) return -1; for(q = p; isdigit(*q); q++); if(*q=='-') { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->eport_min = (u_short)i; q++; p = q; while(isdigit(*q)) q++; *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->eport_max = (u_short)i; if(perm->eport_min > perm->eport_max) return -1; } else if(isspace(*q)) { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->eport_min = perm->eport_max = (u_short)i; } else { return -1; } p = q + 1; while(isspace(*p)) p++; /* third token: ip/mask */ if(!isdigit(*p)) return -1; for(q = p; isdigit(*q) || (*q == '.'); q++); if(*q=='/') { *q = '\0'; if(!inet_aton(p, &perm->address)) return -1; q++; p = q; while(isdigit(*q)) q++; if(*q == '.') { while(*q == '.' || isdigit(*q)) q++; if(!isspace(*q)) return -1; *q = '\0'; if(!inet_aton(p, &perm->mask)) return -1; } else if(!isspace(*q)) return -1; else { *q = '\0'; n_bits = atoi(p); if(n_bits > 32) return -1; perm->mask.s_addr = htonl(n_bits ? (0xffffffffu << (32 - n_bits)) : 0); } } else if(isspace(*q)) { *q = '\0'; if(!inet_aton(p, &perm->address)) return -1; perm->mask.s_addr = 0xffffffffu; } else { return -1; } p = q + 1; /* fourth token: iport or iport_min-iport_max */ while(isspace(*p)) p++; if(!isdigit(*p)) return -1; for(q = p; isdigit(*q); q++); if(*q=='-') { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->iport_min = (u_short)i; q++; p = q; while(isdigit(*q)) q++; *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->iport_max = (u_short)i; if(perm->iport_min > perm->iport_max) return -1; } else if(isspace(*q) || *q == '\0') { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->iport_min = perm->iport_max = (u_short)i; } else { return -1; } p = q; /* fifth token: (optional) regex */ p = get_next_token(p, &perm->re, 1); if(!p) { fprintf(stderr, "err when copying regex: out of memory\n"); return -1; } if(perm->re) { if(perm->re[0] == '\0') { free(perm->re); perm->re = NULL; } else { #ifdef ENABLE_REGEX /* icase: if case matters, it must be someone doing something nasty */ int err; err = regcomp(&perm->regex, perm->re, REG_EXTENDED | REG_ICASE | REG_NOSUB); if(err) { char errbuf[256]; regerror(err, &perm->regex, errbuf, sizeof(errbuf)); fprintf(stderr, "err when compiling regex \"%s\": %s\n", perm->re, errbuf); free(perm->re); perm->re = NULL; return -1; } #else fprintf(stderr, "MiniUPnP is not compiled with ENABLE_REGEX. " "Please remove any regex filter and restart.\n"); free(perm->re); perm->re = NULL; return -1; #endif } } #ifdef DEBUG printf("perm rule added : %s %hu-%hu %08x/%08x %hu-%hu %s\n", (perm->type==UPNPPERM_ALLOW) ? "allow" : "deny", perm->eport_min, perm->eport_max, ntohl(perm->address.s_addr), ntohl(perm->mask.s_addr), perm->iport_min, perm->iport_max, perm->re ? perm->re : ""); #endif return 0; } void free_permission_line(struct upnpperm * perm) { if(perm->re) { free(perm->re); perm->re = NULL; #ifdef ENABLE_REGEX regfree(&perm->regex); #endif } } #ifdef USE_MINIUPNPDCTL void write_permlist(int fd, const struct upnpperm * permary, int nperms) { int l; const struct upnpperm * perm; int i; char buf[128]; write(fd, "Permissions :\n", 14); for(i = 0; itype==UPNPPERM_ALLOW)?"allow":"deny", perm->eport_min, perm->eport_max, ntohl(perm->address.s_addr), ntohl(perm->mask.s_addr), perm->iport_min, perm->iport_max); if(l<0) return; write(fd, buf, l); if(perm->re) { write(fd, " ", 1); write(fd, perm->re, strlen(perm->re)); } write(fd, "\n", 1); } } #endif /* match_permission() * returns: 1 if eport, address, iport matches the permission rule * 0 if no match */ static int match_permission(const struct upnpperm * perm, u_short eport, struct in_addr address, u_short iport, const char * desc) { if( (eport < perm->eport_min) || (perm->eport_max < eport)) return 0; if( (iport < perm->iport_min) || (perm->iport_max < iport)) return 0; if( (address.s_addr & perm->mask.s_addr) != (perm->address.s_addr & perm->mask.s_addr) ) return 0; #ifdef ENABLE_REGEX if(desc && perm->re && regexec(&perm->regex, desc, 0, NULL, 0) == REG_NOMATCH) return 0; #else UNUSED(desc); #endif return 1; } #if 0 /* match_permission_internal() * returns: 1 if address, iport matches the permission rule * 0 if no match */ static int match_permission_internal(const struct upnpperm * perm, struct in_addr address, u_short iport) { if( (iport < perm->iport_min) || (perm->iport_max < iport)) return 0; if( (address.s_addr & perm->mask.s_addr) != (perm->address.s_addr & perm->mask.s_addr) ) return 0; return 1; } #endif int check_upnp_rule_against_permissions(const struct upnpperm * permary, int n_perms, u_short eport, struct in_addr address, u_short iport, const char * desc) { int i; for(i=0; i= 0; i--) { if( (addr & permary[i].mask.s_addr) != (permary[i].address.s_addr & permary[i].mask.s_addr) ) continue; if( (iport < permary[i].iport_min) || (permary[i].iport_max < iport)) continue; for (j = (int)permary[i].eport_min ; j <= (int)permary[i].eport_max; ) { if ((j % 32) == 0 && ((int)permary[i].eport_max >= (j + 31))) { /* 32bits at once */ allowed[j / 32] = (permary[i].type == UPNPPERM_ALLOW) ? 0xffffffff : 0; j += 32; } else { do { /* one bit at once */ if (permary[i].type == UPNPPERM_ALLOW) allowed[j / 32] |= (1 << (j % 32)); else allowed[j / 32] &= ~(1 << (j % 32)); j++; } while ((j % 32) != 0 && (j <= (int)permary[i].eport_max)); } } } } miniupnpd-2.3.7/testupnppermissions.c010064400017500000024000000026421261345656100172470ustar00nanardstaff/* $Id: testupnppermissions.c,v 1.4 2015/10/26 16:53:26 nanard Exp $ */ /* (c) 2007-2015 Thomas Bernard * MiniUPnP Project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ */ #include #include #include #include #include #include #include #include "upnppermissions.h" void print_upnpperm(const struct upnpperm * p) { switch(p->type) { case UPNPPERM_ALLOW: printf("allow "); break; case UPNPPERM_DENY: printf("deny "); break; default: printf("error ! "); } printf("%hu-%hu ", p->eport_min, p->eport_max); printf("%s/", inet_ntoa(p->address)); printf("%s ", inet_ntoa(p->mask)); printf("%hu-%hu", p->iport_min, p->iport_max); putchar('\n'); } int main(int argc, char * * argv) { int i, r, ret; struct upnpperm p; if(argc < 2) { fprintf(stderr, "Usage: %s \"permission line\" [...]\n", argv[0]); fprintf(stderr, "Example: %s \"allow 1234 10.10.10.10/32 1234\"\n", argv[0]); return 1; } openlog("testupnppermissions", LOG_PERROR, LOG_USER); ret = 0; for(i=1; i #include #include #include #include #include #include #include #include #include #ifdef IP_RECVIF #include #include #include #include #endif #include "config.h" #if defined(ENABLE_IPV6) && defined(UPNP_STRICT) #include #endif /* defined(ENABLE_IPV6) && defined(UPNP_STRICT) */ #include "upnpdescstrings.h" #include "miniupnpdpath.h" #include "upnphttp.h" #include "upnpglobalvars.h" #include "minissdp.h" #include "upnputils.h" #include "getroute.h" #include "asyncsendto.h" #include "codelength.h" #include "macros.h" #ifndef MIN #define MIN(x,y) (((x)<(y))?(x):(y)) #endif /* MIN */ /* SSDP ip/port */ #define SSDP_PORT (1900) #define SSDP_MCAST_ADDR ("239.255.255.250") #define LL_SSDP_MCAST_ADDR "FF02::C" #define SL_SSDP_MCAST_ADDR "FF05::C" #define GL_SSDP_MCAST_ADDR "FF0E::C" /* AddMulticastMembership() * param s socket * param lan_addr lan address */ static int AddMulticastMembership(int s, struct lan_addr_s * lan_addr) { #ifndef HAVE_IP_MREQN /* The ip_mreqn structure appeared in Linux 2.4. */ struct ip_mreq imr; /* Ip multicast membership */ #else /* HAVE_IP_MREQN */ struct ip_mreqn imr; /* Ip multicast membership */ #endif /* HAVE_IP_MREQN */ /* setting up imr structure */ imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/ #ifndef HAVE_IP_MREQN imr.imr_interface.s_addr = lan_addr->addr.s_addr; #else /* HAVE_IP_MREQN */ imr.imr_address.s_addr = lan_addr->addr.s_addr; #ifndef MULTIPLE_EXTERNAL_IP #ifdef ENABLE_IPV6 imr.imr_ifindex = lan_addr->index; #else /* ENABLE_IPV6 */ imr.imr_ifindex = if_nametoindex(lan_addr->ifname); #endif /* ENABLE_IPV6 */ #else /* MULTIPLE_EXTERNAL_IP */ imr.imr_ifindex = 0; #endif /* MULTIPLE_EXTERNAL_IP */ #endif /* HAVE_IP_MREQN */ if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(imr)) < 0) { syslog(LOG_ERR, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m"); return -1; } return 0; } /* AddMulticastMembershipIPv6() * param s socket (IPv6) * param ifindex : interface index (0 : All interfaces) */ #ifdef ENABLE_IPV6 static int AddMulticastMembershipIPv6(int s, unsigned int ifindex) { struct ipv6_mreq mr; memset(&mr, 0, sizeof(mr)); mr.ipv6mr_interface = ifindex; /* 0 : all interfaces */ #ifndef IPV6_ADD_MEMBERSHIP #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP #endif inet_pton(AF_INET6, LL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr); if(setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof(struct ipv6_mreq)) < 0) { syslog(LOG_ERR, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m"); return -1; } inet_pton(AF_INET6, SL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr); if(setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof(struct ipv6_mreq)) < 0) { syslog(LOG_ERR, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m"); return -1; } inet_pton(AF_INET6, GL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr); if(setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof(struct ipv6_mreq)) < 0) { syslog(LOG_ERR, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m"); return -1; } return 0; } #endif #if defined(ENABLE_IPV6) && defined(UPNP_STRICT) static int get_link_local_addr(unsigned scope_id, struct in6_addr * addr6) { struct ifaddrs * ifap; struct ifaddrs * ife; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife != NULL; ife = ife->ifa_next) { if(ife->ifa_addr == NULL) continue; if(ife->ifa_addr->sa_family != AF_INET6) continue; if(!IN6_IS_ADDR_LINKLOCAL(&(((const struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr))) continue; if(scope_id != if_nametoindex(ife->ifa_name)) continue; memcpy(addr6, &(((const struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr), sizeof(struct in6_addr)); break; } freeifaddrs(ifap); return 0; } #endif /* defined(ENABLE_IPV6) && defined(UPNP_STRICT) */ /* Open and configure the socket listening for * SSDP udp packets sent on 239.255.255.250 port 1900 * SSDP v6 udp packets sent on FF02::C, or FF05::C, port 1900 */ int OpenAndConfSSDPReceiveSocket(int ipv6) { int s; struct sockaddr_storage sockname; socklen_t sockname_len; struct lan_addr_s * lan_addr; const int on = 1; if( (s = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "%s: socket(udp): %m", "OpenAndConfSSDPReceiveSocket"); return -1; } memset(&sockname, 0, sizeof(struct sockaddr_storage)); #ifdef ENABLE_IPV6 if(ipv6) { struct sockaddr_in6 * saddr = (struct sockaddr_in6 *)&sockname; saddr->sin6_family = AF_INET6; saddr->sin6_port = htons(SSDP_PORT); saddr->sin6_addr = ipv6_bind_addr; sockname_len = sizeof(struct sockaddr_in6); } else #endif /* ENABLE_IPV6 */ { struct sockaddr_in * saddr = (struct sockaddr_in *)&sockname; saddr->sin_family = AF_INET; saddr->sin_port = htons(SSDP_PORT); /* NOTE : it seems it doesn't work when binding on the specific address */ /*saddr->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/ saddr->sin_addr.s_addr = htonl(INADDR_ANY); /*saddr->sin_addr.s_addr = inet_addr(ifaddr);*/ sockname_len = sizeof(struct sockaddr_in); } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEADDR): %m"); } if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEPORT): %m"); } #ifdef IP_RECVIF /* BSD */ if(!ipv6) { if(setsockopt(s, IPPROTO_IP, IP_RECVIF, &on, sizeof(on)) < 0) { syslog(LOG_WARNING, "setsockopt(udp, IP_RECVIF): %m"); } } #elif defined(IP_PKTINFO) /* IP_RECVIF */ /* Linux */ if(!ipv6) { if(setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)) < 0) { syslog(LOG_WARNING, "setsockopt(udp, IP_PKTINFO): %m"); } } #endif /* IP_PKTINFO */ #if defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO) if(ipv6) { if(setsockopt(s, IPPROTO_IP, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) { syslog(LOG_WARNING, "setsockopt(udp, IPV6_RECVPKTINFO): %m"); } } #endif /* defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO) */ if(!set_non_blocking(s)) { syslog(LOG_WARNING, "%s: set_non_blocking(): %m", "OpenAndConfSSDPReceiveSocket"); } #if defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP) /* One and only one LAN interface */ if(lan_addrs.lh_first != NULL && lan_addrs.lh_first->list.le_next == NULL && lan_addrs.lh_first->ifname[0] != '\0') { if(setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, lan_addrs.lh_first->ifname, strlen(lan_addrs.lh_first->ifname)) < 0) syslog(LOG_WARNING, "%s: setsockopt(udp%s, SO_BINDTODEVICE, %s): %m", "OpenAndConfSSDPReceiveSocket", ipv6 ? "6" : "", lan_addrs.lh_first->ifname); } #endif /* defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP) */ if(bind(s, (struct sockaddr *)&sockname, sockname_len) < 0) { syslog(LOG_ERR, "%s: bind(udp%s): %m", "OpenAndConfSSDPReceiveSocket", ipv6 ? "6" : ""); close(s); return -1; } #ifdef ENABLE_IPV6 if(ipv6) { for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if(AddMulticastMembershipIPv6(s, lan_addr->index) < 0) { syslog(LOG_WARNING, "Failed to add IPv6 multicast membership for interface %s", strlen(lan_addr->str) ? lan_addr->str : "NULL"); } } } else #endif { for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if(AddMulticastMembership(s, lan_addr) < 0) { syslog(LOG_WARNING, "Failed to add multicast membership for interface %s", strlen(lan_addr->str) ? lan_addr->str : "NULL"); } } } return s; } /* open the UDP socket used to send SSDP notifications to * the multicast group reserved for them */ static int OpenAndConfSSDPNotifySocket(struct lan_addr_s * lan_addr) { int s; unsigned char loopchar = 0; int bcast = 1; unsigned char ttl = 2; /* UDA v1.1 says : The TTL for the IP packet SHOULD default to 2 and SHOULD be configurable. */ /* TODO: Make TTL be configurable */ #ifndef HAVE_IP_MREQN struct in_addr mc_if; #else /* HAVE_IP_MREQN */ struct ip_mreqn mc_if; #endif /* HAVE_IP_MREQN */ struct sockaddr_in sockname; if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "socket(udp_notify): %m"); return -1; } #ifndef HAVE_IP_MREQN mc_if.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/ #else /* HAVE_IP_MREQN */ mc_if.imr_address.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/ #ifdef ENABLE_IPV6 mc_if.imr_ifindex = lan_addr->index; #else /* ENABLE_IPV6 */ mc_if.imr_ifindex = if_nametoindex(lan_addr->ifname); #endif /* ENABLE_IPV6 */ #endif /* HAVE_IP_MREQN */ if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0) { syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %m"); close(s); return -1; } if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0) { syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_IF): %m"); close(s); return -1; } if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) { syslog(LOG_WARNING, "setsockopt(udp_notify, IP_MULTICAST_TTL,): %m"); } if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0) { syslog(LOG_ERR, "setsockopt(udp_notify, SO_BROADCAST): %m"); close(s); return -1; } /* bind() socket before using sendto() is not mandatory * (sendto() will implicitly bind the socket when called on * an unbound socket) * here it is used to se a specific sending address */ memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_addr.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/ if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) { syslog(LOG_ERR, "bind(udp_notify): %m"); close(s); return -1; } return s; } #ifdef ENABLE_IPV6 /* open the UDP socket used to send SSDP notifications to * the multicast group reserved for them. IPv6 */ static int OpenAndConfSSDPNotifySocketIPv6(struct lan_addr_s * lan_addr) { int s; unsigned int loop = 0; /* UDA 2.0 : The hop limit of each IP packet for a Site-Local scope * multicast message SHALL be configurable and SHOULD default to 10 */ int hop_limit = 10; struct sockaddr_in6 sockname; s = socket(PF_INET6, SOCK_DGRAM, 0); if(s < 0) { syslog(LOG_ERR, "socket(udp_notify IPv6): %m"); return -1; } if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &lan_addr->index, sizeof(lan_addr->index)) < 0) { syslog(LOG_ERR, "setsockopt(udp_notify IPv6, IPV6_MULTICAST_IF, %u): %m", lan_addr->index); close(s); return -1; } if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) { syslog(LOG_ERR, "setsockopt(udp_notify, IPV6_MULTICAST_LOOP): %m"); close(s); return -1; } if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hop_limit, sizeof(hop_limit)) < 0) { syslog(LOG_ERR, "setsockopt(udp_notify, IPV6_MULTICAST_HOPS): %m"); close(s); return -1; } /* bind() socket before using sendto() is not mandatory * (sendto() will implicitly bind the socket when called on * an unbound socket) * but explicit bind permits to set port/scope_id/etc. */ memset(&sockname, 0, sizeof(sockname)); sockname.sin6_family = AF_INET6; sockname.sin6_addr = in6addr_any; /*sockname.sin6_port = htons(port);*/ /*sockname.sin6_scope_id = if_index;*/ if(bind(s, (struct sockaddr *)&sockname, sizeof(sockname)) < 0) { syslog(LOG_ERR, "bind(udp_notify IPv6): %m"); close(s); return -1; } return s; } #endif int OpenAndConfSSDPNotifySockets(int * sockets) /*OpenAndConfSSDPNotifySockets(int * sockets, struct lan_addr_s * lan_addr, int n_lan_addr)*/ { int i; struct lan_addr_s * lan_addr; for(i=0, lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr); if(sockets[i] < 0) goto error; i++; #ifdef ENABLE_IPV6 if(GETFLAG(IPV6DISABLEDMASK)) { sockets[i] = -1; } else { sockets[i] = OpenAndConfSSDPNotifySocketIPv6(lan_addr); if(sockets[i] < 0) goto error; } i++; #endif } return 0; error: while(--i >= 0) { close(sockets[i]); sockets[i] = -1; } return -1; } /* * response from a LiveBox (Wanadoo) HTTP/1.1 200 OK CACHE-CONTROL: max-age=1800 DATE: Thu, 01 Jan 1970 04:03:23 GMT EXT: LOCATION: http://192.168.0.1:49152/gatedesc.xml SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2 ST: upnp:rootdevice USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice * response from a Linksys 802.11b : HTTP/1.1 200 OK Cache-Control:max-age=120 Location:http://192.168.5.1:5678/rootDesc.xml Server:NT/5.0 UPnP/1.0 ST:upnp:rootdevice USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice EXT: */ /* Responds to a SSDP "M-SEARCH" * s : socket to use * addr : peer * 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 http_port, #ifdef ENABLE_HTTPS unsigned short https_port, #endif const char * uuidvalue, unsigned int delay) { int l, n; char buf[SSDP_PACKET_MAX_LEN]; char addr_str[64]; socklen_t addrlen; int st_is_uuid; #ifdef ENABLE_HTTP_DATE char http_date[64]; time_t t; struct tm tm; time(&t); gmtime_r(&t, &tm); strftime(http_date, sizeof(http_date), "%a, %d %b %Y %H:%M:%S GMT", &tm); #endif st_is_uuid = (st_len == (int)strlen(uuidvalue)) && (memcmp(uuidvalue, st, st_len) == 0); /* * follow guideline from document "UPnP Device Architecture 1.0" * uppercase is recommended. * DATE: is recommended * SERVER: OS/ver UPnP/1.0 miniupnpd/1.0 * CACHE-CONTROL: Should be greater than or equal to 1800 seconds */ l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n" "CACHE-CONTROL: max-age=1800\r\n" #ifdef ENABLE_HTTP_DATE "DATE: %s\r\n" #endif "ST: %.*s%s\r\n" "USN: %s%s%.*s%s\r\n" "EXT:\r\n" "SERVER: " MINIUPNPD_SERVER_STRING "\r\n" #ifndef RANDOMIZE_URLS "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n" #ifdef ENABLE_HTTPS "SECURELOCATION.UPNP.ORG: https://%s:%u" ROOTDESC_PATH "\r\n" #endif /* ENABLE_HTTPS */ #else /* RANDOMIZE_URLS */ "LOCATION: http://%s:%u/%s" ROOTDESC_PATH "\r\n" #ifdef ENABLE_HTTPS "SECURELOCATION.UPNP.ORG: https://%s:%u/%s" ROOTDESC_PATH "\r\n" #endif /* ENABLE_HTTPS */ #endif /* RANDOMIZE_URLS */ "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */ "01-NLS: %u\r\n" /* same as BOOTID. UDA v1.1 */ "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "\r\n", #ifdef ENABLE_HTTP_DATE http_date, /* DATE: */ #endif st_len, st, suffix, /* ST: */ uuidvalue, st_is_uuid ? "" : "::", /* USN: 2/5 */ st_is_uuid ? 0 : st_len, st, suffix, /* USN: 3/5 */ #ifdef DYNAMIC_OS_VERSION os_version, /* SERVER: */ #endif host, (unsigned int)http_port, /* LOCATION: */ #ifdef RANDOMIZE_URLS random_url, /* LOCATION: 3/3 */ #endif /* RANDOMIZE_URLS */ #ifdef ENABLE_HTTPS host, (unsigned int)https_port, /* SECURELOCATION.UPNP.ORG */ #ifdef RANDOMIZE_URLS random_url, /* SECURELOCATION.UPNP.ORG 3/3 */ #endif /* RANDOMIZE_URLS */ #endif /* ENABLE_HTTPS */ upnp_bootid, /* 01-NLS: */ upnp_bootid, /* BOOTID.UPNP.ORG: */ upnp_configid); /* CONFIGID.UPNP.ORG: */ if(l<0) { syslog(LOG_ERR, "%s: snprintf failed %m", "SendSSDPResponse()"); return; } else if((unsigned)l>=sizeof(buf)) { syslog(LOG_WARNING, "%s: truncated output (%u>=%u)", "SendSSDPResponse()", (unsigned)l, (unsigned)sizeof(buf)); l = sizeof(buf) - 1; } addrlen = (addr->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); n = sendto_schedule(s, buf, l, 0, addr, addrlen, delay); sockaddr_to_string(addr, addr_str, sizeof(addr_str)); syslog(LOG_DEBUG, "%s: %d bytes to %s ST: %.*s", "SendSSDPResponse()", n, addr_str, l, buf); if(n < 0) { syslog(LOG_ERR, "%s: sendto(udp): %m", "SendSSDPResponse()"); } } static struct { const char * s; const int version; const char * uuid; } const known_service_types[] = { {"upnp:rootdevice", 0, uuidvalue_igd}, #ifdef IGD_V2 {"urn:schemas-upnp-org:device:InternetGatewayDevice:", 2, uuidvalue_igd}, {"urn:schemas-upnp-org:device:WANConnectionDevice:", 2, uuidvalue_wcd}, {"urn:schemas-upnp-org:device:WANDevice:", 2, uuidvalue_wan}, {"urn:schemas-upnp-org:service:WANIPConnection:", 2, uuidvalue_wcd}, #ifdef ENABLE_DP_SERVICE {"urn:schemas-upnp-org:service:DeviceProtection:", 1, uuidvalue_igd}, #endif #ifdef ENABLE_6FC_SERVICE {"urn:schemas-upnp-org:service:WANIPv6FirewallControl:", 1, uuidvalue_wcd}, #endif #else /* IGD_V2 */ /* IGD v1 */ {"urn:schemas-upnp-org:device:InternetGatewayDevice:", 1, uuidvalue_igd}, {"urn:schemas-upnp-org:device:WANConnectionDevice:", 1, uuidvalue_wcd}, {"urn:schemas-upnp-org:device:WANDevice:", 1, uuidvalue_wan}, {"urn:schemas-upnp-org:service:WANIPConnection:", 1, uuidvalue_wcd}, #endif /* IGD_V2 */ {"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:", 1, uuidvalue_wan}, #ifdef ADVERTISE_WANPPPCONN /* We use WAN IP Connection, not PPP connection, * but buggy control points may try to use WanPPPConnection * anyway */ {"urn:schemas-upnp-org:service:WANPPPConnection:", 1, uuidvalue_wcd}, #endif /* ADVERTISE_WANPPPCONN */ #ifdef ENABLE_L3F_SERVICE {"urn:schemas-upnp-org:service:Layer3Forwarding:", 1, uuidvalue_igd}, #endif /* ENABLE_L3F_SERVICE */ /* we might want to support urn:schemas-wifialliance-org:device:WFADevice:1 * urn:schemas-wifialliance-org:device:WFADevice:1 * in the future */ {0, 0, 0} }; /* SendSSDPNotify() sends the SSDP NOTIFY to a specific * destination, for a specific UPnP service or device */ static void SendSSDPNotify(int s, const struct sockaddr * dest, socklen_t dest_len, const char * dest_str, const char * host, unsigned short http_port, #ifdef ENABLE_HTTPS unsigned short https_port, #endif const char * nt, const char * suffix, const char * usn1, const char * usn2, const char * usn3, unsigned int lifetime) { char bufr[SSDP_PACKET_MAX_LEN]; int n, l; l = snprintf(bufr, sizeof(bufr), "NOTIFY * HTTP/1.1\r\n" "HOST: %s:%d\r\n" "CACHE-CONTROL: max-age=%u\r\n" #ifndef RANDOMIZE_URLS "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n" #ifdef ENABLE_HTTPS "SECURELOCATION.UPNP.ORG: https://%s:%u" ROOTDESC_PATH "\r\n" #endif /* ENABLE_HTTPS */ #else /* RANDOMIZE_URLS */ "LOCATION: http://%s:%u/%s" ROOTDESC_PATH "\r\n" #ifdef ENABLE_HTTPS "SECURELOCATION.UPNP.ORG: https://%s:%u/%s" ROOTDESC_PATH "\r\n" #endif /* ENABLE_HTTPS */ #endif /* RANDOMIZE_URLS */ "SERVER: " MINIUPNPD_SERVER_STRING "\r\n" "NT: %s%s\r\n" "USN: %s%s%s%s\r\n" "NTS: ssdp:alive\r\n" "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */ "01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */ "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "\r\n", dest_str, SSDP_PORT, /* HOST: */ lifetime, /* CACHE-CONTROL: */ host, (unsigned int)http_port, /* LOCATION: */ #ifdef RANDOMIZE_URLS random_url, #endif /* RANDOMIZE_URLS */ #ifdef ENABLE_HTTPS host, (unsigned int)https_port, /* SECURE-LOCATION: */ #ifdef RANDOMIZE_URLS random_url, #endif /* RANDOMIZE_URLS */ #endif /* ENABLE_HTTPS */ #ifdef DYNAMIC_OS_VERSION os_version, /* SERVER: */ #endif nt, suffix, /* NT: */ usn1, usn2, usn3, suffix, /* USN: */ upnp_bootid, /* 01-NLS: */ upnp_bootid, /* BOOTID.UPNP.ORG: */ upnp_configid ); /* CONFIGID.UPNP.ORG: */ if(l<0) { syslog(LOG_ERR, "%s: snprintf error", "SendSSDPNotify()"); return; } else if((unsigned int)l >= sizeof(bufr)) { syslog(LOG_WARNING, "%s: truncated output (%u>=%u)", "SendSSDPNotify()", (unsigned)l, (unsigned)sizeof(bufr)); l = sizeof(bufr) - 1; } n = sendto_or_schedule(s, bufr, l, 0, dest, dest_len); if(n < 0) { syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, host ? host : "NULL"); } else if(n != l) { 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, dest_len, 250); if(n < 0) { syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, host ? host : "NULL"); } } /* SendSSDPNotifies() send SSPD NOTIFY for a specific * LAN (network interface) for all devices / services */ #ifdef ENABLE_HTTPS static void SendSSDPNotifies(int s, const char * host, unsigned short http_port, unsigned short https_port, unsigned int lifetime, int ipv6) #else static void SendSSDPNotifies(int s, const char * host, unsigned short http_port, unsigned int lifetime, int ipv6) #endif { #ifdef ENABLE_IPV6 struct sockaddr_storage sockname; /* UDA 1.1 AnnexA and UDA 2.0 only allow/define the use of * Link-Local and Site-Local multicast scopes */ static struct { const char * p1, * p2; } const mcast_addrs[] = { { LL_SSDP_MCAST_ADDR, "[" LL_SSDP_MCAST_ADDR "]" }, /* Link Local */ { SL_SSDP_MCAST_ADDR, "[" SL_SSDP_MCAST_ADDR "]" }, /* Site Local */ #ifndef UPNP_STRICT { GL_SSDP_MCAST_ADDR, "[" GL_SSDP_MCAST_ADDR "]" }, /* Global */ #endif /* ! UPNP_STRICT */ { NULL, NULL } }; int j; #else /* ENABLE_IPV6 */ struct sockaddr_in sockname; #endif /* ENABLE_IPV6 */ socklen_t sockname_len; const char * dest_str; int i; char ver_str[4]; #ifndef ENABLE_IPV6 UNUSED(ipv6); #endif /* ENABLE_IPV6 */ memset(&sockname, 0, sizeof(sockname)); #ifdef ENABLE_IPV6 /* first iterate destinations for this LAN interface (only 1 for IPv4) */ for(j = 0; (mcast_addrs[j].p1 != 0 && ipv6) || j < 1; j++) { if(ipv6) { struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockname; sockname_len = sizeof(struct sockaddr_in6); p->sin6_family = AF_INET6; p->sin6_port = htons(SSDP_PORT); inet_pton(AF_INET6, mcast_addrs[j].p1, &(p->sin6_addr)); dest_str = mcast_addrs[j].p2; /* UPnP Device Architecture 1.1 : * Devices MUST multicast SSDP messages for each of the UPnP-enabled * interfaces. The scope of multicast SSDP messages MUST be * link local FF02::C if the message is sent from a link local address. * If the message is sent from a global address it MUST be multicast * using either global scope FF0E::C or site local scope FF05::C. * In networks with complex topologies and overlapping sites, use of * global scope is RECOMMENDED. */ } else { #else /* ENABLE_IPV6 */ { #endif /* ENABLE_IPV6 */ /* IPv4 */ struct sockaddr_in *p = (struct sockaddr_in *)&sockname; sockname_len = sizeof(struct sockaddr_in); p->sin_family = AF_INET; p->sin_port = htons(SSDP_PORT); p->sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); dest_str = SSDP_MCAST_ADDR; } /* iterate all services / devices */ for(i = 0; known_service_types[i].s; i++) { if(i==0) ver_str[0] = '\0'; else snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version); SendSSDPNotify(s, (struct sockaddr *)&sockname, sockname_len, dest_str, host, http_port, #ifdef ENABLE_HTTPS https_port, #endif known_service_types[i].s, ver_str, /* NT: */ known_service_types[i].uuid, "::", known_service_types[i].s, /* ver_str, USN: */ lifetime); /* for devices, also send NOTIFY on the uuid */ if(i > 0 && /* only known_service_types[0].s is shorter than "urn:schemas-upnp-org:device" */ 0==memcmp(known_service_types[i].s, "urn:schemas-upnp-org:device", sizeof("urn:schemas-upnp-org:device")-1)) { SendSSDPNotify(s, (struct sockaddr *)&sockname, sockname_len, dest_str, host, http_port, #ifdef ENABLE_HTTPS https_port, #endif known_service_types[i].uuid, "", /* NT: */ known_service_types[i].uuid, "", "", /* ver_str, USN: */ lifetime); } } /* for(i = 0; known_service_types[i].s; i++) */ #ifdef ENABLE_IPV6 } /* for(j = 0; (mcast_addrs[j].p1 != 0 && ipv6) || j < 1; j++) */ #endif /* ENABLE_IPV6 */ } /* SendSSDPNotifies2() sends SSDP NOTIFY packets on all interfaces * for all destinations, all devices / services */ void SendSSDPNotifies2(int * sockets, unsigned short http_port, #ifdef ENABLE_HTTPS unsigned short https_port, #endif unsigned int lifetime) { int i; struct lan_addr_s * lan_addr; for(i = 0, lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { SendSSDPNotifies(sockets[i], lan_addr->str, http_port, #ifdef ENABLE_HTTPS https_port, #endif lifetime, 0); i++; #ifdef ENABLE_IPV6 if(sockets[i] >= 0) { SendSSDPNotifies(sockets[i], ipv6_addr_for_http_with_brackets, http_port, #ifdef ENABLE_HTTPS https_port, #endif lifetime, 1); } i++; #endif /* ENABLE_IPV6 */ } } /* ProcessSSDPRequest() * process SSDP M-SEARCH requests and responds to them */ void #ifdef ENABLE_HTTPS ProcessSSDPRequest(int s, unsigned short http_port, unsigned short https_port) #else ProcessSSDPRequest(int s, unsigned short http_port) #endif { int n; char bufr[1500]; #ifdef ENABLE_IPV6 struct sockaddr_storage sendername; #else struct sockaddr_in sendername; #endif int source_ifindex = -1; #if defined(IP_RECVIF) || defined(IP_PKTINFO) #ifdef IP_RECVIF char cmbuf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; #else /* IP_PKTINFO */ char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))]; #endif struct iovec iovec = { .iov_base = bufr, .iov_len = sizeof(bufr) }; struct msghdr mh = { .msg_name = &sendername, .msg_namelen = sizeof(sendername), .msg_iov = &iovec, .msg_iovlen = 1, .msg_control = cmbuf, .msg_controllen = sizeof(cmbuf) }; struct cmsghdr *cmptr; n = recvmsg(s, &mh, 0); #else socklen_t len_r; len_r = sizeof(sendername); n = recvfrom(s, bufr, sizeof(bufr), 0, (struct sockaddr *)&sendername, &len_r); #endif /* defined(IP_RECVIF) || defined(IP_PKTINFO) */ if(n < 0) { /* EAGAIN, EWOULDBLOCK, EINTR : silently ignore (try again next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { #if defined(IP_RECVIF) || defined(IP_PKTINFO) syslog(LOG_ERR, "recvmsg(udp): %m"); #else syslog(LOG_ERR, "recvfrom(udp): %m"); #endif } return; } #if defined(IP_RECVIF) || defined(IP_PKTINFO) for(cmptr = CMSG_FIRSTHDR(&mh); cmptr != NULL; cmptr = CMSG_NXTHDR(&mh, cmptr)) { syslog(LOG_DEBUG, "level=%d type=%d", cmptr->cmsg_level, cmptr->cmsg_type); #ifdef IP_RECVIF if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) { struct sockaddr_dl *sdl; /* fields : len, family, index, type, nlen, alen, slen, data */ sdl = (struct sockaddr_dl *)CMSG_DATA(cmptr); syslog(LOG_DEBUG, "sdl_index = %d %s", sdl->sdl_index, link_ntoa(sdl)); source_ifindex = sdl->sdl_index; } #elif defined(IP_PKTINFO) /* IP_RECVIF */ if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { struct in_pktinfo * pi; /* fields : ifindex, spec_dst, addr */ pi = (struct in_pktinfo *)CMSG_DATA(cmptr); syslog(LOG_DEBUG, "ifindex = %u %s", pi->ipi_ifindex, inet_ntoa(pi->ipi_spec_dst)); source_ifindex = pi->ipi_ifindex; } #endif /* IP_PKTINFO */ #if defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO) if(cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == IPV6_RECVPKTINFO) { struct in6_pktinfo * pi6; /* fields : ifindex, addr */ pi6 = (struct in6_pktinfo *)CMSG_DATA(cmptr); syslog(LOG_DEBUG, "ifindex = %u", pi6->ipi6_ifindex); source_ifindex = pi6->ipi6_ifindex; } #endif /* defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO) */ } #endif /* defined(IP_RECVIF) || defined(IP_PKTINFO) */ #ifdef ENABLE_HTTPS ProcessSSDPData(s, bufr, n, (struct sockaddr *)&sendername, source_ifindex, http_port, https_port); #else ProcessSSDPData(s, bufr, n, (struct sockaddr *)&sendername, source_ifindex, http_port); #endif } #ifdef ENABLE_HTTPS void ProcessSSDPData(int s, const char *bufr, int n, const struct sockaddr * sender, int source_if, unsigned short http_port, unsigned short https_port) #else void ProcessSSDPData(int s, const char *bufr, int n, const struct sockaddr * sender, int source_if, unsigned short http_port) #endif { int i, l; struct lan_addr_s * lan_addr = NULL; const char * st = NULL; int st_len = 0; int st_ver = 0; char sender_str[64]; char ver_str[4]; const char * announced_host = NULL; #ifdef UPNP_STRICT #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 = 50; /* Non-zero default delay to prevent flooding */ /* 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. */ char atoi_buffer[8]; /* get the string representation of the sender address */ sockaddr_to_string(sender, sender_str, sizeof(sender_str)); lan_addr = get_lan_for_peer(sender); if(source_if > 0) { if(lan_addr != NULL) { #ifndef MULTIPLE_EXTERNAL_IP if(lan_addr->index != (unsigned)source_if && lan_addr->index != 0 && !(lan_addr->add_indexes & (1UL << (source_if - 1)))) #else if(lan_addr->index != (unsigned)source_if && lan_addr->index != 0) #endif { syslog(LOG_WARNING, "interface index not matching %u != %d", lan_addr->index, source_if); } } else { /* use the interface index */ for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if(lan_addr->index == (unsigned)source_if) break; } } } if(lan_addr == NULL) { syslog(LOG_WARNING, "SSDP packet sender %s (if_index=%d) not from a LAN, ignoring", sender_str, source_if); return; } if(memcmp(bufr, "NOTIFY", 6) == 0) { /* ignore NOTIFY packets. We could log the sender and device type */ return; } else if(memcmp(bufr, "M-SEARCH", 8) == 0) { i = 0; while(i < n) { while((i < n - 1) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if((i < n - 3) && (strncasecmp(bufr+i, "st:", 3) == 0)) { st = bufr+i+3; st_len = 0; while((st < bufr + n) && (*st == ' ' || *st == '\t')) st++; while((st + st_len < bufr + n) && (st[st_len]!='\r' && st[st_len]!='\n')) st_len++; l = st_len; while(l > 0 && st[l-1] != ':') l--; memset(atoi_buffer, 0, sizeof(atoi_buffer)); memcpy(atoi_buffer, st + l, MIN((int)(sizeof(atoi_buffer) - 1), st_len - l)); st_ver = atoi(atoi_buffer); syslog(LOG_DEBUG, "ST: %.*s (ver=%d)", st_len, st, st_ver); /*j = 0;*/ /*while(bufr[i+j]!='\r') j++;*/ /*syslog(LOG_INFO, "%.*s", j, bufr+i);*/ } #if defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE) else if((i < n - 3) && (strncasecmp(bufr+i, "mx:", 3) == 0)) { const char * mx; int mx_len; mx = bufr+i+3; mx_len = 0; while((mx < bufr + n) && (*mx == ' ' || *mx == '\t')) mx++; while((mx + mx_len < bufr + n) && (mx[mx_len]!='\r' && mx[mx_len]!='\n')) mx_len++; memset(atoi_buffer, 0, sizeof(atoi_buffer)); memcpy(atoi_buffer, mx, MIN((int)(sizeof(atoi_buffer) - 1), mx_len)); mx_value = atoi(atoi_buffer); syslog(LOG_DEBUG, "MX: %.*s (value=%d)", mx_len, mx, mx_value); } #endif /* defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE) */ #if defined(UPNP_STRICT) /* Fix UDA-1.2.10 Man header empty or invalid */ else if((i < n - 4) && (strncasecmp(bufr+i, "man:", 4) == 0)) { const char * man; int man_len; man = bufr+i+4; man_len = 0; while((man < bufr + n) && (*man == ' ' || *man == '\t')) man++; while((man + man_len < bufr + n) && (man[man_len]!='\r' && man[man_len]!='\n')) man_len++; if((man_len < 15) || (strncmp(man, "\"ssdp:discover\"", 15) != 0)) { syslog(LOG_INFO, "ignoring SSDP packet MAN empty or invalid header"); return; } } #endif /* defined(UPNP_STRICT) */ } #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)) { syslog(LOG_INFO, "SSDP M-SEARCH from %s ST: %.*s", sender_str, st_len, st); /* find in which sub network the client is */ #ifdef ENABLE_IPV6 if((sender->sa_family == AF_INET) || (sender->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)sender)->sin6_addr))) #else if(sender->sa_family == AF_INET) #endif { if (lan_addr == NULL) { syslog(LOG_ERR, "Can't find in which sub network the client %s is", sender_str); return; } announced_host = lan_addr->str; } #ifdef ENABLE_IPV6 else if(sender->sa_family == AF_INET6) { /* IPv6 address with brackets */ #ifdef UPNP_STRICT int index; struct in6_addr addr6; size_t addr6_len = sizeof(addr6); /* retrieve the IPv6 address which * will be used locally to reach sender */ memset(&addr6, 0, sizeof(addr6)); /* UPnP Device Architecture 2.0 - Annex A - IP version 6 support * p112 A2.3 : * a) Devices and control points SHALL use only the Link-Local * unicast address as the source address and when specifying * a literal IP address in LOCATION URLs in all multicast * messages that are multicast to the Link-Local scope FF02::C * for SSDP and FF02::130 for multicast eventing. * f) Devices and control points SHALL use an acquired ULA or * GUA in all multicast messages as the source address and * when specifying a literal IP address in LOCATION URLs * that are multicast to the Site-Local scope addresses of * either FF05::C or FF05::130 */ if(IN6_IS_ADDR_LINKLOCAL(&(((struct sockaddr_in6 *)sender)->sin6_addr))) { get_link_local_addr(((struct sockaddr_in6 *)sender)->sin6_scope_id, &addr6); } else if(get_src_for_route_to (sender, &addr6, &addr6_len, &index) < 0) { syslog(LOG_WARNING, "get_src_for_route_to() failed, using %s", ipv6_addr_for_http_with_brackets); announced_host = ipv6_addr_for_http_with_brackets; } if(announced_host == NULL) { /* convert addr6 to string with brackets */ if(inet_ntop(AF_INET6, &addr6, announced_host_buf+1, sizeof(announced_host_buf) - 2)) { announced_host_buf[0] = '['; i = strlen(announced_host_buf); if(i < (int)sizeof(announced_host_buf) - 1) { announced_host_buf[i] = ']'; announced_host_buf[i+1] = '\0'; } else { syslog(LOG_NOTICE, "cannot suffix %s with ']'", announced_host_buf); } announced_host = announced_host_buf; } else { syslog(LOG_NOTICE, "inet_ntop() failed %m"); announced_host = ipv6_addr_for_http_with_brackets; } } #else announced_host = ipv6_addr_for_http_with_brackets; #endif } #endif else { syslog(LOG_ERR, "Unknown address family %d for client %s", sender->sa_family, sender_str); return; } /* Responds to request with a device as ST header */ for(i = 0; known_service_types[i].s; i++) { l = (int)strlen(known_service_types[i].s); if(l<=st_len && (0 == memcmp(st, known_service_types[i].s, l)) #ifdef UPNP_STRICT && (st_ver <= known_service_types[i].version) /* only answer for service version lower or equal of supported one */ #endif ) { /* SSDP_RESPOND_SAME_VERSION : * response is urn:schemas-upnp-org:service:WANIPConnection:1 when * M-SEARCH included urn:schemas-upnp-org:service:WANIPConnection:1 * else the implemented versions is included in the response * * From UPnP Device Architecture v1.1 : * 1.3.2 [...] Updated versions of device and service types * are REQUIRED to be fully backward compatible with * previous versions. Devices MUST respond to M-SEARCH * requests for any supported version. For example, if a * device implements “urn:schemas-upnporg:service:xyz:2”, * it MUST respond to search requests for both that type * and “urn:schemas-upnp-org:service:xyz:1”. The response * MUST specify the same version as was contained in the * search request. [...] */ #ifndef SSDP_RESPOND_SAME_VERSION if(i==0) ver_str[0] = '\0'; else 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, "", #else known_service_types[i].s, l, ver_str, #endif announced_host, http_port, #ifdef ENABLE_HTTPS https_port, #endif known_service_types[i].uuid, delay); break; } } /* Responds to request with ST: ssdp:all */ /* 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 snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version); l = (int)strlen(known_service_types[i].s); SendSSDPResponse(s, sender, known_service_types[i].s, l, ver_str, announced_host, http_port, #ifdef ENABLE_HTTPS https_port, #endif 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, http_port, #ifdef ENABLE_HTTPS https_port, #endif uuidvalue_igd, delay); #ifdef DELAY_MSEARCH_RESPONSE delay += delay_increment; #endif SendSSDPResponse(s, sender, uuidvalue_wan, strlen(uuidvalue_wan), "", announced_host, http_port, #ifdef ENABLE_HTTPS https_port, #endif uuidvalue_wan, delay); #ifdef DELAY_MSEARCH_RESPONSE delay += delay_increment; #endif SendSSDPResponse(s, sender, uuidvalue_wcd, strlen(uuidvalue_wcd), "", announced_host, http_port, #ifdef ENABLE_HTTPS https_port, #endif 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, http_port, #ifdef ENABLE_HTTPS https_port, #endif 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, http_port, #ifdef ENABLE_HTTPS https_port, #endif 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, http_port, #ifdef ENABLE_HTTPS https_port, #endif uuidvalue_wcd, delay); } } } else { syslog(LOG_INFO, "Invalid SSDP M-SEARCH from %s", sender_str); } } else { syslog(LOG_NOTICE, "Unknown udp packet received from %s", sender_str); } } static int SendSSDPbyebye(int s, const struct sockaddr * dest, socklen_t destlen, const char * dest_str, const char * nt, const char * suffix, const char * usn1, const char * usn2, const char * usn3) { int n, l; char bufr[SSDP_PACKET_MAX_LEN]; l = snprintf(bufr, sizeof(bufr), "NOTIFY * HTTP/1.1\r\n" "HOST: %s:%d\r\n" "NT: %s%s\r\n" "USN: %s%s%s%s\r\n" "NTS: ssdp:byebye\r\n" "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */ "01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */ "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "\r\n", dest_str, SSDP_PORT, /* HOST : */ nt, suffix, /* NT: */ usn1, usn2, usn3, suffix, /* USN: */ upnp_bootid, /* 01-NLS: */ upnp_bootid, /* BOOTID.UPNP.ORG: */ upnp_configid); /* CONFIGID.UPNP.ORG: */ if(l<0) { syslog(LOG_ERR, "%s: snprintf error", "SendSSDPbyebye()"); return -1; } else if((unsigned int)l >= sizeof(bufr)) { syslog(LOG_WARNING, "%s: truncated output (%u>=%u)", "SendSSDPbyebye()", (unsigned)l, (unsigned)sizeof(bufr)); l = sizeof(bufr) - 1; } n = sendto_or_schedule(s, bufr, l, 0, dest, destlen); if(n < 0) { syslog(LOG_ERR, "sendto(udp_shutdown=%d): %m", s); return -1; } else if(n != l) { syslog(LOG_NOTICE, "sendto() sent %d out of %d bytes", n, l); return -1; } return 0; } /* This will broadcast ssdp:byebye notifications to inform * the network that UPnP is going down. */ int SendSSDPGoodbye(int * sockets, int n_sockets) { struct sockaddr_in sockname4; #ifdef ENABLE_IPV6 struct sockaddr_in6 sockname6; struct sockaddr * sockname; socklen_t socknamelen; int ipv6 = 0; #endif int i, j; char ver_str[4]; int ret = 0; const char * dest_str; memset(&sockname4, 0, sizeof(struct sockaddr_in)); sockname4.sin_family = AF_INET; sockname4.sin_port = htons(SSDP_PORT); sockname4.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); #ifdef ENABLE_IPV6 memset(&sockname6, 0, sizeof(struct sockaddr_in6)); sockname6.sin6_family = AF_INET6; sockname6.sin6_port = htons(SSDP_PORT); inet_pton(AF_INET6, LL_SSDP_MCAST_ADDR, &(sockname6.sin6_addr)); #else dest_str = SSDP_MCAST_ADDR; #endif for(j=0; j 0 && /* only known_service_types[0].s is shorter than "urn:schemas-upnp-org:device" */ 0==memcmp(known_service_types[i].s, "urn:schemas-upnp-org:device", sizeof("urn:schemas-upnp-org:device")-1)) { ret += SendSSDPbyebye(sockets[j], #ifdef ENABLE_IPV6 sockname, socknamelen, #else (struct sockaddr *)&sockname4, sizeof(struct sockaddr_in), #endif dest_str, known_service_types[i].uuid, "", /* NT: */ known_service_types[i].uuid, "", ""); /* ver_str, USN: */ } } } return ret; } /* SubmitServicesToMiniSSDPD() : * register services offered by MiniUPnPd to a running instance of * MiniSSDPd */ int SubmitServicesToMiniSSDPD(const char * host, unsigned short port) { struct sockaddr_un addr; int s; unsigned char buffer[2048]; char strbuf[256]; unsigned char * p; int i, l, n; char ver_str[4]; s = socket(AF_UNIX, SOCK_STREAM, 0); if(s < 0) { syslog(LOG_ERR, "socket(unix): %m"); return -1; } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, minissdpdsocketpath, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { syslog(LOG_ERR, "connect(\"%s\"): %m", minissdpdsocketpath); close(s); return -1; } for(i = 0; known_service_types[i].s; i++) { buffer[0] = 4; /* request type 4 : submit service */ if(i==0) ver_str[0] = '\0'; else snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version); /* 4 strings following : ST (service type), USN, Server, Location */ p = buffer + 1; l = snprintf(strbuf, sizeof(strbuf), "%s%s", known_service_types[i].s, ver_str); if(l<0) { syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m"); continue; } else if((unsigned)l>=sizeof(strbuf)) { l = sizeof(strbuf) - 1; } CODELENGTH(l, p); memcpy(p, strbuf, l); p += l; l = snprintf(strbuf, sizeof(strbuf), "%s::%s%s", known_service_types[i].uuid, known_service_types[i].s, ver_str); if(l<0) { syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m"); continue; } else if((unsigned)l>=sizeof(strbuf)) { l = sizeof(strbuf) - 1; } CODELENGTH(l, p); memcpy(p, strbuf, l); p += l; #ifdef DYNAMIC_OS_VERSION l = snprintf(strbuf, sizeof(strbuf), MINIUPNPD_SERVER_STRING, os_version); if(l<0) { syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m"); continue; } else if((unsigned)l>=sizeof(strbuf)) { l = sizeof(strbuf) - 1; } CODELENGTH(l, p); memcpy(p, strbuf, l); #else l = (int)strlen(MINIUPNPD_SERVER_STRING); CODELENGTH(l, p); memcpy(p, MINIUPNPD_SERVER_STRING, l); #endif p += l; l = snprintf(strbuf, sizeof(strbuf), "http://%s:%u" ROOTDESC_PATH, host, (unsigned int)port); if(l<0) { syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m"); continue; } else if((unsigned)l>=sizeof(strbuf)) { l = sizeof(strbuf) - 1; } CODELENGTH(l, p); memcpy(p, strbuf, l); p += l; /* now write the encoded data */ n = p - buffer; /* bytes to send */ p = buffer; /* start */ while(n > 0) { l = write(s, p, n); if (l < 0) { if(errno == EINTR) continue; syslog(LOG_ERR, "write(): %m"); close(s); return -1; } else if (l == 0) { syslog(LOG_ERR, "write() returned 0"); close(s); return -1; } p += l; n -= l; } } close(s); syslog(LOG_DEBUG, "%d service submitted to MiniSSDPd", i); return 0; } miniupnpd-2.3.7/miniupnpdctl.c010064400017500000024000000030061174772426300155760ustar00nanardstaff/* $Id: miniupnpdctl.c,v 1.10 2012/04/30 21:08:00 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2012 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 "macros.h" #if 0 static void sighandler(int sig) { printf("received signal %d\n", sig); } #endif int main(int argc, char * * argv) { /*char test[] = "test!";*/ static const char command[] = "show all\n"; char buf[256]; int l; int s; struct sockaddr_un addr; UNUSED(argc); UNUSED(argv); /*signal(SIGINT, sighandler);*/ s = socket(AF_UNIX, SOCK_STREAM, 0); if(s<0) { perror("socket"); return 1; } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, "/var/run/miniupnpd.ctl", sizeof(addr.sun_path)); if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { perror("connect"); close(s); return 1; } printf("Connected.\n"); if(write(s, command, sizeof(command)) < 0) { perror("write"); close(s); return 1; } for(;;) { l = read(s, buf, sizeof(buf)); if(l<0) { perror("read"); break; } if(l==0) break; /*printf("%d bytes read\n", l);*/ fflush(stdout); if(write(fileno(stdout), buf, l) < 0) { perror("error writing to stdout"); } /*printf("\n");*/ } close(s); return 0; } miniupnpd-2.3.7/linux/miniupnpd.init.d.script010064400017500000024000000037031437357067300205060ustar00nanardstaff#!/bin/sh # $Id: miniupnpd.init.d.script,v 1.5 2023/01/21 12:08:40 nanard Exp $ # MiniUPnP project # author: Thomas Bernard # website: http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ ### BEGIN INIT INFO # Provides: miniupnpd # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: MiniUPnPd port-forwarding daemon ### END INIT INFO set -e ETCPATH=/etc/miniupnpd MINIUPNPD=/usr/sbin/miniupnpd ARGS="-f $ETCPATH/miniupnpd.conf" if [ -f $ETCPATH/nft_init.sh ] ; then IPTABLES_CREATE=$ETCPATH/nft_init.sh IPTABLES_REMOVE=$ETCPATH/nft_removeall.sh elif [ -f $ETCPATH/ip6tables_init.sh ] ; then IPTABLES_CREATE="($ETCPATH/iptables_init.sh ; $ETCPATH/ip6tables_init.sh)" IPTABLES_REMOVE="($ETCPATH/iptables_removeall.sh ; $ETCPATH/iptables_removeall.sh)" else IPTABLES_CREATE=$ETCPATH/iptables_init.sh IPTABLES_REMOVE=$ETCPATH/iptables_removeall.sh fi test -f $MINIUPNPD || exit 0 . /lib/lsb/init-functions case "$1" in start) log_daemon_msg "Starting miniupnpd" "miniupnpd" eval $IPTABLES_CREATE > /dev/null 2>&1 start-stop-daemon --start --quiet --pidfile /var/run/miniupnpd.pid --startas $MINIUPNPD -- $ARGS $LSBNAMES log_end_msg $? ;; stop) log_daemon_msg "Stopping miniupnpd" "miniupnpd" start-stop-daemon --stop --quiet --pidfile /var/run/miniupnpd.pid log_end_msg $? eval $IPTABLES_REMOVE > /dev/null 2>&1 ;; restart|reload|force-reload) log_daemon_msg "Restarting miniupnpd" "miniupnpd" start-stop-daemon --stop --retry 5 --quiet --pidfile /var/run/miniupnpd.pid eval $IPTABLES_REMOVE > /dev/null 2>&1 eval $IPTABLES_CREATE > /dev/null 2>&1 start-stop-daemon --start --quiet --pidfile /var/run/miniupnpd.pid --startas $MINIUPNPD -- $ARGS $LSBNAMES log_end_msg $? ;; status) status_of_proc $MINIUPNPD miniupnpd ;; *) log_action_msg "Usage: /etc/init.d/miniupnpd {start|stop|restart|reload|force-reload}" exit 2 ;; esac exit 0 miniupnpd-2.3.7/ipf/ipfrdr.c010064400017500000024000000457401376770734400151540ustar00nanardstaff/* $Id: ipfrdr.c,v 1.21 2020/12/20 17:43:40 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2007 Darren Reed * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include /* * This is a workaround for troubles on FreeBSD, HPUX, OpenBSD. * Needed here because on some systems gets included by things * like */ #ifndef _KERNEL # define ADD_KERNEL # define _KERNEL # define KERNEL #endif #ifdef __OpenBSD__ struct file; #endif #include #ifdef ADD_KERNEL # undef _KERNEL # undef KERNEL #endif #include #include #include #include #include #if __FreeBSD_version >= 300000 # include #endif #include #include #include #include #ifndef TCP_PAWS_IDLE /* IRIX */ # include #endif #include #include #include #include #include #include #include #include #include #include #if !defined(__SVR4) && !defined(__svr4__) && defined(sun) # include #endif #include #include #include "config.h" #include "netinet/ipl.h" #include "netinet/ip_compat.h" #include "netinet/ip_fil.h" #include "netinet/ip_nat.h" #include "netinet/ip_state.h" #include "../macros.h" #ifndef __P # ifdef __STDC__ # define __P(x) x # else # define __P(x) () # endif #endif #ifndef __STDC__ # undef const # define const #endif #ifndef U_32_T # define U_32_T 1 # if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || \ defined(__sgi) typedef u_int32_t u_32_t; # else # if defined(__alpha__) || defined(__alpha) || defined(_LP64) typedef unsigned int u_32_t; # else # if SOLARIS2 >= 6 typedef uint32_t u_32_t; # else typedef unsigned int u_32_t; # endif # endif # endif /* __NetBSD__ || __OpenBSD__ || __FreeBSD__ || __sgi */ #endif /* U_32_T */ #if defined(__NetBSD__) || defined(__OpenBSD__) || \ (_BSDI_VERSION >= 199701) || (__FreeBSD_version >= 300000) || \ SOLARIS || defined(__sgi) || defined(__osf__) || defined(linux) # include typedef int (* ioctlfunc_t) __P((int, ioctlcmd_t, ...)); #else typedef int (* ioctlfunc_t) __P((dev_t, ioctlcmd_t, void *)); #endif typedef void (* addfunc_t) __P((int, ioctlfunc_t, void *)); typedef int (* copyfunc_t) __P((void *, void *, size_t)); /* * SunOS4 */ #if defined(sun) && !defined(__SVR4) && !defined(__svr4__) extern int ioctl __P((int, int, void *)); #endif #include "../upnpglobalvars.h" /* group name */ static const char group_name[] = "miniupnpd"; static int dev = -1; static int dev_ipl = -1; /* IPFilter cannot store redirection descriptions, so we use our * own structure to store them */ struct rdr_desc { struct rdr_desc * next; unsigned short eport; int proto; unsigned int timestamp; char str[]; }; /* pointer to the chained list where descriptions are stored */ static struct rdr_desc * rdr_desc_list; static void add_redirect_desc(unsigned short eport, int proto, unsigned int timestamp, const char * desc) { struct rdr_desc * p; size_t l; if (desc != NULL) { l = strlen(desc) + 1; p = malloc(sizeof(struct rdr_desc) + l); if (p) { p->next = rdr_desc_list; p->eport = eport; p->proto = proto; p->timestamp = timestamp; memcpy(p->str, desc, l); rdr_desc_list = p; } } } static void del_redirect_desc(unsigned short eport, int proto) { struct rdr_desc * p, * last; last = NULL; for (p = rdr_desc_list; p; p = p->next) { if(p->eport == eport && p->proto == proto) { if (last == NULL) rdr_desc_list = p->next; else last->next = p->next; free(p); return; } } } static void get_redirect_desc(unsigned short eport, int proto, char * desc, int desclen, unsigned int * timestamp) { struct rdr_desc * p; if (desc == NULL || desclen == 0) return; for (p = rdr_desc_list; p; p = p->next) { if (p->eport == eport && p->proto == proto) { strncpy(desc, p->str, desclen); *timestamp = p->timestamp; return; } } return; } int init_redirect(void) { dev = open(IPNAT_NAME, O_RDWR); if (dev < 0) { syslog(LOG_ERR, "open(\"%s\"): %m", IPNAT_NAME); return -1; } dev_ipl = open(IPL_NAME, O_RDWR); if (dev_ipl < 0) { syslog(LOG_ERR, "open(\"%s\"): %m", IPL_NAME); return -1; } return 0; } void shutdown_redirect(void) { if (dev >= 0) { close(dev); dev = -1; } if (dev_ipl >= 0) { close(dev_ipl); dev = -1; } return; } int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { struct ipnat ipnat; struct ipfobj obj; int r; if (dev < 0) { syslog(LOG_ERR, "%s not open", IPNAT_NAME); return -1; } memset(&obj, 0, sizeof(obj)); memset(&ipnat, 0, sizeof(ipnat)); ipnat.in_redir = NAT_REDIRECT; #if IPFILTER_VERSION >= 5000000 ipnat.in_pr[0] = proto; ipnat.in_pr[1] = proto; #else ipnat.in_p = proto; #endif if (proto == IPPROTO_TCP) ipnat.in_flags = IPN_TCP; if (proto == IPPROTO_UDP) ipnat.in_flags = IPN_UDP; ipnat.in_dcmp = FR_EQUAL; #if IPFILTER_VERSION >= 5000000 ipnat.in_dpmin = htons(eport); ipnat.in_dpmax = htons(eport); ipnat.in_dpnext = htons(iport); ipnat.in_v[0] = 4; ipnat.in_v[1] = 4; #else ipnat.in_pmin = htons(eport); ipnat.in_pmax = htons(eport); ipnat.in_pnext = htons(iport); ipnat.in_v = 4; #endif strlcpy(ipnat.in_tag.ipt_tag, group_name, IPFTAG_LEN); #ifdef USE_IFNAME_IN_RULES if (ifname) { #if IPFILTER_VERSION >= 5000000 /* XXX check for stack overflow ! */ ipnat.in_ifnames[0] = 0; ipnat.in_ifnames[1] = 0; strlcpy(ipnat.in_names, ifname, IFNAMSIZ); ipnat.in_namelen = strlen(ipnat.in_names) + 1; #else strlcpy(ipnat.in_ifnames[0], ifname, IFNAMSIZ); strlcpy(ipnat.in_ifnames[1], ifname, IFNAMSIZ); #endif } #endif if(rhost && rhost[0] != '\0' && rhost[0] != '*') { #if IPFILTER_VERSION >= 5000000 inet_pton(AF_INET, rhost, &ipnat.in_nsrc.na_addr[0].in4); /* in_nsrcip */ ipnat.in_nsrc.na_addr[1].in4.s_addr = 0xffffffff; /* in_nsrcmsk */ #else inet_pton(AF_INET, rhost, &ipnat.in_src[0].in4); ipnat.in_src[1].in4.s_addr = 0xffffffff; #endif } #if IPFILTER_VERSION >= 5000000 inet_pton(AF_INET, iaddr, &ipnat.in_ndst.na_addr[0].in4); /* in_ndstip */ ipnat.in_ndst.na_addr[1].in4.s_addr = 0xffffffff; /* in_ndstmsk */ #else inet_pton(AF_INET, iaddr, &ipnat.in_in[0].in4); ipnat.in_in[1].in4.s_addr = 0xffffffff; #endif obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_size = sizeof(ipnat); obj.ipfo_ptr = &ipnat; obj.ipfo_type = IPFOBJ_IPNAT; r = ioctl(dev, SIOCADNAT, &obj); if (r == -1) syslog(LOG_ERR, "ioctl(SIOCADNAT): %m"); else add_redirect_desc(eport, proto, timestamp, desc); return r; } /* get_redirect_rule() * return value : 0 success (found) * -1 = error or rule not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { ipfgeniter_t iter; ipfobj_t obj; ipnat_t ipn; int r; UNUSED(ifname); memset(&obj, 0, sizeof(obj)); obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_type = IPFOBJ_GENITER; obj.ipfo_size = sizeof(iter); obj.ipfo_ptr = &iter; iter.igi_type = IPFGENITER_IPNAT; #if IPFILTER_VERSION > 4011300 iter.igi_nitems = 1; #endif iter.igi_data = &ipn; if (dev < 0) { syslog(LOG_ERR, "%s not open", IPNAT_NAME); return -1; } r = -1; do { if (ioctl(dev, SIOCGENITER, &obj) == -1) { syslog(LOG_ERR, "ioctl(dev, SIOCGENITER): %m"); break; } #if IPFILTER_VERSION >= 5000000 if (eport == ntohs(ipn.in_dpmin) && eport == ntohs(ipn.in_dpmax) && strcmp(ipn.in_tag.ipt_tag, group_name) == 0 && ipn.in_pr[0] == proto) #else if (eport == ntohs(ipn.in_pmin) && eport == ntohs(ipn.in_pmax) && strcmp(ipn.in_tag.ipt_tag, group_name) == 0 && ipn.in_p == proto) #endif { strlcpy(desc, "", desclen); if (packets != NULL) *packets = 0; if (bytes != NULL) *bytes = 0; if (iport != NULL) #if IPFILTER_VERSION >= 5000000 *iport = ntohs(ipn.in_dpnext); #else *iport = ntohs(ipn.in_pnext); #endif if ((desc != NULL) && (timestamp != NULL)) get_redirect_desc(eport, proto, desc, desclen, timestamp); if ((rhost != NULL) && (rhostlen > 0)) #if IPFILTER_VERSION >= 5000000 inet_ntop(AF_INET, &ipn.in_nsrc.na_addr[0].in4, rhost, rhostlen); /* in_nsrcip */ #else inet_ntop(AF_INET, &ipn.in_src[0].in4, rhost, rhostlen); #endif #if IPFILTER_VERSION >= 5000000 inet_ntop(AF_INET, &ipn.in_ndst.na_addr[0].in4, iaddr, iaddrlen); /* in_ndstip */ #else inet_ntop(AF_INET, &ipn.in_in[0].in4, iaddr, iaddrlen); #endif r = 0; } } while (ipn.in_next != NULL); return r; } int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { ipfgeniter_t iter; ipfobj_t obj; ipnat_t ipn; int n, r; if (index < 0) return -1; if (dev < 0) { syslog(LOG_ERR, "%s not open", IPNAT_NAME); return -1; } memset(&obj, 0, sizeof(obj)); obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_ptr = &iter; obj.ipfo_size = sizeof(iter); obj.ipfo_type = IPFOBJ_GENITER; iter.igi_type = IPFGENITER_IPNAT; #if IPFILTER_VERSION > 4011300 iter.igi_nitems = 1; #endif iter.igi_data = &ipn; n = 0; r = -1; do { if (ioctl(dev, SIOCGENITER, &obj) == -1) { syslog(LOG_ERR, "%s:ioctl(SIOCGENITER): %m", "get_redirect_rule_by_index"); break; } if (strcmp(ipn.in_tag.ipt_tag, group_name) != 0) continue; if (index == n++) { #if IPFILTER_VERSION >= 5000000 *proto = ipn.in_pr[0]; *eport = ntohs(ipn.in_dpmax); *iport = ntohs(ipn.in_dpnext); #else *proto = ipn.in_p; *eport = ntohs(ipn.in_pmax); *iport = ntohs(ipn.in_pnext); #endif if (ifname) #if IPFILTER_VERSION >= 5000000 strlcpy(ifname, ipn.in_names + ipn.in_ifnames[0], IFNAMSIZ); #else strlcpy(ifname, ipn.in_ifnames[0], IFNAMSIZ); #endif if (packets != NULL) *packets = 0; if (bytes != NULL) *bytes = 0; if ((desc != NULL) && (timestamp != NULL)) get_redirect_desc(*eport, *proto, desc, desclen, timestamp); if ((rhost != NULL) && (rhostlen > 0)) #if IPFILTER_VERSION >= 5000000 inet_ntop(AF_INET, &ipn.in_nsrc.na_addr[0].in4, rhost, rhostlen); /* in_nsrcip */ #else inet_ntop(AF_INET, &ipn.in_src[0].in4, rhost, rhostlen); #endif #if IPFILTER_VERSION >= 5000000 inet_ntop(AF_INET, &ipn.in_ndst.na_addr[0].in4, iaddr, iaddrlen); /* in_ndstip */ #else inet_ntop(AF_INET, &ipn.in_in[0].in4, iaddr, iaddrlen); #endif r = 0; } } while (ipn.in_next != NULL); return r; } static int real_delete_redirect_rule(const char * ifname, unsigned short eport, int proto) { ipfgeniter_t iter; ipfobj_t obj; ipnat_t ipn; int r; UNUSED(ifname); memset(&obj, 0, sizeof(obj)); obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_type = IPFOBJ_GENITER; obj.ipfo_size = sizeof(iter); obj.ipfo_ptr = &iter; iter.igi_type = IPFGENITER_IPNAT; #if IPFILTER_VERSION > 4011300 iter.igi_nitems = 1; #endif iter.igi_data = &ipn; if (dev < 0) { syslog(LOG_ERR, "%s not open", IPNAT_NAME); return -1; } r = -1; do { if (ioctl(dev, SIOCGENITER, &obj) == -1) { syslog(LOG_ERR, "%s:ioctl(SIOCGENITER): %m", "delete_redirect_rule"); break; } #if IPFILTER_VERSION >= 5000000 if (eport == ntohs(ipn.in_dpmin) && eport == ntohs(ipn.in_dpmax) && strcmp(ipn.in_tag.ipt_tag, group_name) == 0 && ipn.in_pr[0] == proto) #else if (eport == ntohs(ipn.in_pmin) && eport == ntohs(ipn.in_pmax) && strcmp(ipn.in_tag.ipt_tag, group_name) == 0 && ipn.in_p == proto) #endif { obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_size = sizeof(ipn); obj.ipfo_ptr = &ipn; obj.ipfo_type = IPFOBJ_IPNAT; r = ioctl(dev, SIOCRMNAT, &obj); if (r == -1) syslog(LOG_ERR, "%s:ioctl(SIOCRMNAT): %m", "delete_redirect_rule"); /* Delete the desc even if the above failed */ del_redirect_desc(eport, proto); break; } } while (ipn.in_next != NULL); return r; } /* FIXME: For some reason, the iter isn't reset every other delete, * so we attempt 2 deletes. */ int delete_redirect_rule(const char * ifname, unsigned short eport, int proto) { int r; r = real_delete_redirect_rule(ifname, eport, proto); if (r == -1) r = real_delete_redirect_rule(ifname, eport, proto); return r; } /* thanks to Seth Mos for this function */ int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { ipfobj_t obj; frentry_t fr; fripf_t ipffr; int r; UNUSED(ifname); UNUSED(desc); UNUSED(iport); if (dev_ipl < 0) { syslog(LOG_ERR, "%s not open", IPL_NAME); return -1; } memset(&obj, 0, sizeof(obj)); memset(&fr, 0, sizeof(fr)); memset(&ipffr, 0, sizeof(ipffr)); fr.fr_flags = FR_PASS|FR_KEEPSTATE|FR_QUICK|FR_INQUE; if (GETFLAG(LOGPACKETSMASK)) fr.fr_flags |= FR_LOG|FR_LOGFIRST; #if IPFILTER_VERSION >= 5000000 fr.fr_family = PF_INET; #else fr.fr_v = 4; #endif fr.fr_type = FR_T_IPF; fr.fr_dun.fru_ipf = &ipffr; fr.fr_dsize = sizeof(ipffr); fr.fr_isc = (void *)-1; fr.fr_proto = proto; fr.fr_mproto = 0xff; fr.fr_dcmp = FR_EQUAL; fr.fr_dport = eport; #ifdef USE_IFNAME_IN_RULES if (ifname) { #if IPFILTER_VERSION >= 5000000 /* XXX check for stack overflow ! */ fr.fr_ifnames[0] = fr.fr_namelen; strlcpy(fr.fr_names + fr.fr_ifnames[0], ifname, IFNAMSIZ); fr.fr_namelen += strlen(ifname) + 1; #else strlcpy(fr.fr_ifnames[0], ifname, IFNAMSIZ); #endif } #endif #if IPFILTER_VERSION >= 5000000 /* XXX check for stack overflow ! */ fr.fr_group = fr.fr_namelen; strlcpy(fr.fr_names + fr.fr_group, group_name, FR_GROUPLEN); fr.fr_namelen += strlen(group_name) + 1; #else strlcpy(fr.fr_group, group_name, sizeof(fr.fr_group)); #endif if (proto == IPPROTO_TCP) { fr.fr_tcpf = TH_SYN; fr.fr_tcpfm = TH_SYN|TH_ACK|TH_RST|TH_FIN|TH_URG|TH_PUSH; } if(rhost && rhost[0] != '\0' && rhost[0] != '*') { inet_pton(AF_INET, rhost, &fr.fr_saddr); fr.fr_smask = 0xffffffff; } inet_pton(AF_INET, iaddr, &fr.fr_daddr); fr.fr_dmask = 0xffffffff; obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_ptr = &fr; obj.ipfo_size = sizeof(fr); r = ioctl(dev_ipl, SIOCINAFR, &obj); if (r == -1) { if (errno == ESRCH) syslog(LOG_ERR, "SIOCINAFR(missing 'head %s' rule?):%m", group_name); else syslog(LOG_ERR, "SIOCINAFR:%m"); } return r; } int delete_filter_rule(const char * ifname, unsigned short eport, int proto) { ipfobj_t wobj, dobj; ipfruleiter_t rule; u_long darray[1000]; u_long array[1000]; friostat_t fio; frentry_t *fp; int r; UNUSED(ifname); if (dev_ipl < 0) { syslog(LOG_ERR, "%s not open", IPL_NAME); return -1; } wobj.ipfo_rev = IPFILTER_VERSION; wobj.ipfo_type = IPFOBJ_IPFSTAT; wobj.ipfo_size = sizeof(fio); wobj.ipfo_ptr = &fio; if (ioctl(dev_ipl, SIOCGETFS, &wobj) == -1) { syslog(LOG_ERR, "ioctl(SIOCGETFS): %m"); return -1; } wobj.ipfo_rev = IPFILTER_VERSION; wobj.ipfo_ptr = &rule; wobj.ipfo_size = sizeof(rule); wobj.ipfo_type = IPFOBJ_IPFITER; fp = (frentry_t *)array; fp->fr_dun.fru_data = darray; fp->fr_dsize = sizeof(darray); rule.iri_inout = 0; rule.iri_active = fio.f_active; #if IPFILTER_VERSION > 4011300 rule.iri_nrules = 1; rule.iri_v = 4; #endif rule.iri_rule = fp; strlcpy(rule.iri_group, group_name, sizeof(rule.iri_group)); dobj.ipfo_rev = IPFILTER_VERSION; dobj.ipfo_size = sizeof(*fp); dobj.ipfo_type = IPFOBJ_FRENTRY; r = -1; do { memset(array, 0xff, sizeof(array)); if (ioctl(dev_ipl, SIOCIPFITER, &wobj) == -1) { syslog(LOG_ERR, "ioctl(SIOCIPFITER): %m"); break; } if (fp->fr_data != NULL) fp->fr_data = (char *)fp + sizeof(*fp); if ((fp->fr_type & ~FR_T_BUILTIN) == FR_T_IPF && fp->fr_dport == eport && fp->fr_proto == proto) { dobj.ipfo_ptr = fp; r = ioctl(dev_ipl, SIOCRMAFR, &dobj); if (r == -1) syslog(LOG_ERR, "ioctl(SIOCRMAFR): %m"); break; } } while (fp->fr_next != NULL); return r; } unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number) { unsigned short *array, *array2; unsigned int capacity; unsigned short eport; ipfgeniter_t iter; ipfobj_t obj; ipnat_t ipn; *number = 0; if (dev < 0) { syslog(LOG_ERR, "%s not open", IPNAT_NAME); return NULL; } capacity = 128; array = calloc(capacity, sizeof(unsigned short)); if(!array) { syslog(LOG_ERR, "get_portmappings_in_range() : calloc error"); return NULL; } memset(&obj, 0, sizeof(obj)); obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_ptr = &iter; obj.ipfo_size = sizeof(iter); obj.ipfo_type = IPFOBJ_GENITER; iter.igi_type = IPFGENITER_IPNAT; #if IPFILTER_VERSION > 4011300 iter.igi_nitems = 1; #endif iter.igi_data = &ipn; do { if (ioctl(dev, SIOCGENITER, &obj) == -1) { syslog(LOG_ERR, "%s:ioctl(SIOCGENITER): %m", "get_portmappings_in_range"); break; } if (strcmp(ipn.in_tag.ipt_tag, group_name) != 0) continue; #if IPFILTER_VERSION >= 5000000 eport = ntohs(ipn.in_dpmin); if( (eport == ntohs(ipn.in_dpmax)) && (ipn.in_pr[0] == proto) && (startport <= eport) && (eport <= endport) ) #else eport = ntohs(ipn.in_pmin); if( (eport == ntohs(ipn.in_pmax)) && (ipn.in_p == proto) && (startport <= eport) && (eport <= endport) ) #endif { if(*number >= capacity) { /* need to increase the capacity of the array */ capacity += 128; array2 = realloc(array, sizeof(unsigned short)*capacity); if(!array2) { syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%lu) error", sizeof(unsigned short)*capacity); *number = 0; free(array); return NULL; } array = array2; } array[*number] = eport; (*number)++; } } while (ipn.in_next != NULL); return array; } /* update the port mapping internal port, description and timestamp */ int update_portmapping(const char * ifname, unsigned short eport, int proto, unsigned short iport, const char * desc, unsigned int timestamp) { UNUSED(ifname); UNUSED(eport); UNUSED(proto); UNUSED(iport); UNUSED(desc); UNUSED(timestamp); /* TODO: implement update_portmapping() */ syslog(LOG_ERR, __FILE__ " update_portmapping() is not implemented"); return -1; } /* update the port mapping description and timestamp */ 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, timestamp, desc); return 0; } miniupnpd-2.3.7/ipf/ipfrdr.h010064400017500000024000000032441203340734100151250ustar00nanardstaff/* $Id: ipfrdr.h,v 1.6 2012/09/27 15:44:10 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2007 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef IPFRDR_H_INCLUDED #define IPFRDR_H_INCLUDED #include "../commonrdr.h" int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc); /* get_redirect_rule() gets internal IP and port from * interface, external port and protocl */ #if 0 int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes); int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes); #endif /* delete_redirect_rule() */ int delete_redirect_rule(const char * ifname, unsigned short eport, int proto); /* delete_filter_rule() */ int delete_filter_rule(const char * ifname, unsigned short eport, int proto); int clear_redirect_rules(void); #endif miniupnpd-2.3.7/ipf/Makefile010064400017500000024000000006201214663314100151250ustar00nanardstaff# $Id: Makefile,v 1.2 2013/05/21 08:54:57 nanard Exp $ CC=gcc CFLAGS=-Wall -g -I. #CFLAGS=-Wall -g -I. -I../../../../ip_fil5.1.2/ #CFLAGS=-Wall -g -I. -I../../../../ip_fil4.1.35/ #CFLAGS=-Wall -g -I. -I../../../../ip_fil3.4.35/ all: testipfrdr clean: rm -f *.o testipfrdr testipfrdr: testipfrdr.o ipfrdr.o $(CC) -o $@ ${.ALLSRC} # $(CC) -o $@ $^ ipfrdr.o: ipfrdr.c testipfrdr.o: testipfrdr.c miniupnpd-2.3.7/ipf/testipfrdr.c010064400017500000024000000032421173213203200160130ustar00nanardstaff/* $Id: testipfrdr.c,v 1.4 2012/03/19 21:14:13 nanard Exp $ */ #include #include #include #include #include #include "ipfrdr.h" /* test program for ipfrdr.c */ int runtime_flags = 0; void list_eports_tcp(void) { unsigned short * port_list; unsigned int number = 0; unsigned int i; port_list = get_portmappings_in_range(0, 65535, IPPROTO_TCP, &number); printf("%u ports redirected (TCP) :", number); for(i = 0; i < number; i++) { printf(" %hu", port_list[i]); } printf("\n"); free(port_list); port_list = get_portmappings_in_range(0, 65535, IPPROTO_UDP, &number); printf("%u ports redirected (UDP) :", number); for(i = 0; i < number; i++) { printf(" %hu", port_list[i]); } printf("\n"); free(port_list); } int main(int argc, char * * argv) { char c; openlog("testipfrdrd", LOG_CONS|LOG_PERROR, LOG_USER); if(init_redirect() < 0) { fprintf(stderr, "init_redirect() failed\n"); return 1; } printf("List rdr ports :\n"); list_eports_tcp(); printf("Add redirection !\n"); add_redirect_rule2("xennet0", "*", 12345, "192.168.1.100", 54321, IPPROTO_UDP, "redirection description", 0); add_redirect_rule2("xennet0", "8.8.8.8", 12345, "192.168.1.100", 54321, IPPROTO_TCP, "redirection description", 0); printf("Check redirect rules with \"ipnat -l\" then press any key.\n"); c = getchar(); printf("List rdr ports :\n"); list_eports_tcp(); printf("Delete redirection !\n"); delete_redirect_rule("xennet0", 12345, IPPROTO_UDP); delete_redirect_rule("xennet0", 12345, IPPROTO_TCP); printf("List rdr ports :\n"); list_eports_tcp(); return 0; } miniupnpd-2.3.7/miniupnpdtypes.h010064400017500000024000000017311365604170400161610ustar00nanardstaff/* $Id: miniupnpdtypes.h,v 1.7 2020/05/10 17:54:35 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef MINIUPNPDTYPES_H_INCLUDED #define MINIUPNPDTYPES_H_INCLUDED #include "config.h" #include #include #include /* structure and list for storing lan addresses * with ascii representation and mask */ struct lan_addr_s { char ifname[IFNAMSIZ]; /* example: eth0 */ unsigned int index; /* use if_nametoindex() */ char str[16]; /* example: 192.168.0.1 */ struct in_addr addr, mask; /* ip/mask */ #ifdef MULTIPLE_EXTERNAL_IP char ext_ip_str[16]; struct in_addr ext_ip_addr; #else unsigned long add_indexes; /* mask of indexes of associated interfaces */ #endif LIST_ENTRY(lan_addr_s) list; }; LIST_HEAD(lan_addr_list, lan_addr_s); #endif miniupnpd-2.3.7/miniupnpd.8010064400017500000024000000052171457060356700150260ustar00nanardstaff.TH MINIUPNPD 8 .SH NAME miniupnpd \- UPnP Internet Gateway Device Daemon .SH SYNOPSIS .B miniupnpd .RB [--version] .RB [ "\-f \fIconfig_file" "] [" "\-i \fIext_ifname" "] [" "\-I \fIext_ifname6" "] [" "\-o \fIext_ip" ] .RB [ "\-a \fIlistening_ip" "] [" "\-p \fIport" "] [" \-d "] [" \-U "] [" \-S "] [" \-N ] .RB [ "\-u \fIuuid" "] [" "\-s \fIserial" "] [" "\-m \fImodel_number" ] .RB [ "\-t \fInotify_interval" "] [" "\-P \fIpid_filename" ] .RB [ "\-B \fIdown up" "] [" "\-w \fIurl" "] [" "\-r \fIclean_ruleset_interval" ] .RB [ "\-A \fIpermission rule" "] [" "\-b \fIBOOTID" "] [" \-1 ] .SH DESCRIPTION miniupnpd act as a UPnP Internet Gateway Device. It is designed to run on the gateway between the internet and a NAT'ed LAN. It provides an interface, as defined in the UPnP standard, for enabling clients on the LAN to ask for port redirections. .SH OPTIONS .TP .BI \-f " config_file" load the config from file. .TP .BI \-i " ext_ifname" interface used to connect to the internet. .TP .BI \-o " ext_ip" address used to connect to the internet. default address of the interface will be used if not specified. .TP .BI \-a " listening_ip" address on the LAN. \-a option can by used multiple time if LAN is subdivised in several subnetworks. .TP .BI \-p " port" port used for HTTP. .TP .B \-d debug mode : do not go to background, output messages on console and do not filter out low priority messages. .TP .B \-U report system uptime instead of daemon uptime to clients. .TP .B \-S sets "secure" mode : clients can only add mappings to their own ip .TP .B \-N enables NAT-PMP functionality. .TP .BI \-u " uuid" set the uuid of the UPnP Internet Gateway Device. .TP .BI \-s " serial" serial number for the UPnP Internet Gateway Device. .TP .BI \-m " model_number" model number for the UPnP Internet Gateway Device. .TP .BI \-t " notify_interval" SSDP notify interval in seconds : SSDP announce messages will be broadcasted at this interval. .TP .BI \-P " pid_filename" pid file. default is /var/run/miniupnpd.pid .TP .BI \-B " down up" download and upload bitrates reported to clients. .TP .BI \-w " url" presentation url. default is first address on LAN, port 80. .TP .BI \-r " clean_ruleset_interval" (minimum) interval between unused rules cleaning checks. .TP .BI \-A " permission rule" use following syntax for permission rules : (allow|deny) (external port range) ip/mask (internal port range) .br examples : "allow 1024-65535 192.168.1.0/24 1024-65535" "deny 0-65535 0.0.0.0/0 0-65535" .TP .BI \-b " BOOTID" sets the value of BOOTID.UPNP.ORG SSDP header .TP .B \-1 force reporting IGDv1 in rootDesc when compiled as IGDv2 *use with care* .SH "SEE ALSO" minissdpd(1) miniupnpc(3) .SH BUGS miniupnpd-2.3.7/natpmp.h010064400017500000024000000024621233640450700143700ustar00nanardstaff/* $Id: natpmp.h,v 1.13 2014/05/19 13:38:03 nanard Exp $ */ /* MiniUPnP project * author : Thomas Bernard * website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ */ #ifndef NATPMP_H_INCLUDED #define NATPMP_H_INCLUDED /* The NAT-PMP specification which can be found at the url : * http://files.dns-sd.org/draft-cheshire-nat-pmp.txt * draft version 3 of April 2008 * define 5351 as listening port for the gateway, * and the 224.0.0.1 port 5350 as the local link * multicast address for address change announces. * Previous versions of the specification defined 5351 * as the port for address change announces. */ #define NATPMP_PORT (5351) #define NATPMP_NOTIF_PORT (5350) #define NATPMP_NOTIF_ADDR ("224.0.0.1") int OpenAndConfNATPMPSockets(int * sockets); /* receiveraddr is only used with IPV6 sockets */ int ReceiveNATPMPOrPCPPacket(int s, struct sockaddr * senderaddr, socklen_t * senderaddrlen, struct sockaddr_in6 * receiveraddr, unsigned char * msg_buff, size_t msg_buff_size); void ProcessIncomingNATPMPPacket(int s, unsigned char * msg_buff, int len, struct sockaddr_in * senderaddr); void SendNATPMPPublicAddressChangeNotification(int * sockets, int n_sockets); #endif miniupnpd-2.3.7/natpmp.c010064400017500000024000000363011432460530400143570ustar00nanardstaff/* $Id: natpmp.c,v 1.59 2022/10/21 19:44:59 nanard Exp $ */ /* MiniUPnP project * (c) 2007-2022 Thomas Bernard * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * 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 #include "macros.h" #include "rw_unaligned.h" #include "config.h" #include "natpmp.h" #include "upnpglobalvars.h" #include "getifaddr.h" #include "upnpredirect.h" #include "commonrdr.h" #include "upnputils.h" #include "portinuse.h" #include "asyncsendto.h" #ifdef ENABLE_NATPMP int OpenAndConfNATPMPSocket(in_addr_t addr) { int snatpmp; int i = 1; snatpmp = socket(PF_INET, SOCK_DGRAM, 0/*IPPROTO_UDP*/); if(snatpmp<0) { syslog(LOG_ERR, "%s: socket(): %m", "OpenAndConfNATPMPSocket"); return -1; } if(setsockopt(snatpmp, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(SO_REUSEADDR): %m", "OpenAndConfNATPMPSocket"); } if(!set_non_blocking(snatpmp)) { syslog(LOG_WARNING, "%s: set_non_blocking(): %m", "OpenAndConfNATPMPSocket"); } { struct sockaddr_in natpmp_addr; memset(&natpmp_addr, 0, sizeof(natpmp_addr)); natpmp_addr.sin_family = AF_INET; natpmp_addr.sin_port = htons(NATPMP_PORT); /*natpmp_addr.sin_addr.s_addr = INADDR_ANY; */ natpmp_addr.sin_addr.s_addr = addr; if(bind(snatpmp, (struct sockaddr *)&natpmp_addr, sizeof(natpmp_addr)) < 0) { syslog(LOG_ERR, "%s: bind(): %m", "OpenAndConfNATPMPSocket"); close(snatpmp); return -1; } } return snatpmp; } int OpenAndConfNATPMPSockets(int * sockets) { int i; struct lan_addr_s * lan_addr; for(i = 0, lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { sockets[i] = OpenAndConfNATPMPSocket(lan_addr->addr.s_addr); if(sockets[i] < 0) goto error; i++; } return 0; error: while(--i >= 0) { close(sockets[i]); sockets[i] = -1; } return -1; } static void FillPublicAddressResponse(unsigned char * resp, in_addr_t senderaddr) { #ifndef MULTIPLE_EXTERNAL_IP struct in_addr addr; char tmp[16]; UNUSED(senderaddr); if(use_ext_ip_addr) { inet_pton(AF_INET, use_ext_ip_addr, resp+8); } else { 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, &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 */ } } #else struct lan_addr_s * lan_addr; for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (senderaddr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) { memcpy(resp+8, &lan_addr->ext_ip_addr, sizeof(lan_addr->ext_ip_addr)); break; } } #endif } /* * Receives NATPMP and PCP packets and stores them in msg_buff. * The sender information is stored in senderaddr. * Returns number of bytes recevied, even if number is negative. */ int ReceiveNATPMPOrPCPPacket(int s, struct sockaddr * senderaddr, socklen_t * senderaddrlen, struct sockaddr_in6 * receiveraddr, unsigned char * msg_buff, size_t msg_buff_size) { #ifdef IPV6_PKTINFO struct iovec iov; uint8_t c[1000]; struct msghdr msg; int n; struct cmsghdr *h; iov.iov_base = msg_buff; iov.iov_len = msg_buff_size; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = senderaddr; msg.msg_namelen = *senderaddrlen; msg.msg_control = c; msg.msg_controllen = sizeof(c); n = recvmsg(s, &msg, 0); if(n < 0) { /* EAGAIN, EWOULDBLOCK and EINTR : silently ignore (retry next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "recvmsg(natpmp): %m"); } return n; } if(receiveraddr) { memset(receiveraddr, 0, sizeof(struct sockaddr_in6)); } if ((msg.msg_flags & MSG_TRUNC) || (msg.msg_flags & MSG_CTRUNC)) { syslog(LOG_WARNING, "%s: truncated message", "ReceiveNATPMPOrPCPPacket"); } for(h = CMSG_FIRSTHDR(&msg); h; h = CMSG_NXTHDR(&msg, h)) { if(h->cmsg_level == IPPROTO_IPV6 && h->cmsg_type == IPV6_PKTINFO) { char tmp[INET6_ADDRSTRLEN]; struct in6_pktinfo *ipi6 = (struct in6_pktinfo *)CMSG_DATA(h); syslog(LOG_DEBUG, "%s: packet destination: %s scope_id=%u", "ReceiveNATPMPOrPCPPacket", inet_ntop(AF_INET6, &ipi6->ipi6_addr, tmp, sizeof(tmp)), ipi6->ipi6_ifindex); if(receiveraddr) { receiveraddr->sin6_addr = ipi6->ipi6_addr; receiveraddr->sin6_scope_id = ipi6->ipi6_ifindex; receiveraddr->sin6_family = AF_INET6; receiveraddr->sin6_port = htons(NATPMP_PORT); } } } #else /* IPV6_PKTINFO */ int n; n = recvfrom(s, msg_buff, msg_buff_size, 0, senderaddr, senderaddrlen); if(n<0) { /* EAGAIN, EWOULDBLOCK and EINTR : silently ignore (retry next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "recvfrom(natpmp): %m"); } return n; } #endif /* IPV6_PKTINFO */ return n; } /** read the request from the socket, process it and then send the * response back. */ void ProcessIncomingNATPMPPacket(int s, unsigned char *msg_buff, int len, struct sockaddr_in *senderaddr) { unsigned char *req=msg_buff; /* request udp packet */ unsigned char resp[32]; /* response udp packet */ int resplen; int n = len; char senderaddrstr[16]; if(!inet_ntop(AF_INET, &senderaddr->sin_addr, senderaddrstr, sizeof(senderaddrstr))) { syslog(LOG_ERR, "inet_ntop(natpmp): %m"); } syslog(LOG_INFO, "NAT-PMP request received from %s:%hu %dbytes", senderaddrstr, ntohs(senderaddr->sin_port), n); if(n<2 || ((((req[1]-1)&~1)==0) && n<12)) { syslog(LOG_WARNING, "discarding NAT-PMP request (too short) %dBytes", n); return; } if(req[1] & 128) { /* discarding NAT-PMP responses silently */ return; } memset(resp, 0, sizeof(resp)); resplen = 8; resp[1] = 128 + req[1]; /* response OPCODE is request OPCODE + 128 */ /* setting response TIME STAMP : * time elapsed since its port mapping table was initialized on * startup or reset for any other reason */ if(epoch_origin == 0) { epoch_origin = startup_time; } WRITENU32(resp+4, upnp_time() - epoch_origin); if(req[0] > 0) { /* invalid version */ syslog(LOG_WARNING, "unsupported NAT-PMP version : %u", (unsigned)req[0]); resp[3] = 1; /* unsupported version */ } else switch(req[1]) { case 0: /* Public address request */ syslog(LOG_INFO, "NAT-PMP public address request"); FillPublicAddressResponse(resp, senderaddr->sin_addr.s_addr); resplen = 12; break; case 1: /* UDP port mapping request */ case 2: /* TCP port mapping request */ { unsigned short iport; /* private port */ unsigned short eport; /* public port */ uint32_t lifetime; /* lifetime=0 => remove port mapping */ int r; int proto; char iaddr_old[16]; unsigned short iport_old; unsigned int timestamp; iport = READNU16(req+4); eport = READNU16(req+6); lifetime = READNU32(req+8); proto = (req[1]==1)?IPPROTO_UDP:IPPROTO_TCP; syslog(LOG_INFO, "NAT-PMP port mapping request : " "%hu->%s:%hu %s lifetime=%us", eport, senderaddrstr, iport, (req[1]==1)?"udp":"tcp", lifetime); /* TODO: accept port mapping if iport ok but eport not ok * (and set eport correctly) */ if(lifetime == 0) { /* remove the mapping */ /* RFC6886 : * A client MAY also send an explicit packet to request deletion of a * mapping that is no longer needed. A client requests explicit * deletion of a mapping by sending a message to the NAT gateway * requesting the mapping, with the Requested Lifetime in Seconds set to * zero. The Suggested External Port MUST be set to zero by the client * on sending, and MUST be ignored by the gateway on reception. */ int index = 0; unsigned short eport2, iport2; char iaddr2[16]; int proto2; char desc[64]; eport = 0; /* to indicate correct removing of port mapping */ while(get_redirect_rule_by_index(index, 0, &eport2, iaddr2, sizeof(iaddr2), &iport2, &proto2, desc, sizeof(desc), 0, 0, ×tamp, 0, 0) >= 0) { syslog(LOG_DEBUG, "%d %d %hu->'%s':%hu '%s'", index, proto2, eport2, iaddr2, iport2, desc); if(0 == strcmp(iaddr2, senderaddrstr) && 0 == memcmp(desc, "NAT-PMP", 7)) { /* (iport == 0) => remove all the mappings for this client */ if((iport == 0) || ((iport == iport2) && (proto == proto2))) { r = _upnp_delete_redir(eport2, proto2); if(r < 0) { syslog(LOG_ERR, "Failed to remove NAT-PMP mapping eport %hu, protocol %s", eport2, (proto2==IPPROTO_TCP)?"TCP":"UDP"); resp[3] = 2; /* Not Authorized/Refused */ break; } else { syslog(LOG_INFO, "NAT-PMP %s port %hu mapping removed", proto2==IPPROTO_TCP?"TCP":"UDP", eport2); index--; } } } index++; } } else if(iport==0) { resp[3] = 2; /* Not Authorized/Refused */ } else { /* iport > 0 && lifetime > 0 */ unsigned short eport_first = 0; int any_eport_allowed = 0; char desc[64]; if(eport==0) /* if no suggested external port, use same a internal port */ eport = iport; while(resp[3] == 0) { if(eport_first == 0) { /* first time in loop */ eport_first = eport; } else if(eport == eport_first) { /* no eport available */ if(any_eport_allowed == 0) { /* all eports rejected by permissions */ syslog(LOG_ERR, "No allowed eport for NAT-PMP %hu %s->%s:%hu", eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport); resp[3] = 2; /* Not Authorized/Refused */ } else { /* at least one eport allowed (but none 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] = 4; /* Out of resources */ } break; } if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, senderaddr->sin_addr, iport, "NAT-PMP")) { eport++; if(eport == 0) eport++; /* skip port zero */ continue; } any_eport_allowed = 1; /* at lease one eport is allowed */ #ifdef CHECK_PORTINUSE if (port_in_use(ext_if_name, eport, proto, senderaddrstr, iport) > 0) { syslog(LOG_INFO, "port %hu protocol %s already in use", eport, (proto==IPPROTO_TCP)?"tcp":"udp"); eport++; if(eport == 0) eport++; /* skip port zero */ continue; } #endif r = get_redirect_rule(ext_if_name, eport, proto, iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0, 0, 0, ×tamp, 0, 0); if(r==0) { if(strcmp(senderaddrstr, iaddr_old)==0 && iport==iport_old) { /* redirection already existing */ syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing", eport, (proto==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old); /* remove and then add again */ if(_upnp_delete_redir(eport, proto) < 0) { syslog(LOG_ERR, "failed to remove port mapping"); break; } } else { eport++; if(eport == 0) eport++; /* skip port zero */ continue; } } /* do the redirection */ timestamp = upnp_time() + lifetime; snprintf(desc, sizeof(desc), "NAT-PMP %hu %s", eport, (proto==IPPROTO_TCP)?"tcp":"udp"); /* TODO : check return code */ if(upnp_redirect_internal(NULL, eport, senderaddrstr, iport, proto, desc, timestamp) < 0) { syslog(LOG_ERR, "Failed to add NAT-PMP %hu %s->%s:%hu '%s'", eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport, desc); resp[3] = 3; /* Failure */ } break; } } WRITENU16(resp+8, iport); /* private port */ WRITENU16(resp+10, eport); /* public port */ WRITENU32(resp+12, lifetime); /* Port Mapping lifetime */ } resplen = 16; break; default: resp[3] = 5; /* Unsupported OPCODE */ } n = sendto_or_schedule(s, resp, resplen, 0, (struct sockaddr *)senderaddr, sizeof(*senderaddr)); if(n<0) { syslog(LOG_ERR, "sendto(natpmp): %m"); } else if(nlist.le_next; FillPublicAddressResponse(notif, lan_addr->addr.s_addr); } #endif /* Port to use in 2006 version of the NAT-PMP specification */ sockname.sin_port = htons(NATPMP_PORT); n = sendto_or_schedule(sockets[j], notif, 12, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if(n < 0) { syslog(LOG_ERR, "%s: sendto(s_udp=%d, port=%d): %m", "SendNATPMPPublicAddressChangeNotification", sockets[j], NATPMP_PORT); return; } /* Port to use in 2008 version of the NAT-PMP specification */ sockname.sin_port = htons(NATPMP_NOTIF_PORT); n = sendto_or_schedule(sockets[j], notif, 12, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if(n < 0) { syslog(LOG_ERR, "%s: sendto(s_udp=%d, port=%d): %m", "SendNATPMPPublicAddressChangeNotification", sockets[j], NATPMP_NOTIF_PORT); return; } } } #endif /* ENABLE_NATPMP */ miniupnpd-2.3.7/commonrdr.h010064400017500000024000000056301457641723100150770ustar00nanardstaff/* $Id: commonrdr.h,v 1.16 2024/03/11 23:28:19 nanard Exp $ */ /* MiniUPnP project * (c) 2006-2024 Thomas Bernard * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef COMMONRDR_H_INCLUDED #define COMMONRDR_H_INCLUDED #include "config.h" /* init and shutdown functions */ /* init_redirect() return values : * 0 : OK * -1 : error */ int init_redirect(void); void shutdown_redirect(void); /* get_redirect_rule_count() * return value : -1 for error or the number of redirection rules */ int get_redirect_rule_count(const char * ifname); /* get_redirect_rule() gets internal IP and port from * interface, external port and protocol * return value : * 0 success (rule found) * -1 error or rule not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); /* get_redirect_rule_by_index() * return values : * 0 success (rule found) * -1 error or rule not found */ int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); /* return an (malloc'ed) array of "external" port for which there is * a port mapping. number is the size of the array */ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number); /* update the port mapping internal port, description 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 description and timestamp */ int update_portmapping_desc_timestamp(const char * ifname, unsigned short eport, int proto, const char * desc, unsigned int timestamp); #if defined(USE_NETFILTER) /* * only provided by nftables implementation at the moment. * Should be implemented for iptables too, for consistency */ typedef enum { RDR_TABLE_NAME, RDR_NAT_TABLE_NAME, RDR_NAT_PREROUTING_CHAIN_NAME, RDR_NAT_POSTROUTING_CHAIN_NAME, RDR_FORWARD_CHAIN_NAME, RDR_FAMILY_SPLIT, } rdr_name_type; /* * used by the config file parsing in the core * to set */ int set_rdr_name( rdr_name_type param, const char * string ); #endif #endif miniupnpd-2.3.7/upnpevents.h010064400017500000024000000023111317665377200153060ustar00nanardstaff/* $Id: upnpevents.h,v 1.12 2017/11/02 15:48:29 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2008-2017 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPEVENTS_H_INCLUDED #define UPNPEVENTS_H_INCLUDED /* for fd_set */ #include #include "config.h" #ifdef ENABLE_EVENTS enum subscriber_service_enum { EWanCFG = 1, EWanIPC, #ifdef ENABLE_L3F_SERVICE EL3F, #endif #ifdef ENABLE_6FC_SERVICE E6FC, #endif #ifdef ENABLE_DP_SERVICE EDP, #endif }; void upnp_event_var_change_notify(enum subscriber_service_enum service); const char * upnpevents_addSubscriber(const char * eventurl, const char * callback, int callbacklen, int timeout); int upnpevents_removeSubscriber(const char * sid, int sidlen); const char * upnpevents_renewSubscription(const char * sid, int sidlen, int timeout); void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd); void upnpevents_processfds(fd_set *readset, fd_set *writeset); #ifdef USE_MINIUPNPDCTL void write_events_details(int s); #endif #endif #endif miniupnpd-2.3.7/upnpevents.c010064400017500000024000000426021354240146300152710ustar00nanardstaff/* $Id: upnpevents.c,v 1.44 2019/09/24 11:47:06 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2008-2019 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 #include #include #include #include "config.h" #if defined(LIB_UUID) /* as found on linux */ #include #elif defined(BSD_UUID) #include #endif /* LIB_UUID / BSD_UUID */ #include "upnpevents.h" #include "miniupnpdpath.h" #include "upnpglobalvars.h" #include "upnpdescgen.h" #include "upnputils.h" #ifdef ENABLE_EVENTS /*enum subscriber_service_enum { EWanCFG = 1, EWanIPC, EL3F };*/ /* stuctures definitions */ struct subscriber { LIST_ENTRY(subscriber) entries; struct upnp_event_notify * notify; time_t timeout; uint32_t seq; enum subscriber_service_enum service; char uuid[42]; char callback[]; }; struct upnp_event_notify { LIST_ENTRY(upnp_event_notify) entries; int s; /* socket */ enum { ECreated=1, EConnecting, ESending, EWaitingForResponse, EFinished, EError } state; struct subscriber * sub; char * buffer; int buffersize; int tosend; int sent; const char * path; #ifdef ENABLE_IPV6 int ipv6; char addrstr[48]; #else char addrstr[16]; #endif char portstr[8]; }; /* prototypes */ static void upnp_event_create_notify(struct subscriber * sub); /* Subscriber list */ LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; /* notify list */ LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; /* create a new subscriber */ static struct subscriber * newSubscriber(const char * eventurl, const char * callback, int callbacklen) { struct subscriber * tmp; if(!eventurl || !callback || !callbacklen) return NULL; tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); if(!tmp) return NULL; if(strcmp(eventurl, WANCFG_EVENTURL)==0) tmp->service = EWanCFG; else if(strcmp(eventurl, WANIPC_EVENTURL)==0) tmp->service = EWanIPC; #ifdef ENABLE_L3F_SERVICE else if(strcmp(eventurl, L3F_EVENTURL)==0) tmp->service = EL3F; #endif #ifdef ENABLE_6FC_SERVICE else if(strcmp(eventurl, WANIP6FC_EVENTURL)==0) tmp->service = E6FC; #endif #ifdef ENABLE_DP_SERVICE else if(strcmp(eventurl, DP_EVENTURL)==0) tmp->service = EDP; #endif else { free(tmp); return NULL; } memcpy(tmp->callback, callback, callbacklen); tmp->callback[callbacklen] = '\0'; #if defined(LIB_UUID) { uuid_t uuid; uuid_generate(uuid); memcpy(tmp->uuid, "uuid:", 5); uuid_unparse(uuid, tmp->uuid + 5); } #elif defined(BSD_UUID) { uuid_t uuid; uint32_t status; uuid_create(&uuid, &status); if(status != uuid_s_ok) { syslog(LOG_ERR, "uuid_create() failed (%u)", status); } else { char * uuid_str; uuid_to_string(&uuid, &uuid_str, &status); if(status != uuid_s_ok) { syslog(LOG_ERR, "uuid_to_string() failed (%u)", status); } else { if(strlen(uuid_str) != 36) { syslog(LOG_ERR, "uuid_to_string() returned %s", uuid_str); status = (uint32_t)-1; } else { memcpy(tmp->uuid, "uuid:", 5); memcpy(tmp->uuid + 5, uuid_str, 36); tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; } free(uuid_str); } } if(status != uuid_s_ok) { /* make a dummy uuid */ strncpy(tmp->uuid, uuidvalue_igd, sizeof(tmp->uuid)); tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; snprintf(tmp->uuid+sizeof(tmp->uuid)-5, 5, "%04lx", random() & 0xffff); } } #else /* make a dummy uuid */ strncpy(tmp->uuid, uuidvalue_igd, sizeof(tmp->uuid)); tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; snprintf(tmp->uuid+sizeof(tmp->uuid)-5, 5, "%04lx", random() & 0xffff); #endif return tmp; } /* creates a new subscriber and adds it to the subscriber list * also initiate 1st notify * TODO : add a check on the number of subscriber in order to * prevent memory overflow... */ const char * upnpevents_addSubscriber(const char * eventurl, const char * callback, int callbacklen, int timeout) { struct subscriber * tmp; /*static char uuid[42];*/ /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */ syslog(LOG_DEBUG, "addSubscriber(%s, %.*s, %d)", eventurl, callbacklen, callback, timeout); /*strncpy(uuid, uuidvalue, sizeof(uuid)); uuid[sizeof(uuid)-1] = '\0';*/ tmp = newSubscriber(eventurl, callback, callbacklen); if(!tmp) return NULL; if(timeout) tmp->timeout = upnp_time() + timeout; LIST_INSERT_HEAD(&subscriberlist, tmp, entries); upnp_event_create_notify(tmp); return tmp->uuid; } /* renew a subscription (update the timeout) */ const char * upnpevents_renewSubscription(const char * sid, int sidlen, int timeout) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { if((sidlen == 41) && (memcmp(sid, sub->uuid, 41) == 0)) { #ifdef UPNP_STRICT /* check if the subscription already timeouted */ if(sub->timeout && upnp_time() > sub->timeout) continue; #endif sub->timeout = (timeout ? upnp_time() + timeout : 0); return sub->uuid; } } return NULL; } int upnpevents_removeSubscriber(const char * sid, int sidlen) { struct subscriber * sub; if(!sid) return -1; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { if((sidlen == 41) && (memcmp(sid, sub->uuid, 41) == 0)) { if(sub->notify) { sub->notify->sub = NULL; } LIST_REMOVE(sub, entries); free(sub); return 0; } } return -1; } /* notifies all subscriber of a number of port mapping change * or external ip address change */ void upnp_event_var_change_notify(enum subscriber_service_enum service) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { if(sub->service == service && sub->notify == NULL) upnp_event_create_notify(sub); } } /* create and add the notify object to the list */ static void upnp_event_create_notify(struct subscriber * sub) { struct upnp_event_notify * obj; /*struct timeval sock_timeout;*/ obj = calloc(1, sizeof(struct upnp_event_notify)); if(!obj) { syslog(LOG_ERR, "%s: calloc(): %m", "upnp_event_create_notify"); return; } obj->sub = sub; obj->state = ECreated; #ifdef ENABLE_IPV6 obj->s = socket((obj->sub->callback[7] == '[') ? PF_INET6 : PF_INET, SOCK_STREAM, 0); #else obj->s = socket(PF_INET, SOCK_STREAM, 0); #endif if(obj->s<0) { syslog(LOG_ERR, "%s: socket(): %m", "upnp_event_create_notify"); goto error; } #if 0 /* does not work for non blocking connect() */ /* set timeout to 3 seconds */ sock_timeout.tv_sec = 3; sock_timeout.tv_usec = 0; if(setsockopt(obj->s, SOL_SOCKET, SO_RCVTIMEO, &sock_timeout, sizeof(struct timeval)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(SO_RCVTIMEO): %m", "upnp_event_create_notify"); } sock_timeout.tv_sec = 3; sock_timeout.tv_usec = 0; if(setsockopt(obj->s, SOL_SOCKET, SO_SNDTIMEO, &sock_timeout, sizeof(struct timeval)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(SO_SNDTIMEO): %m", "upnp_event_create_notify"); } #endif /* set socket non blocking */ if(!set_non_blocking(obj->s)) { syslog(LOG_ERR, "%s: set_non_blocking(): %m", "upnp_event_create_notify"); goto error; } if(sub) sub->notify = obj; LIST_INSERT_HEAD(¬ifylist, obj, entries); return; error: if(obj->s >= 0) close(obj->s); free(obj); } static void upnp_event_notify_connect(struct upnp_event_notify * obj) { unsigned int i; const char * p; unsigned short port; #ifdef ENABLE_IPV6 struct sockaddr_storage addr; socklen_t addrlen; #else struct sockaddr_in addr; socklen_t addrlen; #endif if(!obj) return; memset(&addr, 0, sizeof(addr)); i = 0; if(obj->sub == NULL) { obj->state = EError; return; } p = obj->sub->callback; p += 7; /* http:// */ #ifdef ENABLE_IPV6 if(*p == '[') { /* ip v6 */ obj->addrstr[i++] = '['; p++; obj->ipv6 = 1; while(*p != ']' && i < (sizeof(obj->addrstr)-1)) obj->addrstr[i++] = *(p++); if(*p == ']') p++; if(i < (sizeof(obj->addrstr)-1)) obj->addrstr[i++] = ']'; } else { #endif while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1)) obj->addrstr[i++] = *(p++); #ifdef ENABLE_IPV6 } #endif obj->addrstr[i] = '\0'; if(*p == ':') { obj->portstr[0] = *p; i = 1; p++; port = (unsigned short)atoi(p); while(*p != '\0' && *p != '/') { if(i<7) obj->portstr[i++] = *p; p++; } obj->portstr[i] = 0; } else { port = 80; obj->portstr[0] = '\0'; } obj->path = p; #ifdef ENABLE_IPV6 if(obj->ipv6) { char addrstr_tmp[48]; struct sockaddr_in6 * sa = (struct sockaddr_in6 *)&addr; sa->sin6_family = AF_INET6; i = (int)strlen(obj->addrstr); if(i > 2) { i -= 2; memcpy(addrstr_tmp, obj->addrstr + 1, i); addrstr_tmp[i] = '\0'; inet_pton(AF_INET6, addrstr_tmp, &(sa->sin6_addr)); } sa->sin6_port = htons(port); addrlen = sizeof(struct sockaddr_in6); } else { struct sockaddr_in * sa = (struct sockaddr_in *)&addr; sa->sin_family = AF_INET; inet_pton(AF_INET, obj->addrstr, &(sa->sin_addr)); sa->sin_port = htons(port); addrlen = sizeof(struct sockaddr_in); } #else addr.sin_family = AF_INET; inet_aton(obj->addrstr, &addr.sin_addr); addr.sin_port = htons(port); addrlen = sizeof(struct sockaddr_in); #endif syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect", obj->addrstr, port, obj->path); obj->state = EConnecting; if(connect(obj->s, (struct sockaddr *)&addr, addrlen) < 0) { if(errno != EINPROGRESS && errno != EWOULDBLOCK) { syslog(LOG_ERR, "%s: connect(%d, %s, %u): %m", "upnp_event_notify_connect", obj->s, obj->addrstr, addrlen); obj->state = EError; } } } static void upnp_event_prepare(struct upnp_event_notify * obj) { static const char notifymsg[] = "NOTIFY %s HTTP/1.1\r\n" "Host: %s%s\r\n" #if (UPNP_VERSION_MAJOR == 1) && (UPNP_VERSION_MINOR == 0) "Content-Type: text/xml\r\n" /* UDA v1.0 */ #else "Content-Type: text/xml; charset=\"utf-8\"\r\n" /* UDA v1.1 or later */ #endif "Content-Length: %d\r\n" "NT: upnp:event\r\n" "NTS: upnp:propchange\r\n" "SID: %s\r\n" "SEQ: %u\r\n" "Connection: close\r\n" "Cache-Control: no-cache\r\n" "\r\n" "%.*s\r\n"; char * xml; int l; if(obj->sub == NULL) { obj->state = EError; return; } switch(obj->sub->service) { case EWanCFG: xml = getVarsWANCfg(&l); break; case EWanIPC: xml = getVarsWANIPCn(&l); break; #ifdef ENABLE_L3F_SERVICE case EL3F: xml = getVarsL3F(&l); break; #endif #ifdef ENABLE_6FC_SERVICE case E6FC: xml = getVars6FC(&l); break; #endif #ifdef ENABLE_DP_SERVICE case EDP: xml = getVarsDP(&l); break; #endif default: xml = NULL; l = 0; } obj->buffersize = 1024; for (;;) { obj->buffer = malloc(obj->buffersize); if(!obj->buffer) { syslog(LOG_ERR, "%s: malloc returned NULL", "upnp_event_prepare"); if(xml) { free(xml); } obj->state = EError; return; } obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg, (obj->path[0] != '\0') ? obj->path : "/", obj->addrstr, obj->portstr, l+2, obj->sub->uuid, obj->sub->seq, l, xml); if (obj->tosend < 0) { syslog(LOG_ERR, "%s: snprintf() failed", "upnp_event_prepare"); if(xml) { free(xml); } obj->state = EError; return; } else if (obj->tosend < obj->buffersize) { break; /* the buffer was large enough */ } /* Try again with a buffer big enough */ free(obj->buffer); obj->buffersize = obj->tosend + 1; /* reserve space for the final 0 */ } if(xml) { free(xml); xml = NULL; } obj->state = ESending; } static void upnp_event_send(struct upnp_event_notify * obj) { int i; syslog(LOG_DEBUG, "%s: sending event notify message to %s%s", "upnp_event_send", obj->addrstr, obj->portstr); syslog(LOG_DEBUG, "%s: msg: %s", "upnp_event_send", obj->buffer + obj->sent); i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); if(i<0) { if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_NOTICE, "%s: send(%s%s): %m", "upnp_event_send", obj->addrstr, obj->portstr); obj->state = EError; return; } else { /* EAGAIN or EWOULDBLOCK or EINTR : no data sent */ i = 0; } } if(i != (obj->tosend - obj->sent)) syslog(LOG_NOTICE, "%s: %d bytes send out of %d", "upnp_event_send", i, obj->tosend - obj->sent); obj->sent += i; if(obj->sent == obj->tosend) obj->state = EWaitingForResponse; } static void upnp_event_recv(struct upnp_event_notify * obj) { int n; n = recv(obj->s, obj->buffer, obj->buffersize, 0); if(n<0) { if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv"); obj->state = EError; } return; } syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv", n, n, obj->buffer); /* TODO : do something with the data recevied ? * right now, n (number of bytes received) is ignored * We may need to recv() more bytes. */ obj->state = EFinished; if(obj->sub) obj->sub->seq++; } static void upnp_event_process_notify(struct upnp_event_notify * obj) { int err; socklen_t len; switch(obj->state) { case EConnecting: /* now connected or failed to connect */ len = sizeof(err); if(getsockopt(obj->s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { syslog(LOG_ERR, "%s: getsockopt: %m", "upnp_event_process_notify"); obj->state = EError; break; } if(err != 0) { errno = err; syslog(LOG_WARNING, "%s: connect(%s%s): %m", "upnp_event_process_notify", obj->addrstr, obj->portstr); obj->state = EError; break; } upnp_event_prepare(obj); if(obj->state == ESending) upnp_event_send(obj); break; case ESending: upnp_event_send(obj); break; case EWaitingForResponse: upnp_event_recv(obj); break; case EFinished: close(obj->s); obj->s = -1; break; default: syslog(LOG_ERR, "%s: unknown state", "upnp_event_process_notify"); } } void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) { struct upnp_event_notify * obj; for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d", obj, obj->state, obj->s); if(obj->s >= 0) { switch(obj->state) { case ECreated: upnp_event_notify_connect(obj); if(obj->state != EConnecting) break; #if defined(__GNUC__) && (__GNUC__ >= 7) __attribute__ ((fallthrough)); #endif case EConnecting: case ESending: FD_SET(obj->s, writeset); if(obj->s > *max_fd) *max_fd = obj->s; break; case EWaitingForResponse: FD_SET(obj->s, readset); if(obj->s > *max_fd) *max_fd = obj->s; break; default: ; } } } } void upnpevents_processfds(fd_set *readset, fd_set *writeset) { struct upnp_event_notify * obj; struct upnp_event_notify * next; struct subscriber * sub; struct subscriber * subnext; time_t curtime; for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { syslog(LOG_DEBUG, "%s: %p %d %d %d %d", "upnpevents_processfds", obj, obj->state, obj->s, FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); if(obj->s >= 0) { if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset)) upnp_event_process_notify(obj); } } obj = notifylist.lh_first; while(obj != NULL) { next = obj->entries.le_next; if(obj->state == EError || obj->state == EFinished) { if(obj->s >= 0) { close(obj->s); } if(obj->sub) obj->sub->notify = NULL; /* remove also the subscriber from the list if there was an error */ if(obj->state == EError && obj->sub) { syslog(LOG_ERR, "%s: %p, remove subscriber %s after an ERROR cb: %s", "upnpevents_processfds", obj, obj->sub->uuid, obj->sub->callback); LIST_REMOVE(obj->sub, entries); free(obj->sub); } if(obj->buffer) { free(obj->buffer); } LIST_REMOVE(obj, entries); free(obj); } obj = next; } /* remove timeouted subscribers */ curtime = upnp_time(); for(sub = subscriberlist.lh_first; sub != NULL; ) { subnext = sub->entries.le_next; if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) { syslog(LOG_INFO, "subscriber timeouted : %u > %u SID=%s", (unsigned)curtime, (unsigned)sub->timeout, sub->uuid); LIST_REMOVE(sub, entries); free(sub); } sub = subnext; } } #ifdef USE_MINIUPNPDCTL void write_events_details(int s) { int n; char buff[80]; struct upnp_event_notify * obj; struct subscriber * sub; write(s, "Events details :\n", 17); for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n", obj, obj->sub, obj->state, obj->s); write(s, buff, n); } write(s, "Subscribers :\n", 14); for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n", sub, (int)sub->timeout, sub->seq, sub->service); write(s, buff, n); n = snprintf(buff, sizeof(buff), " notify=%p %s\n", sub->notify, sub->uuid); write(s, buff, n); n = snprintf(buff, sizeof(buff), " %s\n", sub->callback); write(s, buff, n); } } #endif #endif miniupnpd-2.3.7/codelength.h010064400017500000024000000030251255150331000151700ustar00nanardstaff/* $Id: codelength.h,v 1.5 2015/07/09 12:40:18 nanard Exp $ */ /* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2005-2015 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #ifndef CODELENGTH_H_INCLUDED #define CODELENGTH_H_INCLUDED /* Encode length by using 7bit per Byte : * Most significant bit of each byte specifies that the * following byte is part of the code */ /* n : unsigned * p : unsigned char * */ #define DECODELENGTH(n, p) n = 0; \ do { n = (n << 7) | (*p & 0x7f); } \ while((*(p++)&0x80) && (n<(1<<25))); /* n : unsigned * READ : function/macro to read one byte (unsigned char) */ #define DECODELENGTH_READ(n, READ) \ n = 0; \ do { \ unsigned char c; \ READ(c); \ n = (n << 7) | (c & 0x07f); \ if(!(c&0x80)) break; \ } while(n<(1<<25)); /* n : unsigned * p : unsigned char * * p_limit : unsigned char * */ #define DECODELENGTH_CHECKLIMIT(n, p, p_limit) \ n = 0; \ do { \ if((p) >= (p_limit)) break; \ n = (n << 7) | (*(p) & 0x7f); \ } while((*((p)++)&0x80) && (n<(1<<25))); /* n : unsigned * p : unsigned char * */ #define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ if(n>=16384) *(p++) = (n >> 14) | 0x80; \ if(n>=128) *(p++) = (n >> 7) | 0x80; \ *(p++) = n & 0x7f; #endif /* CODELENGTH_H_INCLUDED */ miniupnpd-2.3.7/testgetifaddr.c010064400017500000024000000027331230717300600157110ustar00nanardstaff/* $Id: testgetifaddr.c,v 1.8 2014/03/09 23:09:36 nanard Exp $ */ /* 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 "config.h" #include "getifaddr.h" #if defined(__sun) /* solaris 10 does not define LOG_PERROR */ #define LOG_PERROR 0 #endif int main(int argc, char * * argv) { char str_addr[64]; struct in_addr addr; struct in_addr mask; #ifdef ENABLE_IPV6 int r; char str_addr6[64]; #endif if(argc < 2) { fprintf(stderr, "Usage:\t%s interface_name\n", argv[0]); return 1; } openlog("testgetifaddr", LOG_CONS|LOG_PERROR, LOG_USER); if(getifaddr(argv[1], str_addr, sizeof(str_addr), &addr, &mask) < 0) { fprintf(stderr, "Cannot get address for interface %s.\n", argv[1]); return 1; } printf("Interface %s has IP address %s.\n", argv[1], str_addr); printf("addr=%s ", inet_ntoa(addr)); printf("mask=%s\n", inet_ntoa(mask)); #ifdef ENABLE_IPV6 r = find_ipv6_addr(argv[1], str_addr6, sizeof(str_addr6)); if(r < 0) { fprintf(stderr, "find_ipv6_addr() failed\n"); return 1; } else if(r == 0) { printf("Interface %s has no IPv6 address.\n", argv[1]); } else { printf("Interface %s has IPv6 address %s.\n", argv[1], str_addr6); } #endif return 0; } miniupnpd-2.3.7/Makefile.macosx010064400017500000024000000105111365604170100156420ustar00nanardstaff# MiniUPnP project # http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ # Author: Thomas Bernard # This Makefile should work for MacOSX # # To compile with pf with OS X 10.7+, you need to specify # path to XNU bsd sources : # INCLUDES="-I.../xnu/bsd -I.../xnu/libkern" make -f Makefile.macosx # # To install use : # $ PREFIX=/dummyinstalldir make -f Makefile.macosx install # or : # $ make -f Makefile.macosx install # CFLAGS = -Wall -O -g3 -DDEBUG #CFLAGS = -Wall -Os #CC = gcc #better use clang ! RM = rm -f MV = mv INSTALL = install STRIP = strip # OSNAME and FWNAME are used for building OS or FW dependent code. OSNAME = $(shell uname) ARCH = $(shell uname -p) # Firewall is ipfw up to OS X 10.6 Snow Leopard # and pf since OS X 10.7 Lion (Darwin 11.0) 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 \ 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 PF_OBJS = pf/obsdrdr.o # pf/pfpinhole.o # SHOULD be used, but doesn't compile on e.g. OS X 10.9. MISC_OBJS = upnpreplyparse.o minixml.o ALL_OBJS = $(STD_OBJS) $(MISC_OBJS) $(MAC_OBJS) ifeq ($(FWNAME), ipfw) ALL_OBJS += $(IPFW_OBJS) else ALL_OBJS += $(PF_OBJS) CFLAGS += -DPF endif TEST_UPNPDESCGEN_OBJS = testupnpdescgen.o upnpdescgen.o TEST_GETIFSTATS_OBJS = testgetifstats.o mac/getifstats.o TEST_UPNPPERMISSIONS_OBJS = testupnppermissions.o upnppermissions.o TEST_GETIFADDR_OBJS = testgetifaddr.o getifaddr.o TEST_PORTINUSE_OBJS = testportinuse.o portinuse.o getifaddr.o TEST_ASYNCSENDTO_OBJS = testasyncsendto.o asyncsendto.o upnputils.o bsd/getroute.o MINIUPNPDCTL_OBJS = miniupnpdctl.o EXECUTABLES = miniupnpd testupnpdescgen testgetifstats \ testupnppermissions miniupnpdctl \ testgetifaddr testportinuse testasyncsendto LIBS = # set PREFIX variable to install in the wanted place INSTALL_BINDIR = $(PREFIX)/sbin INSTALL_ETCDIR = $(PREFIX)/etc/miniupnpd INSTALL_MANDIR = $(PREFIX)/share/man/man8 all: $(EXECUTABLES) clean: $(RM) $(ALL_OBJS) $(EXECUTABLES) \ testupnpdescgen.o testgetifstats.o testupnppermissions.o \ miniupnpdctl.o testgetifaddr.o config.h \ mac/org.tuxfamily.miniupnpd.plist install: miniupnpd genuuid genlaunchd $(STRIP) miniupnpd $(INSTALL) -d $(INSTALL_BINDIR) $(INSTALL) miniupnpd $(INSTALL_BINDIR) $(INSTALL) -d $(INSTALL_ETCDIR) $(INSTALL) -m 0644 -b miniupnpd.conf $(INSTALL_ETCDIR) $(INSTALL) -d $(INSTALL_MANDIR) $(INSTALL) miniupnpd.8 $(INSTALL_MANDIR) $(INSTALL) -d $(PREFIX)/Library/LaunchDaemons $(INSTALL) mac/org.tuxfamily.miniupnpd.plist $(PREFIX)/Library/LaunchDaemons #$(INSTALL) ipfw/ipfw_init.sh $(INSTALL_ETCDIR) #$(INSTALL) ipfw/ipfw_removeall.sh $(INSTALL_ETCDIR) genuuid: $(MV) miniupnpd.conf miniupnpd.conf.before sed -e "s/^uuid=[-0-9a-fA-F]*/uuid=`uuidgen 2>/dev/null`/" miniupnpd.conf.before > miniupnpd.conf $(RM) miniupnpd.conf.before genlaunchd: sed -e "s|INSTALLPREFIX|$(PREFIX)|g" mac/org.tuxfamily.miniupnpd.plist.before > mac/org.tuxfamily.miniupnpd.plist depend: config.h mkdep $(ALL_OBJS:.o=.c) testupnpdescgen.c testgetifstats.c \ testupnppermissions.c miniupnpdctl.c testgetifaddr.c miniupnpd: config.h $(ALL_OBJS) $(CC) $(CFLAGS) -o $@ $(ALL_OBJS) $(LIBS) miniupnpdctl: config.h $(MINIUPNPDCTL_OBJS) $(CC) $(CFLAGS) -o $@ $(MINIUPNPDCTL_OBJS) testupnpdescgen: config.h $(TEST_UPNPDESCGEN_OBJS) $(CC) $(CFLAGS) -o $@ $(TEST_UPNPDESCGEN_OBJS) testgetifstats: config.h $(TEST_GETIFSTATS_OBJS) $(CC) $(CFLAGS) -o $@ $(TEST_GETIFSTATS_OBJS) $(LIBS) testgetifaddr: config.h $(TEST_GETIFADDR_OBJS) $(CC) $(CFLAGS) -o $@ $(TEST_GETIFADDR_OBJS) testupnppermissions: config.h $(TEST_UPNPPERMISSIONS_OBJS) $(CC) $(CFLAGS) -o $@ $(TEST_UPNPPERMISSIONS_OBJS) testasyncsendto: config.h $(TEST_ASYNCSENDTO_OBJS) $(CC) $(CFLAGS) -o $@ $(TEST_ASYNCSENDTO_OBJS) testportinuse: config.h $(TEST_PORTINUSE_OBJS) $(CC) $(CFLAGS) -o $@ $(TEST_PORTINUSE_OBJS) config.h: configure VERSION ./configure .SUFFIXES: .o .c .c.o: $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $< # $(CC) $(CFLAGS) -c -o $(.TARGET) $(.IMPSRC) miniupnpd-2.3.7/mac/Makefile010064400017500000024000000004061153032367000151100ustar00nanardstaff# $Id: Makefile,v 1.2 2011/02/20 23:43:41 nanard Exp $ # made for GNU Make CFLAGS = -Wall -g EXECUTABLES = testgetifstats all: $(EXECUTABLES) clean: rm -f *.o $(EXECUTABLES) testmacrdr.o: testmacrdr.c macrdr.h testgetifstats: testgetifstats.o getifstats.o miniupnpd-2.3.7/mac/getifstats.c010064400017500000024000000047351365610016700160070ustar00nanardstaff/* $Id: getifstats.c,v 1.9 2020/05/10 22:25:45 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009 Jardel Weyrich * 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 #include "config.h" #include "../getifstats.h" int getifstats(const char * ifname, struct ifdata * data) { int mib[] = { CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_IFLIST, if_nametoindex(ifname) }; const size_t mib_len = sizeof(mib) / sizeof(mib[0]); size_t needed; char *buf, *end, *p; struct if_msghdr *ifm; struct if_data ifdata; #ifdef ENABLE_GETIFSTATS_CACHING static time_t cache_timestamp = 0; static struct ifdata cache_data; time_t current_time; #endif if (data == NULL || ifname == NULL || ifname[0] == '\0') return -1; /* error */ data->baudrate = 4200000; data->opackets = 0; data->ipackets = 0; data->obytes = 0; data->ibytes = 0; #ifdef ENABLE_GETIFSTATS_CACHING current_time = upnp_time(); if (current_time == ((time_t)-1)) { syslog(LOG_ERR, "getifstats() : time() error : %m"); } else { if (current_time < cache_timestamp + GETIFSTATS_CACHING_DURATION) { memcpy(data, &cache_data, sizeof(struct ifdata)); return 0; } } #endif if (sysctl(mib, mib_len, NULL, &needed, NULL, 0) == -1) { syslog(LOG_ERR, "sysctl(): %m"); return -1; } buf = (char *) malloc(needed); if (buf == NULL) return -1; /* error */ if (sysctl(mib, mib_len, buf, &needed, NULL, 0) == -1) { syslog(LOG_ERR, "sysctl(): %m"); free(buf); return -1; /* error */ } else { for (end = buf + needed, p = buf; p < end; p += ifm->ifm_msglen) { ifm = (struct if_msghdr *) p; if (ifm->ifm_type == RTM_IFINFO && ifm->ifm_data.ifi_type == IFT_ETHER) { ifdata = ifm->ifm_data; data->opackets = ifdata.ifi_opackets; data->ipackets = ifdata.ifi_ipackets; data->obytes = ifdata.ifi_obytes; data->ibytes = ifdata.ifi_ibytes; data->baudrate = ifdata.ifi_baudrate; free(buf); #ifdef ENABLE_GETIFSTATS_CACHING if (current_time!=((time_t)-1)) { cache_timestamp = current_time; memcpy(&cache_data, data, sizeof(struct ifdata)); } #endif return 0; /* found, ok */ } } } free(buf); return -1; /* not found or error */ } miniupnpd-2.3.7/mac/testgetifstats.c010064400017500000024000000014221172522177200166760ustar00nanardstaff/* $Id: testgetifstats.c,v 1.4 2012/03/05 20:36:20 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009 Jardel Weyrich * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include "../getifstats.h" int main(int argc, char * * argv) { int r; struct ifdata data; printf("usage: %s if_name\n", argv[0]); if (argc < 2) return -1; r = getifstats(argv[1], &data); if (r < 0) printf("getifstats() failed\n"); else { printf("ipackets = %10lu opackets = %10lu\n", data.ipackets, data.opackets); printf("ibytes = %10lu obytes = %10lu\n", data.ibytes, data.obytes); printf("baudrate = %10lu\n", data.baudrate); } return 0; } miniupnpd-2.3.7/mac/org.tuxfamily.miniupnpd.plist.before010064400017500000024000000007061170030524600226010ustar00nanardstaff miniupnpd org.tuxfamily.miniupnpd ProgramArguments INSTALLPREFIX/sbin/miniupnpd -f INSTALLPREFIX/etc/miniupnpd/miniupnpd.conf RunAtLoad miniupnpd-2.3.7/ipfw/Makefile010064400017500000024000000004131203340734100153100ustar00nanardstaff# $Id: Makefile,v 1.3 2012/09/20 12:46:01 nanard Exp $ CC=gcc CFLAGS=-Wall -g -I. RM=rm -f all: testipfwrdr clean: $(RM) *.o testipfwrdr testipfwrdr: testipfwrdr.o ipfwrdr.o ipfwaux.o $(CC) -o $@ $^ ipfwrdr.o: ipfwrdr.c ipfwaux.c testipfwrdr.o: testipfwrdr.c miniupnpd-2.3.7/ipfw/ipfwaux.c010064400017500000024000000044051202661001100154740ustar00nanardstaff/* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009-2012 Jardel Weyrich * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "ipfwaux.h" #include #include #include #include #include int ipfw_exec(int optname, void * optval, uintptr_t optlen) { static int sock = -1; int result; switch (optname) { case IP_FW_INIT: if (sock == -1) sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (sock < 0) { syslog(LOG_ERR, "socket(SOCK_RAW): %m"); return -1; } break; case IP_FW_TERM: if (sock != -1) close(sock); sock = -1; break; case IP_FW_ADD: case IP_FW_DEL: result = setsockopt(sock, IPPROTO_IP, optname, optval, optlen); if (result == -1) { syslog(LOG_ERR, "setsockopt(): %m"); return -1; } break; case IP_FW_GET: result = getsockopt(sock, IPPROTO_IP, optname, optval, (socklen_t *)optlen); if (result == -1) { syslog(LOG_ERR, "getsockopt(): %m"); return -1; } break; default: syslog(LOG_ERR, "unhandled option"); return -1; } return 0; } void ipfw_free_ruleset(struct ip_fw ** rules) { if (rules == NULL || *rules == NULL) return; free(*rules); *rules = NULL; } int ipfw_fetch_ruleset(struct ip_fw ** rules, int * total_fetched, int count) { int fetched; socklen_t size; if (rules == NULL || *total_fetched < 0 || count < 1) return -1; size = sizeof(struct ip_fw) * (*total_fetched + count); *rules = (struct ip_fw *)realloc(*rules, size); if (*rules == NULL) { syslog(LOG_ERR, "realloc(): %m"); return -1; } (*rules)->version = IP_FW_CURRENT_API_VERSION; if (ipfw_exec(IP_FW_GET, *rules, (uintptr_t)&size) < 0) return -1; fetched = *total_fetched; *total_fetched = size / sizeof(struct ip_fw); return *total_fetched - fetched; } int ipfw_validate_protocol(int value) { switch (value) { case IPPROTO_TCP: case IPPROTO_UDP: break; default: syslog(LOG_ERR, "invalid protocol"); return -1; } return 0; } int ipfw_validate_ifname(const char * const value) { int len = strlen(value); if (len < 2 || len > FW_IFNLEN) { syslog(LOG_ERR, "invalid interface name"); return -1; } return 0; } miniupnpd-2.3.7/ipfw/ipfwaux.h010064400017500000024000000015131257653523200155230ustar00nanardstaff/* $Id: ipfwaux.h,v 1.6 2015/09/04 14:20:58 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009-2012 Jardel Weyrich * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef IPFWAUX_H #define IPFWAUX_H #include #include #include #include #include #define IP_FW_BASE (IP_FW_ADD - 5) #define IP_FW_INIT (IP_FW_BASE + 1) #define IP_FW_TERM (IP_FW_BASE + 2) int ipfw_exec(int optname, void * optval, uintptr_t optlen); void ipfw_free_ruleset(struct ip_fw ** rules); int ipfw_fetch_ruleset(struct ip_fw ** rules, int * total_fetched, int count); int ipfw_validate_protocol(int value); int ipfw_validate_ifname(const char * const value); #endif miniupnpd-2.3.7/ipfw/ipfwrdr.c010064400017500000024000000323501432271534200155040ustar00nanardstaff/* $Id: ipfwrdr.c,v 1.18 2022/02/19 21:44:51 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009 Jardel Weyrich * (c) 2011-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include "../macros.h" #include #include #include /* This is a workaround for troubles on FreeBSD, HPUX, OpenBSD. Needed here because on some systems gets included by things like */ #ifndef _KERNEL # define ADD_KERNEL # define _KERNEL # define KERNEL #endif #ifdef __OpenBSD__ struct file; #endif #include #ifdef ADD_KERNEL # undef _KERNEL # undef KERNEL #endif #include #include #include #include #include #if __FreeBSD_version >= 300000 # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipfwaux.h" #include "ipfwrdr.h" #include "../upnpglobalvars.h" /* This ipfw backend is known to work with OS X up to 10.6. * Some work is needed to support FreeBSD ipfw. */ #ifndef IP_FW_CURRENT_API_VERSION #error "ip_fw.h does not contain supported API" #endif /* init and shutdown functions */ int init_redirect(void) { return ipfw_exec(IP_FW_INIT, NULL, 0); } void shutdown_redirect(void) { ipfw_exec(IP_FW_TERM, NULL, 0); } /* ipfw cannot store descriptions and timestamp for port mappings so we keep * our own list in memory */ struct mapping_desc_time { struct mapping_desc_time * next; unsigned int timestamp; unsigned short eport; short proto; char desc[]; }; static struct mapping_desc_time * mappings_list = NULL; /* add an element to the port mappings descriptions & timestamp list */ static void add_desc_time(unsigned short eport, int proto, const char * desc, unsigned int timestamp) { struct mapping_desc_time * tmp; size_t l; if(!desc) desc = "miniupnpd"; l = strlen(desc) + 1; tmp = malloc(sizeof(struct mapping_desc_time) + l); if(tmp) { /* fill the element and insert it as head of the list */ tmp->next = mappings_list; tmp->timestamp = timestamp; tmp->eport = eport; tmp->proto = (short)proto; memcpy(tmp->desc, desc, l); mappings_list = tmp; } } /* remove an element to the port mappings descriptions & timestamp list */ static void del_desc_time(unsigned short eport, int proto) { struct mapping_desc_time * e; struct mapping_desc_time * * p; p = &mappings_list; e = *p; while(e) { if(e->eport == eport && e->proto == (short)proto) { *p = e->next; free(e); return; } else { p = &e->next; e = *p; } } } /* go through the list and find the description and timestamp */ static void get_desc_time(unsigned short eport, int proto, char * desc, int desclen, unsigned int * timestamp) { struct mapping_desc_time * e; for(e = mappings_list; e; e = e->next) { if(e->eport == eport && e->proto == (short)proto) { if(desc) strlcpy(desc, e->desc, desclen); if(timestamp) *timestamp = e->timestamp; return; } } } /* --- */ int add_redirect_rule2( const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { struct ip_fw rule; int r; if (ipfw_validate_protocol(proto) < 0) return -1; if (ipfw_validate_ifname(ifname) < 0) return -1; memset(&rule, 0, sizeof(struct ip_fw)); rule.version = IP_FW_CURRENT_API_VERSION; #if 0 rule.fw_number = 1000; /* rule number */ rule.context = (void *)desc; /* The description is kept in a separate list */ #endif rule.fw_prot = proto; /* protocol */ rule.fw_flg |= IP_FW_F_IIFACE; /* interfaces to check */ rule.fw_flg |= IP_FW_F_IIFNAME; /* interfaces to check by name */ rule.fw_flg |= (IP_FW_F_IN | IP_FW_F_OUT); /* packet direction */ rule.fw_flg |= IP_FW_F_FWD; /* forward action */ #ifdef USE_IFNAME_IN_RULES if (ifname != NULL) { strlcpy(rule.fw_in_if.fu_via_if.name, ifname, IFNAMSIZ); /* src interface */ rule.fw_in_if.fu_via_if.unit = -1; } #endif if (inet_aton(iaddr, &rule.fw_out_if.fu_via_ip) == 0) { syslog(LOG_ERR, "inet_aton(): %m"); return -1; } memcpy(&rule.fw_dst, &rule.fw_out_if.fu_via_ip, sizeof(struct in_addr)); memcpy(&rule.fw_fwd_ip.sin_addr, &rule.fw_out_if.fu_via_ip, sizeof(struct in_addr)); rule.fw_dmsk.s_addr = INADDR_BROADCAST; /* TODO check this */ IP_FW_SETNDSTP(&rule, 1); /* number of external ports */ rule.fw_uar.fw_pts[0] = eport; /* external port */ rule.fw_fwd_ip.sin_port = iport; /* internal port */ if (rhost && rhost[0] != '\0') { inet_aton(rhost, &rule.fw_src); rule.fw_smsk.s_addr = htonl(INADDR_NONE); } r = ipfw_exec(IP_FW_ADD, &rule, sizeof(rule)); if(r >= 0) add_desc_time(eport, proto, desc, timestamp); return r; } /* get_redirect_rule() * return value : 0 success (found) * -1 = error or rule not found */ int get_redirect_rule( const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int i, count_rules, total_rules = 0; struct ip_fw * rules = NULL; if (ipfw_validate_protocol(proto) < 0) return -1; if (ipfw_validate_ifname(ifname) < 0) return -1; if (timestamp) *timestamp = 0; do { count_rules = ipfw_fetch_ruleset(&rules, &total_rules, 10); if (count_rules < 0) goto error; } while (count_rules == 10); for (i=0; ifw_prot && eport == ptr->fw_uar.fw_pts[0]) { if (packets != NULL) *packets = ptr->fw_pcnt; if (bytes != NULL) *bytes = ptr->fw_bcnt; if (iport != NULL) *iport = ptr->fw_fwd_ip.sin_port; if (iaddr != NULL && iaddrlen > 0) { /* looks like fw_out_if.fu_via_ip is zero */ /*if (inet_ntop(AF_INET, &ptr->fw_out_if.fu_via_ip, iaddr, iaddrlen) == NULL) {*/ if (inet_ntop(AF_INET, &ptr->fw_fwd_ip.sin_addr, iaddr, iaddrlen) == NULL) { syslog(LOG_ERR, "inet_ntop(): %m"); goto error; } } if (rhost != NULL && rhostlen > 0) { if (ptr->fw_src.s_addr == 0) rhost[0] = '\0'; else if (inet_ntop(AF_INET, &ptr->fw_src.s_addr, rhost, rhostlen) == NULL) { syslog(LOG_ERR, "inet_ntop(): %m"); goto error; } } /* And what if we found more than 1 matching rule? */ ipfw_free_ruleset(&rules); get_desc_time(eport, proto, desc, desclen, timestamp); return 0; } } error: if (rules != NULL) ipfw_free_ruleset(&rules); return -1; } int delete_redirect_rule( const char * ifname, unsigned short eport, int proto) { int i, count_rules, total_rules = 0; struct ip_fw * rules = NULL; 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); for (i=0; ifw_prot && eport == ptr->fw_uar.fw_pts[0]) { if (ipfw_exec(IP_FW_DEL, (struct ip_fw *)ptr, sizeof(*ptr)) < 0) goto error; /* And what if we found more than 1 matching rule? */ ipfw_free_ruleset(&rules); del_desc_time(eport, proto); return 0; } } error: if (rules != NULL) ipfw_free_ruleset(&rules); return -1; } int add_filter_rule2( const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { UNUSED(ifname); UNUSED(rhost); UNUSED(iaddr); UNUSED(eport); UNUSED(iport); UNUSED(proto); UNUSED(desc); return 0; /* nothing to do, always success */ } int delete_filter_rule( const char * ifname, unsigned short eport, int proto) { UNUSED(ifname); UNUSED(eport); UNUSED(proto); return 0; /* nothing to do, always success */ } int get_redirect_rule_by_index( int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int total_rules = 0; struct ip_fw * rules = NULL; if (index < 0) /* TODO shouldn't we also validate the maximum? */ return -1; if(timestamp) *timestamp = 0; ipfw_fetch_ruleset(&rules, &total_rules, index + 1); if (total_rules > index) { const struct ip_fw const * ptr = &rules[index]; if (ptr->fw_prot == 0) /* invalid rule */ goto error; if (proto != NULL) *proto = ptr->fw_prot; if (eport != NULL) *eport = ptr->fw_uar.fw_pts[0]; if (iport != NULL) *iport = ptr->fw_fwd_ip.sin_port; if (ifname != NULL) strlcpy(ifname, ptr->fw_in_if.fu_via_if.name, IFNAMSIZ); if (packets != NULL) *packets = ptr->fw_pcnt; if (bytes != NULL) *bytes = ptr->fw_bcnt; if (iport != NULL) *iport = ptr->fw_fwd_ip.sin_port; if (iaddr != NULL && iaddrlen > 0) { /* looks like fw_out_if.fu_via_ip is zero */ /*if (inet_ntop(AF_INET, &ptr->fw_out_if.fu_via_ip, iaddr, iaddrlen) == NULL) {*/ if (inet_ntop(AF_INET, &ptr->fw_fwd_ip.sin_addr, iaddr, iaddrlen) == NULL) { syslog(LOG_ERR, "inet_ntop(): %m"); goto error; } } if (rhost != NULL && rhostlen > 0) { if (ptr->fw_src.s_addr == 0) rhost[0] = '\0'; else if (inet_ntop(AF_INET, &ptr->fw_src.s_addr, rhost, rhostlen) == NULL) { syslog(LOG_ERR, "inet_ntop(): %m"); goto error; } } ipfw_free_ruleset(&rules); get_desc_time(*eport, *proto, desc, desclen, timestamp); return 0; } error: if (rules != NULL) ipfw_free_ruleset(&rules); return -1; } /* upnp_get_portmappings_in_range() * return a list of all "external" ports for which a port * mapping exists */ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number) { unsigned short *array = NULL, *array2 = NULL; unsigned int capacity = 128; int i, count_rules, total_rules = 0; struct ip_fw * rules = NULL; if (ipfw_validate_protocol(proto) < 0) return NULL; do { count_rules = ipfw_fetch_ruleset(&rules, &total_rules, 10); if (count_rules < 0) goto error; } while (count_rules == 10); array = calloc(capacity, sizeof(unsigned short)); if(!array) { syslog(LOG_ERR, "get_portmappings_in_range() : calloc error"); goto error; } *number = 0; for (i=0; ifw_uar.fw_pts[0]; if (proto == ptr->fw_prot && startport <= eport && eport <= endport) { if(*number >= capacity) { capacity += 128; array2 = realloc(array, sizeof(unsigned short)*capacity); if(!array2) { syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%lu) error", sizeof(unsigned short)*capacity); *number = 0; free(array); goto error; } array = array2; } array[*number] = eport; (*number)++; } } error: if (rules != NULL) ipfw_free_ruleset(&rules); 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; } miniupnpd-2.3.7/ipfw/ipfwrdr.h010064400017500000024000000032121203340734100154760ustar00nanardstaff/* $Id: ipfwrdr.h,v 1.8 2012/09/27 15:44:10 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009 Jardel Weyrich * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef IPFWRDR_H_INCLUDED #define IPFWRDR_H_INCLUDED #include "../commonrdr.h" int add_redirect_rule2( const char * ifname, /* src interface (external) */ const char * rhost, /* remote host (ip) */ unsigned short eport, /* src port (external) */ const char * iaddr, /* dst address (internal) */ unsigned short iport, /* dst port (internal) */ int proto, const char * desc, unsigned int timestamp); int add_filter_rule2( const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc); #if 0 /* * get_redirect_rule() gets internal IP and port from * interface, external port and protocl */ int get_redirect_rule( const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes); int get_redirect_rule_by_index( int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes); #endif /* * delete_redirect_rule() */ int delete_redirect_rule(const char * ifname, unsigned short eport, int proto); /* * delete_filter_rule() */ int delete_filter_rule(const char * ifname, unsigned short eport, int proto); int clear_redirect_rules(void); #endif miniupnpd-2.3.7/ipfw/testipfwrdr.c010064400017500000024000000050211160046616100163750ustar00nanardstaff/* $Id: testipfwrdr.c,v 1.7 2011/06/22 21:57:17 nanard Exp $ */ /* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2009-2011 Jardel Weyrich, Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "ipfwrdr.h" // test program for ipfwrdr.c static const char * ifname = "lo0"; static void list_port_mappings(void) { int i; unsigned short eport; char iaddr[16]; unsigned short iport; int proto; char desc[64]; char rhost[32]; unsigned int timestamp; u_int64_t packets, bytes; printf("== Port Mapping List ==\n"); for(i = 0;; i++) { iaddr[0] = '\0'; desc[0] = '\0'; eport = iport = 0; timestamp = 0; packets = bytes = 0; proto = -1; if(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr), &iport, &proto, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) < 0) break; printf("%2d - %5hu=>%15s:%5hu %d '%s' '%s' %u %" PRIu64 " %" PRIu64 "\n", i, eport, iaddr, iport, proto, desc, rhost, timestamp, packets, bytes); } printf("== %d Port Mapping%s ==\n", i, (i > 1)?"s":""); } int main(int argc, char * * argv) { unsigned int timestamp; char desc[64]; char addr[16]; char rhost[40]; unsigned short iport = 0; const char * in_rhost = "8.8.8.8"; desc[0] = '\0'; addr[0] = '\0'; openlog("testipfwrdrd", LOG_CONS | LOG_PERROR, LOG_USER); if(init_redirect() < 0) { fprintf(stderr, "init_redirect() failed.\n"); return 1; } list_port_mappings(); delete_redirect_rule(ifname, 2222, IPPROTO_TCP); delete_redirect_rule(ifname, 2223, IPPROTO_TCP); add_redirect_rule2(ifname, "", 2223, "10.1.1.17", 4445, IPPROTO_TCP, "test miniupnpd", time(NULL) + 60); add_redirect_rule2(ifname, in_rhost, 2222, "10.1.1.16", 4444, IPPROTO_TCP, "test miniupnpd", time(NULL) + 60); get_redirect_rule(ifname, 2222, IPPROTO_TCP, addr, sizeof(addr), &iport, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, NULL, NULL); printf("'%s' %s:%hu '%s' %u\n", rhost, addr, iport, desc, timestamp); list_port_mappings(); delete_redirect_rule(ifname, 2222, IPPROTO_TCP); delete_redirect_rule(ifname, 2223, IPPROTO_TCP); list_port_mappings(); shutdown_redirect(); return 0; } miniupnpd-2.3.7/VERSION010064400017500000024000000000061463565007700137710ustar00nanardstaff2.3.7 miniupnpd-2.3.7/netfilter/iptables_init_and_clean.sh010075500017500000024000000032221307636575000220700ustar00nanardstaff#! /bin/sh # $Id: iptables_init_and_clean.sh,v 1.7 2017/04/21 11:16:09 nanard Exp $ # Improved Miniupnpd iptables init script. # Checks for state of filter before doing anything.. IPTABLES="`which iptables`" || exit 1 IPTABLES="$IPTABLES -w" IP="`which ip`" || exit 1 #EXTIF=eth0 EXTIF="`LC_ALL=C $IP -4 route | grep 'default' | sed -e 's/.*dev[[:space:]]*//' -e 's/[[:space:]].*//'`" || exit 1 EXTIP="`LC_ALL=C $IP -4 addr show $EXTIF | awk '/inet/ { print $2 }' | cut -d "/" -f 1`" NDIRTY="`LC_ALL=C $IPTABLES -t nat -L -n | awk '/MINIUPNPD/ {printf $1}'`" FDIRTY="`LC_ALL=C $IPTABLES -t filter -L -n | awk '/MINIUPNPD/ {printf $1}'`" echo "External IP = $EXTIP" if [[ $NDIRTY = "MINIUPNPDChain" ]]; then echo "Nat table dirty; Cleaning..." $IPTABLES -t nat -F MINIUPNPD elif [[ $NDIRTY = "Chain" ]]; then echo "Dirty NAT chain but no reference..? Fixsted." $IPTABLES -t nat -A PREROUTING -d $EXTIP -i $EXTIF -j MINIUPNPD $IPTABLES -t nat -F MINIUPNPD else echo "NAT table clean..initalizing.." $IPTABLES -t nat -N MINIUPNPD $IPTABLES -t nat -A PREROUTING -d $EXTIP -i $EXTIF -j MINIUPNPD fi if [[ $FDIRTY = "MINIUPNPDChain" ]]; then echo "Filter table dirty; Cleaning..." $IPTABLES -t filter -F MINIUPNPD elif [[ $FDIRTY = "Chain" ]]; then echo "Dirty filter chain but no reference..? Fixsted." $IPTABLES -t filter -I FORWARD 4 -i $EXTIF ! -o $EXTIF -j MINIUPNPD $IPTABLES -t filter -F MINIUPNPD else echo "Filter table clean..initalizing.." $IPTABLES -t filter -N MINIUPNPD $IPTABLES -t filter -I FORWARD 4 -i $EXTIF ! -o $EXTIF -j MINIUPNPD fi miniupnpd-2.3.7/upnputils.c010064400017500000024000000137001326361241600151240ustar00nanardstaff/* $Id: upnputils.c,v 1.13 2018/04/12 08:12:34 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #ifdef AF_LINK #include #endif #include #include "upnputils.h" #include "upnpglobalvars.h" #ifdef ENABLE_IPV6 #include "getroute.h" #endif int sockaddr_to_string(const struct sockaddr * addr, char * str, size_t size) { char buffer[64]; unsigned short port = 0; int n = -1; switch(addr->sa_family) { #ifdef AF_INET6 case AF_INET6: if(inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr, buffer, sizeof(buffer)) == NULL) { snprintf(buffer, sizeof(buffer), "inet_ntop: %s", strerror(errno)); } port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port); if(((struct sockaddr_in6 *)addr)->sin6_scope_id > 0) { char ifname[IF_NAMESIZE]; if(if_indextoname(((struct sockaddr_in6 *)addr)->sin6_scope_id, ifname) == NULL) strncpy(ifname, "ERROR", sizeof(ifname)); n = snprintf(str, size, "[%s%%%s]:%hu", buffer, ifname, port); } else { n = snprintf(str, size, "[%s]:%hu", buffer, port); } break; #endif /* AF_INET6 */ case AF_INET: if(inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr, buffer, sizeof(buffer)) == NULL) { snprintf(buffer, sizeof(buffer), "inet_ntop: %s", strerror(errno)); } port = ntohs(((struct sockaddr_in *)addr)->sin_port); n = snprintf(str, size, "%s:%hu", buffer, port); break; #ifdef AF_LINK #if defined(__sun) /* solaris does not seem to have link_ntoa */ /* #define link_ntoa _link_ntoa */ #define link_ntoa(x) "dummy-link_ntoa" #endif case AF_LINK: { struct sockaddr_dl * sdl = (struct sockaddr_dl *)addr; n = snprintf(str, size, "index=%hu type=%d %s", sdl->sdl_index, sdl->sdl_type, link_ntoa(sdl)); } break; #endif /* AF_LINK */ default: n = snprintf(str, size, "unknown address family %d", addr->sa_family); #if 0 n = snprintf(str, size, "unknown address family %d " "%02x %02x %02x %02x %02x %02x %02x %02x", addr->sa_family, addr->sa_data[0], addr->sa_data[1], (unsigned)addr->sa_data[2], addr->sa_data[3], addr->sa_data[4], addr->sa_data[5], (unsigned)addr->sa_data[6], addr->sa_data[7]); #endif } return n; } int set_non_blocking(int fd) { int flags = fcntl(fd, F_GETFL); if(flags < 0) return 0; if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) return 0; return 1; } struct lan_addr_s * get_lan_for_peer(const struct sockaddr * peer) { struct lan_addr_s * lan_addr = NULL; #ifdef DEBUG char dbg_str[64]; #endif /* DEBUG */ #ifdef ENABLE_IPV6 if(peer->sa_family == AF_INET6) { struct sockaddr_in6 * peer6 = (struct sockaddr_in6 *)peer; if(IN6_IS_ADDR_V4MAPPED(&peer6->sin6_addr)) { struct in_addr peer_addr; memcpy(&peer_addr, &peer6->sin6_addr.s6_addr[12], 4); for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (peer_addr.s_addr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) break; } } else { int index = -1; if(peer6->sin6_scope_id > 0) index = (int)peer6->sin6_scope_id; else { if(get_src_for_route_to(peer, NULL, NULL, &index) < 0) return NULL; } syslog(LOG_DEBUG, "%s looking for LAN interface index=%d", "get_lan_for_peer()", index); for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { syslog(LOG_DEBUG, "ifname=%s index=%u str=%s addr=%08x mask=%08x", lan_addr->ifname, lan_addr->index, lan_addr->str, ntohl(lan_addr->addr.s_addr), ntohl(lan_addr->mask.s_addr)); if(index == (int)lan_addr->index) break; } } } else if(peer->sa_family == AF_INET) { #endif /* ENABLE_IPV6 */ for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (((const struct sockaddr_in *)peer)->sin_addr.s_addr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) break; } #ifdef ENABLE_IPV6 } #endif /* ENABLE_IPV6 */ #ifdef DEBUG sockaddr_to_string(peer, dbg_str, sizeof(dbg_str)); if(lan_addr) { syslog(LOG_DEBUG, "%s: %s found in LAN %s %s", "get_lan_for_peer()", dbg_str, lan_addr->ifname, lan_addr->str); } else { syslog(LOG_DEBUG, "%s: %s not found !", "get_lan_for_peer()", dbg_str); } #endif /* DEBUG */ return lan_addr; } time_t upnp_time(void) { #if defined(CLOCK_MONOTONIC_FAST) || defined(CLOCK_MONOTONIC) #if defined(CLOCK_MONOTONIC_FAST) #define UPNP_CLOCKID CLOCK_MONOTONIC_FAST #else #define UPNP_CLOCKID CLOCK_MONOTONIC #endif struct timespec ts; if (clock_gettime(UPNP_CLOCKID, &ts) < 0) return time(NULL); else return ts.tv_sec; #else return time(NULL); #endif } time_t upnp_get_uptime(void) { #if defined(CLOCK_UPTIME_FAST) || defined(CLOCK_UPTIME) #if defined(CLOCK_UPTIME_FAST) #define UPNP_CLOCKID_UPTIME CLOCK_UPTIME_FAST #else #define UPNP_CLOCKID_UPTIME CLOCK_UPTIME #endif if(GETFLAG(SYSUPTIMEMASK)) { struct timespec ts; if (clock_gettime(UPNP_CLOCKID_UPTIME, &ts) >= 0) return ts.tv_sec; } #endif return upnp_time() - startup_time; } int upnp_gettimeofday(struct timeval * tv) { #if defined(CLOCK_MONOTONIC_FAST) || defined(CLOCK_MONOTONIC) struct timespec ts; int ret_code = clock_gettime(UPNP_CLOCKID, &ts); if (ret_code == 0) { tv->tv_sec = ts.tv_sec; tv->tv_usec = ts.tv_nsec / 1000; } return ret_code; #else return gettimeofday(tv, NULL); #endif } miniupnpd-2.3.7/upnputils.h010064400017500000024000000032731326361241600151350ustar00nanardstaff/* $Id: upnputils.h,v 1.10 2018/04/12 08:12:34 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2011-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPUTILS_H_INCLUDED #define UPNPUTILS_H_INCLUDED /** * convert a struct sockaddr to a human readable string. * [ipv6]:port or ipv4:port * return the number of characters used (as snprintf) */ int sockaddr_to_string(const struct sockaddr * addr, char * str, size_t size); /** * set the file description as non blocking * return 0 in case of failure, 1 in case of success */ int set_non_blocking(int fd); /** * get the LAN which the peer belongs to */ struct lan_addr_s * get_lan_for_peer(const struct sockaddr * peer); /** * get the time for upnp (release expiration, etc.) * Similar to a monotonic time(NULL) */ time_t upnp_time(void); /** * return either the machine or the daemon uptime */ time_t upnp_get_uptime(void); /** * get the time for upnp * Similar to a monotonic gettimeofday(tv, NULL) */ int upnp_gettimeofday(struct timeval * tv); /** * define portability macros */ #if defined(__sun) static __inline size_t _sa_len(const struct sockaddr *addr) { if (addr->sa_family == AF_INET) return (sizeof(struct sockaddr_in)); else if (addr->sa_family == AF_INET6) return (sizeof(struct sockaddr_in6)); else return (sizeof(struct sockaddr)); } # define SA_LEN(sa) (_sa_len(sa)) #else #if !defined(SA_LEN) # define SA_LEN(sa) ((sa)->sa_len) #endif #endif #ifndef MAX # define MAX(a,b) (((a)>(b))?(a):(b)) #endif #endif miniupnpd-2.3.7/upnpurns.h010064400017500000024000000022301203340734000147440ustar00nanardstaff/* $Id: upnpurns.h,v 1.3 2012/09/27 15:46:18 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2011 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPURNS_H_INCLUDED #define UPNPURNS_H_INCLUDED #include "config.h" #ifdef IGD_V2 /* IGD v2 */ #define DEVICE_TYPE_IGD "urn:schemas-upnp-org:device:InternetGatewayDevice:2" #define DEVICE_TYPE_WAN "urn:schemas-upnp-org:device:WANDevice:2" #define DEVICE_TYPE_WANC "urn:schemas-upnp-org:device:WANConnectionDevice:2" #define SERVICE_TYPE_WANIPC "urn:schemas-upnp-org:service:WANIPConnection:2" #define SERVICE_ID_WANIPC "urn:upnp-org:serviceId:WANIPConn1" #else /* IGD v1 */ #define DEVICE_TYPE_IGD "urn:schemas-upnp-org:device:InternetGatewayDevice:1" #define DEVICE_TYPE_WAN "urn:schemas-upnp-org:device:WANDevice:1" #define DEVICE_TYPE_WANC "urn:schemas-upnp-org:device:WANConnectionDevice:1" #define SERVICE_TYPE_WANIPC "urn:schemas-upnp-org:service:WANIPConnection:1" #define SERVICE_ID_WANIPC "urn:upnp-org:serviceId:WANIPConn1" #endif #endif miniupnpd-2.3.7/getconnstatus.c010064400017500000024000000031111212532024700157470ustar00nanardstaff/* $Id: getconnstatus.c,v 1.6 2013/03/23 10:46:54 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2011-2013 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include "getconnstatus.h" #include "getifaddr.h" #define STATUS_UNCONFIGURED (0) #define STATUS_CONNECTING (1) #define STATUS_CONNECTED (2) #define STATUS_PENDINGDISCONNECT (3) #define STATUS_DISCONNECTING (4) #define STATUS_DISCONNECTED (5) /** * get the connection status * return values : * 0 - Unconfigured * 1 - Connecting * 2 - Connected * 3 - PendingDisconnect * 4 - Disconnecting * 5 - Disconnected */ int get_wan_connection_status(const char * ifname) { char addr[INET_ADDRSTRLEN]; int r; /* we need a better implementation here. * I'm afraid it should be device specific */ r = getifaddr(ifname, addr, INET_ADDRSTRLEN, NULL, NULL); return (r < 0) ? STATUS_DISCONNECTED : STATUS_CONNECTED; } /** * return the same value as get_wan_connection_status() * as a C string */ const char * get_wan_connection_status_str(const char * ifname) { int status; const char * str = NULL; status = get_wan_connection_status(ifname); switch(status) { case 0: str = "Unconfigured"; break; case 1: str = "Connecting"; break; case 2: str = "Connected"; break; case 3: str = "PendingDisconnect"; break; case 4: str = "Disconnecting"; break; case 5: str = "Disconnected"; break; } return str; } miniupnpd-2.3.7/getconnstatus.h010064400017500000024000000013521203340734000157570ustar00nanardstaff/* $Id: getconnstatus.h,v 1.4 2012/09/27 16:00:10 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2011 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef GETCONNSTATUS_H_INCLUDED #define GETCONNSTATUS_H_INCLUDED /** * get the connection status * return values : * 0 - Unconfigured * 1 - Connecting * 2 - Connected * 3 - PendingDisconnect * 4 - Disconnecting * 5 - Disconnected */ int get_wan_connection_status(const char * ifname); /** * return the same value as get_wan_connection_status() * as a C string */ const char * get_wan_connection_status_str(const char * ifname); #endif miniupnpd-2.3.7/ifacewatcher.h010064400017500000024000000041401203340734000155010ustar00nanardstaff/* $Id: ifacewatcher.h,v 1.4 2012/09/27 16:00:44 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2009 Thomas Bernard * * ifacewatcher.h * * This file implements dynamic serving of new network interfaces * which weren't available during daemon start. It also takes care * of interfaces which become unavailable. * * Copyright (c) 2011, Alexey Osipov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef IFACEWATCHER_H_INCLUDED #define IFACEWATCHER_H_INCLUDED #include "config.h" #ifdef USE_IFACEWATCHER int OpenAndConfInterfaceWatchSocket(void); void ProcessInterfaceWatchNotify(int s); #endif #endif miniupnpd-2.3.7/bsd/ifacewatcher.c010064400017500000024000000071201365610016700162560ustar00nanardstaff/* $Id: ifacewatcher.c,v 1.9 2020/05/10 22:23:56 nanard Exp $ */ /* Project MiniUPnP * web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2011-2020 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 #define SALIGN (sizeof(long) - 1) #define SA_RLEN(sa) (SA_LEN(sa) ? ((SA_LEN(sa) + SALIGN) & ~SALIGN) : (SALIGN + 1)) #include "config.h" #include "../upnputils.h" #include "../upnpglobalvars.h" extern volatile sig_atomic_t should_send_public_address_change_notif; int OpenAndConfInterfaceWatchSocket(void) { int s; /*s = socket(PF_ROUTE, SOCK_RAW, AF_INET);*/ s = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); /* The family parameter may be AF_UNSPEC which will provide routing informa- * tion for all address families, or can be restricted to a specific address * family by specifying which one is desired. There can be more than one * routing socket open per system. */ if(s < 0) { syslog(LOG_ERR, "OpenAndConfInterfaceWatchSocket socket: %m"); } return s; } void ProcessInterfaceWatchNotify(int s) { char buf[4096]; ssize_t len; char tmp[64]; struct rt_msghdr * rtm; struct if_msghdr * ifm; struct ifa_msghdr * ifam; #ifdef RTM_IFANNOUNCE struct if_announcemsghdr * ifanm; #endif char * p; struct sockaddr * sa; unsigned int ext_if_name_index = 0; len = recv(s, buf, sizeof(buf), 0); if(len < 0) { syslog(LOG_ERR, "ProcessInterfaceWatchNotify recv: %m"); return; } if(ext_if_name) { ext_if_name_index = if_nametoindex(ext_if_name); } rtm = (struct rt_msghdr *)buf; syslog(LOG_DEBUG, "%u rt_msg : msglen=%d version=%d type=%d", (unsigned)len, rtm->rtm_msglen, rtm->rtm_version, rtm->rtm_type); switch(rtm->rtm_type) { case RTM_IFINFO: /* iface going up/down etc. */ ifm = (struct if_msghdr *)buf; syslog(LOG_DEBUG, " RTM_IFINFO: addrs=%x flags=%x index=%hu", ifm->ifm_addrs, ifm->ifm_flags, ifm->ifm_index); break; case RTM_ADD: /* Add Route */ syslog(LOG_DEBUG, " RTM_ADD"); break; case RTM_DELETE: /* Delete Route */ syslog(LOG_DEBUG, " RTM_DELETE"); break; case RTM_CHANGE: /* Change Metrics or flags */ syslog(LOG_DEBUG, " RTM_CHANGE"); break; case RTM_GET: /* Report Metrics */ syslog(LOG_DEBUG, " RTM_GET"); break; #ifdef RTM_IFANNOUNCE case RTM_IFANNOUNCE: /* iface arrival/departure */ ifanm = (struct if_announcemsghdr *)buf; syslog(LOG_DEBUG, " RTM_IFANNOUNCE: index=%hu what=%hu ifname=%s", ifanm->ifan_index, ifanm->ifan_what, ifanm->ifan_name); break; #endif #ifdef RTM_IEEE80211 case RTM_IEEE80211: /* IEEE80211 wireless event */ syslog(LOG_DEBUG, " RTM_IEEE80211"); break; #endif case RTM_NEWADDR: /* address being added to iface */ ifam = (struct ifa_msghdr *)buf; syslog(LOG_DEBUG, " RTM_NEWADDR: addrs=%x flags=%x index=%hu", ifam->ifam_addrs, ifam->ifam_flags, ifam->ifam_index); p = buf + sizeof(struct ifa_msghdr); while(p < buf + len) { sa = (struct sockaddr *)p; sockaddr_to_string(sa, tmp, sizeof(tmp)); syslog(LOG_DEBUG, " %s", tmp); p += SA_RLEN(sa); } if(ifam->ifam_index == ext_if_name_index) { should_send_public_address_change_notif = 1; } break; case RTM_DELADDR: /* address being removed from iface */ ifam = (struct ifa_msghdr *)buf; if(ifam->ifam_index == ext_if_name_index) { should_send_public_address_change_notif = 1; } break; default: syslog(LOG_DEBUG, "unprocessed RTM message type=%d", rtm->rtm_type); } } miniupnpd-2.3.7/bsd/testifacewatcher.c010064400017500000024000000013051175640135600171600ustar00nanardstaff/* $Id: testifacewatcher.c,v 1.2 2012/05/21 08:55:10 nanard Exp $ */ #include int OpenAndConfInterfaceWatchSocket(void); void ProcessInterfaceWatchNotify(int s); const char * ext_if_name; volatile sig_atomic_t should_send_public_address_change_notif = 0; int main(int argc, char * * argv) { int s; ext_if_name = "ep0"; openlog("testifacewatcher", LOG_CONS|LOG_PERROR, LOG_USER); syslog(LOG_DEBUG, "test"); s = OpenAndConfInterfaceWatchSocket(); for(;;) { if(should_send_public_address_change_notif) { syslog(LOG_DEBUG, "should_send_public_address_change_notif !"); should_send_public_address_change_notif = 0; } ProcessInterfaceWatchNotify(s); } closelog(); return 0; } miniupnpd-2.3.7/linux/ifacewatcher.c010064400017500000024000000237161365604170400166570ustar00nanardstaff/* $Id: ifacewatcher.c,v 1.11 2020/05/10 17:50:25 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * * ifacewatcher.c * * This file implements dynamic serving of new network interfaces * which weren't available during daemon start. It also takes care * of interfaces which become unavailable. * * Copyright (c) 2011, Alexey Osipov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "../macros.h" #ifdef USE_IFACEWATCHER #include "../ifacewatcher.h" #include "../minissdp.h" #include "../getifaddr.h" #include "../upnpglobalvars.h" #include "../natpmp.h" extern volatile sig_atomic_t should_send_public_address_change_notif; int OpenAndConfInterfaceWatchSocket(void) { int s; struct sockaddr_nl addr; s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (s == -1) { syslog(LOG_ERR, "socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE): %m"); return -1; } memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR; /*addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;*/ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "bind(netlink): %m"); close(s); return -1; } return s; } #if 0 /* disabled at the moment */ int ProcessInterfaceUp(struct ifinfomsg *ifi) { struct lan_iface_s * lan_iface; struct lan_iface_s * lan_iface2; struct lan_addr_s * lan_addr; char ifname[IFNAMSIZ]; char ifstraddr[16]; struct in_addr ifaddr; /* check if we already have this iface */ for(lan_iface = lan_ifaces.lh_first; lan_iface != NULL; lan_iface = lan_iface->list.le_next) if (lan_iface->iface.index == ifi->ifi_index) break; if (lan_iface != NULL) return 0; if (if_indextoname(ifi->ifi_index, ifname) == NULL) { syslog(LOG_ERR, "if_indextoname(%d, ifname) failed", ifi->ifi_index); return -1; } if (getifaddr(ifname, ifstraddr, 16) < 0) { syslog(LOG_DEBUG, "getifaddr(%s, ifaddr, 16) failed", ifname); return 1; } if (inet_pton(AF_INET, ifstraddr, &ifaddr) != 1) { syslog(LOG_ERR, "inet_pton(AF_INET, \"%s\", &ifaddr) failed", ifstraddr); return -1; } /* check if this new interface has address which we need to listen to */ for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if (lan_addr->addr.s_addr != ifaddr.s_addr) continue; syslog(LOG_INFO, "Interface up: %s (%s)", ifname, ifstraddr); /* adding new lan_iface entry */ lan_iface = (struct lan_iface_s *) malloc(sizeof(struct lan_iface_s)); if (lan_iface == NULL) { syslog(LOG_ERR, "malloc(sizeof(struct lan_iface_s): %m"); continue; } lan_iface->lan_addr = lan_addr; strncpy(lan_iface->iface.name, ifname, IFNAMSIZ); lan_iface->iface.index = ifi->ifi_index; lan_iface->iface.addr = ifaddr; lan_iface->snotify = -1; #ifdef ENABLE_NATPMP lan_iface->snatpmp = -1; #endif LIST_INSERT_HEAD(&lan_ifaces, lan_iface, list); /* adding multicast membership for SSDP */ if(AddMulticastMembership(sudp, ifaddr.s_addr, ifi->ifi_index) < 0) syslog(LOG_WARNING, "Failed to add multicast membership for interface %s (%s)", ifname, ifstraddr); else syslog(LOG_INFO, "Multicast membership added for %s (%s)", ifname, ifstraddr); /* create SSDP notify socket */ if (OpenAndConfSSDPNotifySocket(lan_iface) < 0) syslog(LOG_WARNING, "Failed to open SSDP notify socket for interface %s (%s)", ifname, ifstraddr); #ifdef ENABLE_NATPMP /* create NAT-PMP socket */ for(lan_iface2 = lan_ifaces.lh_first; lan_iface2 != NULL; lan_iface2 = lan_iface2->list.le_next) if (lan_iface2->lan_addr->addr.s_addr == lan_iface->lan_addr->addr.s_addr && lan_iface2->snatpmp >= 0) lan_iface->snatpmp = lan_iface2->snatpmp; if (lan_iface->snatpmp < 0) { lan_iface->snatpmp = OpenAndConfNATPMPSocket(ifaddr.s_addr); if (lan_iface->snatpmp < 0) syslog(LOG_ERR, "OpenAndConfNATPMPSocket(ifaddr.s_addr) failed for %s (%s)", ifname, ifstraddr); else syslog(LOG_INFO, "Listening for NAT-PMP connection on %s:%d", ifstraddr, NATPMP_PORT); } #endif } return 0; } int ProcessInterfaceDown(struct ifinfomsg *ifi) { struct lan_iface_s * lan_iface; struct lan_iface_s * lan_iface2; /* check if we have this iface */ for(lan_iface = lan_ifaces.lh_first; lan_iface != NULL; lan_iface = lan_iface->list.le_next) if (lan_iface->iface.index == ifi->ifi_index) break; if (lan_iface == NULL) return 0; /* one of our interfaces is going down, clean up */ syslog(LOG_INFO, "Interface down: %s", lan_iface->iface.name); /* remove multicast membership for SSDP */ if(DropMulticastMembership(sudp, lan_iface->lan_addr->addr.s_addr, lan_iface->iface.index) < 0) syslog(LOG_WARNING, "Failed to drop multicast membership for interface %s (%s)", lan_iface->iface.name, lan_iface->lan_addr->str); else syslog(LOG_INFO, "Multicast membership dropped for %s (%s)", lan_iface->iface.name, lan_iface->lan_addr->str); /* closing SSDP notify socket */ close(lan_iface->snotify); #ifdef ENABLE_NATPMP /* closing NAT-PMP socket if it's not used anymore */ for(lan_iface2 = lan_ifaces.lh_first; lan_iface2 != NULL; lan_iface2 = lan_iface2->list.le_next) if (lan_iface2 != lan_iface && lan_iface2->snatpmp == lan_iface->snatpmp) break; if (lan_iface2 == NULL) close(lan_iface->snatpmp); #endif /* del corresponding lan_iface entry */ LIST_REMOVE(lan_iface, list); free(lan_iface); return 0; } #endif void ProcessInterfaceWatchNotify(int s) { char buffer[4096]; struct iovec iov; struct msghdr hdr; struct nlmsghdr *nlhdr; #if 0 /* disabled at the moment */ struct ifinfomsg *ifi; #endif struct ifaddrmsg *ifa; int len; struct rtattr *rth; int rtl; unsigned int ext_if_name_index = 0; iov.iov_base = buffer; iov.iov_len = sizeof(buffer); memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = &iov; hdr.msg_iovlen = 1; len = recvmsg(s, &hdr, 0); if (len < 0) { syslog(LOG_ERR, "recvmsg(s, &hdr, 0): %m"); return; } if(ext_if_name) { ext_if_name_index = if_nametoindex(ext_if_name); } for (nlhdr = (struct nlmsghdr *) buffer; NLMSG_OK (nlhdr, (unsigned int)len); nlhdr = NLMSG_NEXT (nlhdr, len)) { int is_del = 0; char address[48]; char ifname[IFNAMSIZ]; address[0] = '\0'; ifname[0] = '\0'; if (nlhdr->nlmsg_type == NLMSG_DONE) break; switch(nlhdr->nlmsg_type) { case RTM_DELLINK: is_del = 1; FALL_THROUGH; case RTM_NEWLINK: #if 0 /* disabled at the moment */ ifi = (struct ifinfomsg *) NLMSG_DATA(nlhdr); if(is_del) { if(ProcessInterfaceDown(ifi) < 0) syslog(LOG_ERR, "ProcessInterfaceDown(ifi) failed"); } else { if(ProcessInterfaceUp(ifi) < 0) syslog(LOG_ERR, "ProcessInterfaceUp(ifi) failed"); } #endif break; case RTM_DELADDR: is_del = 1; FALL_THROUGH; case RTM_NEWADDR: /* see /usr/include/linux/netlink.h * and /usr/include/linux/rtnetlink.h */ ifa = (struct ifaddrmsg *) NLMSG_DATA(nlhdr); syslog(LOG_DEBUG, "%s %s index=%d fam=%d", "ProcessInterfaceWatchNotify", is_del ? "RTM_DELADDR" : "RTM_NEWADDR", ifa->ifa_index, ifa->ifa_family); for(rth = IFA_RTA(ifa), rtl = IFA_PAYLOAD(nlhdr); rtl && RTA_OK(rth, rtl); rth = RTA_NEXT(rth, rtl)) { char tmp[128]; memset(tmp, 0, sizeof(tmp)); switch(rth->rta_type) { case IFA_ADDRESS: case IFA_LOCAL: case IFA_BROADCAST: case IFA_ANYCAST: inet_ntop(ifa->ifa_family, RTA_DATA(rth), tmp, sizeof(tmp)); if(rth->rta_type == IFA_ADDRESS) strncpy(address, tmp, sizeof(address)); break; case IFA_LABEL: strncpy(tmp, RTA_DATA(rth), sizeof(tmp)); strncpy(ifname, tmp, sizeof(ifname)); break; case IFA_CACHEINFO: { struct ifa_cacheinfo *cache_info; cache_info = RTA_DATA(rth); snprintf(tmp, sizeof(tmp), "valid=%u preferred=%u", cache_info->ifa_valid, cache_info->ifa_prefered); } break; default: strncpy(tmp, "*unknown*", sizeof(tmp)); } syslog(LOG_DEBUG, " - %u - %s type=%d", ifa->ifa_index, tmp, rth->rta_type); } if(ifa->ifa_index == ext_if_name_index) { should_send_public_address_change_notif = 1; } break; default: syslog(LOG_DEBUG, "%s type %d ignored", "ProcessInterfaceWatchNotify", nlhdr->nlmsg_type); } } } #endif miniupnpd-2.3.7/netfilter/tiny_nf_nat.h010064400017500000024000000015361161500167400173730ustar00nanardstaff/* $Id: tiny_nf_nat.h,v 1.1 2011/07/30 13:14:36 nanard Exp $ */ /* Only what miniupnpd needs, until linux-libc-dev gains nf_nat.h */ #ifndef TINY_NF_NAT_H #define TINY_NF_NAT_H #include #define IP_NAT_RANGE_MAP_IPS 1 #define IP_NAT_RANGE_PROTO_SPECIFIED 2 #define IP_NAT_RANGE_PROTO_RANDOM 4 #define IP_NAT_RANGE_PERSISTENT 8 union nf_conntrack_man_proto { __be16 all; struct { __be16 port; } tcp; struct { __be16 port; } udp; struct { __be16 id; } icmp; struct { __be16 port; } dccp; struct { __be16 port; } sctp; struct { __be16 key; } gre; }; struct nf_nat_range { unsigned int flags; __be32 min_ip, max_ip; union nf_conntrack_man_proto min, max; }; struct nf_nat_multi_range_compat { unsigned int rangesize; struct nf_nat_range range[1]; }; #define nf_nat_multi_range nf_nat_multi_range_compat #endif /*TINY_NF_NAT_H*/ miniupnpd-2.3.7/pf/pfpinhole.c010064400017500000024000000413421463564776700155020ustar00nanardstaff/* $Id: pfpinhole.c,v 1.31 2024/06/22 16:48:54 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include #include #include #include #include #include #include #ifdef __DragonFly__ #include #else #ifdef __APPLE__ #define PRIVATE 1 #endif #include #endif #include #include #include #include #include #include #include #ifdef USE_LIBPFCTL #include #endif #include "pfpinhole.h" #include "../upnpglobalvars.h" #include "../macros.h" #include "../upnputils.h" #include "rtickets.h" /* the pass rules created by add_pinhole() are as follow : * * pass in quick on ep0 inet6 proto udp * from any to dead:beef::42:42 port = 8080 * flags S/SA keep state * label "pinhole-2 ts-4321000" * * with the label "pinhole-$uid ts-$timestamp: $description" */ #ifdef ENABLE_UPNPPINHOLE /* /dev/pf when opened */ extern int dev; static int next_uid = 1; #define PINEHOLE_LABEL_FORMAT "pinhole-%d ts-%u: %s" #define PINEHOLE_LABEL_FORMAT_SKIPDESC "pinhole-%d ts-%u: %*s" #define RULE (pr.rule) int add_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, const char * desc, unsigned int timestamp) { int uid; struct pfioc_rule pr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m"); return -1; } else { pr.pool_ticket = pp.ticket; #else { #endif RULE.direction = PF_IN; RULE.action = PF_PASS; RULE.af = AF_INET6; #ifdef PF_NEWSTYLE RULE.nat.addr.type = PF_ADDR_NONE; RULE.rdr.addr.type = PF_ADDR_NONE; #endif #ifdef USE_IFNAME_IN_RULES if(ifname) strlcpy(RULE.ifname, ifname, IFNAMSIZ); #endif RULE.proto = proto; RULE.quick = 1;/*(GETFLAG(PFNOQUICKRULESMASK))?0:1;*/ RULE.log = (GETFLAG(LOGPACKETSMASK))?1:0; /*logpackets;*/ /* see the discussion on the forum : * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=638 */ RULE.flags = TH_SYN; RULE.flagset = (TH_SYN|TH_ACK); #ifdef PFRULE_HAS_RTABLEID RULE.rtableid = -1; /* first appeared in OpenBSD 4.0 */ #endif #ifdef PFRULE_HAS_ONRDOMAIN RULE.onrdomain = -1; /* first appeared in OpenBSD 5.0 */ #endif RULE.keep_state = 1; uid = next_uid; snprintf(RULE.label, PF_RULE_LABEL_SIZE, PINEHOLE_LABEL_FORMAT, uid, timestamp, desc); if(queue) strlcpy(RULE.qname, queue, PF_QNAME_SIZE); if(tag) strlcpy(RULE.tagname, tag, PF_TAG_NAME_SIZE); if(rem_port) { RULE.src.port_op = PF_OP_EQ; RULE.src.port[0] = htons(rem_port); } if(rem_host && rem_host[0] != '\0' && rem_host[0] != '*') { RULE.src.addr.type = PF_ADDR_ADDRMASK; if(inet_pton(AF_INET6, rem_host, &RULE.src.addr.v.a.addr.v6) != 1) { syslog(LOG_ERR, "inet_pton(%s) failed", rem_host); } memset(&RULE.src.addr.v.a.mask.addr8, 255, 16); } RULE.dst.port_op = PF_OP_EQ; RULE.dst.port[0] = htons(int_port); RULE.dst.addr.type = PF_ADDR_ADDRMASK; if(inet_pton(AF_INET6, int_client, &RULE.dst.addr.v.a.addr.v6) != 1) { syslog(LOG_ERR, "inet_pton(%s) failed", int_client); } memset(&RULE.dst.addr.v.a.mask.addr8, 255, 16); if(ifname) strlcpy(RULE.ifname, ifname, IFNAMSIZ); pr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); return -1; } else { pr.action = PF_CHANGE_ADD_TAIL; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); return -1; } } } if(++next_uid >= 65535) { next_uid = 1; } return uid; } int find_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, char *desc, int desc_len, unsigned int * timestamp) { int uid; unsigned int ts, tnum; int i, n; #undef RULE #ifdef USE_LIBPFCTL struct pfctl_rules_info ri; struct pfctl_rule rule; #define RULE (rule) char anchor_call[MAXPATHLEN] = ""; #else /* USE_LIBPFCTL */ struct pfioc_rule pr; #define RULE (pr.rule) #endif /* USE_LIBPFCTL */ struct in6_addr saddr; struct in6_addr daddr; UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } if(rem_host && (rem_host[0] != '\0')) { inet_pton(AF_INET6, rem_host, &saddr); } else { memset(&saddr, 0, sizeof(struct in6_addr)); } inet_pton(AF_INET6, int_client, &daddr); #ifdef USE_LIBPFCTL if(pfctl_get_rules_info(dev, &ri, PF_PASS, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); return -1; } n = ri.nr; #ifdef PF_RELEASETICKETS tnum = ri.ticket; #endif /* PF_RELEASETICKETS */ #else /* USE_LIBPFCTL */ memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE RULE.action = PF_PASS; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ #endif /* USE_LIBPFCTL */ for(i=0; i= 0; i--) { #ifdef USE_LIBPFCTL if(pfctl_get_rule(dev, i, ri.ticket, anchor_name, PF_PASS, &rule, anchor_call) < 0) { syslog(LOG_ERR, "pfctl_get_rule: %m"); release_ticket(dev, tnum); return -1; } if(sscanf(RULE.label[0], PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]); continue; } #else /* USE_LIBPFCTL */ pr.nr = i; if(ioctl(dev, DIOCGETRULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); release_ticket(dev, tnum); return -1; } if(sscanf(RULE.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label); continue; } #endif /* USE_LIBPFCTL */ if(ts <= (unsigned int)current_time) { #ifdef USE_LIBPFCTL /* TODO: convert DIOCCHANGERULE to libpfctl */ struct pfioc_rule pr; pr.action = PF_CHANGE_GET_TICKET; memset(&pr, 0, sizeof(pr)); pr.ticket = ri.ticket; pr.nr = i; strlcpy(pr.anchor, anchor_name, MAXPATHLEN); syslog(LOG_INFO, "removing expired pinhole '%s'", RULE.label[0]); #else /* USE_LIBPFCTL */ syslog(LOG_INFO, "removing expired pinhole '%s'", RULE.label); #endif /* USE_LIBPFCTL */ pr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); release_ticket(dev, tnum); return -1; } pr.action = PF_CHANGE_REMOVE; pr.nr = i; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m"); release_ticket(dev, tnum); return -1; } n++; #ifdef USE_LIBPFCTL if(pfctl_get_rules_info(dev, &ri, PF_PASS, anchor_name) < 0) { syslog(LOG_ERR, "pfctl_get_rules_info: %m"); return -1; } #ifdef PF_RELEASETICKETS tnum = ri.ticket; #endif /* PF_RELEASETICKETS */ #else /* USE_LIBPFCTL */ #ifndef PF_NEWSTYLE RULE.action = PF_PASS; #endif release_ticket(dev, tnum); if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif #endif /* USE_LIBPFCTL */ } else { if(uid > max_uid) max_uid = uid; else if(uid < min_uid) min_uid = uid; if(ts < min_ts) min_ts = ts; } } if(next_timestamp && (min_ts != UINT_MAX)) *next_timestamp = min_ts; if(max_uid > 0) { if(((min_uid - 32000) <= next_uid) && (next_uid <= max_uid)) { next_uid = max_uid + 1; } if(next_uid >= 65535) { next_uid = 1; } } release_ticket(dev, tnum); return n; /* number of rules removed */ } #endif /* ENABLE_UPNPPINHOLE */ miniupnpd-2.3.7/pf/pfpinhole.h010064400017500000024000000026501264740756400154740ustar00nanardstaff/* $Id: pfpinhole.h,v 1.13 2016/01/19 10:03:30 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2012-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef PFPINHOLE_H_INCLUDED #define PFPINHOLE_H_INCLUDED #ifdef ENABLE_UPNPPINHOLE int find_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, char *desc, int desc_len, unsigned int * timestamp); int add_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, const char * desc, unsigned int timestamp); int delete_pinhole(unsigned short uid); int get_pinhole_info(unsigned short uid, char * rem_host, int rem_hostlen, unsigned short * rem_port, char * int_client, int int_clientlen, unsigned short * int_port, int * proto, char * desc, int desclen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); int update_pinhole(unsigned short uid, unsigned int timestamp); int clean_pinhole_list(unsigned int * next_timestamp); #endif /* ENABLE_UPNPPINHOLE */ #endif miniupnpd-2.3.7/pf/testpfpinhole.c010064400017500000024000000056211463564776700164020ustar00nanardstaff/* $Id: testpfpinhole.c,v 1.16 2024/06/22 16:48:54 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include "config.h" #include "obsdrdr.h" #include "pfpinhole.h" #include "../miniupnpdtypes.h" int runtime_flags = 0; time_t startup_time = 0; const char * tag = NULL; const char * anchor_name = "miniupnpd"; const char * queue = NULL; const char * use_ext_ip_addr = "42.42.42.42"; struct lan_addr_list lan_addrs; #ifdef ENABLE_IPV6 static int print_pinhole(int uid) { int r; char rem_host[64]; unsigned short rem_port; char int_client[64]; unsigned short int_port; int proto; unsigned int timestamp; u_int64_t packets, bytes; char desc[64]; r = get_pinhole_info((unsigned short)uid, rem_host, sizeof(rem_host), &rem_port, int_client, sizeof(int_client), &int_port, &proto, desc, sizeof(desc), ×tamp, &packets, &bytes); if(r < 0) { fprintf(stderr, "get_pinhole(%d) returned %d\n", uid, r); } else { printf("pinhole %d : [%s]:%hu => [%s]:%hu proto=%d ts=%u\n", uid, rem_host, rem_port, int_client, int_port, proto, timestamp); printf(" desc='%s'\n", desc); printf(" packets=%" PRIu64 " bytes=%" PRIu64 "\n", packets, bytes); } return r; } #endif int main(int argc, char * *argv) { #ifndef ENABLE_IPV6 fprintf(stderr,"nothing to test, ENABLE_IPV6 is not defined in config.h\n"); return 1; #else int uid; int uid2; int ret; unsigned int timestamp; (void)argc; (void)argv; openlog("testpfpinhole", LOG_PERROR, LOG_USER); if(init_redirect() < 0) { fprintf(stderr, "init_redirect() failed\n"); return 1; } uid = add_pinhole("ep0", "2001::1:2:3", 12345, "123::ff", 54321, IPPROTO_UDP, "description test 1", 424242); if(uid < 0) { fprintf(stderr, "add_pinhole() failed\n"); } printf("add_pinhole() returned %d\n", uid); uid = add_pinhole("ep0", NULL, 0, "dead:beef::42:42", 8080, IPPROTO_UDP, "description test 2", 4321000); if(uid < 0) { fprintf(stderr, "add_pinhole() failed\n"); } printf("add_pinhole() returned %d\n", uid); uid2 = find_pinhole("ep0", NULL, 0, "dead:beef::42:42", 8080, IPPROTO_UDP, NULL, 0, ×tamp); if(uid2 < 0) { fprintf(stderr, "find_pinhole() failed\n"); } else { printf("find_pinhole() uid=%d timestamp=%u\n", uid2, timestamp); } print_pinhole(1); print_pinhole(2); clean_pinhole_list(NULL); ret = delete_pinhole(1); printf("delete_pinhole() returned %d\n", ret); ret = delete_pinhole(2); printf("delete_pinhole() returned %d\n", ret); #endif return 0; } miniupnpd-2.3.7/netfilter/ip6tables_display.sh010075500017500000024000000003631326165065400206730ustar00nanardstaff#! /bin/sh # $Id: ip6tables_display.sh,v 1.2 2018/04/06 09:21:11 nanard Exp $ IPV6=1 . $(dirname "$0")/miniupnpd_functions.sh #display all chains relative to miniupnpd $IPTABLES -v -n -t filter -L FORWARD $IPTABLES -v -n -t filter -L $CHAIN miniupnpd-2.3.7/netfilter/ip6tables_flush.sh010075500017500000024000000003001326165065400203360ustar00nanardstaff#! /bin/sh # $Id: ip6tables_flush.sh,v 1.2 2018/04/06 09:21:11 nanard Exp $ IPV6=1 . $(dirname "$0")/miniupnpd_functions.sh #flush all rules owned by miniupnpd $IPTABLES -t filter -F $CHAIN miniupnpd-2.3.7/netfilter/ip6tables_init_and_clean.sh010075500017500000024000000014761174562262500221650ustar00nanardstaff#! /bin/sh # $Id: ip6tables_init_and_clean.sh,v 1.1 2012/04/24 22:13:41 nanard Exp $ # Improved Miniupnpd iptables init script. # Checks for state of filter before doing anything.. EXTIF=eth0 IPTABLES=/sbin/ip6tables FDIRTY="`LC_ALL=C /sbin/ip6tables -t filter -L -n | grep 'MINIUPNPD' | awk '{printf $1}'`" if [[ $FDIRTY = "MINIUPNPDChain" ]]; then echo "Filter table dirty; Cleaning..." $IPTABLES -t filter -F MINIUPNPD elif [[ $FDIRTY = "Chain" ]]; then echo "Dirty filter chain but no reference..? Fixsted." $IPTABLES -t filter -I FORWARD 4 -i $EXTIF ! -o $EXTIF -j MINIUPNPD $IPTABLES -t filter -F MINIUPNPD else echo "Filter table clean..initalizing.." $IPTABLES -t filter -N MINIUPNPD $IPTABLES -t filter -I FORWARD 4 -i $EXTIF ! -o $EXTIF -j MINIUPNPD fi miniupnpd-2.3.7/netfilter/ip6tables_init.sh010075500017500000024000000014361345115742100201660ustar00nanardstaff#! /bin/sh # $Id: ip6tables_init.sh,v 1.3 2019/04/03 16:25:55 nanard Exp $ # Improved Miniupnpd iptables init script. # Checks for state of filter before doing anything.. IPV6=1 EXT=1 . $(dirname "$0")/miniupnpd_functions.sh # -I inserts the rule at the head of the chain, # -A appends the rule at the end of the chain ADDCMD=-I #ADDCMD=-A if [ "$FDIRTY" = "${CHAIN}Chain" ]; then echo "Filter table dirty; Cleaning..." elif [ "$FDIRTY" = "Chain" ]; then echo "Dirty filter chain but no reference..? Fixing..." $IPTABLES -t filter $ADDCMD FORWARD -i $EXTIF ! -o $EXTIF -j $CHAIN else echo "Filter table clean..initalizing.." $IPTABLES -t filter -N $CHAIN $IPTABLES -t filter $ADDCMD FORWARD -i $EXTIF ! -o $EXTIF -j $CHAIN fi if [ "$CLEAN" = "yes" ]; then $IPTABLES -t filter -F $CHAIN fi miniupnpd-2.3.7/netfilter/ip6tables_removeall.sh010075500017500000024000000006741326165065400212210ustar00nanardstaff#! /bin/sh # $Id: ip6tables_removeall.sh,v 1.2 2018/04/06 09:21:11 nanard Exp $ IPV6=1 EXT=1 . $(dirname "$0")/miniupnpd_functions.sh #removing the MINIUPNPD chain for filter if [ "$FDIRTY" = "${CHAIN}Chain" ]; then $IPTABLES -t filter -F $CHAIN $IPTABLES -t filter -D FORWARD -i $EXTIF ! -o $EXTIF -j $CHAIN $IPTABLES -t filter -X $CHAIN elif [ "$FDIRTY" = "Chain" ]; then $IPTABLES -t filter -F $CHAIN $IPTABLES -t filter -X $CHAIN fi miniupnpd-2.3.7/netfilter/iptpinhole.c010064400017500000024000000316121432271534300172320ustar00nanardstaff/* $Id: iptpinhole.c,v 1.23 2022/05/18 06:37:50 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2020 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 "config.h" #include "../macros.h" #include "iptpinhole.h" #include "../upnpglobalvars.h" #include "../upnputils.h" #ifdef ENABLE_UPNPPINHOLE #include #include #include "tiny_nf_nat.h" #define IP6TC_HANDLE struct ip6tc_handle * static int next_uid = 1; static const char * miniupnpd_v6_filter_chain = "MINIUPNPD"; static LIST_HEAD(pinhole_list_t, pinhole_t) pinhole_list; static struct pinhole_t * get_pinhole(unsigned short uid); struct pinhole_t { struct in6_addr saddr; struct in6_addr daddr; LIST_ENTRY(pinhole_t) entries; unsigned int timestamp; unsigned short sport; unsigned short dport; unsigned short uid; unsigned char proto; char desc[]; }; void init_iptpinhole(void) { LIST_INIT(&pinhole_list); } void shutdown_iptpinhole(void) { struct pinhole_t * p; while(pinhole_list.lh_first != NULL) { p = pinhole_list.lh_first; LIST_REMOVE(p, entries); free(p); } } /* return uid */ static int add_to_pinhole_list(struct in6_addr * saddr, unsigned short sport, struct in6_addr * daddr, unsigned short dport, int proto, const char *desc, unsigned int timestamp) { struct pinhole_t * p; p = calloc(1, sizeof(struct pinhole_t) + strlen(desc) + 1); if(!p) { syslog(LOG_ERR, "add_to_pinhole_list calloc() error"); return -1; } strcpy(p->desc, desc); memcpy(&p->saddr, saddr, sizeof(struct in6_addr)); p->sport = sport; memcpy(&p->daddr, daddr, sizeof(struct in6_addr)); p->dport = dport; p->timestamp = timestamp; p->proto = (unsigned char)proto; LIST_INSERT_HEAD(&pinhole_list, p, entries); while(get_pinhole(next_uid) != NULL) { next_uid++; if(next_uid > 65535) next_uid = 1; } p->uid = next_uid; next_uid++; if(next_uid > 65535) next_uid = 1; return p->uid; } static struct pinhole_t * get_pinhole(unsigned short uid) { struct pinhole_t * p; for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) { if(p->uid == uid) return p; } return NULL; /* not found */ } /* new_match() * Allocate and set a new ip6t_entry_match structure * The caller must free() it after usage */ static struct ip6t_entry_match * new_match(int proto, unsigned short sport, unsigned short dport) { struct ip6t_entry_match *match; struct ip6t_tcp *info; /* TODO : use ip6t_udp if needed */ size_t size; const char * name; size = XT_ALIGN(sizeof(struct ip6t_entry_match)) + XT_ALIGN(sizeof(struct ip6t_tcp)); match = calloc(1, size); match->u.user.match_size = size; switch(proto) { case IPPROTO_TCP: name = "tcp"; break; case IPPROTO_UDP: name = "udp"; break; case IPPROTO_UDPLITE: name = "udplite"; break; default: name = NULL; } if(name) strncpy(match->u.user.name, name, sizeof(match->u.user.name)); else syslog(LOG_WARNING, "no name for protocol %d", proto); info = (struct ip6t_tcp *)match->data; if(sport) { info->spts[0] = sport; /* specified source port */ info->spts[1] = sport; } else { info->spts[0] = 0; /* all source ports */ info->spts[1] = 0xFFFF; } info->dpts[0] = dport; /* specified destination port */ info->dpts[1] = dport; return match; } static struct ip6t_entry_target * get_accept_target(void) { struct ip6t_entry_target * target = NULL; size_t size; size = XT_ALIGN(sizeof(struct ip6t_entry_target)) + XT_ALIGN(sizeof(int)); target = calloc(1, size); target->u.user.target_size = size; strncpy(target->u.user.name, "ACCEPT", sizeof(target->u.user.name)); return target; } static int ip6tc_init_verify_append(const char * table, const char * chain, struct ip6t_entry * e) { IP6TC_HANDLE h; h = ip6tc_init(table); if(!h) { syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno)); return -1; } if(!ip6tc_is_chain(chain, h)) { syslog(LOG_ERR, "chain %s not found", chain); goto error; } if(!ip6tc_append_entry(chain, e, h)) { syslog(LOG_ERR, "ip6tc_append_entry() error : %s", ip6tc_strerror(errno)); goto error; } if(!ip6tc_commit(h)) { syslog(LOG_ERR, "ip6tc_commit() error : %s", ip6tc_strerror(errno)); goto error; } ip6tc_free(h); return 0; /* ok */ error: ip6tc_free(h); return -1; } /* ip6tables -I %s %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j ACCEPT ip6tables -I %s %d -p %s -i %s --sport %hu -d %s --dport %hu -j ACCEPT miniupnpd_forward_chain, line_number, proto, ext_if_name, raddr, rport, iaddr, iport ip6tables -t raw -I PREROUTING %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j TRACE ip6tables -t raw -I PREROUTING %d -p %s -i %s --sport %hu -d %s --dport %hu -j TRACE */ int add_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, const char * desc, unsigned int timestamp) { int uid; struct ip6t_entry * e; struct ip6t_entry * tmp; struct ip6t_entry_match *match = NULL; struct ip6t_entry_target *target = NULL; e = calloc(1, sizeof(struct ip6t_entry)); if(!e) { syslog(LOG_ERR, "%s: calloc(%d) failed", "add_pinhole", (int)sizeof(struct ip6t_entry)); return -1; } e->ipv6.proto = proto; if (proto) e->ipv6.flags |= IP6T_F_PROTO; /* TODO: check if enforcing USE_IFNAME_IN_RULES is needed */ if(ifname) strncpy(e->ipv6.iniface, ifname, IFNAMSIZ); if(rem_host && (rem_host[0] != '\0')) { if(inet_pton(AF_INET6, rem_host, &e->ipv6.src) < 1) { syslog(LOG_WARNING, "failed to parse INET6 address \"%s\"", rem_host); } else { memset(&e->ipv6.smsk, 0xff, sizeof(e->ipv6.smsk)); } } if (inet_pton(AF_INET6, int_client, &e->ipv6.dst) < 1) { syslog(LOG_WARNING, "failed to parse INET6 address \"%s\"", int_client); } else { memset(&e->ipv6.dmsk, 0xff, sizeof(e->ipv6.dmsk)); } /*e->nfcache = NFC_IP_DST_PT;*/ /*e->nfcache |= NFC_UNKNOWN;*/ match = new_match(proto, rem_port, int_port); target = get_accept_target(); tmp = realloc(e, sizeof(struct ip6t_entry) + match->u.match_size + target->u.target_size); if(!tmp) { syslog(LOG_ERR, "%s: realloc(%d) failed", "add_pinhole", (int)(sizeof(struct ip6t_entry) + match->u.match_size + target->u.target_size)); free(e); free(match); free(target); return -1; } e = tmp; memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ip6t_entry) + match->u.match_size; e->next_offset = sizeof(struct ip6t_entry) + match->u.match_size + target->u.target_size; free(match); free(target); if(ip6tc_init_verify_append("filter", miniupnpd_v6_filter_chain, e) < 0) { free(e); return -1; } uid = add_to_pinhole_list(&e->ipv6.src, rem_port, &e->ipv6.dst, int_port, proto, desc, timestamp); free(e); return uid; } int find_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, char *desc, int desc_len, unsigned int * timestamp) { struct pinhole_t * p; struct in6_addr saddr; struct in6_addr daddr; UNUSED(ifname); if(rem_host && (rem_host[0] != '\0')) { if (inet_pton(AF_INET6, rem_host, &saddr) < 1) { syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", rem_host); memset(&saddr, 0, sizeof(struct in6_addr)); } } else { memset(&saddr, 0, sizeof(struct in6_addr)); } if (inet_pton(AF_INET6, int_client, &daddr) < 1) { syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", int_client); memset(&daddr, 0, sizeof(struct in6_addr)); } for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) { if((proto == p->proto) && (rem_port == p->sport) && (0 == memcmp(&saddr, &p->saddr, sizeof(struct in6_addr))) && (int_port == p->dport) && (0 == memcmp(&daddr, &p->daddr, sizeof(struct in6_addr)))) { if(desc) strncpy(desc, p->desc, desc_len); if(timestamp) *timestamp = p->timestamp; return (int)p->uid; } } return -2; /* not found */ } int delete_pinhole(unsigned short uid) { struct pinhole_t * p; IP6TC_HANDLE h; const struct ip6t_entry * e; const struct ip6t_entry_match *match = NULL; /*const struct ip6t_entry_target *target = NULL;*/ unsigned int index; p = get_pinhole(uid); if(!p) return -2; /* not found */ h = ip6tc_init("filter"); if(!h) { syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno)); return -1; } if(!ip6tc_is_chain(miniupnpd_v6_filter_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_v6_filter_chain); goto error; } index = 0; for(e = ip6tc_first_rule(miniupnpd_v6_filter_chain, h); e; e = ip6tc_next_rule(e, h)) { if((e->ipv6.proto == p->proto) && (0 == memcmp(&e->ipv6.src, &p->saddr, sizeof(e->ipv6.src))) && (0 == memcmp(&e->ipv6.dst, &p->daddr, sizeof(e->ipv6.dst)))) { const struct ip6t_tcp * info; match = (const struct ip6t_entry_match *)&e->elems; info = (const struct ip6t_tcp *)&match->data; if((info->spts[0] == p->sport) && (info->dpts[0] == p->dport)) { if(!ip6tc_delete_num_entry(miniupnpd_v6_filter_chain, index, h)) { syslog(LOG_ERR, "ip6tc_delete_num_entry(%s,%u,...): %s", miniupnpd_v6_filter_chain, index, ip6tc_strerror(errno)); goto error; } if(!ip6tc_commit(h)) { syslog(LOG_ERR, "ip6tc_commit(): %s", ip6tc_strerror(errno)); goto error; } ip6tc_free(h); LIST_REMOVE(p, entries); return 0; /* ok */ } } index++; } ip6tc_free(h); syslog(LOG_WARNING, "delete_pinhole() rule with PID=%hu not found", uid); LIST_REMOVE(p, entries); return -2; /* not found */ error: ip6tc_free(h); return -1; } int update_pinhole(unsigned short uid, unsigned int timestamp) { struct pinhole_t * p; p = get_pinhole(uid); if(p) { p->timestamp = timestamp; return 0; } else { return -2; /* Not found */ } } int get_pinhole_info(unsigned short uid, char * rem_host, int rem_hostlen, unsigned short * rem_port, char * int_client, int int_clientlen, unsigned short * int_port, int * proto, char * desc, int desclen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { struct pinhole_t * p; p = get_pinhole(uid); if(!p) return -2; /* Not found */ if(rem_host && (rem_host[0] != '\0')) { if(inet_ntop(AF_INET6, &p->saddr, rem_host, rem_hostlen) == NULL) return -1; } if(rem_port) *rem_port = p->sport; if(int_client) { if(inet_ntop(AF_INET6, &p->daddr, int_client, int_clientlen) == NULL) return -1; } if(int_port) *int_port = p->dport; if(proto) *proto = p->proto; if(timestamp) *timestamp = p->timestamp; if (desc) strncpy(desc, p->desc, desclen); if(packets || bytes) { /* theses informations need to be read from netfilter */ IP6TC_HANDLE h; const struct ip6t_entry * e; const struct ip6t_entry_match * match; h = ip6tc_init("filter"); if(!h) { syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno)); return -1; } for(e = ip6tc_first_rule(miniupnpd_v6_filter_chain, h); e; e = ip6tc_next_rule(e, h)) { if((e->ipv6.proto == p->proto) && (0 == memcmp(&e->ipv6.src, &p->saddr, sizeof(e->ipv6.src))) && (0 == memcmp(&e->ipv6.dst, &p->daddr, sizeof(e->ipv6.dst)))) { const struct ip6t_tcp * info; match = (const struct ip6t_entry_match *)&e->elems; info = (const struct ip6t_tcp *)&match->data; if((info->spts[0] == p->sport) && (info->dpts[0] == p->dport)) { if(packets) *packets = e->counters.pcnt; if(bytes) *bytes = e->counters.bcnt; break; } } } ip6tc_free(h); } return 0; } int get_pinhole_uid_by_index(int index) { struct pinhole_t * p; for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) if (!index--) return p->uid; return -1; } int clean_pinhole_list(unsigned int * next_timestamp) { unsigned int min_ts = UINT_MAX; struct pinhole_t * p; time_t current_time; int n = 0; current_time = upnp_time(); p = pinhole_list.lh_first; while(p != NULL) { if(p->timestamp <= (unsigned int)current_time) { unsigned short uid = p->uid; syslog(LOG_INFO, "removing expired pinhole with uid=%hu", uid); p = p->entries.le_next; if(delete_pinhole(uid) == 0) n++; else break; } else { if(p->timestamp < min_ts) min_ts = p->timestamp; p = p->entries.le_next; } } if(next_timestamp && (min_ts != UINT_MAX)) *next_timestamp = min_ts; return n; } #endif /* ENABLE_UPNPPINHOLE */ miniupnpd-2.3.7/netfilter/iptpinhole.h010064400017500000024000000027721264740756400172570ustar00nanardstaff/* $Id: iptpinhole.h,v 1.10 2016/01/19 10:03:30 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2012-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef IPTPINHOLE_H_INCLUDED #define IPTPINHOLE_H_INCLUDED #ifdef ENABLE_UPNPPINHOLE #include int find_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, char *desc, int desc_len, unsigned int * timestamp); int add_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, const char *desc, unsigned int timestamp); int update_pinhole(unsigned short uid, unsigned int timestamp); int delete_pinhole(unsigned short uid); int get_pinhole_info(unsigned short uid, char * rem_host, int rem_hostlen, unsigned short * rem_port, char * int_client, int int_clientlen, unsigned short * int_port, int * proto, char * desc, int desclen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); int get_pinhole_uid_by_index(int index); int clean_pinhole_list(unsigned int * next_timestamp); #endif /* ENABLE_UPNPPINHOLE */ #endif miniupnpd-2.3.7/netfilter/testiptpinhole.c010064400017500000024000000012011174625127000201220ustar00nanardstaff/* $Id: testiptpinhole.c,v 1.1 2012/04/26 13:50:48 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2012 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "../config.h" #include "iptpinhole.h" #include "../commonrdr.h" int main(int argc, char * * argv) { int uid; openlog("testiptpinhole", LOG_PERROR|LOG_CONS, LOG_LOCAL0); uid = add_pinhole("eth0", NULL, 0, "ff::123", 54321, IPPROTO_TCP); return 0; } miniupnpd-2.3.7/macros.h010064400017500000024000000014421432271757600143640ustar00nanardstaff/* $Id: macros.h,v 1.7 2022/10/16 06:03:56 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2022 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef MACROS_H_INCLUDED #define MACROS_H_INCLUDED #define UNUSED(arg) (void)(arg) #if defined(__GNUC__) && (__GNUC__ >= 7) #define FALL_THROUGH __attribute__((fallthrough)) #else #define FALL_THROUGH #endif /* Macro to print errors during initialization. * Print them on both stderr and syslog. * if debug_flag is on, syslog already print on console */ #define INIT_PRINT_ERR(...) do { if (!debug_flag) fprintf(stderr, __VA_ARGS__); syslog(LOG_ERR, __VA_ARGS__); } while(0) #endif /* MACROS_H_INCLUDED */ miniupnpd-2.3.7/rw_unaligned.h010064400017500000024000000023751432271713100155470ustar00nanardstaff/* $Id: rw_unaligned.h,v 1.1 2022/10/16 06:02:01 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2022 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef RW_UNALIGNED_H_INCLUDED #define RW_UNALIGNED_H_INCLUDED #include #ifndef INLINE #define INLINE static inline #endif /* theses macros are designed to read/write unsigned short/long int * from an unsigned char array in network order (big endian). * Avoid pointer casting, so avoid accessing unaligned memory, which * can crash with some cpu's */ INLINE uint32_t readnu32(const uint8_t * p) { return (p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]); } #define READNU32(p) readnu32(p) INLINE uint16_t readnu16(const uint8_t * p) { return (p[0] << 8 | p[1]); } #define READNU16(p) readnu16(p) INLINE void writenu32(uint8_t * p, uint32_t n) { p[0] = (n & 0xff000000) >> 24; p[1] = (n & 0xff0000) >> 16; p[2] = (n & 0xff00) >> 8; p[3] = n & 0xff; } #define WRITENU32(p, n) writenu32(p, n) INLINE void writenu16(uint8_t * p, uint16_t n) { p[0] = (n & 0xff00) >> 8; p[1] = n & 0xff; } #define WRITENU16(p, n) writenu16(p, n) #endif /* RW_UNALIGNED_H_INCLUDED */ miniupnpd-2.3.7/upnppinhole.c010064400017500000024000000657011461426441100154310ustar00nanardstaff/* $Id: upnppinhole.c,v 1.17 2024/04/28 23:32:21 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2024 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 #include #include "macros.h" #include "config.h" #include "upnpredirect.h" #include "upnpglobalvars.h" #include "upnpevents.h" #include "upnputils.h" #include "upnppinhole.h" #ifdef __APPLE__ /* XXX - Apple version of PF API seems to differ from what * pf/pfpinhole.c expects so don't use that at least.. */ #ifdef USE_PF #undef USE_PF #endif /* USE_PF */ #endif /* __APPLE__ */ #if defined(USE_NETFILTER) #include "netfilter/iptpinhole.h" #endif #if defined(USE_PF) #include "pf/pfpinhole.h" #endif #if defined(USE_IPF) #endif #if defined(USE_IPFW) #endif #ifdef ENABLE_LEASEFILE #include #endif #ifdef ENABLE_UPNPPINHOLE #if 0 int upnp_check_outbound_pinhole(int proto, int * timeout) { int s, tmptimeout, tmptime_out; switch(proto) { case IPPROTO_UDP: s = retrieve_timeout("udp_timeout", timeout); return s; break; case IPPROTO_UDPLITE: s = retrieve_timeout("udp_timeout_stream", timeout); return s; break; case IPPROTO_TCP: s = retrieve_timeout("tcp_timeout_established", timeout); return s; break; case 65535: s = retrieve_timeout("udp_timeout", timeout); s = retrieve_timeout("udp_timeout_stream", &tmptimeout); s = retrieve_timeout("tcp_timeout_established", &tmptime_out); if(tmptimeout 0) ? time(NULL) + leaseduration : 0; /* convert our time to unix time */ if (timestamp != 0) { timestamp -= upnp_time(); } if (rem_client == NULL) { rem_client = ""; } if (desc == NULL) { desc = ""; } fprintf(fd, "%s;%s;%hu;%s;%hu;%u;%u;%s\n", proto_itoa(proto), int_client, int_port, rem_client, rem_port, uid, timestamp, desc); fclose(fd); return 0; } static int lease_file6_update(int uid, unsigned int leaseduration) { FILE* fd, *fdt; char * p, * p2; unsigned short int_port, rem_port; char * proto; char * int_client; char * desc; char * rem_client; unsigned int timestamp_rule; unsigned int timestamp; char line[128]; char tmpfilename[128]; int uid_rule; int tmp; if (lease_file6 == NULL) return 0; if (strlen(lease_file6) + 7 > sizeof(tmpfilename)) { syslog(LOG_ERR, "Lease filename is too long"); return -1; } snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file6); fd = fopen( lease_file6, "r"); if (fd==NULL) { return 0; } tmp = mkstemp(tmpfilename); if (tmp==-1) { fclose(fd); syslog(LOG_ERR, "could not open temporary lease file"); return -1; } fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fdt = fdopen(tmp, "a"); timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0; /* convert our time to unix time */ if (timestamp != 0) { timestamp -= upnp_time(); } while(fgets(line, sizeof(line), fd)) { syslog(LOG_DEBUG, "parsing lease file line '%s'", line); proto = line; p = strchr(line, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; int_port = (unsigned short)atoi(p2); int_client = p; p = strchr(p2, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; rem_port = (unsigned short)atoi(p2); rem_client = p; p = strchr(p2, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; desc = strchr(p2, ';'); uid_rule = atoi(p); if(!desc) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(desc++) = '\0'; timestamp_rule = (unsigned int)strtoul(p2, NULL, 10); if (uid == uid_rule) { timestamp_rule = timestamp; } fprintf(fdt, "%s;%s;%hu;%s;%hu;%u;%u;%s\n", proto, int_client, int_port, rem_client, rem_port, uid, timestamp_rule, desc); } fclose(fdt); fclose(fd); if (rename(tmpfilename, lease_file6) < 0) { syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file6); remove(tmpfilename); } return 0; } static int lease_file6_remove(const char * int_client, unsigned short int_port, int proto, int uid) { FILE* fd, *fdt; int tmp, uid_tmp; char buf[512], buf2[512]; char str[32]; char tmpfilename[128]; char *p, *p2; int str_size, buf_size; if (lease_file6 == NULL) return 0; if (strlen(lease_file6) + 7 > sizeof(tmpfilename)) { syslog(LOG_ERR, "Lease filename is too long"); return -1; } snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file6); fd = fopen( lease_file6, "r"); if (fd==NULL) { return 0; } snprintf( str, sizeof(str), "%s;%s;%u", proto_itoa(proto), int_client, int_port); str_size = strlen(str); tmp = mkstemp(tmpfilename); if (tmp==-1) { fclose(fd); syslog(LOG_ERR, "could not open temporary lease file"); return -1; } fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fdt = fdopen(tmp, "a"); buf[sizeof(buf)-1] = 0; while( fgets(buf, sizeof(buf)-1, fd) != NULL) { buf_size = strlen(buf); if (uid > 0) { strncpy(buf2, buf, buf_size); // Internal Host p = strchr(buf2, ';'); *(p++) = '\0'; // Internal Port p = strchr(p, ';'); *(p++) = '\0'; // External Host p = strchr(p, ';'); *(p++) = '\0'; // External Port p = strchr(p, ';'); *(p++) = '\0'; // uid p = strchr(p, ';'); *(p++) = '\0'; p2 = strchr(p, ';'); *(p2++) = '\0'; uid_tmp = atoi(p); if (uid != uid_tmp) { fwrite(buf, buf_size, 1, fdt); } } else if (buf_size < str_size || strncmp(str, buf, str_size)!=0) { fwrite(buf, buf_size, 1, fdt); } } fclose(fdt); fclose(fd); if (rename(tmpfilename, lease_file6) < 0) { syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file6); remove(tmpfilename); } return 0; } int lease_file6_expire() { FILE* fd, *fdt; char * p, * p2; int tmp; char buf[512]; char line[512]; char tmpfilename[128]; char * desc; int buf_size; unsigned int timestamp; time_t current_unix_time; if (lease_file6 == NULL) return 0; if (strlen(lease_file6) + 7 > sizeof(tmpfilename)) { syslog(LOG_ERR, "Lease filename is too long"); return -1; } snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file6); fd = fopen( lease_file6, "r"); if (fd==NULL) { return 0; } current_unix_time = time(NULL); tmp = mkstemp(tmpfilename); if (tmp==-1) { fclose(fd); syslog(LOG_ERR, "could not open temporary lease file"); return -1; } fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fdt = fdopen(tmp, "a"); buf[sizeof(buf)-1] = 0; while(fgets(line, sizeof(line), fd)) { strncpy(buf, line, sizeof(buf)); syslog(LOG_DEBUG, "Expire: parsing lease file line '%s'", line); // Internal Host p = strchr(line, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; // Internal Port p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; // External Host p = strchr(p2, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; // External Port p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; // uid p = strchr(p2, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; // Timestamp p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; // descr desc = strchr(p2, ';'); if(!desc) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(desc++) = '\0'; /*timestamp = (unsigned int)atoi(p2);*/ timestamp = (unsigned int)strtoul(p2, NULL, 10); syslog(LOG_DEBUG, "Expire: timestamp is '%u'", timestamp); syslog(LOG_DEBUG, "Expire: current timestamp is '%u'", (unsigned int)current_unix_time); if((timestamp > 0 && timestamp <= (unsigned int)current_unix_time) || timestamp <= 0) { continue; } buf_size = strlen(buf); fwrite(buf, buf_size, 1, fdt); } fclose(fdt); fclose(fd); if (rename(tmpfilename, lease_file6) < 0) { syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file6); remove(tmpfilename); } return 0; } /* reload_from_lease_file() * read lease_file and add the rules contained */ int reload_from_lease_file6() { FILE * fd; char * p, * p2; unsigned short int_port, rem_port; char * proto; char * int_client; char * desc; char * rem_client; unsigned int leaseduration; unsigned int timestamp; time_t current_time; time_t current_unix_time; char line[128]; int r, uid; if(!lease_file6) return -1; fd = fopen( lease_file6, "r"); if (fd==NULL) { syslog(LOG_ERR, "could not open lease file: %s", lease_file6); return -1; } if(unlink(lease_file6) < 0) { syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file6); } current_time = upnp_time(); current_unix_time = time(NULL); while(fgets(line, sizeof(line), fd)) { syslog(LOG_DEBUG, "parsing lease file line '%s'", line); proto = line; p = strchr(line, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; int_port = (unsigned short)atoi(p2); int_client = p; p = strchr(p2, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; rem_port = (unsigned short)atoi(p2); rem_client = p; p = strchr(p2, ';'); if(!p) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p++) = '\0'; p2 = strchr(p, ';'); if(!p2) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(p2++) = '\0'; desc = strchr(p2, ';'); uid = atoi(p); if(!desc) { syslog(LOG_ERR, "unrecognized data in lease file"); continue; } *(desc++) = '\0'; /*timestamp = (unsigned int)atoi(p2);*/ timestamp = (unsigned int)strtoul(p2, NULL, 10); /* trim description */ while(isspace(*desc)) desc++; p = desc; while(*(p+1)) p++; while(isspace(*p) && (p > desc)) *(p--) = '\0'; if(timestamp > 0) { if(timestamp <= (unsigned int)current_unix_time) { syslog(LOG_NOTICE, "already expired lease in lease file"); continue; } else { leaseduration = timestamp - current_unix_time; timestamp = leaseduration + current_time; /* convert to our time */ } } else { leaseduration = 0; /* default value */ } r = upnp_add_inboundpinhole(rem_client, rem_port, int_client, int_port, proto_atoi(proto), desc, leaseduration, &uid); if(r == -1) { syslog(LOG_ERR, "Failed to add %s:%hu -> %s:%hu protocol %s", rem_client, rem_port, int_client, int_port, proto); } else if(r == -2) { /* Add the redirection again to the lease file */ lease_file6_add(rem_client, rem_port, int_client, int_port, proto_atoi(proto), uid, desc, timestamp); } } fclose(fd); return 0; } #endif int upnp_find_inboundpinhole(const char * raddr, unsigned short rport, const char * iaddr, unsigned short iport, int proto, char * desc, int desc_len, unsigned int * leasetime) { #if defined(USE_PF) || defined(USE_NETFILTER) int uid; uid = find_pinhole(NULL, raddr, rport, iaddr, iport, proto, desc, desc_len, leasetime); return uid; #else return -42; #endif } /* upnp_add_inboundpinhole() * returns: 1 on success * -1 Pinhole space exhausted * -4 invalid arguments * -42 not implemented * TODO : return uid on success (positive) or error value (negative) */ int upnp_add_inboundpinhole(const char * raddr, unsigned short rport, const char * iaddr, unsigned short iport, int proto, char * desc, unsigned int leasetime, int * uid) { int r; time_t current; unsigned int timestamp; struct in6_addr address; r = inet_pton(AF_INET6, iaddr, &address); if(r <= 0) { syslog(LOG_ERR, "inet_pton(%d, %s, %p) FAILED", AF_INET6, iaddr, &address); return -4; } current = upnp_time(); timestamp = current + leasetime; r = 0; *uid = upnp_find_inboundpinhole(raddr, rport, iaddr, iport, proto, NULL, 0, NULL); if(*uid >= 0) { syslog(LOG_INFO, "Pinhole for inbound traffic from [%s]:%hu to [%s]:%hu with proto %d found uid=%d. Updating it.", raddr, rport, iaddr, iport, proto, *uid); r = upnp_update_inboundpinhole(*uid, leasetime); #ifdef ENABLE_LEASEFILE if (r >= 0) { lease_file6_remove(iaddr, iport, proto, -1); lease_file6_add(raddr, rport, iaddr, iport, proto, *uid, desc, timestamp); } #endif /* ENABLE_LEASEFILE */ return (r >= 0) ? 1 : r; } #if defined(USE_PF) || defined(USE_NETFILTER) *uid = add_pinhole (ext_if_name6, raddr, rport, iaddr, iport, proto, desc, timestamp); #ifdef ENABLE_LEASEFILE if (*uid >= 0) { lease_file6_remove(iaddr, iport, proto, -1); lease_file6_add(raddr, rport, iaddr, iport, proto, *uid, desc, timestamp); } #endif /* ENABLE_LEASEFILE */ return *uid >= 0 ? 1 : -1; #else return -42; /* not implemented */ #endif } #if 0 int upnp_add_inboundpinhole_internal(const char * raddr, unsigned short rport, const char * iaddr, unsigned short iport, const char * proto, int * uid) { int c = 9999; char cmd[256], cmd_raw[256], cuid[42]; #if 0 static const char cmdval_full_udptcp[] = "ip6tables -I %s %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j ACCEPT"; static const char cmdval_udptcp[] = "ip6tables -I %s %d -p %s -i %s --sport %hu -d %s --dport %hu -j ACCEPT"; static const char cmdval_full_udplite[] = "ip6tables -I %s %d -p %s -i %s -s %s -d %s -j ACCEPT"; static const char cmdval_udplite[] = "ip6tables -I %s %d -p %s -i %s -d %s -j ACCEPT"; // raw table command static const char cmdval_full_udptcp_raw[] = "ip6tables -t raw -I PREROUTING %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j TRACE"; static const char cmdval_udptcp_raw[] = "ip6tables -t raw -I PREROUTING %d -p %s -i %s --sport %hu -d %s --dport %hu -j TRACE"; static const char cmdval_full_udplite_raw[] = "ip6tables -t raw -I PREROUTING %d -p %s -i %s -s %s -d %s -j TRACE"; static const char cmdval_udplite_raw[] = "ip6tables -t raw -I PREROUTING %d -p %s -i %s -d %s -j TRACE"; #endif /*printf("%s\n", raddr);*/ if(raddr!=NULL) { #ifdef IPPROTO_UDPLITE if(atoi(proto) == IPPROTO_UDPLITE) { /* snprintf(cmd, sizeof(cmd), cmdval_full_udplite, miniupnpd_forward_chain, line_number, proto, ext_if_name, raddr, iaddr); snprintf(cmd_raw, sizeof(cmd_raw), cmdval_full_udplite_raw, line_number, proto, ext_if_name, raddr, iaddr);*/ } else #endif { /* snprintf(cmd, sizeof(cmd), cmdval_full_udptcp, miniupnpd_forward_chain, line_number, proto, ext_if_name, raddr, rport, iaddr, iport); snprintf(cmd_raw, sizeof(cmd_raw), cmdval_full_udptcp_raw, line_number, proto, ext_if_name, raddr, rport, iaddr, iport);*/ } } else { #ifdef IPPROTO_UDPLITE if(atoi(proto) == IPPROTO_UDPLITE) { /*snprintf(cmd, sizeof(cmd), cmdval_udplite, miniupnpd_forward_chain, line_number, proto, ext_if_name, iaddr); snprintf(cmd_raw, sizeof(cmd_raw), cmdval_udplite_raw, line_number, proto, ext_if_name, iaddr);*/ } else #endif { /*snprintf(cmd, sizeof(cmd), cmdval_udptcp, miniupnpd_forward_chain, line_number, proto, ext_if_name, rport, iaddr, iport); snprintf(cmd_raw, sizeof(cmd_raw), cmdval_udptcp_raw, line_number, proto, ext_if_name, rport, iaddr, iport); */ } } #ifdef DEBUG syslog(LOG_INFO, "Adding following ip6tables rule:"); syslog(LOG_INFO, " -> %s", cmd); syslog(LOG_INFO, " -> %s", cmd_raw); #endif /* TODO Add a better checking error.*/ if(system(cmd) < 0 || system(cmd_raw) < 0) { return 0; } srand(time(NULL)); snprintf(cuid, sizeof(cuid), "%.4d", rand()%c); *uid = atoi(cuid); printf("\t_add_ uid: %s\n", cuid); return 1; } #endif /* upnp_get_pinhole_info() * return values : * 0 OK * -1 Internal error * -2 NOT FOUND (no such entry) * .. * -42 Not implemented */ int upnp_get_pinhole_info(unsigned short uid, char * raddr, int raddrlen, unsigned short * rport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, unsigned int * leasetime, unsigned int * packets) { /* Call Firewall specific code to get IPv6 pinhole infos */ #if defined(USE_PF) || defined(USE_NETFILTER) int r; unsigned int timestamp; u_int64_t packets_tmp; /*u_int64_t bytes_tmp;*/ r = get_pinhole_info(uid, raddr, raddrlen, rport, iaddr, iaddrlen, iport, proto, desc, desclen, leasetime ? ×tamp : NULL, packets ? &packets_tmp : NULL, NULL/*&bytes_tmp*/); if(r >= 0) { if(leasetime) { time_t current_time; current_time = upnp_time(); if(timestamp > (unsigned int)current_time) *leasetime = timestamp - current_time; else *leasetime = 0; } if(packets) *packets = (unsigned int)packets_tmp; } return r; #else UNUSED(uid); UNUSED(raddr); UNUSED(raddrlen); UNUSED(rport); UNUSED(iaddr); UNUSED(iaddrlen); UNUSED(iport); UNUSED(proto); UNUSED(desc); UNUSED(desclen); UNUSED(leasetime); UNUSED(packets); return -42; /* not implemented */ #endif } int upnp_get_pinhole_uid_by_index(int index) { #if defined (USE_NETFILTER) return get_pinhole_uid_by_index(index); #else UNUSED(index); return -42; #endif /* defined (USE_NETFILTER) */ } int upnp_update_inboundpinhole(unsigned short uid, unsigned int leasetime) { #if defined(USE_PF) || defined(USE_NETFILTER) unsigned int timestamp; int ret; timestamp = upnp_time() + leasetime; ret = update_pinhole(uid, timestamp); #ifdef ENABLE_LEASEFILE if (ret == 0) lease_file6_update(uid, timestamp); #endif return ret; #else UNUSED(uid); UNUSED(leasetime); return -42; /* not implemented */ #endif } int upnp_delete_inboundpinhole(unsigned short uid) { #if defined(USE_PF) || defined(USE_NETFILTER) int ret; ret = delete_pinhole(uid); #ifdef ENABLE_LEASEFILE if (ret == 0) lease_file6_remove((const char *)"*", 0, 0, uid); #endif return ret; #else UNUSED(uid); return -1; #endif } #if 0 /* * Result: * 1: Found Result * -4: No result * -5: Result in another table * -6: Result in another chain * -7: Result in a chain not a rule */ int upnp_check_pinhole_working(const char * uid, char * eaddr, char * iaddr, unsigned short * eport, unsigned short * iport, char * protocol, int * rulenum_used) { /* TODO : to be implemented */ #if 0 FILE * fd; time_t expire = upnp_time(); char buf[1024], filename[] = "/var/log/kern.log", expire_time[32]=""; int res = -4, str_len; str_len = strftime(expire_time, sizeof(expire_time), "%b %d %H:%M:%S", localtime(&expire)); fd = fopen(filename, "r"); if (fd==NULL) { syslog(LOG_ERR, "Get_rule: could not open file: %s", filename); return -1; } syslog(LOG_INFO, "Get_rule: Starting getting info in file %s for %s\n", filename, uid); buf[sizeof(buf)-1] = 0; while(fgets(buf, sizeof(buf)-1, fd) != NULL && res != 1) { //printf("line: %s\n", buf); char * r, * t, * c, * p; // looking for something like filter:FORWARD:rule: or filter:MINIUPNPD:rule: r = strstr(buf, ":rule:"); p = strstr(buf, ":policy:"); t = strstr(buf, "TRACE:"); // table pointeur t += 7; c = t + 7; // chain pointeur if(r) { printf("\t** Found %.*s\n", 24 ,t); char * src, * dst, * sport, * dport, * proto, * line; char time[15]="", src_addr[40], dst_addr[40], proto_tmp[8]; int proto_int; strncpy(time, buf, sizeof(time)); /*if(compare_time(time, expire_time)<0) { printf("\t\tNot corresponding time\n"); continue; }*/ line = r + 6; printf("\trule line = %d\n", atoi(line)); src = strstr(buf, "SRC="); src += 4; snprintf(src_addr, sizeof(src_addr), "%.*s", 39, src); #if 0 del_char(src_addr); add_char(src_addr); #endif dst = strstr(buf, "DST="); dst += 4; snprintf(dst_addr, sizeof(dst_addr), "%.*s", 39, dst); #if 0 del_char(dst_addr); add_char(dst_addr); #endif proto = strstr(buf, "PROTO="); proto += 6; proto_int = atoi(protocol); if(proto_int == IPPROTO_UDP) strcpy(proto_tmp, "UDP"); else if(proto_int == IPPROTO_TCP) strcpy(proto_tmp, "TCP"); #ifdef IPPROTO_UDPLITE else if(proto_int == IPPROTO_UDPLITE) strcpy(proto_tmp, "UDPLITE"); #endif else strcpy(proto_tmp, "UnsupportedProto"); // printf("\tCompare eaddr: %s // protocol: %s\n\t to addr: %s // protocol: %.*s\n", eaddr, proto_tmp, src_addr, strlen(proto_tmp), proto); // printf("\tCompare iaddr: %s // protocol: %s\n\t to addr: %s // protocol: %.*s\n", iaddr, proto_tmp, dst_addr, strlen(proto_tmp), proto); // TODO Check time // Check that the paquet found in trace correspond to the one we are looking for if( /*(strcmp(eaddr, src_addr) == 0) &&*/ (strcmp(iaddr, dst_addr) == 0) && (strncmp(proto_tmp, proto, strlen(proto_tmp))==0)) { sport = strstr(buf, "SPT="); sport += 4; dport = strstr(buf, "DPT="); dport += 4; printf("\tCompare eport: %hu\n\t to port: %d\n", *eport, atoi(sport)); printf("\tCompare iport: %hu\n\t to port: %d\n", *iport, atoi(dport)); if(/*eport != atoi(sport) &&*/ *iport != atoi(dport)) { printf("\t\tPort not corresponding\n"); continue; } printf("\ttable found: %.*s\n", 6, t); printf("\tchain found: %.*s\n", 9, c); // Check that the table correspond to the filter table if(strncmp(t, "filter", 6)==0) { // Check that the table correspond to the MINIUPNP table if(strncmp(c, "MINIUPNPD", 9)==0) { *rulenum_used = atoi(line); res = 1; } else { res = -6; continue; } } else { res = -5; continue; } } else { printf("Packet information not corresponding\n"); continue; } } if(!r && p) { printf("\t** Policy case\n"); char * src, * dst, * sport, * dport, * proto, * line; char time[15], src_addr[40], dst_addr[40], proto_tmp[8]; int proto_int; strncpy(time, buf, sizeof(time)); /*if(compare_time(time, expire_time)<0) { printf("\t\tNot corresponding time\n"); continue; }*/ line = p + 8; printf("\trule line = %d\n", atoi(line)); src = strstr(buf, "SRC="); src += 4; snprintf(src_addr, sizeof(src_addr), "%.*s", 39, src); #if 0 del_char(src_addr); add_char(src_addr); #endif dst = strstr(buf, "DST="); dst += 4; snprintf(dst_addr, sizeof(dst_addr), "%.*s", 39, dst); #if 0 del_char(dst_addr); add_char(dst_addr); #endif proto = strstr(buf, "PROTO="); proto += 6; proto_int = atoi(protocol); if(proto_int == IPPROTO_UDP) strcpy(proto_tmp, "UDP"); else if(proto_int == IPPROTO_TCP) strcpy(proto_tmp, "TCP"); #ifdef IPPROTO_UDPLITE else if(proto_int == IPPROTO_UDPLITE) strcpy(proto_tmp, "UDPLITE"); #endif else strcpy(proto_tmp, "UnsupportedProto"); // printf("\tCompare eaddr: %s // protocol: %s\n\t to addr: %s // protocol: %.*s\n", eaddr, proto_tmp, src_addr, strlen(proto_tmp), proto); // printf("\tCompare iaddr: %s // protocol: %s\n\t to addr: %s // protocol: %.*s\n", iaddr, proto_tmp, dst_addr, strlen(proto_tmp), proto); // Check that the paquet found in trace correspond to the one we are looking for if( (strcmp(eaddr, src_addr) == 0) && (strcmp(iaddr, dst_addr) == 0) && (strncmp(proto_tmp, proto, 5)==0)) { sport = strstr(buf, "SPT="); sport += 4; dport = strstr(buf, "DPT="); dport += 4; printf("\tCompare eport: %hu\n\t to port: %d\n", *eport, atoi(sport)); printf("\tCompare iport: %hu\n\t to port: %d\n", *iport, atoi(dport)); if(*eport != atoi(sport) && *iport != atoi(dport)) { printf("\t\tPort not corresponding\n"); continue; } else { printf("Find a corresponding policy trace in the chain: %.*s\n", 10, c); res = -7; continue; } } else continue; } } fclose(fd); return res; #else return -42; /* to be implemented */ #endif } #endif int upnp_clean_expired_pinholes(unsigned int * next_timestamp) { int ret = 0; #if defined(USE_PF) || defined(USE_NETFILTER) ret = clean_pinhole_list(next_timestamp); #else UNUSED(next_timestamp); #endif #ifdef ENABLE_LEASEFILE lease_file6_expire(); #endif return ret; } #endif /* ENABLE_UPNPPINHOLE */ miniupnpd-2.3.7/upnppinhole.h010064400017500000024000000053231411013505400154170ustar00nanardstaff/* $Id: upnppinhole.h,v 1.7 2021/08/21 08:12:49 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPPINHOLE_H_INCLUDED #define UPNPPINHOLE_H_INCLUDED #include "config.h" #ifdef ENABLE_UPNPPINHOLE #ifdef ENABLE_LEASEFILE int reload_from_lease_file6(void); int lease_file6_expire(void); #endif /* functions to be used by WANIPv6_FirewallControl implementation * and PCP (IPv6) */ #if 0 /* retrieve outbound pinhole timeout */ int upnp_check_outbound_pinhole(int proto, int * timeout); #endif /* find an inbound pinhole base on remove host:port / local host:port * return the (positive) uid or a negative value if not found */ int upnp_find_inboundpinhole(const char * raddr, unsigned short rport, const char * iaddr, unsigned short iport, int proto, char * desc, int desc_len, unsigned int * leasetime); /* add an inbound pinehole * return value : * 1 = success * -1 = Pinhole space exhausted * .. = error */ int upnp_add_inboundpinhole(const char * raddr, unsigned short rport, const char * iaddr, unsigned short iport, int proto, char * desc, unsigned int leasetime, int * uid); /* get from uid * return values : * -1 not found * */ int upnp_get_pinhole_info(unsigned short uid, char * raddr, int raddrlen, unsigned short * rport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, unsigned int * leasetime, unsigned int * packets); /* * return values: * -1 = not found * 0 .. 65535 = uid of the rule for the index */ int upnp_get_pinhole_uid_by_index(int index); /* update the lease time */ int upnp_update_inboundpinhole(unsigned short uid, unsigned int leasetime); /* remove the inbound pinhole */ int upnp_delete_inboundpinhole(unsigned short uid); /* ... */ #if 0 int upnp_check_pinhole_working(const char * uid, char * eaddr, char * iaddr, unsigned short * eport, unsigned short * iport, char * protocol, int * rulenum_used); #endif /* return the number of expired pinhole removed * write timestamp to next pinhole to exprire to next_timestamp * next_timestamp is left untouched if there is no pinhole lest */ int upnp_clean_expired_pinholes(unsigned int * next_timestamp); #endif /* ENABLE_UPNPPINHOLE */ #endif /* !UPNPPINHOLE_H_INCLUDED */ miniupnpd-2.3.7/linux/getroute.c010064400017500000024000000123521251745671000160640ustar00nanardstaff/* $Id: getroute.c,v 1.6 2015/04/26 14:43:28 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2015 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 #include #ifdef USE_LIBNFNETLINK /* define USE_LIBNFNETLINK in order to use libnfnetlink * instead of custom code * see https://github.com/miniupnp/miniupnp/issues/110 */ #include #endif /* USE_LIBNFNETLINK */ #include "../getroute.h" #include "../upnputils.h" int get_src_for_route_to(const struct sockaddr * dst, void * src, size_t * src_len, int * index) { int fd = -1; struct nlmsghdr *h; int status; struct { struct nlmsghdr n; struct rtmsg r; char buf[1024]; } req; struct sockaddr_nl nladdr; struct iovec iov = { .iov_base = (void*) &req.n, }; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1, }; const struct sockaddr_in * dst4; const struct sockaddr_in6 * dst6; #ifndef USE_LIBNFNETLINK struct rtattr * rta; #endif /* USE_LIBNFNETLINK */ memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_GETROUTE; req.r.rtm_family = dst->sa_family; req.r.rtm_table = 0; req.r.rtm_protocol = 0; req.r.rtm_scope = 0; req.r.rtm_type = 0; req.r.rtm_src_len = 0; req.r.rtm_dst_len = 0; req.r.rtm_tos = 0; { char dst_str[128]; sockaddr_to_string(dst, dst_str, sizeof(dst_str)); syslog(LOG_DEBUG, "get_src_for_route_to (%s)", dst_str); } /* add address */ #ifndef USE_LIBNFNETLINK rta = (struct rtattr *)(((char*)&req) + NLMSG_ALIGN(req.n.nlmsg_len)); rta->rta_type = RTA_DST; #endif /* USE_LIBNFNETLINK */ if(dst->sa_family == AF_INET) { dst4 = (const struct sockaddr_in *)dst; #ifdef USE_LIBNFNETLINK nfnl_addattr_l(&req.n, sizeof(req), RTA_DST, &dst4->sin_addr, 4); #else rta->rta_len = RTA_SPACE(sizeof(dst4->sin_addr)); memcpy(RTA_DATA(rta), &dst4->sin_addr, sizeof(dst4->sin_addr)); #endif /* USE_LIBNFNETLINK */ req.r.rtm_dst_len = 32; } else { dst6 = (const struct sockaddr_in6 *)dst; #ifdef USE_LIBNFNETLINK nfnl_addattr_l(&req.n, sizeof(req), RTA_DST, &dst6->sin6_addr, 16); #else rta->rta_len = RTA_SPACE(sizeof(dst6->sin6_addr)); memcpy(RTA_DATA(rta), &dst6->sin6_addr, sizeof(dst6->sin6_addr)); #endif /* USE_LIBNFNETLINK */ req.r.rtm_dst_len = 128; } #ifndef USE_LIBNFNETLINK req.n.nlmsg_len += rta->rta_len; #endif /* USE_LIBNFNETLINK */ fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { syslog(LOG_ERR, "socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) : %m"); return -1; } memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; req.n.nlmsg_seq = 1; iov.iov_len = req.n.nlmsg_len; status = sendmsg(fd, &msg, 0); if (status < 0) { syslog(LOG_ERR, "sendmsg(rtnetlink) : %m"); goto error; } memset(&req, 0, sizeof(req)); for(;;) { iov.iov_len = sizeof(req); status = recvmsg(fd, &msg, 0); if(status < 0) { if (errno == EINTR || errno == EAGAIN) continue; syslog(LOG_ERR, "recvmsg(rtnetlink) %m"); goto error; } if(status == 0) { syslog(LOG_ERR, "recvmsg(rtnetlink) EOF"); goto error; } for (h = (struct nlmsghdr*)&req.n; status >= (int)sizeof(*h); ) { int len = h->nlmsg_len; int l = len - sizeof(*h); if (l<0 || len>status) { if (msg.msg_flags & MSG_TRUNC) { syslog(LOG_ERR, "Truncated message"); } syslog(LOG_ERR, "malformed message: len=%d", len); goto error; } if(nladdr.nl_pid != 0 || h->nlmsg_seq != 1/*seq*/) { syslog(LOG_ERR, "wrong seq = %d\n", h->nlmsg_seq); /* Don't forget to skip that message. */ status -= NLMSG_ALIGN(len); h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); continue; } if(h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); syslog(LOG_ERR, "NLMSG_ERROR %d : %s", err->error, strerror(-err->error)); goto error; } if(h->nlmsg_type == RTM_NEWROUTE) { struct rtattr * rta; int len = h->nlmsg_len; len -= NLMSG_LENGTH(sizeof(struct rtmsg)); for(rta = RTM_RTA(NLMSG_DATA((h))); RTA_OK(rta, len); rta = RTA_NEXT(rta,len)) { unsigned char * data = RTA_DATA(rta); if(rta->rta_type == RTA_PREFSRC) { if(src_len && src) { if(*src_len < RTA_PAYLOAD(rta)) { syslog(LOG_WARNING, "cannot copy src: %u<%lu", (unsigned)*src_len, (unsigned long)RTA_PAYLOAD(rta)); goto error; } *src_len = RTA_PAYLOAD(rta); memcpy(src, data, RTA_PAYLOAD(rta)); } } else if(rta->rta_type == RTA_OIF) { if(index) memcpy(index, data, sizeof(int)); } } close(fd); return 0; } status -= NLMSG_ALIGN(len); h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); } } syslog(LOG_WARNING, "get_src_for_route_to() : src not found"); error: if(fd >= 0) close(fd); return -1; } miniupnpd-2.3.7/bsd/getroute.c010064400017500000024000000074241365610016700154760ustar00nanardstaff/* $Id: getroute.c,v 1.15 2020/05/10 22:24:11 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2020 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 #ifdef AF_LINK #include #endif #include "config.h" #include "../upnputils.h" /* SA_SIZE() is a multiple of sizeof(long) with a minimum value of sizeof(long) */ #ifndef SA_SIZE #define SA_SIZE(sa) (((SA_LEN(sa)) == 0) ? sizeof(long) : (1 + (((SA_LEN(sa)) - 1) | (sizeof(long) - 1)))) #endif /* SA_SIZE */ int get_src_for_route_to(const struct sockaddr * dst, void * src, size_t * src_len, int * index) { int found = 0; int s; int l, i; char * p; struct sockaddr * sa; struct { struct rt_msghdr m_rtm; char m_space[512]; } m_rtmsg; #define rtm m_rtmsg.m_rtm if(dst == NULL) return -1; if(SA_LEN(dst) > 0) { l = SA_LEN(dst); } else { if(dst->sa_family == AF_INET) l = sizeof(struct sockaddr_in); else if(dst->sa_family == AF_INET6) l = sizeof(struct sockaddr_in6); else { syslog(LOG_ERR, "unknown address family %d", dst->sa_family); return -1; } } s = socket(PF_ROUTE, SOCK_RAW, dst->sa_family); if(s < 0) { syslog(LOG_ERR, "socket(PF_ROUTE) failed : %m"); return -1; } memset(&rtm, 0, sizeof(rtm)); rtm.rtm_type = RTM_GET; rtm.rtm_flags = RTF_UP; rtm.rtm_version = RTM_VERSION; rtm.rtm_seq = 1; rtm.rtm_addrs = RTA_DST | RTA_IFA | RTA_IFP; /* pass destination address, request source address & interface */ memcpy(m_rtmsg.m_space, dst, l); #if !defined(__sun) ((struct sockaddr *)m_rtmsg.m_space)->sa_len = l; #endif rtm.rtm_msglen = sizeof(struct rt_msghdr) + l; if(write(s, &m_rtmsg, rtm.rtm_msglen) < 0) { syslog(LOG_ERR, "write: %m"); close(s); return -1; } do { l = read(s, &m_rtmsg, sizeof(m_rtmsg)); if(l<0) { syslog(LOG_ERR, "read: %m"); close(s); return -1; } syslog(LOG_DEBUG, "read l=%d seq=%d pid=%d sizeof(struct rt_msghdr)=%d", l, rtm.rtm_seq, rtm.rtm_pid, (int)sizeof(struct rt_msghdr)); } while(l > 0 && (rtm.rtm_pid != getpid() || rtm.rtm_seq != 1)); close(s); if(l <= 0) { syslog(LOG_WARNING, "no matching ROUTE response message"); return -1; } p = m_rtmsg.m_space; if(rtm.rtm_addrs) { for(i=1; i<0x8000; i <<= 1) { if(i & rtm.rtm_addrs) { char tmp[256] = { 0 }; if(p >= (char *)&m_rtmsg + l) { syslog(LOG_ERR, "error parsing ROUTE response message"); break; } sa = (struct sockaddr *)p; sockaddr_to_string(sa, tmp, sizeof(tmp)); syslog(LOG_DEBUG, "offset=%3d type=%2d sa_len=%d sa_family=%d %s", (int)(p - m_rtmsg.m_space), i, SA_LEN(sa), sa->sa_family, tmp); if(i == RTA_IFA) { size_t len = 0; void * paddr = NULL; if(sa->sa_family == AF_INET) { paddr = &((struct sockaddr_in *)sa)->sin_addr; len = sizeof(struct in_addr); } else if(sa->sa_family == AF_INET6) { paddr = &((struct sockaddr_in6 *)sa)->sin6_addr; len = sizeof(struct in6_addr); } if(paddr) { if(src && src_len) { if(*src_len < len) { syslog(LOG_WARNING, "cannot copy src. %u<%u", (unsigned)*src_len, (unsigned)len); return -1; } memcpy(src, paddr, len); *src_len = len; } found = 1; } } #ifdef AF_LINK else if((i == RTA_IFP) && (sa->sa_family == AF_LINK)) { struct sockaddr_dl * sdl = (struct sockaddr_dl *)sa; if(index) *index = sdl->sdl_index; } #endif p += SA_SIZE(sa); } } } return found ? 0 : -1; } miniupnpd-2.3.7/getroute.h010064400017500000024000000007621210443263100147200ustar00nanardstaff/* $Id: getroute.h,v 1.3 2013/02/06 10:50:04 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2013 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef GETROUTE_H_INCLUDED #define GETROUTE_H_INCLUDED int get_src_for_route_to(const struct sockaddr * dst, void * src, size_t * src_len, int * index); #endif miniupnpd-2.3.7/testgetroute.c010064400017500000024000000051071325172356500156260ustar00nanardstaff/* $Id: testgetroute.c,v 1.7 2018/03/13 10:25:52 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2018 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 "getroute.h" #include "upnputils.h" #include "upnpglobalvars.h" #ifndef LOG_PERROR /* solaris does not define LOG_PERROR */ #define LOG_PERROR 0 #endif struct lan_addr_list lan_addrs; int runtime_flags = 0; time_t startup_time = 0; int main(int argc, char ** argv) { struct sockaddr_in dst4; struct sockaddr_in6 dst6; struct sockaddr * dst; void * src; size_t src_len; int r; int index = -1; memset(&dst4, 0, sizeof(dst4)); memset(&dst6, 0, sizeof(dst6)); dst = NULL; if(argc < 2) { fprintf(stderr, "usage: %s \n", argv[0]); fprintf(stderr, "both v4 and v6 IP addresses are supported.\n"); return 1; } openlog("testgetroute", LOG_CONS|LOG_PERROR, LOG_USER); r = inet_pton (AF_INET, argv[1], &dst4.sin_addr); if(r < 0) { syslog(LOG_ERR, "inet_pton(AF_INET, %s) : %m", argv[1]); closelog(); return 2; } if (r == 0) { r = inet_pton (AF_INET6, argv[1], &dst6.sin6_addr); if(r < 0) { syslog(LOG_ERR, "inet_pton(AF_INET6, %s) : %m", argv[1]); closelog(); return 2; } else if(r > 0) { dst6.sin6_family = AF_INET6; dst = (struct sockaddr *)&dst6; src = &dst6.sin6_addr; src_len = sizeof(dst6.sin6_addr); } else { /* r == 0 */ syslog(LOG_ERR, "%s is not a correct IPv4 or IPv6 address", argv[1]); closelog(); return 1; } } else { dst4.sin_family = AF_INET; dst = (struct sockaddr *)&dst4; src = &dst4.sin_addr; src_len = sizeof(dst4.sin_addr); } if (dst) { syslog(LOG_DEBUG, "calling get_src_for_route_to(%p, NULL, NULL, %p)", dst, &index); r = get_src_for_route_to (dst, NULL, NULL, &index); syslog(LOG_DEBUG, "get_src_for_route_to() returned %d", r); if(r >= 0) { syslog(LOG_DEBUG, "index=%d", index); } syslog(LOG_DEBUG, "calling get_src_for_route_to(%p, %p, %p(%u), %p)", dst, src, &src_len, (unsigned)src_len, &index); r = get_src_for_route_to (dst, src, &src_len, &index); syslog(LOG_DEBUG, "get_src_for_route_to() returned %d", r); if(r >= 0) { char src_str[128]; sockaddr_to_string(dst, src_str, sizeof(src_str)); syslog(LOG_DEBUG, "src=%s", src_str); syslog(LOG_DEBUG, "index=%d", index); } } closelog(); return 0; } miniupnpd-2.3.7/netfilter/nfct_get.c010064400017500000024000000144121225260353000166410ustar00nanardstaff#include #include #include #include #include #include #ifdef USE_NFCT #include #include #include struct data_cb_s { struct sockaddr_storage * ext; uint8_t found; }; static int data_cb(const struct nlmsghdr *nlh, void *data) { struct nf_conntrack *ct; struct data_cb_s * d = (struct data_cb_s*) data; struct sockaddr_in* ext4 = (struct sockaddr_in*) d->ext; ct = nfct_new(); if (ct == NULL) return MNL_CB_OK; nfct_nlmsg_parse(nlh, ct); if (data) { ext4->sin_addr.s_addr = nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST); ext4->sin_port = nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST); } d->found = 1; nfct_destroy(ct); return MNL_CB_OK; } int get_nat_ext_addr(struct sockaddr* src, struct sockaddr *dst, uint8_t proto, struct sockaddr_storage* ret_ext) { struct mnl_socket *nl; struct nlmsghdr *nlh; struct nfgenmsg *nfh; char buf[MNL_SOCKET_BUFFER_SIZE]; unsigned int seq, portid; struct nf_conntrack *ct; int ret; struct data_cb_s data; if ((!src)&&(!dst)) { return 0; } if (src->sa_family != dst->sa_family) { return 0; } nl = mnl_socket_open(NETLINK_NETFILTER); if (nl == NULL) { // perror("mnl_socket_open"); goto free_nl; } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { // perror("mnl_socket_bind"); goto free_nl; } portid = mnl_socket_get_portid(nl); memset(buf, 0, sizeof(buf)); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET; nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK; nlh->nlmsg_seq = seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = src->sa_family; nfh->version = NFNETLINK_V0; nfh->res_id = 0; ct = nfct_new(); if (ct == NULL) { goto free_nl; } nfct_set_attr_u8(ct, ATTR_L3PROTO, src->sa_family); if (src->sa_family == AF_INET) { struct sockaddr_in *src4 = (struct sockaddr_in *)src; struct sockaddr_in *dst4 = (struct sockaddr_in *)dst; nfct_set_attr_u32(ct, ATTR_IPV4_SRC, src4->sin_addr.s_addr); nfct_set_attr_u32(ct, ATTR_IPV4_DST, dst4->sin_addr.s_addr); nfct_set_attr_u16(ct, ATTR_PORT_SRC, src4->sin_port); nfct_set_attr_u16(ct, ATTR_PORT_DST, dst4->sin_port); } else if (src->sa_family == AF_INET6) { struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)src; struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst; nfct_set_attr(ct, ATTR_IPV6_SRC, &src6->sin6_addr); nfct_set_attr(ct, ATTR_IPV6_DST, &dst6->sin6_addr); nfct_set_attr_u16(ct, ATTR_PORT_SRC, src6->sin6_port); nfct_set_attr_u16(ct, ATTR_PORT_DST, dst6->sin6_port); } nfct_set_attr_u8(ct, ATTR_L4PROTO, proto); nfct_nlmsg_build(nlh, ct); ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len); if (ret == -1) { goto free_ct; } ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); data.ext = ret_ext; data.found = 0; while (ret > 0) { ret = mnl_cb_run(buf, ret, seq, portid, data_cb, &data); if (ret <= MNL_CB_STOP) break; ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); } free_ct: nfct_destroy(ct); free_nl: mnl_socket_close(nl); return data.found; } #else #define DST "dst=" #define DST_PORT "dport=" #define SRC "src=" #define SRC_PORT "sport=" #define IP_CONNTRACK_LOCATION "/proc/net/ip_conntrack" #define NF_CONNTRACK_LOCATION "/proc/net/nf_conntrack" int get_nat_ext_addr(struct sockaddr* src, struct sockaddr *dst, uint8_t proto, struct sockaddr_storage* ret_ext) { FILE *f; int af; if (!src) return -2; af = src->sa_family; if ((f = fopen(NF_CONNTRACK_LOCATION, "r")) == NULL) { if ((f = fopen(IP_CONNTRACK_LOCATION, "r")) == NULL) { printf("could not read info about connections from the kernel, " "make sure netfilter is enabled in kernel or by modules.\n"); return -1; } } while (!feof(f)) { char line[256], *str; memset(line, 0, sizeof(line)); str = fgets(line, sizeof(line), f); if (line[0] != 0) { char *token, *saveptr; int j; uint8_t src_f, src_port_f, dst_f, dst_port_f; src_f=src_port_f=dst_f=dst_port_f=0; for (j = 1; ; j++, str = NULL) { token = strtok_r(str, " ", &saveptr); if (token == NULL) break; if ((j==2)&&(af!=atoi(token))) break; if ((j==4)&&(proto!=atoi(token))) break; if (j<=4) continue; if (strncmp(token, SRC, sizeof(SRC) - 1) == 0) { char *srcip = token + sizeof(SRC) - 1; uint32_t buf[4]; memset(buf,0,sizeof(buf)); if (inet_pton(af, srcip, buf)!=1) break; if (af==AF_INET) { struct sockaddr_in *src4=(struct sockaddr_in*)src; if (!src_f) { if (src4->sin_addr.s_addr != buf[0]) break; src_f = 1; } } } if (strncmp(token, SRC_PORT, sizeof(SRC_PORT) - 1) == 0) { char *src_port = token + sizeof(SRC_PORT) - 1; uint16_t port=atoi(src_port); if (af==AF_INET) { struct sockaddr_in *src4=(struct sockaddr_in*)src; if (!src_port_f) { if (ntohs(src4->sin_port) != port) break; src_port_f = 1; } } } if (strncmp(token, DST, sizeof(DST) - 1) == 0) { char *dstip = token + sizeof(DST) - 1; uint32_t buf[4]; memset(buf,0,sizeof(buf)); if (inet_pton(af, dstip, buf)!=1) break; if (af==AF_INET) { struct sockaddr_in *dst4=(struct sockaddr_in*)dst; if (!dst_f) { if (dst4->sin_addr.s_addr != buf[0]) break; dst_f = 1; } else { struct sockaddr_in*ret4=(struct sockaddr_in*)ret_ext; ret_ext->ss_family = AF_INET; ret4->sin_addr.s_addr = buf[0]; } } } if (strncmp(token, DST_PORT, sizeof(DST_PORT)-1) == 0) { char *dst_port = token + sizeof(DST_PORT) - 1; uint16_t port=atoi(dst_port); if (af==AF_INET) { struct sockaddr_in *dst4=(struct sockaddr_in*)dst; if (!dst_port_f) { if (ntohs(dst4->sin_port) != port) break; dst_port_f = 1; } else { struct sockaddr_in*ret4=(struct sockaddr_in*)ret_ext; ret_ext->ss_family = AF_INET; ret4->sin_port = htons(port); } } } } if (src_f && src_port_f && dst_f && dst_port_f) { fclose(f); return 1; } } } fclose(f); return 0; } #endif miniupnpd-2.3.7/netfilter/test_nfct_get.c010064400017500000024000000024661225260353000177060ustar00nanardstaff#include "nfct_get.c" int main(int argc, char *argv[]) { struct sockaddr_storage src, dst, ext; char buff[INET6_ADDRSTRLEN]; if (argc!=5) return 0; if (1 != inet_pton(AF_INET, argv[1], &((struct sockaddr_in*)&src)->sin_addr)) { if (1 != inet_pton(AF_INET6, argv[1], &((struct sockaddr_in6*) &src)->sin6_addr)) { perror("bad input param"); } else { ((struct sockaddr_in6*)(&src))->sin6_port = htons(atoi(argv[2])); src.ss_family = AF_INET6; } } else { ((struct sockaddr_in*)(&src))->sin_port = htons(atoi(argv[2])); src.ss_family = AF_INET; } if (1 != inet_pton(AF_INET, argv[3], &((struct sockaddr_in*)&dst)->sin_addr)) { if (1 != inet_pton(AF_INET6, argv[3], &((struct sockaddr_in6*) &dst)->sin6_addr)) { perror("bad input param"); } else { ((struct sockaddr_in6*)(&dst))->sin6_port = htons(atoi(argv[4])); dst.ss_family = AF_INET6; } } else { ((struct sockaddr_in*)(&dst))->sin_port = htons(atoi(argv[4])); dst.ss_family = AF_INET; } if (get_nat_ext_addr((struct sockaddr*)&src, (struct sockaddr*)&dst, IPPROTO_TCP, &ext)) { printf("Ext address %s:%d\n", inet_ntop(src.ss_family, &((struct sockaddr_in*)&ext)->sin_addr, buff, sizeof(buff)), ntohs(((struct sockaddr_in*)(&ext))->sin_port)); } else { printf("no entry\n"); } return 0; } miniupnpd-2.3.7/netfilter/testiptcrdr_dscp.c010064400017500000024000000034071225260353000204320ustar00nanardstaff/* $Id: testiptcrdr_dscp.c,v 1.1 2013/12/13 13:10:48 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2012 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "iptcrdr.h" #include "../commonrdr.h" #include "iptcrdr.c" #ifndef PRIu64 #define PRIu64 "llu" #endif int main(int argc, char ** argv) { unsigned char dscp; unsigned short iport, rport; const char * iaddr, *rhost; printf("Usage %s \n", argv[0]); if(argc<6) return -1; openlog("testiptcrdr_peer", LOG_PERROR|LOG_CONS, LOG_LOCAL0); dscp = (unsigned short)atoi(argv[1]); iaddr = argv[2]; iport = (unsigned short)atoi(argv[3]); rhost = argv[4]; rport = (unsigned short)atoi(argv[5]); #if 1 if(addpeerdscprule(IPPROTO_TCP, dscp, iaddr, iport, rhost, rport) < 0) return -1; #endif /* test */ { unsigned short p1, p2; char addr[16]; int proto2; char desc[256]; char rhost[256]; unsigned int timestamp; u_int64_t packets, bytes; desc[0] = '\0'; if(get_redirect_rule_by_index(0, "", &p1, addr, sizeof(addr), &p2, &proto2, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) < 0) { printf("rule not found\n"); } else { printf("redirected port %hu to %s:%hu proto %d packets=%" PRIu64 " bytes=%" PRIu64 "\n", p1, addr, p2, proto2, packets, bytes); } } printf("trying to list nat rules :\n"); list_redirect_rule(argv[1]); printf("deleting\n"); // delete_redirect_and_filter_rules(eport, IPPROTO_TCP); return 0; } miniupnpd-2.3.7/netfilter/testiptcrdr_peer.c010064400017500000024000000035571225260353000204420ustar00nanardstaff/* $Id: testiptcrdr_peer.c,v 1.1 2013/12/13 13:10:48 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2012 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "iptcrdr.h" #include "../commonrdr.h" #include "iptcrdr.c" #ifndef PRIu64 #define PRIu64 "llu" #endif int main(int argc, char ** argv) { unsigned short eport, iport, rport; const char * eaddr, *iaddr, *rhost; printf("Usage %s \n", argv[0]); if(argc<6) return -1; openlog("testiptcrdr_peer", LOG_PERROR|LOG_CONS, LOG_LOCAL0); eaddr = argv[1]; eport = (unsigned short)atoi(argv[2]); iaddr = argv[3]; iport = (unsigned short)atoi(argv[4]); rhost = argv[5]; rport = (unsigned short)atoi(argv[6]); #if 1 printf("trying to redirect port %hu to %s:%hu\n", eport, iaddr, iport); if(addpeernatrule(IPPROTO_TCP, eaddr, eport, iaddr, iport, rhost, rport) < 0) return -1; #endif /* test */ { unsigned short p1, p2; char addr[16]; int proto2; char desc[256]; char rhost[256]; unsigned int timestamp; u_int64_t packets, bytes; desc[0] = '\0'; if(get_redirect_rule_by_index(0, "", &p1, addr, sizeof(addr), &p2, &proto2, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) < 0) { printf("rule not found\n"); } else { printf("redirected port %hu to %s:%hu proto %d packets=%" PRIu64 " bytes=%" PRIu64 "\n", p1, addr, p2, proto2, packets, bytes); } } printf("trying to list nat rules :\n"); list_redirect_rule(argv[1]); printf("deleting\n"); delete_redirect_and_filter_rules(eport, IPPROTO_TCP); return 0; } miniupnpd-2.3.7/pcpserver.h010064400017500000024000000051141327441376500151070ustar00nanardstaff/* $Id: pcpserver.h,v 1.6 2018/05/08 21:28:28 nanard Exp $ */ /* MiniUPnP project * Website : http://miniupnp.free.fr/ * Author : Peter Tatrai Copyright (c) 2013 by Cisco Systems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef PCPSERVER_H_INCLUDED #define PCPSERVER_H_INCLUDED #define PCP_MIN_LEN 24 #define PCP_MAX_LEN 1100 struct sockaddr; /* * receiveraddr is only used for IPV6 * * returns 0 upon success 1 otherwise */ int ProcessIncomingPCPPacket(int s, unsigned char *msg_buff, int len, const struct sockaddr *senderaddr, const struct sockaddr_in6 *receiveraddr); /* * returns the socket */ int OpenAndConfPCPv6Socket(void); /* * Send Unsolicited ANNOUNCE Message */ #ifdef ENABLE_IPV6 void PCPSendUnsolicitedAnnounce(int * sockets, int n_sockets, int socket6); #else /* IPv4 only */ void PCPSendUnsolicitedAnnounce(int * sockets, int n_sockets); #endif /* * To be called when Public IP address changed (IPv4) */ #ifdef ENABLE_IPV6 void PCPPublicAddressChanged(int * sockets, int n_sockets, int socket6); #else /* IPV4 Only */ void PCPPublicAddressChanged(int * sockets, int n_sockets); #endif #endif /* PCPSERVER_H_INCLUDED */ miniupnpd-2.3.7/pcpserver.c010064400017500000024000001477451463115324100151070ustar00nanardstaff/* $Id: pcpserver.c,v 1.58 2024/06/04 23:08:01 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author : Peter Tatrai Copyright (c) 2013 by Cisco Systems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Current assumptions: - IPv4 is always NATted (internal -> external) - IPv6 is always firewalled (this may need some work, NAT6* do exist) - we make the judgement based on (in order, picking first one available): - third party address - internal client address TODO : handle NAT46, NAT64, NPT66. In addition, beyond FW/NAT choice, should also add for proxy (=as much as possible transparent pass-through to one or more servers). TODO: IPv6 permission handling (for the time being, we just assume anyone on IPv6 is a good guy, but fixing that would include upnppermissions rewrite to be AF neutral). */ #include "config.h" #ifdef ENABLE_PCP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcpserver.h" #include "natpmp.h" #include "macros.h" #include "rw_unaligned.h" #include "upnpglobalvars.h" #include "pcplearndscp.h" #include "upnpredirect.h" #include "commonrdr.h" #include "getifaddr.h" #include "asyncsendto.h" #include "upnputils.h" #include "portinuse.h" #include "pcp_msg_struct.h" #ifdef ENABLE_UPNPPINHOLE #include "upnppinhole.h" #endif /* ENABLE_UPNPPINHOLE */ #ifdef PCP_PEER /* TODO make this platform independent */ #ifdef USE_NETFILTER #include "netfilter/iptcrdr.h" #else #error "PCP Peer is only supported with NETFILTER" #endif /* USE_NETFILTER */ #endif /* PCP_PEER */ /* server specific information */ struct pcp_server_info { uint8_t server_version; }; /* default server settings, highest version supported is the default */ static const struct pcp_server_info this_server_info = {2}; /* structure holding information from PCP msg*/ /* all variables are in host byte order except IP addresses */ typedef struct pcp_info { uint8_t version; uint8_t opcode; uint8_t result_code; uint32_t lifetime; /* lifetime of the mapping */ uint32_t epochtime; /* both MAP and PEER opcode specific information */ uint32_t nonce[3]; /* random value generated by client */ uint8_t protocol; uint16_t int_port; const struct in6_addr *int_ip; /* in network order */ uint16_t ext_port; const struct in6_addr *ext_ip; /* Suggested external IP in network order*/ /* PEER specific information */ #ifdef PCP_PEER uint16_t peer_port; const struct in6_addr *peer_ip; /* Destination IP in network order */ #endif /* PCP_PEER */ #ifdef PCP_SADSCP /* SADSCP specific information */ uint8_t delay_tolerance; uint8_t loss_tolerance; uint8_t jitter_tolerance; uint8_t app_name_len; const char* app_name; uint8_t sadscp_dscp; uint8_t matched_name; int8_t is_sadscp_op; #endif #ifdef PCP_FLOWP uint8_t dscp_up; uint8_t dscp_down; int flowp_present; #endif uint8_t is_map_op; uint8_t is_peer_op; const struct in6_addr *thirdp_ip; const struct in6_addr *mapped_ip; char mapped_str[INET6_ADDRSTRLEN]; int pfailure_present; struct in6_addr sender_ip; int is_fw; /* is this firewall operation? if not, nat. */ char desc[64]; } pcp_info_t; /* getPCPOpCodeStr() * return a string representation of the PCP OpCode * can be used for debug output */ static const char * getPCPOpCodeStr(uint8_t opcode) { switch(opcode) { case PCP_OPCODE_ANNOUNCE: return "ANNOUNCE"; case PCP_OPCODE_MAP: return "MAP"; case PCP_OPCODE_PEER: return "PEER"; #ifdef PCP_SADSCP case PCP_OPCODE_SADSCP: return "SADSCP"; #endif /* PCP_SADSCP */ default: return "UNKNOWN"; } } /* useful to copy ext_ip only if needed, as request and response * buffers are same */ static void copyIPv6IfDifferent(void * dest, const void * src) { if(dest != src && src != NULL) { memcpy(dest, src, sizeof(struct in6_addr)); } } #ifdef PCP_SADSCP int get_dscp_value(pcp_info_t *pcp_msg_info) { unsigned int ind; for (ind = 0; ind < num_dscp_values; ind++) { if ((dscp_values_list[ind].app_name) && (!strcmp(dscp_values_list[ind].app_name, pcp_msg_info->app_name)) && (pcp_msg_info->delay_tolerance == dscp_values_list[ind].delay) && (pcp_msg_info->loss_tolerance == dscp_values_list[ind].loss) && (pcp_msg_info->jitter_tolerance == dscp_values_list[ind].jitter) ) { pcp_msg_info->sadscp_dscp = dscp_values_list[ind].dscp_value; pcp_msg_info->matched_name = 1; return 0; } else if ((pcp_msg_info->app_name_len==0) && (dscp_values_list[ind].app_name_len==0) && (pcp_msg_info->delay_tolerance == dscp_values_list[ind].delay) && (pcp_msg_info->loss_tolerance == dscp_values_list[ind].loss) && (pcp_msg_info->jitter_tolerance == dscp_values_list[ind].jitter) ) { pcp_msg_info->sadscp_dscp = dscp_values_list[ind].dscp_value; pcp_msg_info->matched_name = 0; return 0; } else if ((dscp_values_list[ind].app_name_len==0) && (pcp_msg_info->delay_tolerance == dscp_values_list[ind].delay) && (pcp_msg_info->loss_tolerance == dscp_values_list[ind].loss) && (pcp_msg_info->jitter_tolerance == dscp_values_list[ind].jitter) ) { pcp_msg_info->sadscp_dscp = dscp_values_list[ind].dscp_value; pcp_msg_info->matched_name = 0; return 0; } } //if nothing matched return Default value i.e. 0 pcp_msg_info->sadscp_dscp = 0; pcp_msg_info->matched_name = 0; return 0; } #endif /* * Function extracting information from common_req (common request header) * into pcp_msg_info. * @return : when no problem occurred 0 is returned, 1 otherwise and appropriate * result code is assigned to pcp_msg_info->result_code to indicate * what kind of error occurred */ static int parseCommonRequestHeader(const uint8_t *common_req, pcp_info_t *pcp_msg_info) { pcp_msg_info->version = common_req[0] ; pcp_msg_info->opcode = common_req[1] & 0x7f; pcp_msg_info->lifetime = READNU32(common_req + 4); pcp_msg_info->int_ip = (struct in6_addr *)(common_req + 8); pcp_msg_info->mapped_ip = (struct in6_addr *)(common_req + 8); if ( (pcp_msg_info->version > this_server_info.server_version) ) { pcp_msg_info->result_code = PCP_ERR_UNSUPP_VERSION; return 1; } if (pcp_msg_info->lifetime > max_lifetime ) { pcp_msg_info->lifetime = max_lifetime; } if ( (pcp_msg_info->lifetime < min_lifetime) && (pcp_msg_info->lifetime != 0) ) { pcp_msg_info->lifetime = min_lifetime; } return 0; } #ifdef DEBUG static void printMAPOpcodeVersion1(const uint8_t *buf) { char map_addr[INET6_ADDRSTRLEN]; syslog(LOG_DEBUG, "PCP MAP: v1 Opcode specific information. \n"); syslog(LOG_DEBUG, "MAP protocol: \t\t %d\n", (int)buf[0] ); syslog(LOG_DEBUG, "MAP int port: \t\t %d\n", (int)READNU16(buf+4)); syslog(LOG_DEBUG, "MAP ext port: \t\t %d\n", (int)READNU16(buf+6)); syslog(LOG_DEBUG, "MAP Ext IP: \t\t %s\n", inet_ntop(AF_INET6, buf+8, map_addr, INET6_ADDRSTRLEN)); } static void printMAPOpcodeVersion2(const uint8_t *buf) { char map_addr[INET6_ADDRSTRLEN]; syslog(LOG_DEBUG, "PCP MAP: v2 Opcode specific information."); syslog(LOG_DEBUG, "MAP nonce: \t%08x%08x%08x", READNU32(buf), READNU32(buf+4), READNU32(buf+8)); syslog(LOG_DEBUG, "MAP protocol:\t%d", (int)buf[12]); syslog(LOG_DEBUG, "MAP int port:\t%d", (int)READNU16(buf+16)); syslog(LOG_DEBUG, "MAP ext port:\t%d", (int)READNU16(buf+18)); syslog(LOG_DEBUG, "MAP Ext IP: \t%s", inet_ntop(AF_INET6, buf+20, map_addr, INET6_ADDRSTRLEN)); } #endif /* DEBUG */ static void parsePCPMAP_version1(const uint8_t *map_v1, pcp_info_t *pcp_msg_info) { pcp_msg_info->is_map_op = 1; pcp_msg_info->protocol = map_v1[0]; pcp_msg_info->int_port = READNU16(map_v1 + 4); pcp_msg_info->ext_port = READNU16(map_v1 + 6); pcp_msg_info->ext_ip = (struct in6_addr *)(map_v1 + 8); } static void parsePCPMAP_version2(const uint8_t *map_v2, pcp_info_t *pcp_msg_info) { pcp_msg_info->is_map_op = 1; memcpy(pcp_msg_info->nonce, map_v2, 12); pcp_msg_info->protocol = map_v2[12]; pcp_msg_info->int_port = READNU16(map_v2 + 16); pcp_msg_info->ext_port = READNU16(map_v2 + 18); pcp_msg_info->ext_ip = (struct in6_addr *)(map_v2 + 20); } #ifdef PCP_PEER #ifdef DEBUG static void printPEEROpcodeVersion1(const uint8_t *buf) { char ext_addr[INET6_ADDRSTRLEN]; char peer_addr[INET6_ADDRSTRLEN]; syslog(LOG_DEBUG, "PCP PEER: v1 Opcode specific information. \n"); syslog(LOG_DEBUG, "Protocol: \t\t %d\n", (int)buf[0]); syslog(LOG_DEBUG, "Internal port: \t\t %d\n", READNU16(buf + 4)); syslog(LOG_DEBUG, "External IP: \t\t %s\n", inet_ntop(AF_INET6, buf + 8, ext_addr,INET6_ADDRSTRLEN)); syslog(LOG_DEBUG, "External port port: \t\t %d\n", READNU16(buf + 6)); syslog(LOG_DEBUG, "PEER IP: \t\t %s\n", inet_ntop(AF_INET6, buf + 28, peer_addr,INET6_ADDRSTRLEN)); syslog(LOG_DEBUG, "PEER port port: \t\t %d\n", READNU16(buf + 24)); } static void printPEEROpcodeVersion2(const uint8_t *buf) { char ext_addr[INET6_ADDRSTRLEN]; char peer_addr[INET6_ADDRSTRLEN]; syslog(LOG_DEBUG, "PCP PEER: v2 Opcode specific information."); syslog(LOG_DEBUG, "nonce: \t%08x%08x%08x", READNU32(buf), READNU32(buf+4), READNU32(buf+8)); syslog(LOG_DEBUG, "Protocol: \t%d", buf[12]); syslog(LOG_DEBUG, "Internal port:\t%d", READNU16(buf + 16)); syslog(LOG_DEBUG, "External IP: \t%s", inet_ntop(AF_INET6, buf + 20, ext_addr, INET6_ADDRSTRLEN)); syslog(LOG_DEBUG, "External port:\t%d", READNU16(buf + 18)); syslog(LOG_DEBUG, "PEER IP: \t%s", inet_ntop(AF_INET6, buf + 40, peer_addr, INET6_ADDRSTRLEN)); syslog(LOG_DEBUG, "PEER port: \t%d", READNU16(buf + 36)); } #endif /* DEBUG */ /* * Function extracting information from peer_buf to pcp_msg_info * @return : when no problem occurred 0 is returned, 1 otherwise */ static void parsePCPPEER_version1(const uint8_t *buf, pcp_info_t *pcp_msg_info) { pcp_msg_info->is_peer_op = 1; pcp_msg_info->protocol = buf[0]; pcp_msg_info->int_port = READNU16(buf + 4); pcp_msg_info->ext_port = READNU16(buf + 6); pcp_msg_info->peer_port = READNU16(buf + 24); pcp_msg_info->ext_ip = (struct in6_addr *)(buf + 8); pcp_msg_info->peer_ip = (struct in6_addr *)(buf + 28); } /* * Function extracting information from peer_buf to pcp_msg_info * @return : when no problem occurred 0 is returned, 1 otherwise */ static void parsePCPPEER_version2(const uint8_t *buf, pcp_info_t *pcp_msg_info) { pcp_msg_info->is_peer_op = 1; memcpy(pcp_msg_info->nonce, buf, 12); pcp_msg_info->protocol = buf[12]; pcp_msg_info->int_port = READNU16(buf + 16); pcp_msg_info->ext_port = READNU16(buf + 18); pcp_msg_info->peer_port = READNU16(buf + 36); pcp_msg_info->ext_ip = (struct in6_addr *)(buf + 20); pcp_msg_info->peer_ip = (struct in6_addr *)(buf + 40); } #endif /* PCP_PEER */ #ifdef PCP_SADSCP #ifdef DEBUG static void printSADSCPOpcode(const uint8_t *buf) { unsigned char sadscp_tol; sadscp_tol = buf[12]; /* tolerance_fields */ syslog(LOG_DEBUG, "PCP SADSCP: Opcode specific information.\n"); syslog(LOG_DEBUG, "Delay tolerance %d \n", (sadscp_tol>>6)&3); syslog(LOG_DEBUG, "Loss tolerance %d \n", (sadscp_tol>>4)&3); syslog(LOG_DEBUG, "Jitter tolerance %d \n", (sadscp_tol>>2)&3); syslog(LOG_DEBUG, "RRR %d \n", sadscp_tol&3); syslog(LOG_DEBUG, "AppName Length %d \n", buf[13]); syslog(LOG_DEBUG, "Application name %.*s \n", buf[13], buf + 14); } #endif //DEBUG static int parseSADSCP(const uint8_t *buf, pcp_info_t *pcp_msg_info) { pcp_msg_info->delay_tolerance = (buf[12]>>6)&3; pcp_msg_info->loss_tolerance = (buf[12]>>4)&3; pcp_msg_info->jitter_tolerance = (buf[12]>>2)&3; if (pcp_msg_info->delay_tolerance == 3 || pcp_msg_info->loss_tolerance == 3 || pcp_msg_info->jitter_tolerance == 3 ) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return 1; } pcp_msg_info->app_name = (const char *)(buf + 14); pcp_msg_info->app_name_len = buf[13]; return 0; } #endif /* PCP_SADSCP */ static int parsePCPOption(uint8_t* pcp_buf, int remain, pcp_info_t *pcp_msg_info) { #ifdef DEBUG char third_addr[INET6_ADDRSTRLEN]; #endif /* DEBUG */ unsigned short option_length; /* Do centralized option sanity checks here. */ if (remain < (int)PCP_OPTION_HDR_SIZE) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } option_length = READNU16(pcp_buf + 2) + 4; /* len */ if (remain < option_length) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } switch (pcp_buf[0]) { /* code */ case PCP_OPTION_3RD_PARTY: if (option_length != PCP_3RD_PARTY_OPTION_SIZE) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } #ifdef DEBUG syslog(LOG_DEBUG, "PCP OPTION: \t Third party\n"); syslog(LOG_DEBUG, "Third PARTY IP: \t %s\n", inet_ntop(AF_INET6, pcp_buf + 4, third_addr, INET6_ADDRSTRLEN)); #endif if (pcp_msg_info->thirdp_ip ) { syslog(LOG_ERR, "PCP: THIRD PARTY OPTION was already present. \n"); pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } else { pcp_msg_info->thirdp_ip = (struct in6_addr *)(pcp_buf + 4); pcp_msg_info->mapped_ip = (struct in6_addr *)(pcp_buf + 4); } break; case PCP_OPTION_PREF_FAIL: if (option_length != PCP_PREFER_FAIL_OPTION_SIZE) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } #ifdef DEBUG syslog(LOG_DEBUG, "PCP OPTION: \t Prefer failure \n"); #endif if (pcp_msg_info->opcode != PCP_OPCODE_MAP) { syslog(LOG_DEBUG, "PCP: Unsupported OPTION for given OPCODE.\n"); pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; } if (pcp_msg_info->pfailure_present != 0 ) { syslog(LOG_DEBUG, "PCP: PREFER FAILURE OPTION was already present.\n"); pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; } else { pcp_msg_info->pfailure_present = 1; } break; case PCP_OPTION_FILTER: /* TODO fully implement filter */ if (option_length != PCP_FILTER_OPTION_SIZE) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } #ifdef DEBUG syslog(LOG_DEBUG, "PCP OPTION: \t Filter\n"); #endif if (pcp_msg_info->opcode != PCP_OPCODE_MAP) { syslog(LOG_ERR, "PCP: Unsupported OPTION for given OPCODE.\n"); pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return 0; } break; #ifdef PCP_FLOWP case PCP_OPTION_FLOW_PRIORITY: #ifdef DEBUG syslog(LOG_DEBUG, "PCP OPTION: \t Flow priority\n"); #endif if (option_length != PCP_FLOW_PRIORITY_OPTION_SIZE) { syslog(LOG_ERR, "PCP: Error processing DSCP. sizeof %d and remaining %d. flow len %d \n", PCP_FLOW_PRIORITY_OPTION_SIZE, remain, READNU16(pcp_buf + 2)); pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } #ifdef DEBUG syslog(LOG_DEBUG, "DSCP UP: \t %d\n", pcp_buf[4]); syslog(LOG_DEBUG, "DSCP DOWN: \t %d\n", pcp_buf[5]); #endif pcp_msg_info->dscp_up = pcp_buf[4]; pcp_msg_info->dscp_down = pcp_buf[5]; pcp_msg_info->flowp_present = 1; break; #endif default: if (pcp_buf[0] < 128) { syslog(LOG_ERR, "PCP: Unrecognized mandatory PCP OPTION: %d \n", (int)pcp_buf[0]); /* Mandatory to understand */ pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPTION; remain = 0; break; } /* TODO - log optional not understood options? */ break; } return option_length; } static void parsePCPOptions(void* pcp_buf, int remain, pcp_info_t *pcp_msg_info) { int option_length; while (remain > 0) { option_length = parsePCPOption(pcp_buf, remain, pcp_msg_info); if (!option_length) break; remain -= option_length; pcp_buf += option_length; } if (remain > 0) { syslog(LOG_WARNING, "%s: remain=%d", "parsePCPOptions", remain); } } /* CheckExternalAddress() * Check that suggested external address in request match a real external * IP address. * Suggested address can also be 0 IPv4 or IPv6 address. * (see http://tools.ietf.org/html/rfc6887#section-10 ) * return values : * 0 : check is OK * -1 : check failed */ static int CheckExternalAddress(pcp_info_t* pcp_msg_info) { /* can contain a IPv4-mapped IPv6 address */ static struct in6_addr external_addr; int af; af = IN6_IS_ADDR_V4MAPPED(pcp_msg_info->mapped_ip) ? AF_INET : AF_INET6; pcp_msg_info->is_fw = af == AF_INET6; if (pcp_msg_info->is_fw) { external_addr = *pcp_msg_info->mapped_ip; } else { /* TODO : be able to handle case with multiple * external addresses */ if(use_ext_ip_addr) { if (inet_pton(AF_INET, use_ext_ip_addr, ((uint32_t*)external_addr.s6_addr)+3) == 1) { ((uint32_t*)external_addr.s6_addr)[0] = 0; ((uint32_t*)external_addr.s6_addr)[1] = 0; ((uint32_t*)external_addr.s6_addr)[2] = htonl(0xFFFF); } else if (inet_pton(AF_INET6, use_ext_ip_addr, external_addr.s6_addr) != 1) { pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE; return -1; } #ifdef ENABLE_IPV6 } else if ((af == AF_INET6) && (ext_if_name6 != ext_if_name)) { if(!ext_if_name6 || ext_if_name6[0]=='\0') { pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE; return -1; } if(getifaddr_in6(ext_if_name6, af, &external_addr) < 0) { pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE; return -1; } #endif } else { if(!ext_if_name || ext_if_name[0]=='\0') { pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE; return -1; } if(getifaddr_in6(ext_if_name, af, &external_addr) < 0) { pcp_msg_info->result_code = PCP_ERR_NETWORK_FAILURE; return -1; } } } if (pcp_msg_info->ext_ip == NULL || IN6_IS_ADDR_UNSPECIFIED(pcp_msg_info->ext_ip) || (IN6_IS_ADDR_V4MAPPED(pcp_msg_info->ext_ip) && ((uint32_t *)pcp_msg_info->ext_ip->s6_addr)[3] == INADDR_ANY)) { /* no suggested external address : use real external address */ pcp_msg_info->ext_ip = &external_addr; return 0; } if (!IN6_ARE_ADDR_EQUAL(pcp_msg_info->ext_ip, &external_addr)) { syslog(LOG_ERR, "PCP: External IP in request didn't match interface IP \n"); #ifdef DEBUG { char s[INET6_ADDRSTRLEN]; syslog(LOG_DEBUG, "Interface IP %s \n", inet_ntop(AF_INET6, &external_addr.s6_addr, s, sizeof(s))); syslog(LOG_DEBUG, "IP in the PCP request %s \n", inet_ntop(AF_INET6, pcp_msg_info->ext_ip, s, sizeof(s))); } #endif if (pcp_msg_info->pfailure_present) { pcp_msg_info->result_code = PCP_ERR_CANNOT_PROVIDE_EXTERNAL; return -1; } else { pcp_msg_info->ext_ip = &external_addr; } } return 0; } static const char* inet_n46top(const struct in6_addr* in, char* buf, size_t buf_len) { if (IN6_IS_ADDR_V4MAPPED(in)) { return inet_ntop(AF_INET, ((uint32_t*)(in->s6_addr))+3, buf, buf_len); } else { return inet_ntop(AF_INET6, in, buf, buf_len); } } #ifdef PCP_PEER static void FillSA(struct sockaddr *sa, const struct in6_addr *in6, uint16_t port) { if (IN6_IS_ADDR_V4MAPPED(in6)) { struct sockaddr_in *sa4 = (struct sockaddr_in *)sa; sa4->sin_family = AF_INET; sa4->sin_addr.s_addr = ((uint32_t*)(in6)->s6_addr)[3]; sa4->sin_port = htons(port); } else { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa; sa6->sin6_family = AF_INET6; sa6->sin6_addr = *in6; sa6->sin6_port = htons(port); } } static const char* inet_satop(struct sockaddr* sa, char* buf, size_t buf_len) { if (sa->sa_family == AF_INET) { return inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr), buf, buf_len); } else { return inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr), buf, buf_len); } } static int CreatePCPPeer_NAT(pcp_info_t *pcp_msg_info) { struct sockaddr_storage intip; struct sockaddr_storage peerip; struct sockaddr_storage extip; struct sockaddr_storage ret_extip; uint8_t proto = pcp_msg_info->protocol; uint16_t eport = pcp_msg_info->ext_port; /* public port */ char peerip_s[INET6_ADDRSTRLEN], extip_s[INET6_ADDRSTRLEN]; time_t timestamp = upnp_time() + pcp_msg_info->lifetime; int r; const char * ext_if = ext_if_name; FillSA((struct sockaddr*)&intip, pcp_msg_info->mapped_ip, pcp_msg_info->int_port); FillSA((struct sockaddr*)&peerip, pcp_msg_info->peer_ip, pcp_msg_info->peer_port); FillSA((struct sockaddr*)&extip, pcp_msg_info->ext_ip, eport); inet_satop((struct sockaddr*)&peerip, peerip_s, sizeof(peerip_s)); inet_satop((struct sockaddr*)&extip, extip_s, sizeof(extip_s)); /* check if connection with given peer exists, if it was */ /* already established use this external port */ if (get_nat_ext_addr( (struct sockaddr*)&intip, (struct sockaddr*)&peerip, proto, (struct sockaddr*)&ret_extip) == 1) { if (ret_extip.ss_family == AF_INET) { struct sockaddr_in* ret_ext4 = (struct sockaddr_in*)&ret_extip; uint16_t ret_eport = ntohs(ret_ext4->sin_port); eport = ret_eport; } else if (ret_extip.ss_family == AF_INET6) { struct sockaddr_in6* ret_ext6 = (struct sockaddr_in6*)&ret_extip; uint16_t ret_eport = ntohs(ret_ext6->sin6_port); eport = ret_eport; } else { return PCP_ERR_CANNOT_PROVIDE_EXTERNAL; } } /* Create Peer Mapping */ if (eport == 0) { eport = pcp_msg_info->int_port; } #ifdef ENABLE_IPV6 if (ret_extip.ss_family == AF_INET6) { ext_if = ext_if_name6; } #endif #ifdef PCP_FLOWP if (pcp_msg_info->flowp_present && pcp_msg_info->dscp_up) { if (add_peer_dscp_rule2(ext_if, peerip_s, pcp_msg_info->peer_port, pcp_msg_info->dscp_up, pcp_msg_info->mapped_str, pcp_msg_info->int_port, proto, pcp_msg_info->desc, timestamp) < 0 ) { syslog(LOG_ERR, "PCP: failed to add flowp upstream mapping %s:%hu->%s:%hu '%s'", pcp_msg_info->mapped_str, pcp_msg_info->int_port, peerip_s, pcp_msg_info->peer_port, pcp_msg_info->desc); return PCP_ERR_NO_RESOURCES; } } if (pcp_msg_info->flowp_present && pcp_msg_info->dscp_down) { if (add_peer_dscp_rule2(ext_if, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->dscp_down, peerip_s, pcp_msg_info->peer_port, proto, pcp_msg_info->desc, timestamp) < 0 ) { syslog(LOG_ERR, "PCP: failed to add flowp downstream mapping %s:%hu->%s:%hu '%s'", pcp_msg_info->mapped_str, pcp_msg_info->int_port, peerip_s, pcp_msg_info->peer_port, pcp_msg_info->desc); pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES; return PCP_ERR_NO_RESOURCES; } } #endif r = add_peer_redirect_rule2(ext_if, peerip_s, pcp_msg_info->peer_port, extip_s, eport, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->protocol, pcp_msg_info->desc, timestamp); if (r < 0) return PCP_ERR_NO_RESOURCES; pcp_msg_info->ext_port = eport; return PCP_SUCCESS; } static void CreatePCPPeer(pcp_info_t *pcp_msg_info) { char peerip_s[INET6_ADDRSTRLEN]; int r = -1; if (!inet_n46top(pcp_msg_info->peer_ip, peerip_s, sizeof(peerip_s))) { syslog(LOG_ERR, "inet_n46top(peer_ip): %m"); return; } if (pcp_msg_info->is_fw) { #if 0 /* Someday, something like this is available.. and we're ready! */ #ifdef ENABLE_UPNPPINHOLE pcp_msg_info->ext_port = pcp_msg_info->int_port; r = upnp_add_outbound_pinhole(peerip_s, pcp_msg_info->peer_port, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->protocol, pcp_msg_info->desc, pcp_msg_info->lifetime, NULL); #endif /* ENABLE_UPNPPINHOLE */ #else r = PCP_ERR_UNSUPP_OPCODE; #endif /* 0 */ } else { r = CreatePCPPeer_NAT(pcp_msg_info); } /* TODO: add upnp function for PI */ pcp_msg_info->result_code = r; syslog(r == PCP_SUCCESS ? LOG_INFO : LOG_ERR, "PCP PEER: %s peer mapping %s %s:%hu(%hu)->%s:%hu '%s'", r == PCP_SUCCESS ? "added" : "failed to add", (pcp_msg_info->protocol==IPPROTO_TCP)?"TCP":"UDP", pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->ext_port, peerip_s, pcp_msg_info->peer_port, pcp_msg_info->desc); } static void DeletePCPPeer(pcp_info_t *pcp_msg_info) { uint16_t iport = pcp_msg_info->int_port; /* private port */ uint16_t rport = pcp_msg_info->peer_port; /* private port */ uint8_t proto = pcp_msg_info->protocol; char rhost[INET6_ADDRSTRLEN]; int r = -1; /* remove requested mappings for this client */ int index = 0; unsigned short eport2, iport2, rport2; char iaddr2[INET6_ADDRSTRLEN], rhost2[INET6_ADDRSTRLEN]; int proto2; char desc[64]; unsigned int timestamp; #if 0 int uid; #endif /* 0 */ if (pcp_msg_info->is_fw) { pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPCODE; return; } inet_n46top((struct in6_addr*)pcp_msg_info->peer_ip, rhost, sizeof(rhost)); for (index = 0 ; (!pcp_msg_info->is_fw && get_peer_rule_by_index(index, 0, &eport2, iaddr2, sizeof(iaddr2), &iport2, &proto2, desc, sizeof(desc), rhost2, sizeof(rhost2), &rport2, ×tamp, 0, 0) >= 0) #if 0 /* Some day if outbound pinholes are supported.. */ || (pcp_msg_info->is_fw && (uid=upnp_get_pinhole_uid_by_index(index))>=0 && upnp_get_pinhole_info((unsigned short)uid, rhost2, sizeof(rhost2), &rport2, iaddr2, sizeof(iaddr2), &iport2, &proto2, desc, sizeof(desc), ×tamp, NULL) >= 0) #endif /* 0 */ ; index++) if((0 == strcmp(iaddr2, pcp_msg_info->mapped_str)) && (0 == strcmp(rhost2, rhost)) && (proto2==proto) && 0 == strcmp(desc, pcp_msg_info->desc) && (iport2==iport) && (rport2==rport)) { if (!pcp_msg_info->is_fw) r = _upnp_delete_redir(eport2, proto2); #if 0 else r = upnp_delete_outboundpinhole(uid); #endif /* 0 */ if(r<0) { syslog(LOG_ERR, "PCP PEER: failed to remove peer mapping"); } else { syslog(LOG_INFO, "PCP PEER: %s port %hu peer mapping removed", proto2==IPPROTO_TCP?"TCP":"UDP", eport2); } return; } if (r==-1) { syslog(LOG_ERR, "PCP PEER: Failed to find PCP mapping internal port %hu, protocol %s", iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP"); pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES; } } #endif /* PCP_PEER */ static int CreatePCPMap_NAT(pcp_info_t *pcp_msg_info) { int r = 0; char iaddr_old[INET6_ADDRSTRLEN]; uint16_t iport_old, eport_first = 0; int any_eport_allowed = 0; unsigned int timestamp = upnp_time() + pcp_msg_info->lifetime; if (pcp_msg_info->ext_port == 0) { pcp_msg_info->ext_port = pcp_msg_info->int_port; } /* TODO: Support non-TCP/UDP */ if (pcp_msg_info->ext_port == 0) { return PCP_ERR_MALFORMED_REQUEST; } do { if (eport_first == 0) { /* first time in loop */ eport_first = pcp_msg_info->ext_port; } else if (pcp_msg_info->ext_port == eport_first) { /* no eport available */ /* all eports rejected by permissions? */ if (any_eport_allowed == 0) return PCP_ERR_NOT_AUTHORIZED; /* at least one eport allowed (but none available) */ return PCP_ERR_NO_RESOURCES; } if ((IN6_IS_ADDR_V4MAPPED(pcp_msg_info->mapped_ip) && (!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, pcp_msg_info->ext_port, ((struct in_addr*)pcp_msg_info->mapped_ip->s6_addr)[3], pcp_msg_info->int_port, pcp_msg_info->desc)))) { if (pcp_msg_info->pfailure_present) { return PCP_ERR_CANNOT_PROVIDE_EXTERNAL; } pcp_msg_info->ext_port++; if (pcp_msg_info->ext_port == 0) { /* skip port zero */ pcp_msg_info->ext_port++; } continue; } any_eport_allowed = 1; #ifdef CHECK_PORTINUSE if (port_in_use(ext_if_name, pcp_msg_info->ext_port, pcp_msg_info->protocol, pcp_msg_info->mapped_str, pcp_msg_info->int_port) > 0) { syslog(LOG_INFO, "port %hu protocol %s already in use", pcp_msg_info->ext_port, (pcp_msg_info->protocol==IPPROTO_TCP)?"tcp":"udp"); pcp_msg_info->ext_port++; if (pcp_msg_info->ext_port == 0) { /* skip port zero */ pcp_msg_info->ext_port++; } continue; } #endif r = get_redirect_rule(ext_if_name, pcp_msg_info->ext_port, pcp_msg_info->protocol, iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0, 0, 0, NULL/*×tamp*/, 0, 0); if(r==0) { if((strcmp(pcp_msg_info->mapped_str, iaddr_old)!=0) || (pcp_msg_info->int_port != iport_old)) { /* redirection already existing */ if (pcp_msg_info->pfailure_present) { return PCP_ERR_CANNOT_PROVIDE_EXTERNAL; } } else { syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing", pcp_msg_info->ext_port, (pcp_msg_info->protocol==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old); /* remove and then add again */ if (_upnp_delete_redir(pcp_msg_info->ext_port, pcp_msg_info->protocol)==0) { break; } else if (pcp_msg_info->pfailure_present) { return PCP_ERR_CANNOT_PROVIDE_EXTERNAL; } } pcp_msg_info->ext_port++; if (pcp_msg_info->ext_port == 0) { /* skip port zero */ pcp_msg_info->ext_port++; } } } while (r==0); r = upnp_redirect_internal(NULL, pcp_msg_info->ext_port, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->protocol, pcp_msg_info->desc, timestamp); if (r < 0) return PCP_ERR_NO_RESOURCES; return PCP_SUCCESS; } static int CreatePCPMap_FW(pcp_info_t *pcp_msg_info) { #ifdef ENABLE_UPNPPINHOLE int uid; int r; /* first check if pinhole already exists */ uid = upnp_find_inboundpinhole(NULL, 0, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->protocol, NULL, 0, /* desc */ NULL /* lifetime */); if(uid >= 0) { /* pinhole already exists, updating */ syslog(LOG_INFO, "updating pinhole to %s:%hu %s", pcp_msg_info->mapped_str, pcp_msg_info->int_port, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP"); r = upnp_update_inboundpinhole((unsigned short)uid, pcp_msg_info->lifetime); return r >= 0 ? PCP_SUCCESS : PCP_ERR_NO_RESOURCES; } else { r = upnp_add_inboundpinhole(NULL, 0, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->protocol, pcp_msg_info->desc, pcp_msg_info->lifetime, &uid); if (r < 0) return PCP_ERR_NO_RESOURCES; pcp_msg_info->ext_port = pcp_msg_info->int_port; return PCP_SUCCESS; } #else UNUSED(pcp_msg_info); return PCP_ERR_NO_RESOURCES; #endif /* ENABLE_UPNPPINHOLE */ } /* internal external PCP remote peer actual remote peer * -------- ------- --------------- ------------------ * IPv4 firewall IPv4 IPv4 IPv4 IPv4 * IPv6 firewall IPv6 IPv6 IPv6 IPv6 * NAT44 IPv4 IPv4 IPv4 IPv4 * NAT46 IPv4 IPv6 IPv4 IPv6 * NAT64 IPv6 IPv4 IPv6 IPv4 * NPTv6 IPv6 IPv6 IPv6 IPv6 * * Address Families with MAP and PEER * * The 'internal' address is implicitly the same as the source IP * address of the PCP request, except when the THIRD_PARTY option is * used. * * The 'external' address is the Suggested External Address field of the * MAP or PEER request, and its address family is usually the same as * the 'internal' address family, except when technologies like NAT64 * are used. * * The 'remote peer' address is the remote peer IP address of the PEER * request or the FILTER option of the MAP request, and is always the * same address family as the 'internal' address, even when NAT64 is * used. In NAT64, the IPv6 PCP client is not necessarily aware of the * NAT64 or aware of the actual IPv4 address of the remote peer, so it * expresses the IPv6 address from its perspective. */ /* TODO: Support more than basic NAT44 / IPv6 firewall cases. */ static void CreatePCPMap(pcp_info_t *pcp_msg_info) { int r; if (pcp_msg_info->is_fw) r = CreatePCPMap_FW(pcp_msg_info); else r = CreatePCPMap_NAT(pcp_msg_info); pcp_msg_info->result_code = r; syslog(r == PCP_SUCCESS ? LOG_INFO : LOG_ERR, "PCP MAP: %s mapping %s %hu->%s:%hu '%s'", r == PCP_SUCCESS ? "added" : "failed to add", (pcp_msg_info->protocol==IPPROTO_TCP)?"TCP":"UDP", pcp_msg_info->ext_port, pcp_msg_info->mapped_str, pcp_msg_info->int_port, pcp_msg_info->desc); } static void DeletePCPMap(pcp_info_t *pcp_msg_info) { uint16_t iport = pcp_msg_info->int_port; /* private port */ uint8_t proto = pcp_msg_info->protocol; int r=-1; /* remove the mapping */ /* remove all the mappings for this client */ unsigned short eport2, iport2; char iaddr2[INET6_ADDRSTRLEN]; int proto2; char desc[64]; unsigned int timestamp; syslog(LOG_DEBUG, "is_fw=%d addr=%s iport=%hu proto=%d", pcp_msg_info->is_fw, pcp_msg_info->mapped_str, iport, (int)proto); if (!pcp_msg_info->is_fw) { int index; /* iterate through all rules and delete the requested ones */ for (index = 0; get_redirect_rule_by_index(index, 0, &eport2, iaddr2, sizeof(iaddr2), &iport2, &proto2, desc, sizeof(desc), 0, 0, ×tamp, 0, 0) >= 0; index++) { syslog(LOG_DEBUG, "%d: %s %hu %d", index, iaddr2, iport2, proto2); if(0 == strcmp(iaddr2, pcp_msg_info->mapped_str) && (proto2==proto) && ((iport2==iport) || (iport==0))) { if(0 != strcmp(desc, pcp_msg_info->desc)) { /* nonce does not match */ pcp_msg_info->result_code = PCP_ERR_NOT_AUTHORIZED; syslog(LOG_ERR, "Unauthorized to remove PCP mapping internal port %hu, protocol %s", iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP"); return; } else { r = _upnp_delete_redir(eport2, proto2); } break; } } } else { #ifdef ENABLE_UPNPPINHOLE int uid; uid = upnp_find_inboundpinhole(NULL, 0, pcp_msg_info->mapped_str, iport, pcp_msg_info->protocol, desc, sizeof(desc), NULL /* lifetime */); if (uid < 0) { syslog(LOG_ERR, "Failed to find mapping to %s:%hu, protocol %s", pcp_msg_info->mapped_str, iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP"); return; } else { if(0 != strcmp(desc, pcp_msg_info->desc)) { /* nonce does not match */ pcp_msg_info->result_code = PCP_ERR_NOT_AUTHORIZED; syslog(LOG_ERR, "Unauthorized to remove PCP mapping internal port %hu, protocol %s", iport, (pcp_msg_info->protocol == IPPROTO_TCP)?"TCP":"UDP"); return; } else { r = upnp_delete_inboundpinhole(uid); } } #else syslog(LOG_WARNING, "ENABLE_UPNPPINHOLE was not enabled at compile time"); #endif /* ENABLE_UPNPPINHOLE */ } if (r >= 0) { syslog(LOG_INFO, "PCP: %s port %hu mapping removed", proto==IPPROTO_TCP?"TCP":"UDP", (pcp_msg_info->is_fw ? iport : eport2)); } else { syslog(LOG_ERR, "Failed to remove PCP mapping to %s:%hu %s", pcp_msg_info->mapped_str, iport, (proto == IPPROTO_TCP)?"TCP":"UDP"); pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES; } } static int ValidatePCPMsg(pcp_info_t *pcp_msg_info) { if (pcp_msg_info->result_code) { return 0; } /* RFC 6887, section 8.2: MUST return address mismatch if NAT * in middle. */ if (memcmp(pcp_msg_info->int_ip, &pcp_msg_info->sender_ip, sizeof(pcp_msg_info->sender_ip)) != 0) { pcp_msg_info->result_code = PCP_ERR_ADDRESS_MISMATCH; return 0; } if (pcp_msg_info->thirdp_ip) { if (!GETFLAG(PCP_ALLOWTHIRDPARTYMASK)) { pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPTION; return 0; } /* RFC687, section 13.1 - if sender ip == THIRD_PARTY, * it's an error. */ if (memcmp(pcp_msg_info->thirdp_ip, &pcp_msg_info->sender_ip, sizeof(pcp_msg_info->sender_ip)) == 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return 0; } } /* Produce mapped_str for future use. */ if (!inet_n46top(pcp_msg_info->mapped_ip, pcp_msg_info->mapped_str, sizeof(pcp_msg_info->mapped_str))) { syslog(LOG_ERR, "inet_ntop(pcpserver): %m"); return 0; } /* protocol zero means 'all protocols' : internal port MUST be zero */ if (pcp_msg_info->protocol == 0 && pcp_msg_info->int_port != 0) { syslog(LOG_ERR, "PCP %s: Protocol was ZERO, but internal port " "has non-ZERO value.", getPCPOpCodeStr(pcp_msg_info->opcode)); pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return 0; } if (pcp_msg_info->pfailure_present) { if ( (IN6_IS_ADDR_UNSPECIFIED(pcp_msg_info->ext_ip) || ((IN6_IS_ADDR_V4MAPPED(pcp_msg_info->ext_ip)) && (((uint32_t*)pcp_msg_info->ext_ip->s6_addr)[3] == 0))) && (pcp_msg_info->ext_port == 0) ) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return 0; } } if (CheckExternalAddress(pcp_msg_info)) { return 0; } /* Fill in the desc that describes uniquely what flow we're * dealing with (same code used in both create + delete of * MAP/PEER) */ switch (pcp_msg_info->opcode) { case PCP_OPCODE_MAP: case PCP_OPCODE_PEER: snprintf(pcp_msg_info->desc, sizeof(pcp_msg_info->desc), "PCP %s %08x%08x%08x", getPCPOpCodeStr(pcp_msg_info->opcode), pcp_msg_info->nonce[0], pcp_msg_info->nonce[1], pcp_msg_info->nonce[2]); break; } return 1; } /* * return value indicates whether the request is valid or not. * Based on the return value simple response can be formed. */ static int processPCPRequest(void * req, int req_size, pcp_info_t *pcp_msg_info) { int remainingSize; /* start with PCP_SUCCESS as result code, * if everything is OK value will be unchanged */ pcp_msg_info->result_code = PCP_SUCCESS; remainingSize = req_size; /* discard request that exceeds maximal length, or that is shorter than PCP_MIN_LEN (=24) or that is not the multiple of 4 */ if (req_size < 3) return 0; /* ignore msg */ if (req_size < PCP_MIN_LEN) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return 1; /* send response */ } if ( (req_size > PCP_MAX_LEN) || ( (req_size & 3) != 0)) { syslog(LOG_ERR, "PCP: Size of PCP packet(%d) is larger than %d bytes or " "the size is not multiple of 4.\n", req_size, PCP_MAX_LEN); pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return 1; /* send response */ } /* first parse request header */ if (parseCommonRequestHeader(req, pcp_msg_info) ) { return 1; } remainingSize -= PCP_COMMON_REQUEST_SIZE; req += PCP_COMMON_REQUEST_SIZE; if (pcp_msg_info->version == 1) { /* legacy PCP version 1 support */ switch (pcp_msg_info->opcode) { case PCP_OPCODE_MAP: remainingSize -= PCP_MAP_V1_SIZE; if (remainingSize < 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return pcp_msg_info->result_code; } #ifdef DEBUG printMAPOpcodeVersion1(req); #endif /* DEBUG */ parsePCPMAP_version1(req, pcp_msg_info); req += PCP_MAP_V1_SIZE; parsePCPOptions(req, remainingSize, pcp_msg_info); if (ValidatePCPMsg(pcp_msg_info)) { if (pcp_msg_info->lifetime == 0) { DeletePCPMap(pcp_msg_info); } else { CreatePCPMap(pcp_msg_info); } } else { syslog(LOG_ERR, "PCP: Invalid PCP v1 MAP message."); return pcp_msg_info->result_code; } break; #ifdef PCP_PEER case PCP_OPCODE_PEER: remainingSize -= PCP_PEER_V1_SIZE; if (remainingSize < 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return pcp_msg_info->result_code; } #ifdef DEBUG printPEEROpcodeVersion1(req); #endif /* DEBUG */ parsePCPPEER_version1(req, pcp_msg_info); req += PCP_PEER_V1_SIZE; parsePCPOptions(req, remainingSize, pcp_msg_info); if (ValidatePCPMsg(pcp_msg_info)) { if (pcp_msg_info->lifetime == 0) { DeletePCPPeer(pcp_msg_info); } else { CreatePCPPeer(pcp_msg_info); } } else { syslog(LOG_ERR, "PCP: Invalid PCP v1 PEER message."); return pcp_msg_info->result_code; } break; #endif /* PCP_PEER */ default: pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPCODE; break; } } else if (pcp_msg_info->version == 2) { /* RFC 6887 PCP support * http://tools.ietf.org/html/rfc6887 */ switch (pcp_msg_info->opcode) { case PCP_OPCODE_ANNOUNCE: /* should check PCP Client's IP Address in request */ /* see http://tools.ietf.org/html/rfc6887#section-14.1 */ break; case PCP_OPCODE_MAP: remainingSize -= PCP_MAP_V2_SIZE; if (remainingSize < 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return pcp_msg_info->result_code; } #ifdef DEBUG printMAPOpcodeVersion2(req); #endif /* DEBUG */ parsePCPMAP_version2(req, pcp_msg_info); req += PCP_MAP_V2_SIZE; parsePCPOptions(req, remainingSize, pcp_msg_info); if (ValidatePCPMsg(pcp_msg_info)) { if (pcp_msg_info->lifetime == 0) { DeletePCPMap(pcp_msg_info); } else { CreatePCPMap(pcp_msg_info); } } else { syslog(LOG_ERR, "PCP: Invalid PCP v2 MAP message."); return pcp_msg_info->result_code; } break; #ifdef PCP_PEER case PCP_OPCODE_PEER: remainingSize -= PCP_PEER_V2_SIZE; if (remainingSize < 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return pcp_msg_info->result_code; } #ifdef DEBUG printPEEROpcodeVersion2(req); #endif /* DEBUG */ parsePCPPEER_version2(req, pcp_msg_info); req += PCP_PEER_V2_SIZE; if (pcp_msg_info->result_code != 0) { return pcp_msg_info->result_code; } parsePCPOptions(req, remainingSize, pcp_msg_info); if (ValidatePCPMsg(pcp_msg_info)) { if (pcp_msg_info->lifetime == 0) { DeletePCPPeer(pcp_msg_info); } else { CreatePCPPeer(pcp_msg_info); } } else { syslog(LOG_ERR, "PCP: Invalid PCP v2 PEER message."); } break; #endif /* PCP_PEER */ #ifdef PCP_SADSCP case PCP_OPCODE_SADSCP: remainingSize -= PCP_SADSCP_REQ_SIZE; if (remainingSize < 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_REQUEST; return pcp_msg_info->result_code; } remainingSize -= ((uint8_t *)req)[13]; /* app_name_length */ if (remainingSize < 0) { pcp_msg_info->result_code = PCP_ERR_MALFORMED_OPTION; return pcp_msg_info->result_code; } #ifdef DEBUG printSADSCPOpcode(req); #endif parseSADSCP(req, pcp_msg_info); req += PCP_SADSCP_REQ_SIZE; if (pcp_msg_info->result_code != 0) { return pcp_msg_info->result_code; } req += pcp_msg_info->app_name_len; get_dscp_value(pcp_msg_info); break; #endif default: pcp_msg_info->result_code = PCP_ERR_UNSUPP_OPCODE; break; } } else { pcp_msg_info->result_code = PCP_ERR_UNSUPP_VERSION; return pcp_msg_info->result_code; } return 1; } static void createPCPResponse(unsigned char *response, const pcp_info_t *pcp_msg_info) { response[2] = 0; /* reserved */ memset(response + 12, 0, 12); /* reserved */ if (pcp_msg_info->result_code == PCP_ERR_UNSUPP_VERSION ) { /* highest supported version */ response[0] = this_server_info.server_version; } else { response[0] = pcp_msg_info->version; } response[1] = pcp_msg_info->opcode | 0x80; /* r_opcode */ response[3] = pcp_msg_info->result_code; if(epoch_origin == 0) { epoch_origin = startup_time; } WRITENU32(response + 8, upnp_time() - epoch_origin); /* epochtime */ switch (pcp_msg_info->result_code) { /*long lifetime errors*/ case PCP_ERR_UNSUPP_VERSION: case PCP_ERR_NOT_AUTHORIZED: case PCP_ERR_MALFORMED_REQUEST: case PCP_ERR_UNSUPP_OPCODE: case PCP_ERR_UNSUPP_OPTION: case PCP_ERR_MALFORMED_OPTION: case PCP_ERR_UNSUPP_PROTOCOL: case PCP_ERR_ADDRESS_MISMATCH: case PCP_ERR_CANNOT_PROVIDE_EXTERNAL: case PCP_ERR_EXCESSIVE_REMOTE_PEERS: WRITENU32(response + 4, 0); /* lifetime */ break; case PCP_ERR_NETWORK_FAILURE: case PCP_ERR_NO_RESOURCES: case PCP_ERR_USER_EX_QUOTA: WRITENU32(response + 4, 30); /* lifetime */ break; case PCP_SUCCESS: default: WRITENU32(response + 4, pcp_msg_info->lifetime); /* lifetime */ break; } if (response[1] == 0x81) { /* MAP response */ if (response[0] == 1) { /* version */ WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 4, pcp_msg_info->int_port); WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 6, pcp_msg_info->ext_port); copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 8, pcp_msg_info->ext_ip); } else if (response[0] == 2) { WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 16, pcp_msg_info->int_port); WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 18, pcp_msg_info->ext_port); copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 20, pcp_msg_info->ext_ip); } } #ifdef PCP_PEER else if (response[1] == 0x82) { /* PEER response */ if (response[0] == 1) { WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 4, pcp_msg_info->int_port); WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 6, pcp_msg_info->ext_port); WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 24, pcp_msg_info->peer_port); copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 8, pcp_msg_info->ext_ip); } else if (response[0] == 2) { WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 16, pcp_msg_info->int_port); WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 18, pcp_msg_info->ext_port); WRITENU16(response + PCP_COMMON_RESPONSE_SIZE + 36, pcp_msg_info->peer_port); copyIPv6IfDifferent(response + PCP_COMMON_RESPONSE_SIZE + 20, pcp_msg_info->ext_ip); } } #endif /* PCP_PEER */ #ifdef PCP_SADSCP else if (response[1] == 0x83) { /*SADSCP response*/ response[PCP_COMMON_RESPONSE_SIZE + 12] = ((pcp_msg_info->matched_name<<7) & ~(1<<6)) | (pcp_msg_info->sadscp_dscp & PCP_SADSCP_MASK); memset(response + PCP_COMMON_RESPONSE_SIZE + 13, 0, 3); } #endif /* PCP_SADSCP */ } int ProcessIncomingPCPPacket(int s, unsigned char *buff, int len, const struct sockaddr *senderaddr, const struct sockaddr_in6 *receiveraddr) { pcp_info_t pcp_msg_info; struct lan_addr_s * lan_addr; char addr_str[64]; memset(&pcp_msg_info, 0, sizeof(pcp_info_t)); if(senderaddr->sa_family == AF_INET) { const struct sockaddr_in * senderaddr_v4 = (const struct sockaddr_in *)senderaddr; pcp_msg_info.sender_ip.s6_addr[11] = 0xff; pcp_msg_info.sender_ip.s6_addr[10] = 0xff; memcpy(pcp_msg_info.sender_ip.s6_addr+12, &senderaddr_v4->sin_addr, 4); } else if(senderaddr->sa_family == AF_INET6) { const struct sockaddr_in6 * senderaddr_v6 = (const struct sockaddr_in6 *)senderaddr; pcp_msg_info.sender_ip = senderaddr_v6->sin6_addr; } else { syslog(LOG_WARNING, "unknown PCP packet sender address family %d", senderaddr->sa_family); return 0; } if(sockaddr_to_string(senderaddr, addr_str, sizeof(addr_str))) syslog(LOG_DEBUG, "PCP request received from %s %dbytes", addr_str, len); if(buff[1] & 128) { /* discarding PCP responses silently */ return 0; } /* If we're in allow third party-mode, we probably don't care * about locality either. Let's hope firewall is ok. */ if (!GETFLAG(PCP_ALLOWTHIRDPARTYMASK)) { lan_addr = get_lan_for_peer(senderaddr); if(lan_addr == NULL) { syslog(LOG_WARNING, "PCP packet sender %s not from a LAN, ignoring", addr_str); return 0; } } if (processPCPRequest(buff, len, &pcp_msg_info) ) { createPCPResponse(buff, &pcp_msg_info); if(len < PCP_MIN_LEN) len = PCP_MIN_LEN; else len = (len + 3) & ~3; /* round up resp. length to multiple of 4 */ len = sendto_or_schedule2(s, buff, len, 0, senderaddr, (senderaddr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), receiveraddr); if( len < 0 ) { syslog(LOG_ERR, "sendto(pcpserver): %m"); } } return 0; } #ifdef ENABLE_IPV6 int OpenAndConfPCPv6Socket(void) { int s; int i = 1; struct sockaddr_in6 addr; s = socket(PF_INET6, SOCK_DGRAM, 0/*IPPROTO_UDP*/); if(s < 0) { syslog(LOG_ERR, "%s: socket(): %m", "OpenAndConfPCPv6Socket"); return -1; } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(SO_REUSEADDR): %m", "OpenAndConfPCPv6Socket"); } #ifdef IPV6_V6ONLY /* force IPV6 only for IPV6 socket. * see http://www.ietf.org/rfc/rfc3493.txt section 5.3 */ if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(IPV6_V6ONLY): %m", "OpenAndConfPCPv6Socket"); } #endif #ifdef IPV6_RECVPKTINFO /* see RFC3542 */ if(setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(IPV6_RECVPKTINFO): %m", "OpenAndConfPCPv6Socket"); } #endif if(!set_non_blocking(s)) { syslog(LOG_WARNING, "%s: set_non_blocking(): %m", "OpenAndConfPCPv6Socket"); } memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_port = htons(NATPMP_PORT); addr.sin6_addr = ipv6_bind_addr; if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "%s: bind(): %m", "OpenAndConfPCPv6Socket"); close(s); return -1; } return s; } #endif /*ENABLE_IPV6*/ #ifdef ENABLE_IPV6 void PCPSendUnsolicitedAnnounce(int * sockets, int n_sockets, int socket6) #else /* IPv4 only */ void PCPSendUnsolicitedAnnounce(int * sockets, int n_sockets) #endif { int i; unsigned char buff[PCP_MIN_LEN]; pcp_info_t info; ssize_t len; struct sockaddr_in addr; #ifdef ENABLE_IPV6 struct sockaddr_in6 addr6; #endif /* ENABLE_IPV6 */ /* this is an Unsolicited ANNOUNCE response */ info.version = this_server_info.server_version; info.opcode = PCP_OPCODE_ANNOUNCE; info.result_code = PCP_SUCCESS; info.lifetime = 0; createPCPResponse(buff, &info); /* Multicast PCP restart announcements are sent to * 224.0.0.1:5350 and/or [ff02::1]:5350 */ memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("224.0.0.1"); addr.sin_port = htons(5350); for(i = 0; i < n_sockets; i++) { if (sockets[i] < 0) { continue; } len = sendto_or_schedule(sockets[i], buff, PCP_MIN_LEN, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); if( len < 0 ) { syslog(LOG_ERR, "PCPSendUnsolicitedAnnounce(sockets[%d]) sendto(): %m", i); } } #ifdef ENABLE_IPV6 if (socket6 >= 0) { memset(&addr6, 0, sizeof(struct sockaddr_in6)); addr6.sin6_family = AF_INET6; inet_pton(AF_INET6, "FF02::1", &(addr6.sin6_addr)); addr6.sin6_port = htons(5350); len = sendto_or_schedule(socket6, buff, PCP_MIN_LEN, 0, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)); if( len < 0 ) { syslog(LOG_ERR, "PCPSendUnsolicitedAnnounce() IPv6 sendto(): %m"); } } #endif /* ENABLE_IPV6 */ } #ifdef ENABLE_IPV6 void PCPPublicAddressChanged(int * sockets, int n_sockets, int socket6) #else /* IPv4 only */ void PCPPublicAddressChanged(int * sockets, int n_sockets) #endif { /* according to RFC 6887 8.5 : * if the external IP address(es) of the NAT (controlled by * the PCP server) changes, the Epoch time MUST be reset. */ epoch_origin = upnp_time(); #ifdef ENABLE_IPV6 PCPSendUnsolicitedAnnounce(sockets, n_sockets, socket6); #else /* IPv4 Only */ PCPSendUnsolicitedAnnounce(sockets, n_sockets); #endif } #endif /*ENABLE_PCP*/ miniupnpd-2.3.7/pcplearndscp.h010064400017500000024000000040101411013505300155230ustar00nanardstaff/* $Id: pcplearndscp.h,v 1.3 2021/08/21 08:20:11 nanard Exp $ */ /* MiniUPnP project * Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author : Miroslav Bagljas Copyright (c) 2013 by Cisco Systems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef PCPLEARNDSCP_H_INCLUDED #define PCPLEARNDSCP_H_INCLUDED struct dscp_values { char *app_name; unsigned int app_name_len; unsigned char delay; unsigned char loss; unsigned char jitter; unsigned char dscp_value; }; /* #set_learn_dscp "Webex" 1 1 1 34 */ int read_learn_dscp_line(struct dscp_values *dscpvalues, char *p, int debug_flag); #endif /* PCPLEARNDSCP_H_INCLUDED */ miniupnpd-2.3.7/pcplearndscp.c010064400017500000024000000156721411013505300155360ustar00nanardstaff/* $Id: pcplearndscp.c,v 1.3 2021/08/21 08:20:11 nanard Exp $ */ /* MiniUPnP project * Website : http://miniupnp.free.fr/ * Author : Miroslav Bagljas Copyright (c) 2013 by Cisco Systems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include "upnpglobalvars.h" #include "pcplearndscp.h" #include "macros.h" #ifdef PCP_SADSCP void print_dscp(void) { unsigned int i; for (i=0; i < num_dscp_values; i++){ syslog(LOG_DEBUG, "Appname %*.s, del %d, loss %d, jitter %d, dscp %d", dscp_values_list[i].app_name_len, dscp_values_list[i].app_name, dscp_values_list[i].delay, dscp_values_list[i].loss, dscp_values_list[i].jitter, dscp_values_list[i].dscp_value ); } } int read_learn_dscp_line(struct dscp_values *dscpvalues, char *p, int debug_flag) { char * q; size_t len; unsigned int sizeof_first_token = sizeof("set_learn_dscp") - 1; int af_value; int cs_value; /* first token: (set_learn_dscp) skip it */ while(isspace(*p)) p++; if(0 == memcmp(p, "set_learn_dscp", sizeof_first_token)) { p += sizeof_first_token; } else { return -1; } while(isspace(*p)) p++; /* second token: name of the application */ // if if(!(*p == '"')) return -1; p++; for(q = p; !(*q == '"'); q++); len = q - p; if (len != 0) { dscpvalues->app_name = strndup(p, len); } else { dscpvalues->app_name = NULL; } dscpvalues->app_name_len = len; p = q + 1; /* third token: delay */ while(isspace(*p)) p++; if(!isdigit(*p)) goto exit_err_and_cleanup; for(q = p; isdigit(*q); q++); if(isspace(*q)) { *q = '\0'; dscpvalues->delay = (unsigned char)atoi(p); if (dscpvalues->delay >= 3) { INIT_PRINT_ERR("Wrong delay value %d in \n", dscpvalues->delay); INIT_PRINT_ERR("Delay can be from set {0,1,2} 0=low delay, 1=medium delay, 2=high delay\n"); goto exit_err_and_cleanup; } } else { goto exit_err_and_cleanup; } p = q + 1; /* fourth token: loss */ while(isspace(*p)) p++; if(!isdigit(*p)) goto exit_err_and_cleanup; for(q = p; isdigit(*q); q++); if(isspace(*q)) { *q = '\0'; dscpvalues->loss = (unsigned char)atoi(p); if (dscpvalues->loss >= 3) { INIT_PRINT_ERR("Wrong loss value %d \n", dscpvalues->loss); INIT_PRINT_ERR("Delay can be from set {0,1,2} 0=low loss, 1=medium loss, 2=high loss\n"); goto exit_err_and_cleanup; } } else { goto exit_err_and_cleanup; } p = q + 1; /* fifth token: jitter */ while(isspace(*p)) p++; if(!isdigit(*p)) goto exit_err_and_cleanup; for(q = p; isdigit(*q); q++); if(isspace(*q)) { *q = '\0'; dscpvalues->jitter = (unsigned char)atoi(p); if (dscpvalues->jitter >= 3) { INIT_PRINT_ERR("Wrong jitter value %d \n", dscpvalues->jitter); INIT_PRINT_ERR("Delay can be from set {0,1,2} 0=low jitter, 1=medium jitter, 2=high jitter \n"); goto exit_err_and_cleanup; } } else { goto exit_err_and_cleanup; } p = q + 1; while(isspace(*p)) p++; /*{ }*/ p = q + 1; /* sixth token: DSCP value */ while(isspace(*p)) p++; if(!isdigit(*p) && !( toupper(*p) == 'A' && toupper(*(p+1)) == 'F') && !( toupper(*p) == 'C' && toupper(*(p+1)) == 'S') && !( toupper(*p) == 'E' && toupper(*(p+1)) == 'F') ) goto exit_err_and_cleanup; // for(q = p; isdigit(*q) || (toupper(*q) == 'A') || (toupper(*q) == 'F'); q++); for(q = p; isdigit(*q) || isalpha(*q); q++); if(isspace(*q) || *q == '\0') { *q = '\0'; if (toupper(*p) == 'A' && toupper(*(p+1)) == 'F'){ p = p+2; if (*p == '\0') { dscpvalues->dscp_value = 0; } else if (!isdigit(*p)) { goto exit_err_and_cleanup; } else { af_value = atoi(p); switch(af_value) { case 11: dscpvalues->dscp_value = 10; break; case 12: dscpvalues->dscp_value = 12; break; case 13: dscpvalues->dscp_value = 14; break; case 21: dscpvalues->dscp_value = 18; break; case 22: dscpvalues->dscp_value = 20; break; case 23: dscpvalues->dscp_value = 22; break; case 31: dscpvalues->dscp_value = 26; break; case 32: dscpvalues->dscp_value = 28; break; case 33: dscpvalues->dscp_value = 30; break; case 41: dscpvalues->dscp_value = 34; break; case 42: dscpvalues->dscp_value = 36; break; case 43: dscpvalues->dscp_value = 38; break; default: INIT_PRINT_ERR("Unknown AF value %d \n", af_value); goto exit_err_and_cleanup; } } } else if (toupper(*p) == 'C' && toupper(*(p+1)) == 'S'){ p=p+2; if (*p == '\0') { dscpvalues->dscp_value = 0; } else if (!isdigit(*p)) { INIT_PRINT_ERR("Not digit after CS but %c \n", *p); goto exit_err_and_cleanup; } else { cs_value = atoi(p); switch(cs_value) { case 1: dscpvalues->dscp_value = 8; break; case 2: dscpvalues->dscp_value = 16; break; case 3: dscpvalues->dscp_value = 24; break; case 4: dscpvalues->dscp_value = 32; break; case 5: dscpvalues->dscp_value = 40; break; case 6: dscpvalues->dscp_value = 48; break; case 7: dscpvalues->dscp_value = 56; break; default: INIT_PRINT_ERR("Unknown CS value %d \n", cs_value); goto exit_err_and_cleanup; } } } else if (toupper(*p) == 'E' && toupper(*(p+1)) == 'F'){ dscpvalues->dscp_value = 46; } else { dscpvalues->dscp_value = (unsigned char)atoi(p); } } else { goto exit_err_and_cleanup; } return 0; exit_err_and_cleanup: free(dscpvalues->app_name); dscpvalues->app_name = NULL; dscpvalues->app_name_len = 0; return -1; } #endif miniupnpd-2.3.7/pcp_msg_struct.h010064400017500000024000000242751260022451100161200ustar00nanardstaff/* $Id: pcp_msg_struct.h,v 1.6 2015/09/22 10:10:54 nanard Exp $ */ /* MiniUPnP project * Website : http://miniupnp.free.fr/ * Author : Peter Tatrai Copyright (c) 2013 by Cisco Systems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef PCP_MSG_STRUCT_H_INCLUDED #define PCP_MSG_STRUCT_H_INCLUDED #define PCP_OPCODE_ANNOUNCE 0 #define PCP_OPCODE_MAP 1 #define PCP_OPCODE_PEER 2 #ifdef PCP_SADSCP #define PCP_OPCODE_SADSCP 3 #endif /* Possible response codes sent by server, as a result of client request*/ #define PCP_SUCCESS 0 #define PCP_ERR_UNSUPP_VERSION 1 /** The version number at the start of the PCP Request * header is not recognized by this PCP server. This is a long * lifetime error. This document describes PCP version 2. */ #define PCP_ERR_NOT_AUTHORIZED 2 /**The requested operation is disabled for this PCP * client, or the PCP client requested an operation that cannot be * fulfilled by the PCP server's security policy. This is a long * lifetime error. */ #define PCP_ERR_MALFORMED_REQUEST 3 /**The request could not be successfully parsed. * This is a long lifetime error. */ #define PCP_ERR_UNSUPP_OPCODE 4 /** Unsupported Opcode. This is a long lifetime error. */ #define PCP_ERR_UNSUPP_OPTION 5 /**Unsupported Option. This error only occurs if the * Option is in the mandatory-to-process range. This is a long * lifetime error. */ #define PCP_ERR_MALFORMED_OPTION 6 /**Malformed Option (e.g., appears too many times, * invalid length). This is a long lifetime error. */ #define PCP_ERR_NETWORK_FAILURE 7 /**The PCP server or the device it controls are * experiencing a network failure of some sort (e.g., has not * obtained an External IP address). This is a short lifetime error. */ #define PCP_ERR_NO_RESOURCES 8 /**Request is well-formed and valid, but the server has * insufficient resources to complete the requested operation at this * time. For example, the NAT device cannot create more mappings at * this time, is short of CPU cycles or memory, or is unable to * handle the request due to some other temporary condition. The * same request may succeed in the future. This is a system-wide * error, different from USER_EX_QUOTA. This can be used as a catch- * all error, should no other error message be suitable. This is a * short lifetime error. */ #define PCP_ERR_UNSUPP_PROTOCOL 9 /**Unsupported transport protocol, e.g. SCTP in a * NAT that handles only UDP and TCP. This is a long lifetime error. */ #define PCP_ERR_USER_EX_QUOTA 10 /** This attempt to create a new mapping would exceed * this subscriber's port quota. This is a short lifetime error. */ #define PCP_ERR_CANNOT_PROVIDE_EXTERNAL 11 /** The suggested external port and/or * external address cannot be provided. This error MUST only be * returned for: * * MAP requests that included the PREFER_FAILURE Option * (normal MAP requests will return an available external port) * * MAP requests for the SCTP protocol (PREFER_FAILURE is implied) * * PEER requests */ #define PCP_ERR_ADDRESS_MISMATCH 12 /** The source IP address of the request packet does * not match the contents of the PCP Client's IP Address field, due * to an unexpected NAT on the path between the PCP client and the * PCP-controlled NAT or firewall. This is a long lifetime error. */ #define PCP_ERR_EXCESSIVE_REMOTE_PEERS 13 /** The PCP server was not able to create the * filters in this request. This result code MUST only be returned * if the MAP request contained the FILTER Option. See Section 13.3 * for processing information. This is a long lifetime error. */ typedef enum pcp_options { PCP_OPTION_3RD_PARTY = 1, PCP_OPTION_PREF_FAIL = 2, PCP_OPTION_FILTER = 3, #ifdef PCP_FLOWP PCP_OPTION_FLOW_PRIORITY = 4, /*TODO: change it to correct value*/ #endif } pcp_options_t; /* PCP common request header*/ #if 0 typedef struct pcp_request { uint8_t ver; uint8_t r_opcode; uint16_t reserved; uint32_t req_lifetime; struct in6_addr ip; /* ipv4 will be represented by the ipv4 mapped ipv6 */ uint8_t next_data[0]; } pcp_request_t; #endif #define PCP_COMMON_REQUEST_SIZE (24) /* PCP common response header*/ #if 0 typedef struct pcp_response { uint8_t ver; uint8_t r_opcode; /* R indicates Request (0) or Response (1) Opcode is 7 bit value specifying operation MAP or PEER */ uint8_t reserved; /* reserved bits, must be 0 on transmission and must be ignored on reception */ uint8_t result_code; /* */ uint32_t lifetime; /* an unsigned 32-bit integer, in seconds {0, 2^32-1}*/ uint32_t epochtime; /* epoch indicates how long has PCP server had its current mappings it increases by 1 every second */ uint32_t reserved1[3];/* For requests that were successfully parsed this must be sent as 0 */ uint8_t next_data[0]; } pcp_response_t; #endif #define PCP_COMMON_RESPONSE_SIZE (24) #if 0 typedef struct pcp_options_hdr { uint8_t code; /* Most significant bit indicates if this option is mandatory (0) or optional (1) */ uint8_t reserved; /* MUST be set to 0 on transmission and MUST be ignored on reception */ uint16_t len; /* indicates the length of the enclosed data in octets (see RFC) */ uint8_t next_data[0]; /* */ } pcp_options_hdr_t; #endif #define PCP_OPTION_HDR_SIZE (4) /* same for both request and response */ #if 0 typedef struct pcp_map_v2 { uint32_t nonce[3]; uint8_t protocol; /* 6 = TCP, 17 = UDP, 0 = 'all protocols' */ uint8_t reserved[3]; uint16_t int_port; /* 0 indicates 'all ports' */ uint16_t ext_port; /* suggested external port */ struct in6_addr ext_ip; /* suggested external IP address * ipv4 will be represented by the ipv4 mapped ipv6 */ uint8_t next_data[0]; } pcp_map_v2_t; #endif #define PCP_MAP_V2_SIZE (36) #if 0 /* same for both request and response */ typedef struct pcp_map_v1 { uint8_t protocol; uint8_t reserved[3]; uint16_t int_port; uint16_t ext_port; struct in6_addr ext_ip; /* ipv4 will be represented by the ipv4 mapped ipv6 */ uint8_t next_data[0]; } pcp_map_v1_t; #endif #define PCP_MAP_V1_SIZE (24) /* same for both request and response */ #if 0 typedef struct pcp_peer_v1 { uint8_t protocol; uint8_t reserved[3]; uint16_t int_port; uint16_t ext_port; struct in6_addr ext_ip; /* ipv4 will be represented by the ipv4 mapped ipv6 */ uint16_t peer_port; uint16_t reserved1; struct in6_addr peer_ip; uint8_t next_data[0]; } pcp_peer_v1_t; #endif #define PCP_PEER_V1_SIZE (44) /* same for both request and response */ #if 0 typedef struct pcp_peer_v2 { uint32_t nonce[3]; uint8_t protocol; uint8_t reserved[3]; uint16_t int_port; uint16_t ext_port; struct in6_addr ext_ip; /* ipv4 will be represented by the ipv4 mapped ipv6 */ uint16_t peer_port; uint16_t reserved1; struct in6_addr peer_ip; uint8_t next_data[0]; } pcp_peer_v2_t; #endif #define PCP_PEER_V2_SIZE (56) #ifdef PCP_SADSCP #if 0 typedef struct pcp_sadscp_req { uint32_t nonce[3]; uint8_t tolerance_fields; uint8_t app_name_length; char app_name[0]; } pcp_sadscp_req_t; #endif #define PCP_SADSCP_REQ_SIZE (14) #if 0 typedef struct pcp_sadscp_resp { uint32_t nonce[3]; uint8_t a_r_dscp_value; uint8_t reserved[3]; } pcp_sadscp_resp_t; #endif #define PCP_SADSCP_MASK ((1<<6)-1) #endif /* PCP_SADSCP */ #if 0 typedef struct pcp_prefer_fail_option { uint8_t option; uint8_t reserved; uint16_t len; uint8_t next_data[0]; } pcp_prefer_fail_option_t; #endif #define PCP_PREFER_FAIL_OPTION_SIZE (4) #if 0 typedef struct pcp_3rd_party_option{ uint8_t option; uint8_t reserved; uint16_t len; struct in6_addr ip; uint8_t next_data[0]; } pcp_3rd_party_option_t; #endif #define PCP_3RD_PARTY_OPTION_SIZE (20) #ifdef PCP_FLOWP #if 0 typedef struct pcp_flow_priority_option{ uint8_t option; uint8_t reserved; uint16_t len; uint8_t dscp_up; uint8_t dscp_down; uint8_t reserved2; /* most significant bit is used for response */ uint8_t response_bit; uint8_t next_data[0]; } pcp_flow_priority_option_t; #endif #define PCP_DSCP_MASK ((1<<6)-1) #define PCP_FLOW_PRIORITY_OPTION_SIZE (8) #endif #if 0 typedef struct pcp_filter_option { uint8_t option; uint8_t reserved1; uint16_t len; uint8_t reserved2; uint8_t prefix_len; uint16_t peer_port; struct in6_addr peer_ip; }pcp_filter_option_t; #endif #define PCP_FILTER_OPTION_SIZE (24) #endif /* PCP_MSG_STRUCT_H_INCLUDED */ miniupnpd-2.3.7/asyncsendto.c010064400017500000024000000234001376024217600154150ustar00nanardstaff/* $Id: asyncsendto.c,v 1.12 2020/11/11 12:13:26 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2020 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 #include #include #include #include "asyncsendto.h" #include "upnputils.h" enum send_state {ESCHEDULED=1, EWAITREADY=2, ESENDNOW=3} state; /* state diagram for a packet : * * | * V * -> ESCHEDULED -> ESENDNOW -> sent * ^ | * | V * EWAITREADY -> sent */ struct scheduled_send { LIST_ENTRY(scheduled_send) entries; struct timeval ts; enum send_state state; int sockfd; const void * buf; size_t len; int flags; const struct sockaddr *dest_addr; socklen_t addrlen; const struct sockaddr_in6 *src_addr; 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); */ static ssize_t send_from_to(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr_in6 *src_addr, const struct sockaddr *dest_addr, socklen_t addrlen) { #ifdef IPV6_PKTINFO if(src_addr) { struct iovec iov; struct in6_pktinfo ipi6; uint8_t c[CMSG_SPACE(sizeof(ipi6))]; struct msghdr msg; struct cmsghdr* cmsg; iov.iov_base = (void *)buf; iov.iov_len = len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; ipi6.ipi6_addr = src_addr->sin6_addr; ipi6.ipi6_ifindex = src_addr->sin6_scope_id; msg.msg_control = c; msg.msg_controllen = sizeof(c); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(ipi6)); memcpy(CMSG_DATA(cmsg), &ipi6, sizeof(ipi6)); msg.msg_name = (void *)dest_addr; msg.msg_namelen = addrlen; return sendmsg(sockfd, &msg, flags); } else { #endif /* IPV6_PKTINFO */ return sendto(sockfd, buf, len, flags, dest_addr, addrlen); #ifdef IPV6_PKTINFO } #endif /* IPV6_PKTINFO */ } /* delay = milli seconds */ ssize_t sendto_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr, unsigned int delay) { enum send_state state; ssize_t n; size_t alloc_len; struct timeval tv; struct scheduled_send * elt; if(delay == 0) { /* first try to send at once */ n = send_from_to(sockfd, buf, len, flags, src_addr, 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(upnp_gettimeofday(&tv) < 0) { return -1; } /* allocate enough space for structure + buffers */ alloc_len = sizeof(struct scheduled_send) + len + addrlen; if(src_addr) alloc_len += sizeof(struct sockaddr_in6); elt = malloc(alloc_len); if(elt == NULL) { syslog(LOG_ERR, "malloc failed to allocate %u bytes", (unsigned)alloc_len); 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; if(src_addr) { elt->src_addr = (struct sockaddr_in6 *)(elt->data + addrlen); memcpy((void *)elt->src_addr, src_addr, sizeof(struct sockaddr_in6)); elt->buf = (void *)(elt->data + addrlen + sizeof(struct sockaddr_in6)); } else { elt->src_addr = NULL; elt->buf = (void *)(elt->data + addrlen); } elt->len = len; memcpy((void *)elt->buf, buf, 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_schedule2(sockfd, buf, len, flags, dest_addr, addrlen, NULL, 0); } ssize_t sendto_or_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr) { return sendto_schedule2(sockfd, buf, len, flags, dest_addr, addrlen, src_addr, 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) { int ret = 0; 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))) { #ifdef DEBUG syslog(LOG_DEBUG, "%s: %d bytes on socket %d", "try_sendto", (int)elt->len, elt->sockfd); #endif n = send_from_to(elt->sockfd, elt->buf, elt->len, elt->flags, elt->src_addr, elt->dest_addr, elt->addrlen); /*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; } else { char addr_str[64]; /* uncatched error */ if(sockaddr_to_string(elt->dest_addr, addr_str, sizeof(addr_str)) <= 0) addr_str[0] = '\0'; syslog(LOG_ERR, "%s(sock=%d, len=%u, dest=%s): sendto: %m", "try_sendto", elt->sockfd, (unsigned)elt->len, addr_str); ret--; } } else if((int)n != (int)elt->len) { syslog(LOG_WARNING, "%s: %d bytes sent out of %d", "try_sendto", (int)n, (int)elt->len); } /* remove from the list */ LIST_REMOVE(elt, entries); free(elt); } } return ret; } /* maximum execution time for finalize_sendto() in milliseconds */ #define FINALIZE_SENDTO_DELAY (500) /* empty the list */ void finalize_sendto(void) { ssize_t n; struct scheduled_send * elt; struct scheduled_send * next; fd_set writefds; struct timeval deadline; struct timeval now; struct timeval timeout; int max_fd; if(upnp_gettimeofday(&deadline) < 0) { syslog(LOG_ERR, "gettimeofday: %m"); return; } deadline.tv_usec += FINALIZE_SENDTO_DELAY*1000; if(deadline.tv_usec > 1000000) { deadline.tv_sec++; deadline.tv_usec -= 1000000; } while(send_list.lh_first) { FD_ZERO(&writefds); max_fd = -1; 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 = send_from_to(elt->sockfd, elt->buf, elt->len, elt->flags, elt->src_addr, elt->dest_addr, elt->addrlen); /*n = sendto(elt->sockfd, elt->buf, elt->len, elt->flags, elt->dest_addr, elt->addrlen);*/ if(n < 0) { if(errno==EAGAIN || errno==EWOULDBLOCK) { FD_SET(elt->sockfd, &writefds); if(elt->sockfd > max_fd) max_fd = elt->sockfd; continue; } syslog(LOG_WARNING, "finalize_sendto(): socket=%d sendto: %m", elt->sockfd); } /* remove from the list */ LIST_REMOVE(elt, entries); free(elt); } /* check deadline */ if(upnp_gettimeofday(&now) < 0) { syslog(LOG_ERR, "gettimeofday: %m"); return; } if(now.tv_sec > deadline.tv_sec || (now.tv_sec == deadline.tv_sec && now.tv_usec > deadline.tv_usec)) { /* deadline ! */ while((elt = send_list.lh_first) != NULL) { LIST_REMOVE(elt, entries); free(elt); } return; } /* compute timeout value */ timeout.tv_sec = deadline.tv_sec - now.tv_sec; timeout.tv_usec = deadline.tv_usec - now.tv_usec; if(timeout.tv_usec < 0) { timeout.tv_sec--; timeout.tv_usec += 1000000; } if(max_fd >= 0) { if(select(max_fd + 1, NULL, &writefds, NULL, &timeout) < 0) { syslog(LOG_ERR, "select: %m"); return; } } } } miniupnpd-2.3.7/asyncsendto.h010064400017500000024000000035311317665377200154360ustar00nanardstaff/* $Id: asyncsendto.h,v 1.3 2017/11/02 15:48:29 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2017 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 /* for fd_set */ #include /* sendto_schedule() : see sendto(2) * schedule sendto() call after delay (milliseconds) */ ssize_t sendto_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr, unsigned int delay); #define sendto_schedule(sockfd, buf, len, flags, dest_addr, addrlen, delay) \ sendto_schedule2(sockfd, buf, len, flags, dest_addr, addrlen, NULL, 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); /* same as sendto_schedule() except it will try to set source address * (for IPV6 only) */ ssize_t sendto_or_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr); /* 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 miniupnpd-2.3.7/testasyncsendto.c010064400017500000024000000070651354240146300163200ustar00nanardstaff/* $Id: testasyncsendto.c,v 1.6 2019/09/24 11:48:47 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2019 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; int runtime_flags = 0; time_t startup_time = 0; #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, (struct sockaddr *)&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=%lld.%06ld", i, (long long)next_send.tv_sec, (long)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) { /* don't 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, %lld.%06ld)", max_fd, (long long)timeout.tv_sec, (long)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; } miniupnpd-2.3.7/portinuse.c010064400017500000024000000256121375231574700151300ustar00nanardstaff/* $Id: portinuse.c,v 1.12 2020/11/04 21:29:50 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * (c) 2007-2020 Thomas Bernard * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #if defined(__DragonFly__) || defined(__FreeBSD__) #include #include #endif #include #include #include #include #include #include #include #include #include #include #if defined(__OpenBSD__) #include #include #include #include #include #include #include #include #include #include #endif #if defined(__DragonFly__) || defined(__FreeBSD__) #include #include /* sys/socketvar.h must be included above the following headers */ #include #include #endif #include "macros.h" #include "config.h" #include "upnpglobalvars.h" #include "getifaddr.h" #include "portinuse.h" #if defined(USE_NETFILTER) #include "netfilter/iptcrdr.h" #endif #ifdef CHECK_PORTINUSE #if defined(USE_NETFILTER) /* Hardcoded for now. Ideally would come from .conf file */ # ifdef TOMATO const char *chains_to_check[] = { "WANPREROUTING" , 0 }; # else const char *chains_to_check[] = { "PREROUTING" , 0 }; # endif #endif int port_in_use(const char *if_name, unsigned eport, int proto, const char *iaddr, unsigned iport) { int found = 0; char ip_addr_str[INET_ADDRSTRLEN]; struct in_addr ip_addr; #ifdef __linux__ /* linux code */ char line[256]; FILE *f; const char * tcpfile = "/proc/net/tcp"; const char * udpfile = "/proc/net/udp"; #endif if(getifaddr(if_name, ip_addr_str, INET_ADDRSTRLEN, &ip_addr, NULL) < 0) { ip_addr.s_addr = 0; ip_addr_str[0] = '\0'; } syslog(LOG_DEBUG, "Check protocol %s for port %u on ext_if %s %s, %08X", (proto==IPPROTO_TCP)?"tcp":"udp", eport, if_name, ip_addr_str, (unsigned)ip_addr.s_addr); /* Phase 1 : check for local sockets (would be listed by netstat) */ #if defined(__linux__) f = fopen((proto==IPPROTO_TCP)?tcpfile:udpfile, "r"); if (!f) { syslog(LOG_ERR, "cannot open %s", (proto==IPPROTO_TCP)?tcpfile:udpfile); return -1; } while (fgets(line, 255, f)) { char eaddr[68]; unsigned tmp_port; if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x " "%*x:%*x %*x %*d %*d %*llu", eaddr, &tmp_port) == 2 ) { /* TODO add IPV6 support if enabled * Presently assumes IPV4 */ #ifdef DEBUG syslog(LOG_DEBUG, "port_in_use check port %u and address %s", tmp_port, eaddr); #endif if (tmp_port == eport) { char tmp_addr[4]; struct in_addr *tmp_ip_addr = (struct in_addr *)tmp_addr; if (sscanf(eaddr,"%2hhx%2hhx%2hhx%2hhx", &tmp_addr[3],&tmp_addr[2],&tmp_addr[1],&tmp_addr[0]) == 4) { if (tmp_ip_addr->s_addr == 0 || tmp_ip_addr->s_addr == ip_addr.s_addr) { found++; break; /* don't care how many, just that we found at least one */ } } } } } fclose(f); #elif defined(__OpenBSD__) static struct nlist list[] = { #if 0 {"_tcpstat", 0, 0, 0, 0}, {"_udpstat", 0, 0, 0, 0}, {"_tcbinfo", 0, 0, 0, 0}, {"_udbinfo", 0, 0, 0, 0}, #endif {"_tcbtable", 0, 0, 0, 0}, {"_udbtable", 0, 0, 0, 0}, {NULL,0, 0, 0, 0} }; char errstr[_POSIX2_LINE_MAX]; kvm_t *kd; ssize_t n; struct inpcbtable table; struct inpcb *next; struct inpcb inpcb; kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errstr); if(!kd) { syslog(LOG_ERR, "%s: kvm_openfiles(): %s", "portinuse()", errstr); return -1; } if(kvm_nlist(kd, list) < 0) { syslog(LOG_ERR, "%s: kvm_nlist(): %s", "portinuse()", kvm_geterr(kd)); kvm_close(kd); return -1; } n = kvm_read(kd, list[(proto==IPPROTO_TCP)?0:1].n_value, &table, sizeof(table)); if(n < 0) { syslog(LOG_ERR, "%s: kvm_read(): %s", "portinuse()", kvm_geterr(kd)); kvm_close(kd); return -1; } /* inpt_queue was CIRCLEQ_HEAD, it is TAILQ_HEAD since OpenBSD 5.5 */ #ifdef INPT_QUEUE_IS_CIRCLEQ next = CIRCLEQ_FIRST(&table.inpt_queue); #else next = TAILQ_FIRST(&table.inpt_queue); #endif while(next != NULL) { if(((u_long)next & 3) != 0) break; n = kvm_read(kd, (u_long)next, &inpcb, sizeof(inpcb)); if(n < 0) { syslog(LOG_ERR, "kvm_read(): %s", kvm_geterr(kd)); break; } #ifdef INPT_QUEUE_IS_CIRCLEQ next = CIRCLEQ_NEXT(&inpcb, inp_queue); #else next = TAILQ_NEXT(&inpcb, inp_queue); #endif /* skip IPv6 sockets */ if((inpcb.inp_flags & INP_IPV6) != 0) continue; #ifdef DEBUG syslog(LOG_DEBUG, "%08lx:%hu %08lx:%hu", (u_long)inpcb.inp_laddr.s_addr, ntohs(inpcb.inp_lport), (u_long)inpcb.inp_faddr.s_addr, ntohs(inpcb.inp_fport)); #endif if(eport == (unsigned)ntohs(inpcb.inp_lport)) { if(inpcb.inp_laddr.s_addr == INADDR_ANY || inpcb.inp_laddr.s_addr == ip_addr.s_addr) { found++; break; /* don't care how many, just that we found at least one */ } } } kvm_close(kd); #elif defined(__DragonFly__) const char *varname; struct xinpcb *xip; struct xtcpcb *xtp; struct inpcb *inp; void *buf = NULL; void *so_begin, *so_end; size_t len; switch (proto) { case IPPROTO_TCP: varname = "net.inet.tcp.pcblist"; break; case IPPROTO_UDP: varname = "net.inet.udp.pcblist"; break; default: syslog(LOG_ERR, "port_in_use() unknown proto=%d", proto); return -1; } if (sysctlbyname(varname, NULL, &len, NULL, 0) < 0) { syslog(LOG_ERR, "sysctlbyname(%s, NULL, ...): %m", varname); return -1; } buf = malloc(len); if (buf == NULL) { syslog(LOG_ERR, "malloc(%u) failed", (unsigned)len); return -1; } if (sysctlbyname(varname, buf, &len, NULL, 0) < 0) { syslog(LOG_ERR, "sysctlbyname(%s, buf, ...): %m", varname); free(buf); return -1; } so_begin = buf; so_end = (uint8_t *)buf + len; for (so_begin = buf, so_end = (uint8_t *)so_begin + len; (uint8_t *)so_begin + sizeof(size_t) < (uint8_t *)so_end && (uint8_t *)so_begin + *(size_t *)so_begin <= (uint8_t *)so_end; so_begin = (uint8_t *)so_begin + *(size_t *)so_begin) { switch (proto) { case IPPROTO_TCP: xtp = (struct xtcpcb *)so_begin; if (xtp->xt_len != sizeof *xtp) { syslog(LOG_WARNING, "struct xtcpcb size mismatch; %ld vs %ld", (long)xtp->xt_len, sizeof *xtp); free(buf); return -1; } inp = &xtp->xt_inp; break; case IPPROTO_UDP: xip = (struct xinpcb *)so_begin; if (xip->xi_len != sizeof *xip) { syslog(LOG_WARNING, "struct xinpcb size mismatch : %ld vs %ld", (long)xip->xi_len, sizeof *xip); free(buf); return -1; } inp = &xip->xi_inp; break; default: abort(); } /* no support for IPv6 */ if (INP_ISIPV6(inp) != 0) continue; syslog(LOG_DEBUG, "%08lx:%hu %08lx:%hu <=> %u %08lx:%u", (u_long)inp->inp_laddr.s_addr, ntohs(inp->inp_lport), (u_long)inp->inp_faddr.s_addr, ntohs(inp->inp_fport), eport, (u_long)ip_addr.s_addr, iport ); if (eport == (unsigned)ntohs(inp->inp_lport)) { if (inp->inp_laddr.s_addr == INADDR_ANY || inp->inp_laddr.s_addr == ip_addr.s_addr) { found++; break; /* don't care how many, just that we found at least one */ } } } if (buf) { free(buf); buf = NULL; } #elif defined(__FreeBSD__) const char *varname; struct xinpgen *xig, *exig; struct xinpcb *xip; struct xtcpcb *xtp; struct in_conninfo *inc; void *buf = NULL; size_t len; switch (proto) { case IPPROTO_TCP: varname = "net.inet.tcp.pcblist"; break; case IPPROTO_UDP: varname = "net.inet.udp.pcblist"; break; default: syslog(LOG_ERR, "port_in_use() unknown proto=%d", proto); return -1; } if (sysctlbyname(varname, NULL, &len, NULL, 0) < 0) { syslog(LOG_ERR, "sysctlbyname(%s, NULL, ...): %m", varname); return -1; } buf = malloc(len); if (buf == NULL) { syslog(LOG_ERR, "malloc(%u) failed", (unsigned)len); return -1; } if (sysctlbyname(varname, buf, &len, NULL, 0) < 0) { syslog(LOG_ERR, "sysctlbyname(%s, buf, ...): %m", varname); free(buf); return -1; } xig = (struct xinpgen *)buf; exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig); if (xig->xig_len != sizeof *xig) { syslog(LOG_WARNING, "struct xinpgen size mismatch; %ld vs %ld", (long)xig->xig_len, sizeof *xig); free(buf); return -1; } if (exig->xig_len != sizeof *exig) { syslog(LOG_WARNING, "struct xinpgen size mismatch; %ld vs %ld", (long)exig->xig_len, sizeof *exig); free(buf); return -1; } while (1) { xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); if (xig >= exig) break; switch (proto) { case IPPROTO_TCP: xtp = (struct xtcpcb *)xig; if (xtp->xt_len != sizeof *xtp) { syslog(LOG_WARNING, "struct xtcpcb size mismatch; %ld vs %ld", (long)xtp->xt_len, sizeof *xtp); free(buf); return -1; } xip = &xtp->xt_inp; inc = &xip->inp_inc; break; case IPPROTO_UDP: xip = (struct xinpcb *)xig; if (xip->xi_len != sizeof *xip) { syslog(LOG_WARNING, "struct xinpcb size mismatch : %ld vs %ld", (long)xip->xi_len, sizeof *xip); free(buf); return -1; } inc = &xip->inp_inc; break; default: abort(); } /* no support for IPv6 */ if ((xip->inp_vflag & INP_IPV6) != 0) continue; syslog(LOG_DEBUG, "%08lx:%hu %08lx:%hu <=> %u %08lx:%u", (u_long)inc->inc_laddr.s_addr, ntohs(inc->inc_lport), (u_long)inc->inc_faddr.s_addr, ntohs(inc->inc_fport), eport, (u_long)ip_addr.s_addr, iport ); if (eport == (unsigned)ntohs(inc->inc_lport)) { if (inc->inc_laddr.s_addr == INADDR_ANY || inc->inc_laddr.s_addr == ip_addr.s_addr) { found++; break; /* don't care how many, just that we found at least one */ } } } if (buf) { free(buf); buf = NULL; } /* #elif __NetBSD__ */ #else /* TODO : NetBSD / Darwin (OS X) / Solaris code */ #error "No port_in_use() implementation available for this OS" #endif /* Phase 2 : check existing mappings * TODO : implement for pf/ipfw/etc. */ #if defined(USE_NETFILTER) if (!found) { char iaddr_old[16]; unsigned short iport_old; int i; for (i = 0; chains_to_check[i]; i++) { if (get_nat_redirect_rule(chains_to_check[i], if_name, eport, proto, iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0, 0, 0, 0, 0, 0) == 0) { syslog(LOG_DEBUG, "port_in_use check port %d on nat chain %s redirected to %s port %d", eport, chains_to_check[i], iaddr_old, iport_old); if (!(strcmp(iaddr, iaddr_old)==0 && iport==iport_old)) { /* only "in use" if redirected to somewhere else */ found++; break; /* don't care how many, just that we found at least one */ } } } } #else /* USE_NETFILTER */ UNUSED(iport); UNUSED(iaddr); #endif /* USE_NETFILTER */ return found; } #endif /* CHECK_PORTINUSE */ miniupnpd-2.3.7/portinuse.h010064400017500000024000000012471231526242000151120ustar00nanardstaff/* $Id: portinuse.h,v 1.2 2014/03/28 12:03:28 nanard Exp $ */ /* 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 __PORTINUSE_H__ #define __PORTINUSE_H__ #ifdef CHECK_PORTINUSE /* portinuse() * determine wether a port is already in use * on a given interface. * returns: 0 not in use, > 0 in use * -1 in case of error */ int port_in_use(const char *if_name, unsigned port, int proto, const char *iaddr, unsigned iport); #endif /* CHECK_PORTINUSE */ #endif miniupnpd-2.3.7/testportinuse.c010064400017500000024000000024071373445570200160210ustar00nanardstaff/* $Id: testportinuse.c,v 1.5 2020/09/28 21:49:18 nanard Exp $ */ /* MiniUPnP project * (c) 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 */ #include #include #include #include #include #include "macros.h" #include "config.h" #include "portinuse.h" int main(int argc, char * * argv) { #ifndef CHECK_PORTINUSE UNUSED(argc); UNUSED(argv); printf("CHECK_PORTINUSE is not defined.\n"); #else /* CHECK_PORTINUSE */ int r; const char * if_name; unsigned eport; int proto; const char * iaddr; unsigned iport; if(argc <= 5) { fprintf(stderr, "usage: %s if_name eport (tcp|udp) iaddr iport\n", argv[0]); return 1; } openlog("testportinuse", LOG_CONS|LOG_PERROR, LOG_USER); if_name = argv[1]; eport = (unsigned)atoi(argv[2]); proto = (0==strcmp(argv[3], "tcp"))?IPPROTO_TCP:IPPROTO_UDP; iaddr = argv[4]; iport = (unsigned)atoi(argv[5]); r = port_in_use(if_name, eport, proto, iaddr, iport); printf("port_in_use(%s, %u, %d, %s, %u) returned %d\n", if_name, eport, proto, iaddr, iport, r); closelog(); #endif /* CHECK_PORTINUSE */ return 0; } miniupnpd-2.3.7/Makefile.linux_nft010064400017500000024000000126261463564776500164130ustar00nanardstaff# MiniUPnP project # (c) 2018-2022 Thomas Bernard # (c) 2015 Tomofumi Hayashi # http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ # Author : Tomofumi Hayashi # for use with GNU Make # # options can be passed to configure through CONFIG_OPTIONS : # $ CONFIG_OPTIONS="--ipv6 --igd2" make # # To install use : # $ DESTDIR=/dummyinstalldir make install # or : # $ INSTALLPREFIX=/usr/local make install # or : # $ make install # (default INSTALLPREFIX is /usr) # # CONFIG_OPTIONS ?= $(cat .configure.cache) CONFIG_OPTIONS += --firewall=nftables #CFLAGS = -O -g #-DDEBUG CFLAGS ?= -Os CFLAGS += -fno-strict-aliasing CFLAGS += -fno-common CFLAGS += -fstack-protector -fPIE CFLAGS += -D_FORTIFY_SOURCE=2 CPPFLAGS += -D_GNU_SOURCE CFLAGS += -Wall CFLAGS += -Wextra -Wstrict-prototypes -Wdeclaration-after-statement #CFLAGS += -Wno-missing-field-initializers LDFLAGS += -Wl,-z,now -Wl,-z,relro -pie CC ?= gcc RM = rm -f INSTALL = install STRIP ?= strip PKG_CONFIG ?= pkg-config CP = cp DOXYGEN ?= doxygen DEPFLAGS = -MM -MG -MT $(patsubst %.d,%.o,$@) -MT $@ # -M : with system headers, -MM : without INSTALLPREFIX ?= $(PREFIX)/usr SBININSTALLDIR ?= $(INSTALLPREFIX)/sbin ETCINSTALLDIR = $(PREFIX)/etc/miniupnpd MANINSTALLDIR = $(INSTALLPREFIX)/share/man/man8 include config.mk include $(SRCDIR)/gitrev.mk include $(SRCDIR)/objects.mk # sources in the netfilter_nft/ directory NETFILTEROBJS = nftnlrdr.o nftpinhole.o nfct_get.o nftnlrdr_misc.o ALLOBJS = $(BASEOBJS) $(LNXOBJS) $(NETFILTEROBJS) DEP = $(ALLOBJS:.o=.d) NFT_SCRIPTS = $(addprefix $(SRCDIR)/netfilter_nft/scripts/, \ nft_init.sh nft_removeall.sh nft_flush.sh nft_delete_chain.sh) PCFILE_FOUND := $(shell $(PKG_CONFIG) --exists libnftnl; echo $$?) ifeq (${PCFILE_FOUND},0) PKG_CONFIG_LIBS = libnftnl libmnl CFLAGS += $(shell $(PKG_CONFIG) --cflags $(PKG_CONFIG_LIBS)) LDLIBS += $(shell $(PKG_CONFIG) --static --libs-only-l $(PKG_CONFIG_LIBS)) LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L $(PKG_CONFIG_LIBS)) LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-other $(PKG_CONFIG_LIBS)) else endif # ifdef PCFILE_FOUND #LDLIBS += -lnfnetlink LDLIBS += $(shell $(PKG_CONFIG) --static --libs-only-l libssl) TEST := $(shell $(PKG_CONFIG) --exists uuid && echo 1) ifeq ($(TEST),1) LDLIBS += $(shell $(PKG_CONFIG) --static --libs-only-l uuid) else $(info please install uuid-dev package / libuuid) endif # ($(TEST),1) # genuuid uses the uuidgen CLI tool which is part of libuuid # from the e2fsprogs # 'cat /proc/sys/kernel/random/uuid' could be also used # ifeq ($(TARGET_OPENWRT),) UUIDEXECDIR='' else UUIDEXECDIR="$(STAGING_DIR_HOST)/bin/" endif UUID=$(shell ($(UUIDEXECDIR)genuuid || $(UUIDEXECDIR)uuidgen || $(UUIDEXECDIR)uuid) 2> /dev/null ) TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o EXECUTABLES = miniupnpd miniupnpdctl \ testupnpdescgen testgetifstats \ testupnppermissions testgetifaddr \ testgetroute testasyncsendto testportinuse .PHONY: all clean install depend dox all: $(EXECUTABLES) clean: $(RM) config.h $(RM) $(ALLOBJS) $(RM) $(EXECUTABLES) $(RM) testupnpdescgen.o testgetifstats.o $(RM) testupnppermissions.o testgetifaddr.o $(RM) testgetroute.o testasyncsendto.o $(RM) testportinuse.o $(RM) miniupnpdctl.o $(RM) -r dox/ install: miniupnpd $(SRCDIR)/miniupnpd.8 $(SRCDIR)/miniupnpd.conf \ $(NFT_SCRIPTS) \ $(SRCDIR)/linux/miniupnpd.init.d.script $(STRIP) miniupnpd $(INSTALL) -d $(DESTDIR)$(SBININSTALLDIR) $(INSTALL) miniupnpd $(DESTDIR)$(SBININSTALLDIR) $(INSTALL) -d $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter_nft/scripts/nft_init.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter_nft/scripts/nft_removeall.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter_nft/scripts/nft_flush.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter_nft/scripts/nft_delete_chain.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) $(SRCDIR)/netfilter_nft/scripts/miniupnpd_functions.sh $(DESTDIR)$(ETCINSTALLDIR) $(INSTALL) -m 0644 -b $(SRCDIR)/miniupnpd.conf $(DESTDIR)$(ETCINSTALLDIR) sed -i'' -e "s/^uuid=[-0-9a-f]*/uuid=$(UUID)/" $(DESTDIR)$(ETCINSTALLDIR)/miniupnpd.conf $(INSTALL) -d $(DESTDIR)$(PREFIX)/etc/init.d $(INSTALL) $(SRCDIR)/linux/miniupnpd.init.d.script $(DESTDIR)$(PREFIX)/etc/init.d/miniupnpd $(INSTALL) -d $(DESTDIR)$(MANINSTALLDIR) $(INSTALL) -m 0644 $(SRCDIR)/miniupnpd.8 $(DESTDIR)$(MANINSTALLDIR) gzip -f $(DESTDIR)$(MANINSTALLDIR)/miniupnpd.8 include $(SRCDIR)/check.mk miniupnpd: $(BASEOBJS) $(LNXOBJS) $(NETFILTEROBJS) testupnpdescgen: $(TESTUPNPDESCGENOBJS) testgetifstats: testgetifstats.o getifstats.o testupnppermissions: testupnppermissions.o upnppermissions.o testgetifaddr: testgetifaddr.o getifaddr.o testgetroute: testgetroute.o getroute.o upnputils.o testasyncsendto: testasyncsendto.o asyncsendto.o upnputils.o \ getroute.o testportinuse: testportinuse.o portinuse.o getifaddr.o \ nftnlrdr.o nftnlrdr_misc.o miniupnpdctl: miniupnpdctl.o config.mk config.h: $(SRCDIR)/configure $(SRCDIR)/VERSION $(SHELL) $< $(CONFIG_OPTIONS) depend: $(DEP) %.d: $(SRCDIR)/%.c $(CC) $(CPPFLAGS) $(DEPFLAGS) -o $@ $< dox: $(SRCDIR)/miniupnpd.doxyconf (cat $< ; echo "INPUT=$(SRCDIR)" ) | $(DOXYGEN) - %.o: $(SRCDIR)/%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ %.o: $(SRCDIR)/linux/%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ %.o: $(SRCDIR)/netfilter_nft/%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ print-%: @echo "$* = $($*)" ifneq ($(MAKECMDGOALS),clean) -include $(DEP) endif miniupnpd-2.3.7/netfilter_nft/Makefile010064400017500000024000000020741457642072000172250ustar00nanardstaffCFLAGS?=-Wall -g -Wstrict-prototypes -Wdeclaration-after-statement CPPFLAGS += -I. CPPFLAGS += -D_GNU_SOURCE -DDEBUG CPPFLAGS += -DUSE_NETFILTER -DUSE_NFTABLES CPPFLAGS += -DENABLE_UPNPPINHOLE CPPFLAGS += -DUSE_NFCT CC = gcc LIBS = -lnftnl -lmnl ARCH := $(shell uname -m | grep -q "x86_64" && echo 64) all: test_nfct_get testnftnlrdr testnftpinhole clean: $(RM) *.o config.h testnftnlcrdr testnftpinhole testnftnlrdr_peer \ test_nfct_get testnftnlrdr config.h: touch $@ testnftnlrdr: nftnlrdr.o nftnlrdr_misc.o testnftnlrdr.o $(LIBS) testnftpinhole: nftpinhole.o nftnlrdr.o nftnlrdr_misc.o testnftpinhole.o \ ../upnputils.o ../linux/getroute.o $(LIBS) test_nfct_get: test_nfct_get.o test_nfct_get.o -lmnl -lnetfilter_conntrack test_nfct_get.o: test_nfct_get.c testnftnlrdr_peer.o: testnftnlrdr_peer.c testnftnlrdr_dscp.o: testnftnlrdr_dscp.c nftnlrdr.o: nftnlrdr.c nftnlrdr.h config.h nftnlrdr_misc.o: nftnlrdr_misc.c nftpinhole.o: nftpinhole.c nftpinhole.h config.h testnftnlrdr.o: config.h nftnlrdr.h nftnlrdr_misc.h testnftpinhole.o: config.h nftpinhole.h miniupnpd-2.3.7/netfilter_nft/nfct_get.c010064400017500000024000000144121252037010400175040ustar00nanardstaff#include #include #include #include #include #include #ifdef USE_NFCT #include #include #include struct data_cb_s { struct sockaddr_storage * ext; uint8_t found; }; static int data_cb(const struct nlmsghdr *nlh, void *data) { struct nf_conntrack *ct; struct data_cb_s * d = (struct data_cb_s*) data; struct sockaddr_in* ext4 = (struct sockaddr_in*) d->ext; ct = nfct_new(); if (ct == NULL) return MNL_CB_OK; nfct_nlmsg_parse(nlh, ct); if (data) { ext4->sin_addr.s_addr = nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST); ext4->sin_port = nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST); } d->found = 1; nfct_destroy(ct); return MNL_CB_OK; } int get_nat_ext_addr(struct sockaddr* src, struct sockaddr *dst, uint8_t proto, struct sockaddr_storage* ret_ext) { struct mnl_socket *nl; struct nlmsghdr *nlh; struct nfgenmsg *nfh; char buf[MNL_SOCKET_BUFFER_SIZE]; unsigned int seq, portid; struct nf_conntrack *ct; int ret; struct data_cb_s data; if ((!src)&&(!dst)) { return 0; } if (src->sa_family != dst->sa_family) { return 0; } nl = mnl_socket_open(NETLINK_NETFILTER); if (nl == NULL) { // perror("mnl_socket_open"); goto free_nl; } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { // perror("mnl_socket_bind"); goto free_nl; } portid = mnl_socket_get_portid(nl); memset(buf, 0, sizeof(buf)); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET; nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK; nlh->nlmsg_seq = seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = src->sa_family; nfh->version = NFNETLINK_V0; nfh->res_id = 0; ct = nfct_new(); if (ct == NULL) { goto free_nl; } nfct_set_attr_u8(ct, ATTR_L3PROTO, src->sa_family); if (src->sa_family == AF_INET) { struct sockaddr_in *src4 = (struct sockaddr_in *)src; struct sockaddr_in *dst4 = (struct sockaddr_in *)dst; nfct_set_attr_u32(ct, ATTR_IPV4_SRC, src4->sin_addr.s_addr); nfct_set_attr_u32(ct, ATTR_IPV4_DST, dst4->sin_addr.s_addr); nfct_set_attr_u16(ct, ATTR_PORT_SRC, src4->sin_port); nfct_set_attr_u16(ct, ATTR_PORT_DST, dst4->sin_port); } else if (src->sa_family == AF_INET6) { struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)src; struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst; nfct_set_attr(ct, ATTR_IPV6_SRC, &src6->sin6_addr); nfct_set_attr(ct, ATTR_IPV6_DST, &dst6->sin6_addr); nfct_set_attr_u16(ct, ATTR_PORT_SRC, src6->sin6_port); nfct_set_attr_u16(ct, ATTR_PORT_DST, dst6->sin6_port); } nfct_set_attr_u8(ct, ATTR_L4PROTO, proto); nfct_nlmsg_build(nlh, ct); ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len); if (ret == -1) { goto free_ct; } ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); data.ext = ret_ext; data.found = 0; while (ret > 0) { ret = mnl_cb_run(buf, ret, seq, portid, data_cb, &data); if (ret <= MNL_CB_STOP) break; ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); } free_ct: nfct_destroy(ct); free_nl: mnl_socket_close(nl); return data.found; } #else #define DST "dst=" #define DST_PORT "dport=" #define SRC "src=" #define SRC_PORT "sport=" #define IP_CONNTRACK_LOCATION "/proc/net/ip_conntrack" #define NF_CONNTRACK_LOCATION "/proc/net/nf_conntrack" int get_nat_ext_addr(struct sockaddr* src, struct sockaddr *dst, uint8_t proto, struct sockaddr_storage* ret_ext) { FILE *f; int af; if (!src) return -2; af = src->sa_family; if ((f = fopen(NF_CONNTRACK_LOCATION, "r")) == NULL) { if ((f = fopen(IP_CONNTRACK_LOCATION, "r")) == NULL) { printf("could not read info about connections from the kernel, " "make sure netfilter is enabled in kernel or by modules.\n"); return -1; } } while (!feof(f)) { char line[256], *str; memset(line, 0, sizeof(line)); str = fgets(line, sizeof(line), f); if (line[0] != 0) { char *token, *saveptr; int j; uint8_t src_f, src_port_f, dst_f, dst_port_f; src_f=src_port_f=dst_f=dst_port_f=0; for (j = 1; ; j++, str = NULL) { token = strtok_r(str, " ", &saveptr); if (token == NULL) break; if ((j==2)&&(af!=atoi(token))) break; if ((j==4)&&(proto!=atoi(token))) break; if (j<=4) continue; if (strncmp(token, SRC, sizeof(SRC) - 1) == 0) { char *srcip = token + sizeof(SRC) - 1; uint32_t buf[4]; memset(buf,0,sizeof(buf)); if (inet_pton(af, srcip, buf)!=1) break; if (af==AF_INET) { struct sockaddr_in *src4=(struct sockaddr_in*)src; if (!src_f) { if (src4->sin_addr.s_addr != buf[0]) break; src_f = 1; } } } if (strncmp(token, SRC_PORT, sizeof(SRC_PORT) - 1) == 0) { char *src_port = token + sizeof(SRC_PORT) - 1; uint16_t port=atoi(src_port); if (af==AF_INET) { struct sockaddr_in *src4=(struct sockaddr_in*)src; if (!src_port_f) { if (ntohs(src4->sin_port) != port) break; src_port_f = 1; } } } if (strncmp(token, DST, sizeof(DST) - 1) == 0) { char *dstip = token + sizeof(DST) - 1; uint32_t buf[4]; memset(buf,0,sizeof(buf)); if (inet_pton(af, dstip, buf)!=1) break; if (af==AF_INET) { struct sockaddr_in *dst4=(struct sockaddr_in*)dst; if (!dst_f) { if (dst4->sin_addr.s_addr != buf[0]) break; dst_f = 1; } else { struct sockaddr_in*ret4=(struct sockaddr_in*)ret_ext; ret_ext->ss_family = AF_INET; ret4->sin_addr.s_addr = buf[0]; } } } if (strncmp(token, DST_PORT, sizeof(DST_PORT)-1) == 0) { char *dst_port = token + sizeof(DST_PORT) - 1; uint16_t port=atoi(dst_port); if (af==AF_INET) { struct sockaddr_in *dst4=(struct sockaddr_in*)dst; if (!dst_port_f) { if (ntohs(dst4->sin_port) != port) break; dst_port_f = 1; } else { struct sockaddr_in*ret4=(struct sockaddr_in*)ret_ext; ret_ext->ss_family = AF_INET; ret4->sin_port = htons(port); } } } } if (src_f && src_port_f && dst_f && dst_port_f) { fclose(f); return 1; } } } fclose(f); return 0; } #endif miniupnpd-2.3.7/netfilter_nft/nftnlrdr.c010064400017500000024000000411571457642072000175670ustar00nanardstaff/* $Id: nftnlrdr.c,v 1.16 2024/03/19 23:35:54 nanard Exp $ * vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2015 Tomofumi Hayashi * (c) 2019 Sven Auhagen * (c) 2019 Paul Chambers * (c) 2020-2024 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tiny_nf_nat.h" #include "config.h" #include "../macros.h" #include "../commonrdr.h" #include "nftnlrdr.h" #include "nftnlrdr_misc.h" #ifdef DEBUG #define d_printf(x) do { printf x; } while (0) #else #define d_printf(x) #endif /* list to keep timestamps for port mappings having a lease duration */ struct timestamp_entry { struct timestamp_entry * next; unsigned int timestamp; unsigned short eport; short protocol; }; static struct timestamp_entry * timestamp_list = NULL; #define NAT_CHAIN_TYPE "nat" #define FILTER_CHAIN_TYPE "filter" /* init and shutdown functions */ int init_redirect(void) { int result; /* requires elevated privileges */ result = nft_mnl_connect(); return result; } void shutdown_redirect(void) { nft_mnl_disconnect(); } /** * used by the core to override default chain names if specified in config file * @param param which string to set * @param string the new name to use. Do not dispose after setting (i.e. use strdup if not static). * @return 0 if successful */ int set_rdr_name(rdr_name_type param, const char *string) { if (string == NULL || strlen(string) > 30 || string[0] == '\0') { syslog(LOG_ERR, "%s(): invalid string argument '%s'", "set_rdr_name", string); return -1; } switch (param) { case RDR_TABLE_NAME: nft_table = string; break; case RDR_NAT_TABLE_NAME: nft_nat_table = string; break; case RDR_NAT_PREROUTING_CHAIN_NAME: nft_prerouting_chain = string; break; case RDR_NAT_POSTROUTING_CHAIN_NAME: nft_postrouting_chain = string; break; case RDR_FORWARD_CHAIN_NAME: nft_forward_chain = string; break; case RDR_FAMILY_SPLIT: if(strcmp(string, "yes") == 0) { nft_nat_family = NFPROTO_IPV4; nft_ipv4_family = NFPROTO_IPV4; nft_ipv6_family = NFPROTO_IPV6; syslog(LOG_INFO, "using IPv4/IPv6 Table"); } break; default: syslog(LOG_ERR, "%s(): tried to set invalid string parameter: %d", "set_rdr_name", param); return -2; } return 0; } static unsigned int get_timestamp(unsigned short eport, int proto) { struct timestamp_entry * e; e = timestamp_list; while(e) { if(e->eport == eport && e->protocol == (short)proto) { syslog(LOG_DEBUG, "timestamp entry found (%hu, %d, %u)", eport, proto, e->timestamp); return e->timestamp; } e = e->next; } syslog(LOG_WARNING, "get_timestamp(%hu, %d) no entry found", eport, proto); return 0; } static void remove_timestamp_entry(unsigned short eport, int proto) { struct timestamp_entry * e; struct timestamp_entry * * p; p = ×tamp_list; e = *p; while(e) { if(e->eport == eport && e->protocol == (short)proto) { syslog(LOG_DEBUG, "timestamp entry removed (%hu, %d, %u)", eport, proto, e->timestamp); /* remove the entry */ *p = e->next; free(e); return; } p = &(e->next); e = *p; } syslog(LOG_WARNING, "remove_timestamp_entry(%hu, %d) no entry found", eport, proto); } static void add_timestamp_entry(unsigned short eport, int proto, unsigned timestamp) { struct timestamp_entry * tmp; tmp = malloc(sizeof(struct timestamp_entry)); if(tmp) { tmp->next = timestamp_list; tmp->timestamp = timestamp; tmp->eport = eport; tmp->protocol = (short)proto; timestamp_list = tmp; syslog(LOG_DEBUG, "timestamp entry added (%hu, %d, %u)", eport, proto, timestamp); } else { syslog(LOG_ERR, "add_timestamp_entry() malloc(%lu) error", sizeof(struct timestamp_entry)); } } int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { int ret; struct nftnl_rule *r; UNUSED(rhost); d_printf(("add redirect rule2(%s, %s, %u, %s, %u, %d, %s)!\n", ifname, rhost, eport, iaddr, iport, proto, desc)); r = rule_set_dnat(nft_nat_family, ifname, proto, 0, eport, inet_addr(iaddr), iport, desc, NULL); ret = nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_REDIRECT); if (ret >= 0) { add_timestamp_entry(eport, proto, timestamp); } return ret; } /* * This function submit the rule as following: * nft add rule nat miniupnpd-pcp-peer ip * saddr ip daddr tcp sport * tcp dport snat : */ int add_peer_redirect_rule2(const char * ifname, const char * rhost, unsigned short rport, const char * eaddr, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { struct nftnl_rule *r; UNUSED(ifname); UNUSED(timestamp); d_printf(("add peer redirect rule2()!\n")); r = rule_set_snat(nft_nat_family, proto, inet_addr(rhost), rport, inet_addr(eaddr), eport, inet_addr(iaddr), iport, desc, NULL); return nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_PEER); } /* * This function submit the rule as following: * nft add rule filter miniupnpd * ip daddr tcp dport accept * */ int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { struct nftnl_rule *r = NULL; in_addr_t rhost_addr = 0; d_printf(("add_filter_rule2(%s, %s, %s, %d, %d, %d, %s)\n", ifname, rhost, iaddr, eport, iport, proto, desc)); if (rhost != NULL && strcmp(rhost, "") != 0 && strcmp(rhost, "*") != 0) { rhost_addr = inet_addr(rhost); } r = rule_set_filter(nft_nat_family, ifname, proto, rhost_addr, inet_addr(iaddr), eport, iport, 0, desc, 0); return nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_FILTER); } /* * add_peer_dscp_rule2() is not supported due to nft does not support * dscp set. */ int add_peer_dscp_rule2(const char * ifname, const char * rhost, unsigned short rport, unsigned char dscp, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { UNUSED(ifname); UNUSED(rhost); UNUSED(rport); UNUSED(dscp); UNUSED(iaddr); UNUSED(iport); UNUSED(proto); UNUSED(desc); UNUSED(timestamp); syslog(LOG_ERR, "add_peer_dscp_rule2: not supported"); return 0; } int delete_filter_rule(const char * ifname, unsigned short port, int proto) { rule_t *p; struct nftnl_rule *r; UNUSED(ifname); refresh_nft_cache_filter(); LIST_FOREACH(p, &head_filter, entry) { if (p->dport == port && p->proto == proto && p->type == RULE_FILTER) { r = rule_del_handle(p); nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER); break; } } return 0; } /* * Clear all rules corresponding eport/proto */ int delete_redirect_and_filter_rules(unsigned short eport, int proto) { rule_t *p; struct nftnl_rule *r = NULL; in_addr_t iaddr = 0; uint16_t iport = 0; d_printf(("delete_redirect_and_filter_rules(%d %d)\n", eport, proto)); refresh_nft_cache_redirect(); // Delete Redirect Rule LIST_FOREACH(p, &head_redirect, entry) { if (p->dport == eport && p->proto == proto && (p->type == RULE_NAT && p->nat_type == NFT_NAT_DNAT)) { iaddr = p->nat_addr; iport = p->nat_port; r = rule_del_handle(p); /* Todo: send bulk request */ nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_REDIRECT); break; } } if (iaddr != 0 && iport != 0) { refresh_nft_cache_filter(); // Delete Forward Rule LIST_FOREACH(p, &head_filter, entry) { if (p->nat_port == iport && p->nat_addr == iaddr && p->type == RULE_FILTER) { r = rule_del_handle(p); /* Todo: send bulk request */ nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER); break; } } } iaddr = 0; iport = 0; refresh_nft_cache_peer(); // Delete Peer Rule LIST_FOREACH(p, &head_peer, entry) { if (p->nat_port == eport && p->proto == proto && (p->type == RULE_NAT && p->nat_type == NFT_NAT_SNAT)) { iaddr = p->daddr; iport = p->dport; r = rule_del_handle(p); /* Todo: send bulk request */ nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_PEER); break; } } if (iaddr != 0 && iport != 0) { refresh_nft_cache_filter(); // Delete Forward Rule LIST_FOREACH(p, &head_filter, entry) { if (p->dport == iport && p->daddr == iaddr && p->type == RULE_FILTER) { r = rule_del_handle(p); /* Todo: send bulk request */ nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER); break; } } } return 0; } /* * get peer by index as array. * return -1 when not found. */ int get_peer_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned short * rport, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { rule_t *r; int i = 0; d_printf(("get_peer_rule_by_index()\n")); refresh_nft_cache_peer(); LIST_FOREACH(r, &head_peer, entry) { if (i++ == index) { if (ifname != NULL) { if_indextoname(r->ingress_ifidx, ifname); } if (eport != NULL) { *eport = r->nat_port; } if (iaddr != NULL) { if (inet_ntop(AF_INET, &r->daddr, iaddr, iaddrlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop: %m", "get_peer_rule_by_index"); } } if (iport != NULL) { *iport = r->dport; } if (proto != NULL) { *proto = r->proto; } if (rhost != NULL) { if (r->saddr) { if (inet_ntop(AF_INET, &r->saddr, rhost, rhostlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop: %m", "get_peer_rule_by_index"); } } else { rhost[0] = '\0'; } } if (rport != NULL) { *rport = r->sport; } if (desc != NULL) { strncpy(desc, r->desc, desclen); } if (packets) { *packets = r->packets; } if (bytes) { *bytes = r->bytes; } if (timestamp) { *timestamp = get_timestamp(r->dport, r->proto); } /* * TODO: Implement counter in case of add {nat,filter} */ return 0; } } return -1; } /* * get_redirect_rule() * returns -1 if the rule is not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { return get_nat_redirect_rule(nft_prerouting_chain, ifname, eport, proto, iaddr, iaddrlen, iport, desc, desclen, rhost, rhostlen, timestamp, packets, bytes); } /* get_redirect_rule_count() * return value : -1 for error or the number of redirection rules */ int get_redirect_rule_count(const char * ifname) { rule_t *r; int n = 0; UNUSED(ifname); refresh_nft_cache_redirect(); LIST_FOREACH(r, &head_redirect, entry) { n++; } return n; } /* * get_redirect_rule_by_index() * return -1 when the rule was not found */ int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { rule_t *r; int i = 0; d_printf(("get_redirect_rule_by_index()\n")); refresh_nft_cache_redirect(); LIST_FOREACH(r, &head_redirect, entry) { if (i++ == index) { if (ifname != NULL) { if_indextoname(r->ingress_ifidx, ifname); } if (eport != NULL) { *eport = r->dport; } if (iaddr != NULL) { if (inet_ntop(AF_INET, &r->nat_addr, iaddr, iaddrlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop: %m", "get_redirect_rule_by_index"); } } if (iport != NULL) { *iport = r->nat_port; } if (proto != NULL) { *proto = r->proto; } if (rhost != NULL) { if (r->saddr) { if (inet_ntop(AF_INET, &r->saddr, rhost, rhostlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop: %m", "get_redirect_rule_by_index"); } } else { rhost[0] = '\0'; } } if (desc != NULL && r->desc) { strncpy(desc, r->desc, desclen); } if (timestamp != NULL) { *timestamp = get_timestamp(*eport, *proto); } if (packets || bytes) { if (packets) *packets = r->packets; if (bytes) *bytes = r->bytes; } /* * TODO: Implement counter in case of add {nat,filter} */ return 0; } } return -1; } /* * return -1 not found. * return 0 found */ int get_nat_redirect_rule(const char * nat_chain_name, const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { rule_t *p; struct in_addr addr; UNUSED(nat_chain_name); UNUSED(ifname); UNUSED(packets); UNUSED(bytes); UNUSED(rhost); UNUSED(rhostlen); refresh_nft_cache_redirect(); LIST_FOREACH(p, &head_redirect, entry) { if (p->proto == proto && p->dport == eport) { if (p->nat_addr && iaddr) { addr.s_addr = p->nat_addr; if (inet_ntop(AF_INET, &addr, iaddr, iaddrlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop: %m", "get_nat_redirect_rule"); } } if (desc != NULL && p->desc) { strncpy(desc, p->desc, desclen); } if (iport) *iport = p->nat_port; if(timestamp != NULL) *timestamp = get_timestamp(eport, proto); return 0; } } return -1; } /* * return an (malloc'ed) array of "external" port for which there is * a port mapping. number is the size of the array */ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number) { uint32_t capacity; rule_t *p; unsigned short *array; unsigned short *tmp; d_printf(("get_portmappings_in_range()\n")); *number = 0; capacity = 128; array = calloc(capacity, sizeof(unsigned short)); if (array == NULL) { syslog(LOG_ERR, "get_portmappings_in_range(): calloc error"); return NULL; } refresh_nft_cache_redirect(); LIST_FOREACH(p, &head_redirect, entry) { if (p->proto == proto && startport <= p->dport && p->dport <= endport) { if (*number >= capacity) { capacity += 128; tmp = realloc(array, sizeof(unsigned short)*capacity); if (tmp == NULL) { syslog(LOG_ERR, "get_portmappings_in_range(): " "realloc(%u) error", (unsigned)sizeof(unsigned short)*capacity); *number = 0; free(array); return NULL; } array = tmp; } array[*number] = p->dport; (*number)++; } } return array; } int update_portmapping_desc_timestamp(const char * ifname, unsigned short eport, int proto, const char * desc, unsigned int timestamp) { UNUSED(ifname); UNUSED(desc); remove_timestamp_entry(eport, proto); add_timestamp_entry(eport, proto, timestamp); return 0; } int update_portmapping(const char * ifname, unsigned short eport, int proto, unsigned short iport, const char * desc, unsigned int timestamp) { char iaddr_str[INET_ADDRSTRLEN]; char rhost[INET_ADDRSTRLEN]; int r; d_printf(("update_portmapping()\n")); iaddr_str[0] = '\0'; rhost[0] = '\0'; if (get_redirect_rule(NULL, eport, proto, iaddr_str, INET_ADDRSTRLEN, NULL, NULL, 0, rhost, INET_ADDRSTRLEN, NULL, 0, 0) < 0) return -1; r = delete_redirect_and_filter_rules(eport, proto); if (r < 0) return -1; 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; } miniupnpd-2.3.7/netfilter_nft/nftnlrdr.h010064400017500000024000000051201354645216200175630ustar00nanardstaff/* * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2015 Tomofumi Hayashi * (c) 2019 Paul Chambers * * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution. */ #ifndef NFTNLRDR_H_INCLUDED #define NFTNLRDR_H_INCLUDED #include "../commonrdr.h" int init_redirect(void); void shutdown_redirect(void); int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int add_peer_redirect_rule2(const char * ifname, const char * rhost, unsigned short rport, const char * eaddr, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc); 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, unsigned char dscp, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp); int get_peer_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned short * rport, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); int get_nat_redirect_rule(const char * nat_chain_name, const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number); /* in nfct_get.c */ int get_nat_ext_addr(struct sockaddr* src, struct sockaddr *dst, uint8_t proto, struct sockaddr* ret_ext); #endif miniupnpd-2.3.7/netfilter_nft/nftnlrdr_misc.c010064400017500000024000001145011457642072000205740ustar00nanardstaff/* $Id: nftnlrdr_misc.c,v 1.20 2024/03/19 23:35:54 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2015 Tomofumi Hayashi * (c) 2019 Paul Chambers * (c) 2019-2024 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../commonrdr.h" #include "nftnlrdr_misc.h" #include "../macros.h" #ifdef DEBUG #define d_printf(x) do { printf x; } while (0) #else #define d_printf(x) #endif #if defined(DEBUG) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && (__GNUC__ >= 3) /* disambiguate log messages by adding position in source. GNU C99 or later. Pesky trailing comma... */ #define log_error( msg, ...) syslog(LOG_ERR, "%s[%d]: " msg, __func__, __LINE__, ##__VA_ARGS__ ) #define log_debug( msg, ...) syslog(LOG_DEBUG, "%s[%d]: " msg, __func__, __LINE__, ##__VA_ARGS__ ) #else /* original style */ #define log_error(args...) syslog(LOG_ERR, args) #define log_debug(args...) syslog(LOG_DEBUG, args) #endif #define RULE_CACHE_INVALID 0 #define RULE_CACHE_VALID 1 const char * nft_table = "filter"; const char * nft_nat_table = "filter"; const char * nft_prerouting_chain = "prerouting_miniupnpd"; const char * nft_postrouting_chain = "postrouting_miniupnpd"; const char * nft_forward_chain = "miniupnpd"; int nft_nat_family = NFPROTO_INET; int nft_ipv4_family = NFPROTO_INET; int nft_ipv6_family = NFPROTO_INET; static struct mnl_socket *mnl_sock = NULL; static uint32_t mnl_portid = 0; static uint32_t mnl_seq = 0; // FILTER struct rule_list head_filter = LIST_HEAD_INITIALIZER(head_filter); // DNAT struct rule_list head_redirect = LIST_HEAD_INITIALIZER(head_redirect); // SNAT struct rule_list head_peer = LIST_HEAD_INITIALIZER(head_peer); static uint32_t rule_list_filter_validate = RULE_CACHE_INVALID; static uint32_t rule_list_redirect_validate = RULE_CACHE_INVALID; static uint32_t rule_list_peer_validate = RULE_CACHE_INVALID; /* * return : 0 for OK, -1 for error */ int nft_mnl_connect(void) { mnl_sock = mnl_socket_open(NETLINK_NETFILTER); if (mnl_sock == NULL) { log_error("mnl_socket_open() FAILED: %m"); return -1; } if (mnl_socket_bind(mnl_sock, 0, MNL_SOCKET_AUTOPID) < 0) { log_error("mnl_socket_bind() FAILED: %m"); return -1; } mnl_portid = mnl_socket_get_portid(mnl_sock); syslog(LOG_INFO, "mnl_socket bound, port_id=%u", mnl_portid); return 0; } void nft_mnl_disconnect(void) { if (mnl_sock != NULL) { mnl_socket_close(mnl_sock); mnl_sock = NULL; } } #ifdef DEBUG void print_rule(const char *func, int line, const struct nftnl_rule *rule) { fprintf(stdout, "%s[%d]: ", func, line); nftnl_rule_fprintf(stdout, rule, NFTNL_OUTPUT_DEFAULT, 0); } void print_rule_t(const char *func, int line, const rule_t *r) { fprintf(stdout, "%s[%d]: ", func, line); printf("%s %s %d %hu => %hu => %hu\n", r->table, r->chain, (int)r->type, r->sport, r->dport, r->nat_port); } /* print out the "filter" and "nat" tables */ void print_redirect_rules(const char * ifname) { rule_t *p; int i; UNUSED(ifname); refresh_nft_cache_filter(); i = 1; LIST_FOREACH(p, &head_filter, entry) { print_rule_t("filter", i++, p); } refresh_nft_cache_redirect(); i = 1; LIST_FOREACH(p, &head_redirect, entry) { print_rule_t("redirect", i++, p); } refresh_nft_cache_peer(); i = 1; LIST_FOREACH(p, &head_peer, entry) { print_rule_t("peer", 0, p); } } #endif static enum rule_reg_type * get_reg_type_ptr(rule_t *r, uint32_t dreg) { switch (dreg) { case NFT_REG_1: return &r->reg1_type; case NFT_REG_2: return &r->reg2_type; default: return NULL; } } static uint32_t * get_reg_val_ptr(rule_t *r, uint32_t dreg) { switch (dreg) { case NFT_REG_1: return &r->reg1_val; case NFT_REG_2: return &r->reg2_val; default: return NULL; } } static void set_reg (rule_t *r, uint32_t dreg, enum rule_reg_type type, uint32_t val) { if (dreg == NFT_REG_1) { r->reg1_type = type; if (type == RULE_REG_IMM_VAL) { r->reg1_val = val; } } else if (dreg == NFT_REG_2) { r->reg2_type = type; if (type == RULE_REG_IMM_VAL) { r->reg2_val = val; } } else if (dreg == NFT_REG_VERDICT) { if (r->type == RULE_FILTER) { r->filter_action = val; } } else { log_error("unknown reg:%d", dreg); } return ; } static void parse_rule_immediate(struct nftnl_expr *e, rule_t *r) { uint32_t dreg, reg_val, reg_len; dreg = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_DREG); if (dreg == NFT_REG_VERDICT) { reg_val = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_VERDICT); } else { const void * p = nftnl_expr_get(e, NFTNL_EXPR_IMM_DATA, ®_len); if (p == NULL) { log_error("nftnl_expr_get() failed for reg:%u", dreg); return; } else switch(reg_len) { case sizeof(uint32_t): reg_val = *(const uint32_t *)p; break; case sizeof(uint16_t): reg_val = *(const uint16_t *)p; break; default: log_error("nftnl_expr_get() reg_len=%u", reg_len); return; } } set_reg(r, dreg, RULE_REG_IMM_VAL, reg_val); } static void parse_rule_counter(struct nftnl_expr *e, rule_t *r) { r->type = RULE_COUNTER; r->bytes = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES); r->packets = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS); } static void parse_rule_meta(struct nftnl_expr *e, rule_t *r) { uint32_t key = nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY); uint32_t dreg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG); enum rule_reg_type reg_type; /* ToDo: body of both cases are identical - bug? */ switch (key) { case NFT_META_IIF: reg_type = RULE_REG_IIF; set_reg(r, dreg, reg_type, 0); break; case NFT_META_OIF: reg_type = RULE_REG_IIF; set_reg(r, dreg, reg_type, 0); break; default: log_debug("parse_rule_meta :Not support key %d\n", key); break; } } static void parse_rule_nat(struct nftnl_expr *e, rule_t *r) { uint32_t addr_min_reg, addr_max_reg, proto_min_reg, proto_max_reg; uint16_t proto_min_val = 0; uint32_t * reg_val_ptr; r->type = RULE_NAT; r->nat_type = nftnl_expr_get_u32(e, NFTNL_EXPR_NAT_TYPE); r->family = nftnl_expr_get_u32(e, NFTNL_EXPR_NAT_FAMILY); addr_min_reg = nftnl_expr_get_u32(e, NFTNL_EXPR_NAT_REG_ADDR_MIN); addr_max_reg = nftnl_expr_get_u32(e, NFTNL_EXPR_NAT_REG_ADDR_MAX); proto_min_reg = nftnl_expr_get_u32(e, NFTNL_EXPR_NAT_REG_PROTO_MIN); proto_max_reg = nftnl_expr_get_u32(e, NFTNL_EXPR_NAT_REG_PROTO_MAX); if (addr_min_reg != addr_max_reg || proto_min_reg != proto_max_reg) { log_error( "Unsupport proto/addr range for NAT"); } reg_val_ptr = get_reg_val_ptr(r, proto_min_reg); if (reg_val_ptr != NULL) { proto_min_val = htons((uint16_t)*reg_val_ptr); syslog(LOG_DEBUG, "%s: proto_min_reg %u : %08x => %hd", "parse_rule_nat", proto_min_reg, *reg_val_ptr, proto_min_val); } else { syslog(LOG_ERR, "%s: invalid proto_min_reg %u", "parse_rule_nat", proto_min_reg); } reg_val_ptr = get_reg_val_ptr(r, addr_min_reg); if (reg_val_ptr != NULL) { r->nat_addr = (in_addr_t)*reg_val_ptr; if (proto_min_reg == NFT_REG_1) { r->nat_port = proto_min_val; } } else { syslog(LOG_ERR, "%s: invalid addr_min_reg %u", "parse_rule_nat", addr_min_reg); } set_reg(r, NFT_REG_1, RULE_REG_NONE, 0); set_reg(r, NFT_REG_2, RULE_REG_NONE, 0); } static void parse_rule_payload(struct nftnl_expr *e, rule_t *r) { uint32_t base, dreg, offset, len; uint32_t *regptr; dreg = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_DREG); base = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_BASE); offset = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET); len = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_LEN); regptr = get_reg_type_ptr(r, dreg); if (regptr == NULL) { syslog(LOG_ERR, "%s: unsupported dreg %u", "parse_rule_payload", dreg); return; } switch (base) { case NFT_PAYLOAD_NETWORK_HEADER: if (offset == offsetof(struct iphdr, daddr) && len == sizeof(in_addr_t)) { *regptr = RULE_REG_IP_DEST_ADDR; } else if (offset == offsetof(struct iphdr, saddr) && len == sizeof(in_addr_t)) { *regptr = RULE_REG_IP_SRC_ADDR; } else if (offset == offsetof(struct iphdr, saddr) && len == sizeof(in_addr_t) * 2) { *regptr = RULE_REG_IP_SD_ADDR; } else if (offset == offsetof(struct iphdr, protocol) && len == sizeof(uint8_t)) { *regptr = RULE_REG_IP_PROTO; } else if (offset == offsetof(struct ipv6hdr, nexthdr) && len == sizeof(uint8_t)) { *regptr = RULE_REG_IP6_PROTO; } else if (offset == offsetof(struct ipv6hdr, daddr) && len == sizeof(struct in6_addr)) { *regptr = RULE_REG_IP6_DEST_ADDR; } else if (offset == offsetof(struct ipv6hdr, saddr) && len == sizeof(struct in6_addr)) { *regptr = RULE_REG_IP6_SRC_ADDR; } else if (offset == offsetof(struct ipv6hdr, saddr) && len == sizeof(struct in6_addr) * 2) { *regptr = RULE_REG_IP6_SD_ADDR; } else { syslog(LOG_WARNING, "%s: Unsupported payload: (dreg:%u, base:NETWORK_HEADER, offset:%u, len:%u)", "parse_rule_payload", dreg, offset, len); } break; case NFT_PAYLOAD_TRANSPORT_HEADER: /* in both UDP and TCP headers, source port is at offset 0, * destination port at offset 2 */ if (offset == offsetof(struct tcphdr, dest) && len == sizeof(uint16_t)) { *regptr = RULE_REG_TCP_DPORT; } else if (offset == offsetof(struct tcphdr, source) && len == sizeof(uint16_t)) { *regptr = RULE_REG_TCP_SPORT; } else if (offset == offsetof(struct tcphdr, source) && len == sizeof(uint16_t) * 2) { *regptr = RULE_REG_TCP_SD_PORT; } else { syslog(LOG_WARNING, "%s: Unsupported payload: (dreg:%u, base:TRANSPORT_HEADER, offset:%u, len:%u)", "parse_rule_payload", dreg, offset, len); } break; default: syslog(LOG_WARNING, "%s: Unsupported payload: (dreg:%u, base:%u, offset:%u, len:%u)", "parse_rule_payload", dreg, base, offset, len); break; } } /* * * Note: Currently support only NFT_REG_1 */ static void parse_rule_cmp(struct nftnl_expr *e, rule_t *r) { uint32_t data_len = 0; const void *data_val; uint32_t op, sreg; op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP); if (op != NFT_CMP_EQ) { /* not a cmp expression, so bail out early */ return; } sreg = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG); if (sreg != NFT_REG_1) { log_error( "parse_rule_cmp: Unsupport reg:%d", sreg); return; } data_val = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &data_len); if (data_val == NULL) { log_error( "parse_rule_cmp: nftnl_expr_get(NFTNL_EXPR_CMP_DATA) returned NULL"); return; } switch (r->reg1_type) { case RULE_REG_IIF: if (data_len == sizeof(uint32_t)) { r->ingress_ifidx = *(const uint32_t *)data_val; } break; case RULE_REG_IP_SRC_ADDR: if (data_len == sizeof(in_addr_t)) { r->saddr = *(const in_addr_t *)data_val; } break; case RULE_REG_IP6_SRC_ADDR: if (data_len == sizeof(struct in6_addr)) { r->saddr6 = *(const struct in6_addr *)data_val; } break; case RULE_REG_IP_DEST_ADDR: if (data_len == sizeof(in_addr_t)) { r->daddr = *(const in_addr_t *)data_val; } break; case RULE_REG_IP6_DEST_ADDR: if (data_len == sizeof(struct in6_addr)) { r->daddr6 = *(const struct in6_addr *)data_val; } break; case RULE_REG_IP_SD_ADDR: if (data_len == sizeof(in_addr_t) * 2) { const in_addr_t *addrp = (const in_addr_t *)data_val; r->saddr = addrp[0]; r->daddr = addrp[1]; } break; case RULE_REG_IP6_SD_ADDR: if (data_len == sizeof(struct in6_addr) * 2) { const struct in6_addr *addrp6 = (const struct in6_addr *)data_val; r->saddr6 = addrp6[0]; r->daddr6 = addrp6[1]; } break; case RULE_REG_IP_PROTO: case RULE_REG_IP6_PROTO: if (data_len == sizeof(uint8_t)) { r->proto = *(const uint8_t *)data_val; } break; case RULE_REG_TCP_SPORT: if (data_len == sizeof(uint16_t)) { r->sport = ntohs(*(const uint16_t *)data_val); } break; case RULE_REG_TCP_DPORT: if (data_len == sizeof(uint16_t)) { r->dport = ntohs(*(const uint16_t *)data_val); } break; case RULE_REG_TCP_SD_PORT: if (data_len == sizeof(uint16_t) * 2) { const uint16_t * ports = (const uint16_t *)data_val; r->sport = ntohs(ports[0]); r->dport = ntohs(ports[1]); } break; default: log_debug("Unknown cmp (r1type:%d, data_len:%d, op:%d)", r->reg1_type, data_len, op); /* return early - don't modify r->reg1_type */ return; } r->reg1_type = RULE_REG_NONE; return; } static int rule_expr_cb(struct nftnl_expr *e, rule_t *r) { const char *attr_name = nftnl_expr_get_str(e, NFTNL_EXPR_NAME); if (strncmp("cmp", attr_name, sizeof("cmp")) == 0) { parse_rule_cmp(e, r); } else if (strncmp("nat", attr_name, sizeof("nat")) == 0) { parse_rule_nat(e, r); } else if (strncmp("meta", attr_name, sizeof("meta")) == 0) { parse_rule_meta(e, r); } else if (strncmp("counter", attr_name, sizeof("counter")) == 0) { parse_rule_counter(e, r); } else if (strncmp("payload", attr_name, sizeof("payload")) == 0) { parse_rule_payload(e, r); } else if (strncmp("immediate", attr_name, sizeof("immediate")) == 0) { parse_rule_immediate(e, r); } else { log_debug("unknown attr: %s\n", attr_name); } return MNL_CB_OK; } struct table_cb_data { const char * table; const char * chain; enum rule_type type; }; /* callback. * return values : * MNL_CB_ERROR : an error has occurred. Stop callback runqueue. * MNL_CB_STOP : top callback runqueue. * MNL_CB_OK : no problems has occurred. */ static int table_cb(const struct nlmsghdr *nlh, void *data) { int result = MNL_CB_OK; struct nftnl_rule *rule; struct nftnl_expr_iter *itr; #define CB_DATA(field) ((struct table_cb_data *)data)->field syslog(LOG_DEBUG, "table_cb(%p, %p) %s %s %d", nlh, data, CB_DATA(table), CB_DATA(chain), CB_DATA(type)); rule = nftnl_rule_alloc(); if (rule == NULL) { log_error("nftnl_rule_alloc() FAILED"); return MNL_CB_ERROR; } if (nftnl_rule_nlmsg_parse(nlh, rule) < 0) { log_error("nftnl_rule_nlmsg_parse FAILED"); result = MNL_CB_ERROR; } else { rule_t *r = malloc(sizeof(rule_t)); if (r == NULL) { syslog(LOG_ERR, "%s: failed to allocate %u bytes", "table_cb", (unsigned)sizeof(rule_t)); result = MNL_CB_ERROR; } else { const char *chain; memset(r, 0, sizeof(rule_t)); chain = nftnl_rule_get_str(rule, NFTNL_RULE_CHAIN); if (strcmp(chain, nft_prerouting_chain) == 0 || strcmp(chain, nft_postrouting_chain) == 0 || strcmp(chain, nft_forward_chain) == 0) { r->table = strdup(nftnl_rule_get_str(rule, NFTNL_RULE_TABLE)); r->chain = strdup(chain); r->family = nftnl_rule_get_u32(rule, NFTNL_RULE_FAMILY); if (nftnl_rule_is_set(rule, NFTNL_RULE_USERDATA)) { const char *descr; descr = (const char *) nftnl_rule_get_data(rule, NFTNL_RULE_USERDATA, &r->desc_len); if (r->desc_len > 0) { r->desc = malloc(r->desc_len + 1); if (r->desc != NULL) { memcpy(r->desc, descr, r->desc_len); r->desc[r->desc_len] = '\0'; } else { syslog(LOG_ERR, "failed to allocate %u bytes for desc", r->desc_len); } } } r->handle = nftnl_rule_get_u64(rule, NFTNL_RULE_HANDLE); r->type = CB_DATA(type); itr = nftnl_expr_iter_create(rule); if (itr == NULL) { syslog(LOG_ERR, "%s: nftnl_expr_iter_create() FAILED", "table_cb"); } else { struct nftnl_expr *expr; while ((expr = nftnl_expr_iter_next(itr)) != NULL) { rule_expr_cb(expr, r); } nftnl_expr_iter_destroy(itr); } switch (r->type) { case RULE_NAT: switch (r->nat_type) { case NFT_NAT_SNAT: LIST_INSERT_HEAD(&head_peer, r, entry); r = NULL; break; case NFT_NAT_DNAT: LIST_INSERT_HEAD(&head_redirect, r, entry); r = NULL; break; default: syslog(LOG_WARNING, "unknown nat type %d", r->nat_type); } break; case RULE_FILTER: LIST_INSERT_HEAD(&head_filter, r, entry); r = NULL; break; default: syslog(LOG_WARNING, "unknown rule type %d", r->type); break; } } else { syslog(LOG_WARNING, "unknown chain '%s'", chain); } if (r != NULL) { free(r); } } } nftnl_rule_free(rule); return result; } #undef CB_DATA int refresh_nft_cache_filter(void) { if (rule_list_filter_validate != RULE_CACHE_VALID) { if (refresh_nft_cache(&head_filter, nft_table, nft_forward_chain, nft_ipv4_family, RULE_FILTER) < 0) return -1; rule_list_filter_validate = RULE_CACHE_VALID; } return 0; } int refresh_nft_cache_peer(void) { if (rule_list_peer_validate != RULE_CACHE_VALID) { if (refresh_nft_cache(&head_peer, nft_nat_table, nft_postrouting_chain, nft_nat_family, RULE_NAT) < 0) return -1; rule_list_peer_validate = RULE_CACHE_VALID; } return 0; } int refresh_nft_cache_redirect(void) { if (rule_list_redirect_validate != RULE_CACHE_VALID) { if (refresh_nft_cache(&head_redirect, nft_nat_table, nft_prerouting_chain, nft_nat_family, RULE_NAT) < 0) return -1; rule_list_redirect_validate = RULE_CACHE_VALID; } return 0; } void flush_nft_cache(struct rule_list *head) { rule_t *p1, *p2; p1 = LIST_FIRST(head); while (p1 != NULL) { p2 = (rule_t *)LIST_NEXT(p1, entry); if (p1->desc != NULL) { free(p1->desc); } if (p1->table != NULL) { free(p1->table); } if (p1->chain != NULL) { free(p1->chain); } free(p1); p1 = p2; } LIST_INIT(head); } /* * return -1 in case of error, 0 if OK */ int refresh_nft_cache(struct rule_list *head, const char *table, const char *chain, uint32_t family, enum rule_type type) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct table_cb_data data; struct nftnl_rule *rule; int ret; ssize_t n; if (mnl_sock == NULL) { log_error("netlink not connected"); return -1; } flush_nft_cache(head); rule = nftnl_rule_alloc(); if (rule == NULL) { log_error("nftnl_rule_alloc() FAILED"); return -1; } mnl_seq = time(NULL); nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, NLM_F_DUMP, mnl_seq); nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, table); nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain); nftnl_rule_nlmsg_build_payload(nlh, rule); nftnl_rule_free(rule); if (mnl_socket_sendto(mnl_sock, nlh, nlh->nlmsg_len) < 0) { log_error("mnl_socket_sendto() FAILED: %m"); return -1; } data.table = table; data.chain = chain; data.type = type; do { n = mnl_socket_recvfrom(mnl_sock, buf, sizeof(buf)); if (n < 0) { syslog(LOG_ERR, "%s: mnl_socket_recvfrom: %m", "refresh_nft_cache"); return -1; } else if (n == 0) { break; } ret = mnl_cb_run(buf, n, mnl_seq, mnl_portid, table_cb, &data); if (ret <= -1 /*== MNL_CB_ERROR*/) { syslog(LOG_ERR, "%s: mnl_cb_run returned %d", "refresh_nft_cache", ret); return -1; } } while(ret >= 1 /*== MNL_CB_OK*/); /* ret == MNL_CB_STOP */ return 0; } static void expr_add_payload(struct nftnl_rule *r, uint32_t base, uint32_t dreg, uint32_t offset, uint32_t len) { struct nftnl_expr *e; e = nftnl_expr_alloc("payload"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "payload"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base); nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg); nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset); nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len); nftnl_rule_add_expr(r, e); } static void expr_add_cmp(struct nftnl_rule *r, uint32_t sreg, uint32_t op, const void *data, uint32_t data_len) { struct nftnl_expr *e; e = nftnl_expr_alloc("cmp"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "cmp"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_CMP_SREG, sreg); nftnl_expr_set_u32(e, NFTNL_EXPR_CMP_OP, op); nftnl_expr_set(e, NFTNL_EXPR_CMP_DATA, data, data_len); nftnl_rule_add_expr(r, e); } static void expr_add_meta(struct nftnl_rule *r, uint32_t meta_key, uint32_t dreg) { struct nftnl_expr *e; e = nftnl_expr_alloc("meta"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "meta"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_META_KEY, meta_key); nftnl_expr_set_u32(e, NFTNL_EXPR_META_DREG, dreg); nftnl_rule_add_expr(r, e); } static void expr_set_reg_val_u32(struct nftnl_rule *r, enum nft_registers dreg, uint32_t val) { struct nftnl_expr *e; e = nftnl_expr_alloc("immediate"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "immediate"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_DREG, dreg); nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_DATA, val); nftnl_rule_add_expr(r, e); } static void expr_set_reg_val_u16(struct nftnl_rule *r, enum nft_registers dreg, uint16_t val) { struct nftnl_expr *e; e = nftnl_expr_alloc("immediate"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "immediate"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_DREG, dreg); nftnl_expr_set_u16(e, NFTNL_EXPR_IMM_DATA, val); nftnl_rule_add_expr(r, e); } static void expr_set_reg_verdict(struct nftnl_rule *r, uint32_t val) { struct nftnl_expr *e; e = nftnl_expr_alloc("immediate"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "immediate"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_VERDICT, val); nftnl_rule_add_expr(r, e); } static void expr_add_nat(struct nftnl_rule *r, uint32_t t, uint32_t family, in_addr_t addr_min, uint16_t proto_min, uint32_t flags) { struct nftnl_expr *e; UNUSED(flags); e = nftnl_expr_alloc("nat"); if (e == NULL) { log_error("nftnl_expr_alloc(\"%s\") FAILED", "nat"); return; } nftnl_expr_set_u32(e, NFTNL_EXPR_NAT_TYPE, t); nftnl_expr_set_u32(e, NFTNL_EXPR_NAT_FAMILY, family); /* To IP Address */ expr_set_reg_val_u32(r, NFT_REG_1, (uint32_t)addr_min); nftnl_expr_set_u32(e, NFTNL_EXPR_NAT_REG_ADDR_MIN, NFT_REG_1); nftnl_expr_set_u32(e, NFTNL_EXPR_NAT_REG_ADDR_MAX, NFT_REG_1); /* To Port */ expr_set_reg_val_u16(r, NFT_REG_2, proto_min); nftnl_expr_set_u32(e, NFTNL_EXPR_NAT_REG_PROTO_MIN, NFT_REG_2); nftnl_expr_set_u32(e, NFTNL_EXPR_NAT_REG_PROTO_MAX, NFT_REG_2); nftnl_rule_add_expr(r, e); } struct nftnl_rule * rule_set_snat(uint8_t family, uint8_t proto, in_addr_t rhost, unsigned short rport, in_addr_t ehost, unsigned short eport, in_addr_t ihost, unsigned short iport, const char *descr, const char *handle) { struct nftnl_rule *r = NULL; uint16_t dport, sport; UNUSED(handle); r = nftnl_rule_alloc(); if (r == NULL) { log_error("nftnl_rule_alloc() FAILED"); return NULL; } nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family); nftnl_rule_set_str(r, NFTNL_RULE_TABLE, nft_nat_table); nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, nft_postrouting_chain); if (descr != NULL && *descr != '\0') { nftnl_rule_set_data(r, NFTNL_RULE_USERDATA, descr, strlen(descr)); } /* Destination IP */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, daddr), sizeof(uint32_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &ihost, sizeof(uint32_t)); /* Source IP */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, saddr), sizeof(in_addr_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &rhost, sizeof(in_addr_t)); /* Protocol */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, protocol), sizeof(uint8_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &proto, sizeof(uint8_t)); /* Source and Destination Port of Protocol */ if (proto == IPPROTO_TCP) { /* Destination Port */ dport = htons(iport); expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct tcphdr, dest), sizeof(uint16_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &dport, sizeof(uint16_t)); /* Source Port */ sport = htons(rport); expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct tcphdr, source), sizeof(uint16_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &sport, sizeof(uint16_t)); } else if (proto == IPPROTO_UDP) { /* Destination Port */ dport = htons(iport); expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct udphdr, dest), sizeof(uint16_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &dport, sizeof(uint16_t)); /* Source Port */ sport = htons(rport); expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct udphdr, source), sizeof(uint16_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &sport, sizeof(uint16_t)); } expr_add_nat(r, NFT_NAT_SNAT, NFPROTO_IPV4, ehost, htons(eport), 0); debug_rule(r); return r; } struct nftnl_rule * rule_set_dnat(uint8_t family, const char * ifname, uint8_t proto, in_addr_t rhost, unsigned short eport, in_addr_t ihost, uint32_t iport, const char *descr, const char *handle) { struct nftnl_rule *r = NULL; uint16_t dport; uint64_t handle_num; uint32_t if_idx; r = nftnl_rule_alloc(); if (r == NULL) { log_error("nftnl_rule_alloc() FAILED"); return NULL; } nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family); nftnl_rule_set_str(r, NFTNL_RULE_TABLE, nft_nat_table); nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, nft_prerouting_chain); if (descr != NULL && *descr != '\0') { nftnl_rule_set_data(r, NFTNL_RULE_USERDATA, descr, strlen(descr)); } if (handle != NULL) { handle_num = atoll(handle); nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, handle_num); } if (ifname != NULL) { if_idx = (uint32_t)if_nametoindex(ifname); expr_add_meta(r, NFT_META_IIF, NFT_REG_1); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &if_idx, sizeof(uint32_t)); } /* Source IP */ if (rhost != 0) { expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, saddr), sizeof(in_addr_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &rhost, sizeof(in_addr_t)); } /* Protocol */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, protocol), sizeof(uint8_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &proto, sizeof(uint8_t)); if (proto == IPPROTO_TCP) { dport = htons(eport); expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct tcphdr, dest), sizeof(uint16_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &dport, sizeof(uint16_t)); } else if (proto == IPPROTO_UDP) { dport = htons(eport); expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct udphdr, dest), sizeof(uint16_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &dport, sizeof(uint16_t)); } expr_add_nat(r, NFT_NAT_DNAT, NFPROTO_IPV4, ihost, htons(iport), 0); debug_rule(r); return r; } struct nftnl_rule * rule_set_filter(uint8_t family, const char * ifname, uint8_t proto, in_addr_t rhost, in_addr_t iaddr, unsigned short eport, unsigned short iport, unsigned short rport, const char *descr, const char *handle) { struct nftnl_rule *r = NULL; r = nftnl_rule_alloc(); if (r == NULL) { log_error("nftnl_rule_alloc() FAILED"); return NULL; } r = rule_set_filter_common(r, family, ifname, proto, eport, iport, rport, descr, handle); /* Destination IP */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, daddr), sizeof(uint32_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &iaddr, sizeof(uint32_t)); /* Source IP */ if (rhost != 0) { expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, saddr), sizeof(in_addr_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &rhost, sizeof(in_addr_t)); } /* Protocol */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct iphdr, protocol), sizeof(uint8_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &proto, sizeof(uint8_t)); expr_set_reg_verdict(r, NF_ACCEPT); debug_rule(r); return r; } /* * Create the IP6 filter rule * called by add_pinhole() and update_pinhole() * eport is always 0 * iport is the destination port of the filter rule * rport is the (optional) source port of the rule */ struct nftnl_rule * rule_set_filter6(uint8_t family, const char * ifname, uint8_t proto, struct in6_addr *rhost6, struct in6_addr *iaddr6, unsigned short eport, unsigned short iport, unsigned short rport, const char *descr, const char *handle) { struct nftnl_rule *r = NULL; r = nftnl_rule_alloc(); if (r == NULL) { log_error("nftnl_rule_alloc() FAILED"); return NULL; } r = rule_set_filter_common(r, family, ifname, proto, eport, iport, rport, descr, handle); /* Destination IP */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct ipv6hdr, daddr), sizeof(struct in6_addr)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, iaddr6, sizeof(struct in6_addr)); /* Source IP */ if (rhost6) { expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct ipv6hdr, saddr), sizeof(struct in6_addr)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, rhost6, sizeof(struct in6_addr)); } /* Protocol */ expr_add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, offsetof(struct ipv6hdr, nexthdr), sizeof(uint8_t)); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &proto, sizeof(uint8_t)); expr_set_reg_verdict(r, NF_ACCEPT); debug_rule(r); return r; } /* * Create common parts for the filter rules (IPv4 or IPv6) * eport is ignored * iport is the destination port of the filter rule * rport is the (optional) source port of the rule */ struct nftnl_rule * rule_set_filter_common(struct nftnl_rule *r, uint8_t family, const char * ifname, uint8_t proto, unsigned short eport, unsigned short iport, unsigned short rport, const char *descr, const char *handle) { uint16_t dport, sport; uint64_t handle_num; uint32_t if_idx; UNUSED(eport); nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family); nftnl_rule_set_str(r, NFTNL_RULE_TABLE, nft_table); nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, nft_forward_chain); if (descr != NULL && *descr != '\0') { nftnl_rule_set_data(r, NFTNL_RULE_USERDATA, descr, strlen(descr)); } if (handle != NULL) { handle_num = atoll(handle); nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, handle_num); } if (ifname != NULL) { if_idx = (uint32_t)if_nametoindex(ifname); expr_add_meta(r, NFT_META_IIF, NFT_REG_1); expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &if_idx, sizeof(uint32_t)); } /* Destination Port */ dport = htons(iport); if (proto == IPPROTO_TCP) { expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct tcphdr, dest), sizeof(uint16_t)); } else if (proto == IPPROTO_UDP) { expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct udphdr, dest), sizeof(uint16_t)); } expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &dport, sizeof(uint16_t)); /* Source Port */ if (rport != 0) { sport = htons(rport); if (proto == IPPROTO_TCP) { expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct tcphdr, source), sizeof(uint16_t)); } else if (proto == IPPROTO_UDP) { expr_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, offsetof(struct udphdr, source), sizeof(uint16_t)); } expr_add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &sport, sizeof(uint16_t)); } return r; } struct nftnl_rule * rule_del_handle(rule_t *rule) { struct nftnl_rule *r = NULL; r = nftnl_rule_alloc(); if (r == NULL) { log_error("nftnl_rule_alloc() FAILED"); return NULL; } if (rule->type == RULE_NAT) { // NAT Family is not chain/rule family nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, nft_nat_family); } else { nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, rule->family); } nftnl_rule_set_str(r, NFTNL_RULE_TABLE, rule->table); nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, rule->chain); nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, rule->handle); return r; } static void nft_mnl_batch_put(char *buf, uint16_t type, uint32_t seq) { struct nlmsghdr *nlh; struct nfgenmsg *nfg; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = type; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = seq; nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); nfg->nfgen_family = AF_INET; nfg->version = NFNETLINK_V0; nfg->res_id = NFNL_SUBSYS_NFTABLES; } int nft_send_rule(struct nftnl_rule * rule, uint16_t cmd, enum rule_chain_type chain_type) { int result = -1; struct nlmsghdr *nlh; struct mnl_nlmsg_batch *batch; char buf[MNL_SOCKET_BUFFER_SIZE*2]; batch = start_batch(buf, MNL_SOCKET_BUFFER_SIZE); if (batch != NULL) { switch (chain_type) { case RULE_CHAIN_FILTER: rule_list_filter_validate = RULE_CACHE_INVALID; break; case RULE_CHAIN_PEER: rule_list_peer_validate = RULE_CACHE_INVALID; break; case RULE_CHAIN_REDIRECT: rule_list_redirect_validate = RULE_CACHE_INVALID; break; } nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), cmd, nftnl_rule_get_u32(rule, NFTNL_RULE_FAMILY), NLM_F_APPEND|NLM_F_CREATE|NLM_F_ACK, mnl_seq++); nftnl_rule_nlmsg_build_payload(nlh, rule); nftnl_rule_free(rule); result = send_batch(batch); if (result < 0) { syslog(LOG_ERR, "%s(%p, %d, %d) send_batch failed %d", "nft_send_rule", rule, (int)cmd, (int)chain_type, result); } } return result; } int table_op( enum nf_tables_msg_types op, uint16_t family, const char * name) { int result; struct nlmsghdr *nlh; struct mnl_nlmsg_batch *batch; char buf[MNL_SOCKET_BUFFER_SIZE*2]; struct nftnl_table *table; // log_debug("(%d, %d, %s)", op, family, name); table = nftnl_table_alloc(); if (table == NULL) { log_error("out of memory: %m"); result = -1; } else { nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, family); nftnl_table_set_str(table, NFTNL_TABLE_NAME, name); batch = start_batch(buf, MNL_SOCKET_BUFFER_SIZE); if (batch == NULL) { log_error("out of memory: %m"); result = -2; } else { nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), op, family, (op == NFT_MSG_NEWTABLE ? NLM_F_CREATE : 0) | NLM_F_ACK, mnl_seq++); nftnl_table_nlmsg_build_payload(nlh, table); result = send_batch(batch); if (result < 0) { syslog(LOG_ERR, "%s(%d, %d, %s) send_batch failed %d", "table_op", (int)op, (int)family, name, result); } } nftnl_table_free(table); } return result; } /* * return values : * -2 : out of memory (nftnl_chain_alloc) * -3 : out of memory (start batch) * -4 : failed to build header */ int chain_op(enum nf_tables_msg_types op, uint16_t family, const char * table, const char * name, const char * type, uint32_t hooknum, signed int priority ) { int result = -1; struct nlmsghdr *nlh; struct mnl_nlmsg_batch *batch; char buf[MNL_SOCKET_BUFFER_SIZE*2]; struct nftnl_chain *chain; // log_debug("(%d, %d, %s, %s, %s, %d, %d)", op, family, table, name, type, hooknum, priority); chain = nftnl_chain_alloc(); if (chain == NULL) { log_error("out of memory: %m"); result = -2; } else { nftnl_chain_set_u32(chain, NFTNL_CHAIN_FAMILY, family); nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, table); nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, name); if (op == NFT_MSG_NEWCHAIN) { nftnl_chain_set_str(chain, NFTNL_CHAIN_TYPE, type); nftnl_chain_set_u32(chain, NFTNL_CHAIN_HOOKNUM, hooknum); nftnl_chain_set_s32(chain, NFTNL_CHAIN_PRIO, priority); } batch = start_batch(buf, MNL_SOCKET_BUFFER_SIZE); if (batch == NULL) { log_error("out of memory: %m"); result = -3; } else { nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), op, family, (op == NFT_MSG_NEWCHAIN ? NLM_F_CREATE : 0) | NLM_F_ACK, mnl_seq++); if (nlh == NULL) { log_error("failed to build header: %m"); result = -4; } else { nftnl_chain_nlmsg_build_payload(nlh, chain); result = send_batch(batch); if (result < 0) { syslog(LOG_ERR, "%s(%d, %d, %s, %s, %s, %u, %d) send_batch failed %d", "chain_op", (int)op, (int)family, table, name, type, hooknum, priority, result); } } } nftnl_chain_free(chain); } return result; } /** * the buffer that you have to use to store the batch must be double * of MNL_SOCKET_BUFFER_SIZE * @see https://www.netfilter.org/projects/libmnl/doxygen/html/group__batch.html */ struct mnl_nlmsg_batch * start_batch(char *buf, size_t buf_size) { struct mnl_nlmsg_batch *result; mnl_seq = time(NULL); if (mnl_sock == NULL) { log_error("netlink not connected"); result = NULL; } else { result = mnl_nlmsg_batch_start(buf, buf_size); if (result != NULL) { nft_mnl_batch_put(mnl_nlmsg_batch_current(result), NFNL_MSG_BATCH_BEGIN, mnl_seq++); mnl_nlmsg_batch_next(result); } } return result; } /** * return codes : * 0 : OK * -1 : netlink not connected * -2 : mnl_socket_sendto() error * -3 : mnl_socket_recvfrom() error * -4 : mnl_cb_run() error */ int send_batch(struct mnl_nlmsg_batch *batch) { int ret; ssize_t n; char buf[MNL_SOCKET_BUFFER_SIZE]; mnl_nlmsg_batch_next(batch); nft_mnl_batch_put(mnl_nlmsg_batch_current(batch), NFNL_MSG_BATCH_END, mnl_seq++); mnl_nlmsg_batch_next(batch); if (mnl_sock == NULL) { log_error("netlink not connected"); return -1; } n = mnl_socket_sendto(mnl_sock, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)); if (n == -1) { log_error("mnl_socket_sendto() FAILED: %m"); return -2; } mnl_nlmsg_batch_stop(batch); do { n = mnl_socket_recvfrom(mnl_sock, buf, sizeof(buf)); if (n == -1) { log_error("mnl_socket_recvfrom() FAILED: %m"); return -3; } else if (n == 0) { break; } ret = mnl_cb_run(buf, n, 0, mnl_portid, NULL, NULL); if (ret <= -1 /*== MNL_CB_ERROR*/) { syslog(LOG_ERR, "%s: mnl_cb_run returned %d", "send_batch", ret); return -4; } } while (ret >= 1 /*== MNL_CB_OK*/); /* ret == MNL_CB_STOP */ return 0; } miniupnpd-2.3.7/netfilter_nft/nftnlrdr_misc.h010064400017500000024000000105271457642072000206040ustar00nanardstaff/* $Id: nftnlrdr_misc.h,v 1.12 2024/03/19 23:35:54 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2015 Tomofumi Hayashi * (c) 2019 Paul Chambers * (c) 2020-2024 Thomas Bernard * * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution. */ #include extern const char * nft_table; extern const char * nft_nat_table; extern const char * nft_prerouting_chain; extern const char * nft_postrouting_chain; extern const char * nft_forward_chain; extern int nft_nat_family; extern int nft_ipv4_family; extern int nft_ipv6_family; #define NFT_DESCR_SIZE 1024 enum rule_reg_type { RULE_REG_NONE, RULE_REG_IIF, RULE_REG_OIF, RULE_REG_IP_SRC_ADDR, RULE_REG_IP_DEST_ADDR, RULE_REG_IP_SD_ADDR, /* source & dest */ RULE_REG_IP6_SRC_ADDR, RULE_REG_IP6_DEST_ADDR, RULE_REG_IP6_SD_ADDR, /* source & dest */ RULE_REG_IP_PROTO, RULE_REG_IP6_PROTO, RULE_REG_TCP_SPORT, RULE_REG_TCP_DPORT, RULE_REG_TCP_SD_PORT, /* source & dest */ RULE_REG_IMM_VAL, /* immediate */ RULE_REG_MAX, }; enum rule_type { RULE_NONE, RULE_NAT, RULE_FILTER, RULE_COUNTER, }; enum rule_chain_type { RULE_CHAIN_FILTER, RULE_CHAIN_PEER, RULE_CHAIN_REDIRECT, }; typedef struct rule_t { LIST_ENTRY(rule_t) entry; char * table; char * chain; uint64_t handle; enum rule_type type; uint32_t nat_type; uint32_t filter_action; uint32_t family; uint32_t ingress_ifidx; uint32_t egress_ifidx; in_addr_t saddr; struct in6_addr saddr6; uint16_t sport; in_addr_t daddr; struct in6_addr daddr6; uint16_t dport; in_addr_t nat_addr; uint16_t nat_port; uint8_t proto; enum rule_reg_type reg1_type; enum rule_reg_type reg2_type; uint32_t reg1_val; uint32_t reg2_val; uint64_t packets; uint64_t bytes; char * desc; uint32_t desc_len; } rule_t; LIST_HEAD(rule_list, rule_t); extern struct rule_list head_filter; extern struct rule_list head_redirect; extern struct rule_list head_peer; /** called at initialization. * establishes persistent connection to mnl/netfilter socket, needs elevated privilege */ int nft_mnl_connect(void); /** called at shutdown, to release the mnl/netfilter socket */ void nft_mnl_disconnect(void); #ifdef DEBUG void print_rule(const char *func, int line, const struct nftnl_rule *rule); void print_redirect_rules(const char * ifname); #define debug_rule(rule) do { print_rule(__func__, __LINE__, rule); } while (0) #else #define debug_rule(rule) #endif int nft_send_rule(struct nftnl_rule * rule, uint16_t cmd, enum rule_chain_type type); struct nftnl_rule * rule_set_dnat(uint8_t family, const char * ifname, uint8_t proto, in_addr_t rhost, unsigned short eport, in_addr_t ihost, uint32_t iport, const char *descr, const char *handle); struct nftnl_rule * rule_set_snat(uint8_t family, uint8_t proto, in_addr_t rhost, unsigned short rport, in_addr_t ehost, unsigned short eport, in_addr_t ihost, unsigned short iport, const char *descr, const char *handle); struct nftnl_rule * rule_set_filter(uint8_t family, const char * ifname, uint8_t proto, in_addr_t rhost, in_addr_t iaddr, unsigned short eport, unsigned short iport, unsigned short rport, const char * descr, const char *handle); struct nftnl_rule * rule_set_filter6(uint8_t family, const char * ifname, uint8_t proto, struct in6_addr *rhost6, struct in6_addr *iaddr6, unsigned short eport, unsigned short iport, unsigned short rport, const char *descr, const char *handle); struct nftnl_rule * rule_set_filter_common(struct nftnl_rule *r, uint8_t family, const char * ifname, uint8_t proto, unsigned short eport, unsigned short iport, unsigned short rport, const char *descr, const char *handle); struct nftnl_rule *rule_del_handle(rule_t *r); int refresh_nft_cache_filter(void); int refresh_nft_cache_redirect(void); int refresh_nft_cache_peer(void); int refresh_nft_cache(struct rule_list *head, const char *table, const char *chain, uint32_t family, enum rule_type type); int table_op(enum nf_tables_msg_types op, uint16_t family, const char * name); int chain_op(enum nf_tables_msg_types op, uint16_t family, const char * table, const char * name, const char * type, uint32_t hooknum, signed int priority ); struct mnl_nlmsg_batch * start_batch( char *buf, size_t buf_size); int send_batch(struct mnl_nlmsg_batch * batch); miniupnpd-2.3.7/netfilter_nft/README.md010064400017500000024000000045171457641723600170600ustar00nanardstaffMiniupnpd nftables support by Tomofumi Hayashi (s1061123@gmail.com). ## Supported Features - IPv4 NAT/Filter add/del. ## How to build miniupnpd with nftables: Run 'configure' command with '--firewall=nftables', `./configure --firewall=nftables && make` ## How to Run Please run 'netfilter_nft/scripts/nft_init.sh' to add miniupnpd chain. `sudo ./netfilter_nft/scripts/nft_init.sh` ## FAQ I will add this section when I get question. Comments and Questions are welcome ;) ### Custom Chains NFTables is very flexible but it comes with some restrictions because of that. If there is a second filter chain than all packets that were passed before with the miniupnpd chain will be reevaluated. This also means that if the chain is a drop chain you loose the packets. In that case you really want to use a custom chain and jump to it in your filter chain. miniupnpd should save all accept rules in that custom chain. For NAT it is the same, a second chain will also evaluate the packets again and therefore it is possible that a second SNAT or DNAT is performed. The following is used in miniupnpd for a table setup but it can be customized: table inet filter { chain forward { type filter hook forward priority 0; policy drop; # miniupnpd jump miniupnpd # Add other rules here } # miniupnpd chain miniupnpd { } chain prerouting { type nat hook prerouting priority -100; policy accept; # miniupnpd jump prerouting_miniupnpd # Add other rules here } chain postrouting { type nat hook postrouting priority 100; policy accept; # miniupnpd jump postrouting_miniupnpd # Add other rules here } chain prerouting_miniupnpd { } chain postrouting_miniupnpd { } } and the following config settings can be used to change the tables and chains : upnp_table_name=filter upnp_nat_table_name=filter upnp_forward_chain=miniupnpd upnp_nat_chain=prerouting_miniupnpd upnp_nat_postrouting_chain=postrouting_miniupnpd If you need to use the old ipv4 NAT family style set the flag upnp_nftables_family_split to yes. Default is to use INET family which combines IPv4 and IPv6. miniupnpd-2.3.7/netfilter_nft/test_nfct_get.c010064400017500000024000000030541350621543700205570ustar00nanardstaff/* $Id: test_nfct_get.c,v 1.2 2019/06/30 19:49:18 nanard Exp $ */ #include #include #include "nfct_get.c" int main(int argc, char *argv[]) { struct sockaddr_storage src, dst, ext; char buff[INET6_ADDRSTRLEN]; if (argc!=5) { fprintf(stderr, "Usage: %s SRC_IP SRC_PORT DST_IP DST_PORT\n", argv[0]); return 1; } openlog("test_nfct_get", LOG_PERROR|LOG_CONS, LOG_LOCAL0); if (1 != inet_pton(AF_INET, argv[1], &((struct sockaddr_in*)&src)->sin_addr)) { if (1 != inet_pton(AF_INET6, argv[1], &((struct sockaddr_in6*) &src)->sin6_addr)) { perror("bad input param"); } else { ((struct sockaddr_in6*)(&src))->sin6_port = htons(atoi(argv[2])); src.ss_family = AF_INET6; } } else { ((struct sockaddr_in*)(&src))->sin_port = htons(atoi(argv[2])); src.ss_family = AF_INET; } if (1 != inet_pton(AF_INET, argv[3], &((struct sockaddr_in*)&dst)->sin_addr)) { if (1 != inet_pton(AF_INET6, argv[3], &((struct sockaddr_in6*) &dst)->sin6_addr)) { perror("bad input param"); } else { ((struct sockaddr_in6*)(&dst))->sin6_port = htons(atoi(argv[4])); dst.ss_family = AF_INET6; } } else { ((struct sockaddr_in*)(&dst))->sin_port = htons(atoi(argv[4])); dst.ss_family = AF_INET; } if (get_nat_ext_addr((struct sockaddr*)&src, (struct sockaddr*)&dst, IPPROTO_TCP, &ext)) { printf("Ext address %s:%d\n", inet_ntop(src.ss_family, &((struct sockaddr_in*)&ext)->sin_addr, buff, sizeof(buff)), ntohs(((struct sockaddr_in*)(&ext))->sin_port)); } else { printf("no entry\n"); } return 0; } miniupnpd-2.3.7/netfilter_nft/testnftnlrdr.c010064400017500000024000000055411457642072000204640ustar00nanardstaff/* $Id: testnftnlrdr.c,v 1.5 2024/03/19 23:35:54 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2023 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include /* for PRIu64 */ #include #include #include #include #include #include #include "nftnlrdr.h" #include "nftnlrdr_misc.h" #include "../commonrdr.h" #ifndef PRIu64 #define PRIu64 "llu" #endif static int add_filter_rule(int proto, const char * rhost, const char * iaddr, unsigned short iport) { return add_filter_rule2(NULL/* ifname */, rhost, iaddr, 0/* eport */, iport, proto, NULL/* desc */); } static int addnatrule(int proto, unsigned short eport, const char * iaddr, unsigned short iport, const char * rhost) { return add_redirect_rule2(NULL/* ifname */, rhost, eport, iaddr, iport, proto, NULL/* desc */, 0/* timestamp */); } int main(int argc, char ** argv) { unsigned short eport, iport; const char * iaddr; if(argc<4) { printf("Usage %s \n", argv[0]); return -1; } openlog("testnftnlrdr", LOG_PERROR|LOG_CONS, LOG_LOCAL0); if (init_redirect() < 0) { fprintf(stderr, "init_redirect() FAILED\n"); return -1; } eport = (unsigned short)atoi(argv[1]); iaddr = argv[2]; iport = (unsigned short)atoi(argv[3]); printf("trying to redirect port %hu to %s:%hu\n", eport, iaddr, iport); if(addnatrule(IPPROTO_TCP, eport, iaddr, iport, NULL) < 0) { printf("addnatrule() failed!\n"); return -1; } if(add_filter_rule(IPPROTO_TCP, NULL, iaddr, iport) < 0) { printf("add_filter_rule() failed!\n"); return -1; } /* test */ { unsigned short p1, p2; char addr[16]; int proto2; char desc[256]; char rhost[256]; unsigned int timestamp; u_int64_t packets, bytes; desc[0] = '\0'; printf("test0\n"); if(get_redirect_rule_by_index(0/* index */, NULL/* ifname */, &p1/* eport */, addr, sizeof(addr), &p2/* iport */, &proto2, desc, sizeof(desc), rhost, sizeof(rhost), ×tamp, &packets, &bytes) < 0) { printf("rule not found\n"); } else { printf("redirected port %hu to %s:%hu proto %d packets=%" PRIu64 " bytes=%" PRIu64 "\n", p1, addr, p2, proto2, packets, bytes); printf("timestamp=%u desc=\"%s\"\n", timestamp, desc); } printf("test\n"); } printf("trying to list nat rules :\n"); print_redirect_rules(argv[1]); printf("deleting\n"); delete_redirect_and_filter_rules(eport, IPPROTO_TCP); shutdown_redirect(); return 0; } miniupnpd-2.3.7/netfilter_nft/tiny_nf_nat.h010064400017500000024000000015361252037010400202330ustar00nanardstaff/* $Id: tiny_nf_nat.h,v 1.1 2015/04/30 09:05:08 nanard Exp $ */ /* Only what miniupnpd needs, until linux-libc-dev gains nf_nat.h */ #ifndef TINY_NF_NAT_H #define TINY_NF_NAT_H #include #define IP_NAT_RANGE_MAP_IPS 1 #define IP_NAT_RANGE_PROTO_SPECIFIED 2 #define IP_NAT_RANGE_PROTO_RANDOM 4 #define IP_NAT_RANGE_PERSISTENT 8 union nf_conntrack_man_proto { __be16 all; struct { __be16 port; } tcp; struct { __be16 port; } udp; struct { __be16 id; } icmp; struct { __be16 port; } dccp; struct { __be16 port; } sctp; struct { __be16 key; } gre; }; struct nf_nat_range { unsigned int flags; __be32 min_ip, max_ip; union nf_conntrack_man_proto min, max; }; struct nf_nat_multi_range_compat { unsigned int rangesize; struct nf_nat_range range[1]; }; #define nf_nat_multi_range nf_nat_multi_range_compat #endif /*TINY_NF_NAT_H*/ miniupnpd-2.3.7/netfilter_nft/scripts/nft_delete_chain.sh010075500017500000024000000003511454540235400230610ustar00nanardstaff#!/bin/sh . "$(dirname "$0")/miniupnpd_functions.sh" # Prerouting $NFT delete chain inet $NAT_TABLE $PREROUTING_CHAIN # Postrouting $NFT delete chain inet $NAT_TABLE $POSTROUTING_CHAIN # Filter $NFT delete chain inet $TABLE $CHAIN miniupnpd-2.3.7/netfilter_nft/scripts/nft_flush.sh010075500017500000024000000003021454540235400215720ustar00nanardstaff#!/bin/sh . "$(dirname "$0")/miniupnpd_functions.sh" $NFT flush chain inet $TABLE $CHAIN $NFT flush chain inet $NAT_TABLE $PREROUTING_CHAIN $NFT flush chain inet $NAT_TABLE $POSTROUTING_CHAIN miniupnpd-2.3.7/netfilter_nft/scripts/nft_init.sh010075500017500000024000000023251454540235400214230ustar00nanardstaff#!/bin/sh # # establish the chains that miniupnpd will update dynamically # # 'add' doesn't raise an error if the object already exists. 'create' does. # . "$(dirname "$0")/miniupnpd_functions.sh" $NFT --check list table inet $TABLE > /dev/null 2>&1 if [ $? -eq "0" ] then echo "Table $TABLE already exists" exit 0 fi echo "Creating nftables structure" cat > /tmp/miniupnpd.nft <> /tmp/miniupnpd.nft <> /tmp/miniupnpd.nft < /dev/null 2>&1 if [ $? -eq "0" ] then # then remove the table itself echo "Remove miniupnpd table" $NFT delete table inet $TABLE fi if [ "$TABLE" != "$NAT_TABLE" ] then $NFT --check list table inet $NAT_TABLE > /dev/null 2>&1 if [ $? -eq "0" ]; then # then remove the table itself echo "Remove miniupnpd nat table" $NFT delete table inet $NAT_TABLE fi fi miniupnpd-2.3.7/testssdppktgen.c010064400017500000024000000070351407602046500161470ustar00nanardstaff/* $Id: testssdppktgen.c,v 1.2 2021/05/21 22:05:17 nanard Exp $ */ #include #include #include #include "config.h" #ifdef DYNAMIC_OS_VERSION #include #include #endif #include "miniupnpdpath.h" #include "upnphttp.h" #include "macros.h" #define SSDP_PORT 1900 const char uuidvalue_igd[] = "uuid:12345678-0000-0000-0000-000000abcd01"; unsigned upnp_bootid; unsigned upnp_configid; #ifdef DYNAMIC_OS_VERSION char * os_version; #endif static int MakeSSDPPacket(const char * dest_str, const char * host, unsigned short http_port, #ifdef ENABLE_HTTPS unsigned short https_port, #endif const char * nt, const char * suffix, const char * usn1, const char * usn2, const char * usn3, unsigned int lifetime) { char bufr[SSDP_PACKET_MAX_LEN]; int l; l = snprintf(bufr, sizeof(bufr), "NOTIFY * HTTP/1.1\r\n" "HOST: %s:%d\r\n" "CACHE-CONTROL: max-age=%u\r\n" "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n" #ifdef ENABLE_HTTPS "SECURELOCATION.UPNP.ORG: https://%s:%u" ROOTDESC_PATH "\r\n" #endif "SERVER: " MINIUPNPD_SERVER_STRING "\r\n" "NT: %s%s\r\n" "USN: %s%s%s%s\r\n" "NTS: ssdp:alive\r\n" "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */ "01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */ "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "\r\n", dest_str, SSDP_PORT, /* HOST: */ lifetime, /* CACHE-CONTROL: */ host, (unsigned int)http_port, /* LOCATION: */ #ifdef ENABLE_HTTPS host, (unsigned int)https_port, /* SECURE-LOCATION: */ #endif #ifdef DYNAMIC_OS_VERSION os_version, #endif nt, suffix, /* NT: */ usn1, usn2, usn3, suffix, /* USN: */ upnp_bootid, /* 01-NLS: */ upnp_bootid, /* BOOTID.UPNP.ORG: */ upnp_configid ); /* CONFIGID.UPNP.ORG: */ if(l<0) { syslog(LOG_ERR, "%s: snprintf error", "MakeSSDPPacket()"); return -1; } else if((unsigned int)l >= sizeof(bufr)) { syslog(LOG_WARNING, "%s: truncated output (%u>=%u)", "MakeSSDPPacket()", (unsigned)l, (unsigned)sizeof(bufr)); l = sizeof(bufr) - 1; return -1; } syslog(LOG_DEBUG, "%s", bufr); return 0; } int main(int argc, char * * argv) { int r; #ifdef DYNAMIC_OS_VERSION struct utsname utsname; #endif UNUSED(argc); UNUSED(argv); openlog("testssdppktgen", LOG_CONS|LOG_PERROR, LOG_USER); #ifdef DYNAMIC_OS_VERSION if (uname(&utsname) < 0) { syslog(LOG_ERR, "uname(): %m"); os_version = strdup("unknown"); } else { os_version = strdup(utsname.release); syslog(LOG_INFO, "OS_VERSION : %s", os_version); } #endif upnp_bootid = (unsigned)time(NULL); upnp_configid = 1234567890; r = MakeSSDPPacket("123.456.789.123", "222.222.222.222", 12345, #ifdef ENABLE_HTTPS 54321, #endif /* ENABLE_HTTPS */ "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:", "1", uuidvalue_igd, "", "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:", 1234567890); if(r < 0) return 1; #ifdef ENABLE_IPV6 r = MakeSSDPPacket("[1234:5678:abcd:ef00:1234:5678:abcd:ef00]", "[1000:2000:3000:4000:5000:6000:7000:8000]", 12345, #ifdef ENABLE_HTTPS 54321, #endif /* ENABLE_HTTPS */ "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:", "1", uuidvalue_igd, "", "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:", 1234567890); if(r < 0) return 1; #endif /* ENABLE_IPV6 */ return 0; } miniupnpd-2.3.7/testminissdp.c010064400017500000024000000036661323023102100156000ustar00nanardstaff/* $Id: testminissdp.c,v 1.3 2018/01/15 16:46:48 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2017-2018 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 "config.h" #include "minissdp.h" #include "upnpglobalvars.h" void test(const char * buffer, size_t n) { int s = 0; struct sockaddr_in dummy_sender; memset(&dummy_sender, 0, sizeof(dummy_sender)); dummy_sender.sin_family = AF_INET; ProcessSSDPData(s, buffer, n, (struct sockaddr *)&dummy_sender, 0, #ifdef ENABLE_HTTPS 80, 443 #else 80 #endif ); } int main(int argc, char * * argv) { FILE * f = NULL; char buffer[1500]; size_t n = 0; struct lan_addr_s * lan_addr; if((argc > 1) && ((strcmp(argv[1], "--help") == 0) || (strcmp(argv[1], "-h") == 0))) { fprintf(stderr, "Usage:\t%s [file]\nIf no file is specified, the program is reading the standard input.\n", argv[0]); return 1; } openlog("testminissdp", LOG_CONS|LOG_PERROR, LOG_USER); /* populate lan_addrs */ LIST_INIT(&lan_addrs); lan_addr = (struct lan_addr_s *) malloc(sizeof(struct lan_addr_s)); memset(lan_addr, 0, sizeof(struct lan_addr_s)); LIST_INSERT_HEAD(&lan_addrs, lan_addr, list); if(argc > 1) { f = fopen(argv[1], "rb"); if(f == NULL) { syslog(LOG_ERR, "error opening file %s", argv[1]); return 1; } } n = fread(buffer, 1, sizeof(buffer), f ? f : stdin); if(n <= 0) { syslog(LOG_ERR, "error reading"); return 1; } test(buffer, n); if(f) fclose(f); /* free memory */ while(lan_addrs.lh_first != NULL) { lan_addr = lan_addrs.lh_first; LIST_REMOVE(lan_addrs.lh_first, list); free(lan_addr); } return 0; } miniupnpd-2.3.7/upnpstun.c010064400017500000024000000417231404660122500147570ustar00nanardstaff/* $Id: upnpstun.c,v 1.8 2021/05/11 21:49:05 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2020 Thomas Bernard * (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 #include #ifndef TEST_LINUX_DEBUG_APP #include "config.h" #endif #include "upnputils.h" #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); #define syslog(priority, format, ...) do { switch(priority) { case LOG_ERR: printf("Error: "); break; case LOG_WARNING: printf("Warning: "); } printf(format, ##__VA_ARGS__); putchar('\n'); } while (0) #endif /* Generate random STUN Transaction Id */ static void generate_transaction_id(unsigned char transaction_id[12]) { size_t 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; /* RFC5389 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]; int r; 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; r = getaddrinfo(stun_host, service, &hints, &result); if (r != 0) { syslog(LOG_ERR, "%s: getaddrinfo(%s, %s, ...) failed : %s", "resolve_stun_host", stun_host, service, gai_strerror(r)); 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) { syslog(LOG_WARNING, "%s: failed to resolve IPv4 address for %s:%s", "resolve_stun_host", stun_host, service); errno = EHOSTUNREACH; return -1; } else { char addr_str[48]; if (sockaddr_to_string((struct sockaddr *)sock_addr, addr_str, sizeof(addr_str)) > 0) { syslog(LOG_DEBUG, "%s: %s:%s => %s", "resolve_stun_host", stun_host, service, addr_str); } } 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) { syslog(LOG_ERR, "%s: socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP): %m", "stun_socket"); 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) { syslog(LOG_ERR, "%s: bind(): %m", "stun_socket"); close(fd); return -1; } addr_len = sizeof(local_addr); if (getsockname(fd, (struct sockaddr *)&local_addr, &addr_len) != 0) { syslog(LOG_ERR, "%s: getsockname(): %m", "stun_socket"); 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 < 0) { syslog(LOG_ERR, "%s: recvfrom(): %m", "receive_stun_response"); return 0; } if (peer_addr_len != sizeof(*peer_addr)) { syslog(LOG_ERR, "%s: recvfrom(): peer_addr_len mismatch", "receive_stun_response"); return 0; } if (len < 20) { syslog(LOG_WARNING, "%s: response too short : %ld", "receive_stun_response", (long)len); 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) { syslog(LOG_WARNING, "%s: STUN message type is 0x%02x%02x", "receive_stun_response", buffer[0], buffer[1]); return 0; } if (memcmp(buffer+8, transaction_id, 12) != 0) { syslog(LOG_WARNING, "%s: transaction id mismatch", "receive_stun_response"); 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); } syslog(LOG_DEBUG, "%s: waiting %ld secs and %ld usecs", "wait_for_stun_responses", (long)timeout.tv_sec, (long)timeout.tv_usec); ret = select(max_fd+1, &fdset, NULL, NULL, &timeout); if (ret < 0) { if (errno == EINTR) continue; syslog(LOG_ERR, "%s: select(): %m", "wait_for_stun_responses"); return -1; } if (ret == 0) { syslog(LOG_DEBUG, "%s: select(): no more responses", "wait_for_stun_responses"); 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]); syslog(LOG_DEBUG, "%s: received responses: %u", "wait_for_stun_responses", (unsigned)(!!lens[0] + !!lens[1] + !!lens[2] + !!lens[3])); 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; int have_xor_mapped_address; if (len < 20) return -1; syslog(LOG_DEBUG, "%s: Type 0x%04x, Length %hu, Magic Cookie %02x%02x%02x%02x", "parse_stun_response", ((uint16_t)buffer[0] << 8) + buffer[1], (uint16_t)((buffer[2] << 8) + buffer[3]), buffer[4], buffer[5], buffer[6], buffer[7]); /* Check that buffer is STUN message with class Response and Binding method */ if (buffer[0] != 0x01 || (buffer[1] & 0xEF) != 0x01) return -1; /* Check that STUN message is not longer as buffer length */ if (((size_t)buffer[2] << 8) + buffer[3] + 20 > len) { syslog(LOG_ERR, "%s: truncated STUN response", "parse_stun_response"); return -1; } ptr = buffer + 20; end = buffer + len; have_address = 0; have_xor_mapped_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) { syslog(LOG_WARNING, "%s: truncated attribute", "parse_stun_response"); break; } switch (attr_type) { case 0x0001: /* MAPPED-ADDRESS */ case 0x0020: /* XOR-MAPPED-ADDRESS (RFC 5389) */ case 0x8020: /* XOR-MAPPED-ADDRESS (2005 draft) */ /* Mapped Address or XOR Mapped Address */ if (attr_len == 8 && ptr[1] == 1) { /* IPv4 address */ if ((attr_type & 0x7fff) == 0x0020) { /* 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]; } syslog(LOG_DEBUG, "%s: %s %hhu.%hhu.%hhu.%hhu:%hu", "parse_stun_response", ((attr_type & 0x7fff) == 0x0020) ? "XOR-MAPPED-ADDRESS" : "MAPPED-ADDRESS", ptr[4], ptr[5], ptr[6], ptr[7], (uint16_t)((ptr[2] << 8) + ptr[3])); /* Prefer XOR Mapped Address, some NATs change IP addresses in UDP packets */ if (!have_xor_mapped_address) { 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]); } if ((attr_type & 0x7fff) == 0x0020) have_xor_mapped_address = 1; have_address = 1; } break; case 0x0009: /* ERROR-CODE */ if (attr_len >= 4) { syslog(LOG_WARNING, "%s: ERROR-CODE %u %.*s", "parse_stun_response", (unsigned)ptr[2] * 100 + ptr[3], attr_len - 4, ptr + 4); } break; case 0x0004: /* SOURCE-ADDRESS (RFC 3489) */ case 0x0005: /* CHANGED-ADDRESS (RFC 3489) */ case 0x802b: /* RESPONSE-ORIGIN (RFC 5780) */ case 0x802c: /* OTHER-ADDRESS (RFC 5780) */ if (attr_len == 8 && ptr[1] == 1) { syslog(LOG_DEBUG, "%s: %s %hhu.%hhu.%hhu.%hhu:%hu", "parse_stun_response", (attr_type == 0x0004) ? "SOURCE-ADDRESS" : (attr_type == 0x0005) ? "CHANGED-ADDRESS" : (attr_type == 0x802b) ? "RESPONSE-ORIGIN" : "OTHER-ADDRESS", ptr[4], ptr[5], ptr[6], ptr[7], (uint16_t)((ptr[2] << 8) + ptr[3])); } break; case 0x8022: /* SOFTWARE (RFC 5780) */ syslog(LOG_DEBUG, "%s: SOFTWARE %.*s", "parse_stun_response", attr_len, ptr); break; default: syslog(LOG_WARNING, "%s: unknown attribute type 0x%04x (len=%hu)", "parse_stun_response", attr_type, attr_len); } ptr += attr_len; } return have_address ? 0 : -1; } /* Perform main STUN operation, return external IP address and check * if host is behind restrictive, symmetric NAT or behind firewall. * Restrictive NAT means any NAT which do some filtering and * which is not static full-cone NAT 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_addr, mapped_addrs_count; 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; } syslog(LOG_INFO, "%s: local ports %hu %hu %hu %hu", "perform_stun", local_ports[0], local_ports[1], local_ports[2], local_ports[3]); /* Unblock local ports */ for (i = 0; i < 4; ++i) { if (add_filter_rule2(if_name, NULL, if_addr, local_ports[i], local_ports[i], IPPROTO_UDP, "stun test") < 0) { syslog(LOG_ERR, "%s: add_filter_rule2(..., %hu, ...) FAILED", "perform_stun", local_ports[i]); } } /* Send STUN requests and wait for responses */ for (j = 0; j < 3; ++j) { for (i = 0; i < 4; ++i) { ssize_t n; if (responses_lens[i]) continue; n = sendto(fds[i], requests[i], sizeof(requests[i]), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr)); if (n != sizeof(requests[i])) { syslog(LOG_ERR, "%s: #%d,%d sendto(): %m", "perform_stun", j, 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; have_mapped_addr = 0; mapped_addrs_count = 0; for (i = 0; i < 4; ++i) { if (parse_stun_response(responses[i], responses_lens[i], &mapped_addrs[i]) == 0) { mapped_addrs_count++; have_mapped_addr |= (1 << i); if (!have_ext_addr) { 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; } *restrictive_nat = 0; if (mapped_addrs_count < 4) { /* We have not received all four responses, * therefore NAT or firewall is doing some filtering */ syslog(LOG_NOTICE, "%s: %d response out of 4 received", "perform_stun", mapped_addrs_count); *restrictive_nat = 1; } 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 */ syslog(LOG_NOTICE, "%s: address changed", "perform_stun"); *restrictive_nat = 1; } for (i = 0; i < 4; ++i) { if (!(have_mapped_addr & (1 << i))) continue; 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 */ syslog(LOG_NOTICE, "%s: #%d external address or port changed", "perform_stun", i); *restrictive_nat = 1; } } /* Otherwise we are either directly connected or behind unrestricted full-cone NAT 1:1 without filtering */ /* There is no filtering, so port forwarding would work fine */ return 0; } #ifdef TEST_LINUX_DEBUG_APP /* This linux test application for debugging purposes can be compiled as: */ /* gcc upnpstun.c upnputils.o -o upnpstun -g3 -W -Wall -O2 -DTEST_LINUX_DEBUG_APP */ #include #include #include "upnpglobalvars.h" struct lan_addr_list lan_addrs; int runtime_flags = 0; time_t startup_time = 0; 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 (geteuid() != 0) { printf("You need to run this application as root\n"); 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 miniupnpd-2.3.7/upnpstun.h010064400017500000024000000006731331765274300147760ustar00nanardstaff/* 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 miniupnpd-2.3.7/Makefile.sunos010064400017500000024000000136401365604170100155250ustar00nanardstaff# $Id: Makefile.sunos,v 1.2 2020/05/10 16:51:52 nanard Exp $ # MiniUPnP project # http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ # Author: Thomas Bernard # # Makefile for miniupnpd (MiniUPnP daemon) # # This Makefile should work for SunOS/Solaris. # This Makefile is NOT compatible with GNU Make. # Linux users, please use Makefile.linux : # make -f Makefile.linux # # options can be passed to configure through CONFIG_OPTIONS : # $ CONFIG_OPTIONS="--ipv6 --igd2" make # CFLAGS = -pipe -Os #CFLAGS = -pipe -O -g -DDEBUG #CFLAGS += -ansi CFLAGS += -Wall CFLAGS += -W CFLAGS += -Wstrict-prototypes #CFLAGS += -Wdeclaration-after-statement #CFLAGS += -Wno-missing-field-initializers CFLAGS += -fno-common CFLAGS += -D_XOPEN_SOURCE CFLAGS += -D_XOPEN_SOURCE_EXTENDED=1 CFLAGS += -D__EXTENSIONS__ CC = gcc RM = rm -f MV = mv INSTALL = install STRIP = strip # OSNAME and FWNAME are used for building OS or FW dependent code. OSNAME :sh = uname -s ARCH :sh = uname -m FWNAME = ipf # Solaris specific CFLAGS CFLAGS :sh += echo "-DSOLARIS2=`uname -r | cut -d. -f2`" # "amd64" CFLAGS += -m64 -mcmodel=kernel -mno-red-zone -ffreestanding # "sparc64" #CFLAGS += -m64 -mcmodel=medlow LDFLAGS += -m64 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 MACOBJS = mac/getifstats.o bsd/ifacewatcher.o bsd/getroute.o PFOBJS = pf/obsdrdr.o pf/pfpinhole.o IPFOBJS = ipf/ipfrdr.o IPFWOBJS = ipfw/ipfwrdr.o ipfw/ipfwaux.o MISCOBJS = upnpreplyparse.o minixml.o ALLOBJS = $(STDOBJS) $(MISCOBJS) ALLOBJS += $(SUNOSOBJS) TESTGETIFSTATSOBJS = testgetifstats.o solaris/getifstats.o TESTGETROUTEOBJS = testgetroute.o upnputils.o bsd/getroute.o #.if $(FWNAME) == "pf" #ALLOBJS += $(PFOBJS) #.elif $(FWNAME) == "ipfw" #ALLOBJS += $(IPFWOBJS) #.else ALLOBJS += $(IPFOBJS) #.endif 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 bsd/getroute.o TESTPORTINUSEOBJS = testportinuse.o portinuse.o getifaddr.o EXECUTABLES = miniupnpd testupnpdescgen testgetifstats \ testupnppermissions miniupnpdctl \ testgetifaddr testgetroute testasyncsendto \ testportinuse testssdppktgen LIBS += -lsocket -lnsl -lkstat -lresolv LIBS += -luuid LIBS += -lssl -lcrypto # set PREFIX variable to install in the wanted place INSTALLBINDIR = $(PREFIX)/sbin INSTALLETCDIR = $(PREFIX)/etc # INSTALLMANDIR = $(PREFIX)/man INSTALLMANDIR = /usr/share/man all: $(EXECUTABLES) clean: $(RM) $(STDOBJS) $(BSDOBJS) $(SUNOSOBJS) $(MACOBJS) $(EXECUTABLES) \ testupnpdescgen.o \ $(MISCOBJS) config.h testgetifstats.o testupnppermissions.o \ miniupnpdctl.o testgetifaddr.o testgetroute.o testasyncsendto.o \ testportinuse.o \ $(PFOBJS) $(IPFOBJS) $(IPFWOBJS) $(RM) validateupnppermissions validategetifaddr validatessdppktgen install: miniupnpd genuuid $(STRIP) miniupnpd $(INSTALL) -d $(DESTDIR)$(INSTALLBINDIR) $(INSTALL) -m 755 miniupnpd $(DESTDIR)$(INSTALLBINDIR) $(INSTALL) -d $(DESTDIR)$(INSTALLETCDIR) $(INSTALL) -b miniupnpd.conf $(DESTDIR)$(INSTALLETCDIR) # TODO : install man page correctly # $(INSTALL) -d $(INSTALLMANDIR) # $(INSTALL) miniupnpd.8 $(INSTALLMANDIR)/cat8/miniupnpd.0 # genuuid is using the uuid cli tool available under OpenBSD 4.0 in # the uuid-1.5.0 package # any other cli tool returning a uuid on stdout should work. UUID :sh = if which uuidgen 2>&1 > /dev/null; then \ echo `uuidgen` ; \ elif which uuid 2>&1 > /dev/null; then \ echo `uuid` ; \ else echo "00000000-0000-0000-0000-000000000000"; \ fi # bash or ksh SH :sh = which bash || which ksh genuuid: $(MV) miniupnpd.conf miniupnpd.conf.before sed -e "s/^uuid=[-0-9a-fA-F]*/uuid=$(UUID)/" miniupnpd.conf.before > miniupnpd.conf $(RM) miniupnpd.conf.before check: validateupnppermissions validategetifaddr validatessdppktgen validateupnppermissions: testupnppermissions testupnppermissions.sh $(SH) ./testupnppermissions.sh touch $@ validategetifaddr: testgetifaddr testgetifaddr.sh ./testgetifaddr.sh touch $@ validatessdppktgen: testssdppktgen ./testssdppktgen touch $@ depend: config.h mkdep $(ALLOBJS:.o=.c) testupnpdescgen.c testgetifstats.c \ testupnppermissions.c miniupnpdctl.c testgetifaddr.c \ testgetroute.c testportinuse.c testasyncsendto.c \ testssdppktgen.c miniupnpd: config.h $(ALLOBJS) $(CC) $(LDFLAGS) -o $@ $(ALLOBJS) $(LIBS) # BSDmake : # $(CC) $(LDFLAGS) -o $@ $> $(LIBS) miniupnpdctl: config.h $(MINIUPNPDCTLOBJS) $(CC) $(LDFLAGS) -o $@ $(MINIUPNPDCTLOBJS) -lsocket testupnpdescgen: config.h $(TESTUPNPDESCGENOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTUPNPDESCGENOBJS) $(LIBS) testgetifstats: config.h $(TESTGETIFSTATSOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTGETIFSTATSOBJS) $(LIBS) testgetifaddr: config.h $(TESTGETIFADDROBJS) $(CC) $(LDFLAGS) -o $@ $(TESTGETIFADDROBJS) $(LIBS) testupnppermissions: config.h $(TESTUPNPPERMISSIONSOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTUPNPPERMISSIONSOBJS) $(LIBS) testgetroute: config.h $(TESTGETROUTEOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTGETROUTEOBJS) $(LIBS) testasyncsendto: config.h $(TESTASYNCSENDTOOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTASYNCSENDTOOBJS) -lsocket -lnsl testportinuse: config.h $(TESTPORTINUSEOBJS) $(CC) $(LDFLAGS) -o $@ $(TESTPORTINUSEOBJS) $(LIBS) # gmake : # $(CC) $(CFLAGS) -o $@ $^ # BSDmake : # $(CC) $(CFLAGS) -o $@ $> config.h: configure VERSION ./configure $(CONFIG_OPTIONS) .SUFFIXES: .o .c .c.o: $(CC) $(CFLAGS) -c -o $@ $< # $(CC) $(CFLAGS) -c -o $(.TARGET) $(.IMPSRC) miniupnpd-2.3.7/netfilter_nft/scripts/nft_display.sh010064400017500000024000000003431454540235400221200ustar00nanardstaff#!/bin/sh . "$(dirname "$0")/miniupnpd_functions.sh" # Prerouting $NFT list chain inet $NAT_TABLE $PREROUTING_CHAIN # Postrouting $NFT list chain inet $NAT_TABLE $POSTROUTING_CHAIN # Filter $NFT list chain inet $TABLE $CHAIN miniupnpd-2.3.7/netfilter_nft/nftpinhole.c010064400017500000024000000257231457642072000201050ustar00nanardstaff/* $Id: nftpinhole.c,v 1.9 2024/03/19 23:35:54 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2024 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 #include #include #include #include #include "../upnputils.h" #include "nftpinhole.h" #include #include #include #include #include #include #include #include "tiny_nf_nat.h" #include "config.h" #include "../macros.h" #include "nftnlrdr.h" #include "../upnpglobalvars.h" #include "nftnlrdr_misc.h" #ifdef DEBUG #define d_printf(x) do { printf x; } while (0) #else #define d_printf(x) #endif #ifdef ENABLE_UPNPPINHOLE static int next_uid = 1; #define PINEHOLE_LABEL_FORMAT "pinhole-%d ts-%u: %s" #define PINEHOLE_LABEL_FORMAT_SKIPDESC "pinhole-%d ts-%u: %*s" void init_iptpinhole(void) { return; } void shutdown_iptpinhole(void) { return; } /* ip saddr ip daddr tcp sport tcp dport */ int add_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, const char * desc, unsigned int timestamp) { int uid, res; char comment[NFT_DESCR_SIZE]; struct nftnl_rule *r = NULL; struct in6_addr rhost_addr, ihost_addr; struct in6_addr *rhost_addr_p; uid = next_uid; d_printf(("add_pinhole(%s, %s, %hu, %s, %hu, %d, %s, %u)\n", ifname, rem_host, rem_port, int_client, int_port, proto, desc, timestamp)); if (rem_host && rem_host[0] != '\0' && rem_host[0] != '*') { inet_pton(AF_INET6, rem_host, &rhost_addr); rhost_addr_p = &rhost_addr; } else { rhost_addr_p = NULL; } inet_pton(AF_INET6, int_client, &ihost_addr); snprintf(comment, NFT_DESCR_SIZE, PINEHOLE_LABEL_FORMAT, uid, timestamp, desc); r = rule_set_filter6(nft_ipv6_family, ifname, proto, rhost_addr_p, &ihost_addr, 0, int_port, rem_port, comment, 0); res = nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_FILTER); if (res < 0) return -1; if (++next_uid >= 65535) { next_uid = 1; } return uid; } int find_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, char *desc, int desc_len, unsigned int * timestamp) { rule_t *p; struct in6_addr saddr; struct in6_addr daddr; int uid; unsigned int ts; UNUSED(ifname); if (rem_host && rem_host[0] != '\0' && rem_host[0] != '*') { if (inet_pton(AF_INET6, rem_host, &saddr) < 1) { syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", rem_host); memset(&saddr, 0, sizeof(struct in6_addr)); } } else { memset(&saddr, 0, sizeof(struct in6_addr)); } if (inet_pton(AF_INET6, int_client, &daddr) < 1) { syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", int_client); memset(&daddr, 0, sizeof(struct in6_addr)); } d_printf(("find_pinhole()\n")); refresh_nft_cache_filter(); LIST_FOREACH(p, &head_filter, entry) { // Only forward entries if (p->type != RULE_FILTER) continue; if (p->desc_len == 0) continue; if ((proto == p->proto) && (rem_port == p->sport) && (0 == memcmp(&saddr, &p->saddr6, sizeof(struct in6_addr))) && (int_port == p->dport) && (0 == memcmp(&daddr, &p->daddr6, sizeof(struct in6_addr)))) { if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc); continue; } if (timestamp) *timestamp = ts; if (desc && (desc_len > 0)) { char * pd = strchr(p->desc, ':'); if(pd) { pd += 2; strncpy(desc, pd, desc_len); desc[desc_len - 1] = '\0'; } } return uid; } } return -2; /* not found */ } int delete_pinhole(unsigned short uid) { rule_t *p; struct nftnl_rule *r; char label_start[NFT_DESCR_SIZE]; char tmp_label[NFT_DESCR_SIZE]; snprintf(label_start, sizeof(label_start), "pinhole-%hu", uid); d_printf(("delete_pinhole()\n")); refresh_nft_cache_filter(); LIST_FOREACH(p, &head_filter, entry) { // Only forward entries if (p->type != RULE_FILTER) continue; if (p->desc_len == 0) continue; strncpy(tmp_label, p->desc, sizeof(tmp_label)); tmp_label[sizeof(tmp_label) - 1] = '\0'; strtok(tmp_label, " "); /* compare the uid value */ if (0 == strcmp(tmp_label, label_start)) { r = rule_del_handle(p); nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER); return 0; } } return -2; } int update_pinhole(unsigned short uid, unsigned int timestamp) { #ifdef DEBUG char iaddr[INET6_ADDRSTRLEN]; #endif char raddr[INET6_ADDRSTRLEN]; char label_start[NFT_DESCR_SIZE]; char tmp_label[NFT_DESCR_SIZE]; char desc[NFT_DESCR_SIZE]; char ifname[IFNAMSIZ]; char comment[NFT_DESCR_SIZE]; char * tmp_p; uint32_t ext_if_indx; int proto, res; unsigned short iport, rport; rule_t *p; struct in6_addr rhost_addr, ihost_addr; struct in6_addr * rhost_addr_p; struct nftnl_rule *r; d_printf(("update_pinhole()\n")); snprintf(label_start, sizeof(label_start), "pinhole-%hu", uid); refresh_nft_cache_filter(); proto = -1; memset(&rhost_addr, 0, sizeof(struct in6_addr)); LIST_FOREACH(p, &head_filter, entry) { // Only forward entries if (p->type != RULE_FILTER) continue; if (p->desc_len == 0) continue; strncpy(tmp_label, p->desc, sizeof(tmp_label)); tmp_label[sizeof(tmp_label) - 1] = '\0'; strtok(tmp_label, " "); /* check for the right uid */ if (0 == strcmp(tmp_label, label_start)) { char *pd; /* Source IP Address */ // Check if empty if (0 == memcmp(&rhost_addr, &p->saddr6, sizeof(struct in6_addr))) { rhost_addr_p = NULL; raddr[0] = '*'; raddr[1] = '\0'; } else { rhost_addr_p = &p->saddr6; if (inet_ntop(AF_INET6, rhost_addr_p, raddr, INET6_ADDRSTRLEN) == NULL) { syslog(LOG_WARNING, "%s: inet_ntop(raddr): %m", "update_pinhole"); } } /* Source Port */ rport = p->sport; /* Destination IP Address */ ihost_addr = p->daddr6; /* Destination Port */ iport = p->dport; proto = p->proto; ext_if_indx = p->ingress_ifidx; if_indextoname(ext_if_indx, ifname); pd = strchr(p->desc, ':'); if (pd) { pd += 2; strncpy(desc, pd, sizeof(desc)); desc[sizeof(desc) - 1] = '\0'; } else { desc[0] = '\0'; } break; } } if (proto == -1) return -2; // Delete rule r = rule_del_handle(p); res = nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER); if (res < 0) return -1; // readd rule with new timestamp snprintf(comment, NFT_DESCR_SIZE, PINEHOLE_LABEL_FORMAT, uid, timestamp, desc); d_printf(("update add_pinhole(%s, %s, %s, %d, %d, %d, %s)\n", ifname, raddr, inet_ntop(AF_INET6, &ihost_addr, iaddr, INET6_ADDRSTRLEN), rport, iport, proto, comment)); r = rule_set_filter6(nft_ipv6_family, ifname, proto, rhost_addr_p, &ihost_addr, 0, iport, rport, comment, 0); res = nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_FILTER); if (res < 0) return -1; return 0; } int get_pinhole_info(unsigned short uid, char * rem_host, int rem_hostlen, unsigned short * rem_port, char * int_client, int int_clientlen, unsigned short * int_port, int * proto, char * desc, int desclen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { rule_t *p; unsigned int ts; char label_start[NFT_DESCR_SIZE]; char tmp_label[NFT_DESCR_SIZE]; snprintf(label_start, sizeof(label_start), "pinhole-%hu", uid); d_printf(("get_pinhole_info()\n")); refresh_nft_cache_filter(); LIST_FOREACH(p, &head_filter, entry) { // Only forward entries if (p->type != RULE_FILTER) continue; if (p->desc_len == 0) continue; strncpy(tmp_label, p->desc, sizeof(tmp_label)); tmp_label[sizeof(tmp_label) - 1] = '\0'; strtok(tmp_label, " "); if (0 == strcmp(tmp_label, label_start)) { /* Source IP Address */ if (rem_host) { if(inet_ntop(AF_INET6, &p->saddr6, rem_host, rem_hostlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop(rem_host): %m", "get_pinhole_info"); return -1; } } /* Source Port */ if (rem_port) *rem_port = p->sport; /* Destination IP Address */ if (int_client) { if(inet_ntop(AF_INET6, &p->daddr6, int_client, int_clientlen) == NULL) { syslog(LOG_ERR, "%s: inet_ntop(rem_host): %m", "get_pinhole_info"); return -1; } } /* Destination Port */ if (int_port) *int_port = p->dport; if (proto) *proto = p->proto; if (timestamp) { int uid_temp; if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid_temp, &ts) != 2) { syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc); continue; } *timestamp = ts; } if (desc && (desclen > 0)) { char * pd = strchr(p->desc, ':'); if(pd) { pd += 2; strncpy(desc, pd, desclen); desc[desclen - 1] = '\0'; } } if (packets || bytes) { if (packets) *packets = p->packets; if (bytes) *bytes = p->bytes; } break; } } d_printf(("end_pinhole_info()\n")); return 0; } int get_pinhole_uid_by_index(int index) { UNUSED(index); return -42; } int clean_pinhole_list(unsigned int * next_timestamp) { rule_t *p; struct nftnl_rule *r; time_t current_time; unsigned int ts; int uid; unsigned int min_ts = UINT_MAX; int min_uid = INT_MAX, max_uid = -1; int n = 0; current_time = upnp_time(); d_printf(("clean_pinhole_list()\n")); refresh_nft_cache_filter(); LIST_FOREACH(p, &head_filter, entry) { // Only forward entries if (p->type != RULE_FILTER) continue; if (p->desc_len == 0) continue; if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc); continue; } if (ts <= (unsigned int)current_time) { syslog(LOG_INFO, "removing expired pinhole '%s'", p->desc); r = rule_del_handle(p); nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER); n++; } else { if (uid > max_uid) max_uid = uid; else if (uid < min_uid) min_uid = uid; if (ts < min_ts) min_ts = ts; } } if (next_timestamp && (min_ts != UINT_MAX)) *next_timestamp = min_ts; if (max_uid > 0) { if (((min_uid - 32000) <= next_uid) && (next_uid <= max_uid)) { next_uid = max_uid + 1; } if (next_uid >= 65535) { next_uid = 1; } } return n; /* number of rules removed */ } #endif /* ENABLE_UPNPPINHOLE */ miniupnpd-2.3.7/netfilter_nft/nftpinhole.h010064400017500000024000000027711350451073500201040ustar00nanardstaff/* $Id: nftpinhole.h,v 1.1 2019/06/25 21:27:57 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2012-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef NFTPINHOLE_H_INCLUDED #define NFTPINHOLE_H_INCLUDED #ifdef ENABLE_UPNPPINHOLE #include int find_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, char *desc, int desc_len, unsigned int * timestamp); int add_pinhole(const char * ifname, const char * rem_host, unsigned short rem_port, const char * int_client, unsigned short int_port, int proto, const char *desc, unsigned int timestamp); int update_pinhole(unsigned short uid, unsigned int timestamp); int delete_pinhole(unsigned short uid); int get_pinhole_info(unsigned short uid, char * rem_host, int rem_hostlen, unsigned short * rem_port, char * int_client, int int_clientlen, unsigned short * int_port, int * proto, char * desc, int desclen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes); int get_pinhole_uid_by_index(int index); int clean_pinhole_list(unsigned int * next_timestamp); #endif /* ENABLE_UPNPPINHOLE */ #endif miniupnpd-2.3.7/netfilter_nft/testnftpinhole.c010064400017500000024000000052011457642072000207720ustar00nanardstaff/* $Id: testnftpinhole.c,v 1.4 2024/03/19 23:35:54 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include "config.h" #include "../miniupnpdtypes.h" #include "nftpinhole.h" #include "../commonrdr.h" #include "../upnputils.h" struct lan_addr_list lan_addrs; time_t startup_time = 0; static void print_infos(unsigned short uid) { char rem_host[64]; unsigned short rem_port; char int_client[64]; unsigned short int_port; int proto; char desc[256]; unsigned int timestamp; u_int64_t packets, bytes; int ret; ret = get_pinhole_info(uid, rem_host, sizeof(rem_host), &rem_port, int_client, sizeof(int_client), &int_port, &proto, desc, sizeof(desc), ×tamp, &packets, &bytes); if (ret < 0) { syslog(LOG_WARNING, "get_pinhole_info(%d) returned %d", uid, ret); } else { syslog(LOG_INFO, "get_pinhole_info(%d) : %s:%hu => %s:%hu %s", uid, rem_host, rem_port, int_client, int_port, proto==IPPROTO_TCP ? "tcp" : "udp"); syslog(LOG_INFO, " desc \"%s\" ts=%u packets=%llu %llu", desc, timestamp, (long long unsigned)packets, (long long unsigned)bytes); } } int main(int argc, char * * argv) { int uid, r; const char * ifname = "eth0"; const char * rem_host = "2a00::dead:beaf"; unsigned short rem_port = 1911; const char * int_client = "fe80::1023:4095"; unsigned short int_port = 34952; char desc[1024] = { 0 }; unsigned int timestamp = 0; openlog("testnftpinhole", LOG_PERROR|LOG_CONS, LOG_LOCAL0); r = init_redirect(); if (r < 0) { syslog(LOG_ERR, "init_redirect() failed"); return 1; } uid = add_pinhole(ifname, rem_host, rem_port, int_client, int_port, IPPROTO_TCP, "dummy description", upnp_time() + 60 /* timestamp */); syslog(LOG_INFO, "add_pinhole(): uid=%d", uid); if (uid >= 0) print_infos(uid); uid = find_pinhole(ifname, rem_host, rem_port, int_client, int_port, IPPROTO_TCP, desc, sizeof(desc), ×tamp); syslog(LOG_INFO, "find_pinhole(): uid=%d desc=\"%s\" timestamp=%u", uid, desc, timestamp); if (uid >= 0) { print_infos(uid); r = update_pinhole(uid, upnp_time() + 3600); syslog(LOG_INFO, "update_pinhole(%d, ...) returned %d", uid, r); print_infos(uid); r = delete_pinhole(uid); syslog(LOG_INFO, "delete_pinhole(%d) returned %d", uid, r); } shutdown_redirect(); return 0; } miniupnpd-2.3.7/linux/miniupnpd.service010064400017500000024000000002711354522132200174330ustar00nanardstaff[Unit] Description=MiniUPnPD After=network.target [Service] Type=forking ExecStart=/usr/sbin/miniupnpd ExecStop=kill `cat /var/run/miniupnpd.pid` [Install] WantedBy=multi-user.target miniupnpd-2.3.7/gitrev.mk010064400017500000024000000013041417311724200145410ustar00nanardstaff# (c) 2019-2021 Thomas Bernard # For GNU Make # CI_COMMIT_TAG / CI_COMMIT_BRANCH / CI_COMMIT_SHORT_SHA are gitlab-ci # predefined variables # see https://docs.gitlab.com/ee/ci/variables/predefined_variables.html ifneq ($(CI_COMMIT_TAG),) GITREF = $(CI_COMMIT_TAG) else ifneq ($(CI_COMMIT_BRANCH),) GITREF = $(CI_COMMIT_BRANCH)-$(CI_COMMIT_SHORT_SHA) else ISGITREPO := $(shell git rev-parse --is-inside-work-tree) ifeq ($(ISGITREPO),true) # or - GITREF := $(shell git describe --exact-match --tags 2> /dev/null || echo "`git rev-parse --abbrev-ref HEAD`-`git rev-parse --short HEAD`" ) endif endif endif ifneq ($(GITREF),) CPPFLAGS += -DMINIUPNPD_GIT_REF=\"$(GITREF)\" endif miniupnpd-2.3.7/check.mk010064400017500000024000000006211365603134700143250ustar00nanardstaff# (c) 2020 Thomas BERNARD check: validateupnppermissions validategetifaddr validatessdppktgen \ validateversion validateversion: miniupnpd $(SRCDIR)/VERSION ./miniupnpd --version [ "`./miniupnpd --version | head -1 | cut -d' ' -f-2`" = "miniupnpd `cat $(SRCDIR)/VERSION`" ] touch $@ validate%: $(SRCDIR)/test%.sh test% $(SHELL) $< touch $@ validatessdppktgen: testssdppktgen ./$< touch $@ miniupnpd-2.3.7/objects.mk010064400017500000024000000007061365603134700147050ustar00nanardstaffBASEOBJS = miniupnpd.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpreplyparse.o minixml.o portinuse.o \ upnpredirect.o getifaddr.o daemonize.o \ options.o upnppermissions.o minissdp.o natpmp.o pcpserver.o \ upnpglobalvars.o upnpevents.o upnputils.o getconnstatus.o \ upnpstun.o upnppinhole.o pcplearndscp.o asyncsendto.o # sources in linux/ directory LNXOBJS = getifstats.o ifacewatcher.o getroute.o miniupnpd-2.3.7/testgetifaddr.sh010075500017500000024000000017421417311724300161070ustar00nanardstaff#!/bin/sh # $Id: testgetifaddr.sh,v 1.7 2021/11/09 17:50:43 nanard Exp $ OS=`uname -s` case $OS in *BSD | Darwin | SunOS) NS="`command -v netstat`" || exit 1 IFCONFIG="`command -v ifconfig`" || exit 1 EXTIF="`$NS -r -f inet | grep 'default' | awk '{ print $NF }' `" || exit 1 EXTIP="`$IFCONFIG $EXTIF | awk '/inet / { print $2 }' `" ;; *) IP="`command -v ip`" || exit 1 EXTIF="`LC_ALL=C $IP -4 route | grep 'default' | sed -e 's/.*dev[[:space:]]*//' -e 's/[[:space:]].*//'`" || exit 1 EXTIF="`LC_ALL=C $IP -4 addr show $EXTIF | awk '/[0-9]+:/ { print $2; exit 0 }' | cut -d ":" -f 1`" EXTIP="`LC_ALL=C $IP -4 addr show $EXTIF | awk '/inet/ { print $2; exit 0 }' | cut -d "/" -f 1`" ;; esac #echo "Interface : $EXTIF IP address : $EXTIP" RES=`./testgetifaddr $EXTIF | head -n1 | sed 's/Interface .* has IP address \(.*\)\./\1/'` || exit 1 if [ "$RES" = "$EXTIP" ] ; then echo "testgetifaddr test OK" exit 0 else echo "testgetifaddr test FAILED : $EXTIP != $RES" exit 1 fi miniupnpd-2.3.7/testupnppermissions.sh010075500017500000024000000023161365604170400174350ustar00nanardstaff#!/bin/sh # $Id: testupnppermissions.sh,v 1.3 2020/05/10 17:52:48 nanard Exp $ RULE_1="allow 1-20000 11.12.13.14/22 1234" RULEA_1="allow 1-20000 0b0c0d0e/fffffc00 1234-1234" RULEB_1="allow 1-20000 11.12.13.14/255.255.252.0 1234-1234" RULE_2="deny 55 21.22.23.24/17 555-559" RULEA_2="deny 55-55 15161718/ffff8000 555-559" RULEB_2="deny 55-55 21.22.23.24/255.255.128.0 555-559" i=1 s=1 ./testupnppermissions "$RULE_1" "$RULE_2" | while read l; do if [ -z "$l" ]; then i=$(($i+1)); s=1; else #echo "$i $s : checking '$l'" case $s in 1) val=$(eval echo "\${RULE_$i}") if [ "$i '$val'" != "$l" ] ; then exit $s fi;; 2) val=$(eval echo "\${RULEA_$i}") if [ "Permission read successfully" = "$l" ] ; then s=$(($s+1)) elif [ "perm rule added : $val" != "$l" ] ; then exit $s fi;; 3) if [ "Permission read successfully" != "$l" ] ; then exit $s fi;; 4) val=$(eval echo "\${RULEB_$i}") if [ "$val" != "$l" ] ; then exit $s fi;; *) echo "$i $s : $l" exit $s ;; esac s=$(($s+1)) fi done # retrieve return status from subshell r=$? if [ $r -eq 0 ] ; then echo "testupnppermissions tests OK" else echo "testupnppermissions tests FAILED" fi exit $r miniupnpd-2.3.7/netfilter_nft/scripts/miniupnpd_functions.sh010075500017500000024000000010711417305016300236730ustar00nanardstaff#! /bin/sh NFT=$(which nft) || { echo "Can't find nft" >&2 exit 1 } TABLE="filter" NAT_TABLE="filter" CHAIN="miniupnpd" PREROUTING_CHAIN="prerouting_miniupnpd" POSTROUTING_CHAIN="postrouting_miniupnpd" while getopts ":t:n:c:p:r:" opt; do case $opt in t) TABLE=$OPTARG ;; n) NAT_TABLE=$OPTARG ;; c) CHAIN=$OPTARG ;; p) PREROUTING_CHAIN=$OPTARG ;; r) POSTROUTING_CHAIN=$OPTARG ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; :) echo "Option -$OPTARG requires an argument." >&2 exit 1 ;; esac done miniupnpd-2.3.7/pf/rtickets.h010064400017500000024000000012211455107227500153220ustar00nanardstaff/* $Id: rtickets.h,v 1.1 2024/01/14 23:56:45 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2023 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef RTICKETS_H_INCLUDED #define RTICKETS_H_INCLUDED #if defined(PF_NEWSTYLE) && defined(DIOCXEND) #define PF_RELEASETICKETS #define release_ticket(device, ticket_num) {\ if (ioctl((device), DIOCXEND, &(ticket_num)) < 0) {\ syslog(LOG_ERR, "ioctl(dev, DIOCXEND, ...): %m");\ }\ } #else #define release_ticket(device, ticket_num) (void)(ticket_num) #endif #endif