From e6e8cb038afd3ccd98b77402b23fb9db1f1efcc3 Mon Sep 17 00:00:00 2001 From: Logan oos Even <46396513+Logan007@users.noreply.github.com> Date: Thu, 23 Dec 2021 12:27:55 +0100 Subject: [PATCH] added port forwarding (upnp and natpmp) (#905) * UPnP port redirection is supported. * compile fixes * compile fix * optimize reconnection code * prepared upnp threadification to counter main loop stall at supernode change * NAT-PMP port forwarding support, temporarily merge codes to resolve conflicts. * make compile fix * prepared threadification in more detail * adopted threadification to new file setup * cleaned up * renamed functions and data structures * fixes * differentiated between miniupnp and natpmp and added corresponding lib support to makefile * name * commented unused header includes * comments * license * fixes * fixes * fixes * NAT-PMP is already available. * added CLI parameter to disable port forwarding if required * preliminary made use of multithreading * adjusted log level * added man page documentation * def'ed conf * made pmpnat adjustments Co-authored-by: fengdaolong --- .gitignore | 1 + .gitmodules | 8 + CMakeLists.txt | 26 +- configure.ac | 14 + edge.8 | 7 +- include/n2n.h | 4 + include/n2n_port_mapping.h | 21 ++ include/n2n_typedefs.h | 18 ++ src/edge.c | 19 +- src/edge_utils.c | 32 +- src/n2n_port_mapping.c | 635 +++++++++++++++++++++++++++++++++++++ thirdparty/libnatpmp | 1 + thirdparty/miniupnp | 1 + 13 files changed, 776 insertions(+), 11 deletions(-) create mode 100644 .gitmodules create mode 100644 include/n2n_port_mapping.h create mode 100644 src/n2n_port_mapping.c create mode 160000 thirdparty/libnatpmp create mode 160000 thirdparty/miniupnp diff --git a/.gitignore b/.gitignore index 17d6441..84c2026 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ tools/n2n-decode tools/n2n-keygen build .idea +.vscode cmake-build-default packages/debian/debian/changelog packages/debian/debian/control diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..df11e25 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "thirdparty/miniupnp"] + path = thirdparty/miniupnp + url = https://github.com/miniupnp/miniupnp.git + ignore = dirty +[submodule "thirdparty/libnatpmp"] + path = thirdparty/libnatpmp + url = https://github.com/miniupnp/libnatpmp.git + ignore = dirty diff --git a/CMakeLists.txt b/CMakeLists.txt index 906c715..022ed8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ add_definitions(-DCMAKE_BUILD) add_definitions(-DPACKAGE_OSNAME="${CMAKE_SYSTEM_NAME}") add_definitions(-DPACKAGE_VERSION="${PACKAGE_VERSION}") +# third-party directory +set(THIRD_PARTY_DIR ${CMAKE_SOURCE_DIR}/thirdparty) # Build information OPTION(BUILD_SHARED_LIBS "BUILD Shared Library" OFF) @@ -39,6 +41,7 @@ OPTION(N2N_OPTION_USE_PTHREAD "USE PTHREAD Library" ON) OPTION(N2N_OPTION_USE_OPENSSL "USE OPENSSL Library" OFF) OPTION(N2N_OPTION_USE_PCAPLIB "USE PCAP Library" OFF) OPTION(N2N_OPTION_USE_ZSTD "USE ZSTD Library" OFF) +OPTION(N2N_OPTION_USE_PORTMAPPING "USE MINIUPNP and NATPMP Libraries" ON) if(N2N_OPTION_USE_PTHREAD) @@ -47,7 +50,7 @@ if(N2N_OPTION_USE_PTHREAD) ADD_DEFINITIONS("-DHAVE_PTHREAD") else() MESSAGE(WARNING "libpthread not found.") - set(N2N_OPTION_USE_PTHREAD OFF) + set(N2N_OPTION_USE_PTHREAD OFF) endif(PTHREAD_LIB) endif(N2N_OPTION_USE_PTHREAD) @@ -75,6 +78,14 @@ if(N2N_OPTION_USE_ZSTD) add_definitions(-DN2N_HAVE_ZSTD) endif(N2N_OPTION_USE_ZSTD) +if(N2N_OPTION_USE_PORTMAPPING) + ADD_DEFINITIONS("-DHAVE_MINIUPNP") + ADD_DEFINITIONS("-DHAVE_NATPMP") + include_directories(${THIRD_PARTY_DIR}/miniupnp/miniupnpc/include) + include_directories(${PROJECT_BINARY_DIR}/lib_miniupnpc) + include_directories(${THIRD_PARTY_DIR}/libnatpmp) +endif(N2N_OPTION_USE_PORTMAPPING) + if(NOT DEFINED CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE None) endif(NOT DEFINED CMAKE_BUILD_TYPE) @@ -85,7 +96,7 @@ if (DEFINED UNIX) # None set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wshadow -Wpointer-arith -Wmissing-declarations -Wnested-externs") set(CMAKE_CXX_FLAGS "-Wall -Wshadow -Wpointer-arith -Wmissing-declarations -Wnested-externs") -# Debug +# Debug set(CMAKE_C_FLAGS_DEBUG "-g") set(CMAKE_CXX_FLAGS_DEBUG "-g") # Release @@ -139,7 +150,8 @@ add_library(n2n STATIC src/network_traffic_filter.c src/sn_selection.c src/auth.c - src/curve25519.c) + src/curve25519.c + src/n2n_port_mapping.c) if(N2N_OPTION_USE_PTHREAD) @@ -155,6 +167,14 @@ if(N2N_OPTION_USE_ZSTD) target_link_libraries(n2n zstd) endif(N2N_OPTION_USE_ZSTD) +if(N2N_OPTION_USE_PORTMAPPING) + add_subdirectory(${THIRD_PARTY_DIR}/miniupnp/miniupnpc lib_miniupnpc) + link_directories(${PROJECT_BINARY_DIR}/lib_miniupnpc) + add_subdirectory(${THIRD_PARTY_DIR}/libnatpmp libnatpmp) + link_directories(${PROJECT_BINARY_DIR}/libnatpmp) + target_link_libraries(n2n libminiupnpc-static natpmp) +endif(N2N_OPTION_USE_PORTMAPPING) + if(DEFINED WIN32) add_library(edge_utils_win32 src/edge_utils_win32.c) add_subdirectory(win32) diff --git a/configure.ac b/configure.ac index 75107ec..5bd8937 100644 --- a/configure.ac +++ b/configure.ac @@ -58,6 +58,20 @@ if test "x$with_openssl" != xno; then fi fi +AC_CHECK_LIB([miniupnpc], [upnpDiscover], miniupnp=true) + +if test x$miniupnp != x; then + AC_DEFINE([HAVE_MINIUPNP], [], [Have miniupnp library]) + N2N_LIBS="-lminiupnpc ${N2N_LIBS}" +fi + +AC_CHECK_LIB([natpmp], [initnatpmp], natpmp=true) + +if test x$natpmp != x; then + AC_DEFINE([HAVE_NATPMP], [], [Have natpmp library]) + N2N_LIBS="-lnatpmp ${N2N_LIBS}" +fi + AC_CHECK_LIB([pcap], [pcap_open_live], pcap=true) if test x$pcap != x; then diff --git a/edge.8 b/edge.8 index 529fd6f..75861f6 100644 --- a/edge.8 +++ b/edge.8 @@ -109,6 +109,11 @@ defaults to load-based selection strategy if not provided. \fB\-\-select-mac\fR select supernode by MAC address if several to choose from (federation), lowest MAC address first. +.TP +\fB\-\-no-port-forwarding\fR +disables the default behavior of trying to have the edge's port forwarded +through a router eventually supporting it (only if compiled with miniupnp +and/or natpmp library support). .SH TAP DEVICE AND OVERLAY NETWORK CONFIGURATION .TP \fB\-a \fR[\fImode\fR]<\fIip\fR>[\fI/n\fR] @@ -245,7 +250,7 @@ The MAC address (-m ) and virtual IP address (-a ) must be different on all edges in the same community. .SH CLEARTEXT MODE -If +If .B -k is not specified then edge uses cleartext mode. In cleartext mode there is no transform of the packet data it is simply encrypted. This is useful for diff --git a/include/n2n.h b/include/n2n.h index 87f87f1..6ecfb4c 100644 --- a/include/n2n.h +++ b/include/n2n.h @@ -158,6 +158,10 @@ #include "network_traffic_filter.h" #include "auth.h" +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) +#include "n2n_port_mapping.h" +#endif // HAVE_MINIUPNP || HAVE_NATPMP + /* ************************************** */ #include "header_encryption.h" diff --git a/include/n2n_port_mapping.h b/include/n2n_port_mapping.h new file mode 100644 index 0000000..cf5cacb --- /dev/null +++ b/include/n2n_port_mapping.h @@ -0,0 +1,21 @@ +#ifndef _N2N_PORT_MAPPING_H_ +#define _N2N_PORT_MAPPING_H_ + +#include + +#ifdef HAVE_MINIUPNP +#include +#include +#include +#endif // HAVE_MINIUPNP + + +#ifdef HAVE_NATPMP +#include "natpmp.h" +#endif // HAVE_NATPMP + + +void n2n_chg_port_mapping (struct n2n_edge *eee, const uint16_t port); + + +#endif // _N2N_PORT_MAPPING_H_ diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h index 3ba65f2..2cef156 100644 --- a/include/n2n_typedefs.h +++ b/include/n2n_typedefs.h @@ -637,6 +637,21 @@ typedef struct n2n_resolve_parameter { /* *************************************************** */ +// structure to hold port mapping thread's parameters +typedef struct n2n_port_map_parameter { +#ifdef HAVE_PTHREAD + pthread_t id; /* thread id */ + pthread_mutex_t access; /* mutex for shared access */ +#endif + uint16_t mgmt_port; + uint16_t mapped_port; + uint16_t new_port; /* REVISIT: remove with management port subscriptions */ +} n2n_port_map_parameter_t; + + +/* *************************************************** */ + + typedef struct n2n_edge_conf { struct peer_info *supernodes; /**< List of supernodes */ n2n_route_t *routes; /**< Networks to route through n2n */ @@ -676,6 +691,7 @@ typedef struct n2n_edge_conf { uint8_t sn_selection_strategy; /**< encodes currently chosen supernode selection strategy. */ uint8_t number_max_sn_pings; /**< Number of maximum concurrently allowed supernode pings. */ uint64_t mgmt_password_hash; /**< contains hash of managament port password. */ + uint8_t port_forwarding; /**< indicates if port forwarding UPNP/PMP is enabled */ } n2n_edge_conf_t; @@ -733,6 +749,8 @@ struct n2n_edge { n2n_resolve_parameter_t *resolve_parameter; /**< Pointer to name resolver's parameter block */ uint8_t resolution_request; /**< Flag an immediate DNS resolution request */ + n2n_port_map_parameter_t *port_map_parameter; /**< Pointer to port mapping thread's parameter block */ + n2n_tuntap_priv_config_t tuntap_priv_conf; /**< Tuntap config */ network_traffic_filter_t *network_traffic_filter; diff --git a/src/edge.c b/src/edge.c index 6ccdd28..5ace7fb 100644 --- a/src/edge.c +++ b/src/edge.c @@ -180,7 +180,10 @@ static void help (int level) { "\n " "[-e ] [-S]" "\n " - "[--select-rtt]" + "[--select-rtt] " +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + "[--no-port-forwarding] " +#endif // HAVE_MINIUPNP || HAVE_NATPMP "\n\n tap device and " "[-a [static:|dhcp:][/]] " "\n overlay network " @@ -233,6 +236,9 @@ static void help (int level) { "\n [-E] accept multicast MAC addresses" "\n [--select-rtt] select supernode by round trip time" "\n [--select-mac] select supernode by MAC address" +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + "\n [--no-port-forwarding] disable UPnP/PMP port forwarding" +#endif // HAVE_MINIUPNP || HAVE_NATPMP #ifndef WIN32 "\n [-f] do not fork but run in foreground" #endif @@ -294,6 +300,10 @@ static void help (int level) { printf("--select-rtt | supernode selection based on round trip time\n" "--select-mac | supernode selection based on MAC address (default:\n" " | by load)\n"); +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + printf("--no-port-... | disable UPnP/PMP port forwarding\n" + "...forwarding | \n"); +#endif // HAVE_MINIUPNP || HAVE_NATPMP printf ("\n"); printf (" TAP DEVICE AND OVERLAY NETWORK CONFIGURATION\n"); @@ -752,6 +762,12 @@ static int setOption (int optkey, char *optargument, n2n_tuntap_priv_config_t *e break; } + case '}': /* disable port forwarding */ { + conf->port_forwarding = 0; + + break; + } + case 'h': /* quick reference */ { return 2; } @@ -808,6 +824,7 @@ static const struct option long_options[] = { "select-rtt", no_argument, NULL, '[' }, /* '[' rtt selection strategy */ { "select-mac", no_argument, NULL, ']' }, /* ']' mac selection strategy */ { "management-password", required_argument, NULL, '{' }, /* '{' management port password */ + { "no-port-forwarding", no_argument, NULL, '}' }, /* '}' disable port forwarding */ { NULL, 0, NULL, 0 } }; diff --git a/src/edge_utils.c b/src/edge_utils.c index a6d03f4..bd6783b 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -30,6 +30,9 @@ int resolve_create_thread (n2n_resolve_parameter_t **param, struct peer_info *sn int resolve_check (n2n_resolve_parameter_t *param, uint8_t resolution_request, time_t now); int resolve_cancel_thread (n2n_resolve_parameter_t *param); +int port_map_create_thread (n2n_port_map_parameter_t **param, uint16_t mgmt_port); +int port_map_cancel_thread (n2n_port_map_parameter_t *param); + static const char * supernode_ip (const n2n_edge_t * eee); static void send_register (n2n_edge_t *eee, const n2n_sock_t *remote_peer, const n2n_mac_t peer_mac, n2n_cookie_t cookie); @@ -329,6 +332,12 @@ int supernode_connect (n2n_edge_t *eee) { eee->cb.sock_opened(eee); } +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + if(eee->conf.port_forwarding) + // REVISIT: replace with mgmt port notification to listener for mgmt port + // subscription support + n2n_chg_port_mapping(eee, eee->conf.preferred_sock.port); +#endif // HAVE_MINIUPNP || HAVE_NATPMP return 0; } @@ -482,7 +491,12 @@ n2n_edge_t* edge_init (const n2n_edge_conf_t *conf, int *rv) { if(resolve_create_thread(&(eee->resolve_parameter), eee->conf.supernodes) == 0) { traceEvent(TRACE_NORMAL, "successfully created resolver thread"); } - +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + if(eee->conf.port_forwarding) + if(port_map_create_thread(&eee->port_map_parameter, eee->conf.mgmt_port) == 0) { + traceEvent(TRACE_NORMAL, "successfully created port mapping thread"); + } +#endif // HAVE_MINIUPNP || HAVE_NATPMP eee->network_traffic_filter = create_network_traffic_filter(); network_traffic_filter_add_rule(eee->network_traffic_filter, eee->conf.network_traffic_filter_rules); @@ -1539,7 +1553,6 @@ void update_supernode_reg (n2n_edge_t * eee, time_t now) { sn_selection_sort(&(eee->conf.supernodes)); eee->curr_sn = eee->conf.supernodes; traceEvent(TRACE_WARNING, "supernode not responding, now trying [%s]", supernode_ip(eee)); - supernode_connect(eee); reset_sup_attempts(eee); // trigger out-of-schedule DNS resolution eee->resolution_request = 1; @@ -1567,9 +1580,9 @@ void update_supernode_reg (n2n_edge_t * eee, time_t now) { } } - supernode_connect(eee); traceEvent(TRACE_DEBUG, "reconnected to supernode"); } + supernode_connect(eee); } else { --(eee->sup_attempts); @@ -3182,9 +3195,9 @@ int run_edge_loop (n2n_edge_t *eee) { WaitForSingleObject(tun_read_thread, INFINITE); #endif - closesocket(eee->sock); + supernode_disconnect(eee); - return(0); + return 0; } /* ************************************** */ @@ -3193,7 +3206,10 @@ int run_edge_loop (n2n_edge_t *eee) { void edge_term (n2n_edge_t * eee) { resolve_cancel_thread(eee->resolve_parameter); - +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + if(eee->conf.port_forwarding) + port_map_cancel_thread(eee->port_map_parameter); +#endif // HAVE_MINIUPNP || HAVE_NATPMP if(eee->sock >= 0) closesocket(eee->sock); @@ -3712,6 +3728,10 @@ void edge_init_conf_defaults (n2n_edge_conf_t *conf) { free(tmp_string); } +#if defined(HAVE_MINIUPNP) || defined(HAVE_NATPMP) + conf->port_forwarding = 1; +#endif // HAVE_MINIUPNP || HAVE_NATPMP + conf->sn_selection_strategy = SN_SELECTION_STRATEGY_LOAD; conf->metric = 0; } diff --git a/src/n2n_port_mapping.c b/src/n2n_port_mapping.c new file mode 100644 index 0000000..2f487ec --- /dev/null +++ b/src/n2n_port_mapping.c @@ -0,0 +1,635 @@ +/** + * (C) 2007-21 - ntop.org and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not see see + * + */ + + +// This file contains code taken from MiniUPnPc and natpmp found at +// https://github.com/miniupnp/miniupnp/ or +// https://github.com/miniupnp/natpmp/ respectively +// both as of October 2021 + + +/** + * MiniUPnPc + * Copyright (c) 2005-2021, 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 "n2n.h" + + +#ifdef HAVE_MINIUPNP + + +#if 0 /* unused code */ +/* protofix() checks if protocol is "UDP" or "TCP" + * returns NULL if not */ +static const char *protofix (const char *proto) { + + int i, b; + const char proto_tcp[4] = {'T', 'C', 'P', 0}; + const char proto_udp[4] = {'U', 'D', 'P', 0}; + + for(i = 0, b = 1; i < 4; i++) + b = b && ((proto[i] == proto_tcp[i]) || (proto[i] == (proto_tcp[i] | 32))); + if(b) + return proto; + for(i = 0, b = 1; i < 4; i++) + b = b && ((proto[i] == proto_udp[i]) || (proto[i] == (proto_udp[i] | 32))); + if(b) + return proto; + + return NULL; +} +#endif // unused code + + +static int n2n_UPNP_GetValidIGD (struct UPNPUrls *urls, struct IGDdatas *data, char *lanaddr, char *externaladdr) { + + struct UPNPDev *devlist = NULL; + struct UPNPDev *device = NULL; + int delay = 2000; + const char *multicastif = NULL; + const char *minissdpdpath = NULL; + int localport = UPNP_LOCAL_PORT_ANY; + int ipv6 = 0; + unsigned char ttl = 2; /* defaulting to 2 */ + int error = 0; + int ret = 0; + + devlist = upnpDiscover(delay, multicastif, minissdpdpath, localport, ipv6, ttl, &error); + if((error != UPNPDISCOVER_SUCCESS) || (devlist == NULL) ) { + traceEvent(TRACE_WARNING, "no IGD UPnP device found on the network"); + return -1; + } + + traceEvent(TRACE_INFO, "list of UPnP devices found on the network:"); + for(device = devlist; device; device = device->pNext) { + traceEvent(TRACE_INFO, " desc: %s", device->descURL); + traceEvent(TRACE_INFO, " st: %s", device->st); + traceEvent(TRACE_INFO, " usn: %s", device->usn); + } + + ret = UPNP_GetValidIGD(devlist, urls, data, lanaddr, N2N_NETMASK_STR_SIZE); + if(ret == 0) { + traceEvent(TRACE_WARNING, "UPnP get valid IGD failed, code %d (%s)", ret, strupnperror(ret)); + freeUPNPDevlist(devlist); + devlist = NULL; + return -1; + } + freeUPNPDevlist(devlist); + devlist = NULL; + traceEvent(TRACE_INFO, "UPnP found valid IGD: %s", urls->controlURL); + + ret = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externaladdr); + if(ret != UPNPCOMMAND_SUCCESS) { + traceEvent(TRACE_WARNING, "UPnP get external ip address failed, code %d (%s)", ret, strupnperror(ret)); + } + + return 0; +} + + +#if 0 /* unused code */ +static int n2n_upnp_get_port_mapping (struct UPNPUrls *urls, const struct IGDdatas *data, const uint16_t port, const char *proto, + char *lanaddr, char *lanport, char *description, char *enabled, char *duration) { + + int errorcode = 0; + // struct UPNPUrls urls; + // struct IGDdatas data; + // char lanaddr[N2N_NETMASK_STR_SIZE] = {'\0'}; + // char lanport[6] = {'\0'}; + // char externaladdr[N2N_NETMASK_STR_SIZE] = {'\0'}; + char externalport[6] = {'\0'}; + // char description[64] = {'\0'}; + // char enabled[16] = {'\0'}; + // char duration[16] = {'\0'}; + int ret = 0; + + proto = protofix(proto); + if(!proto) { + traceEvent(TRACE_ERROR, "invalid protocol"); + errorcode = -1; + goto end; + } + + snprintf(externalport, sizeof(externalport), "%d", port); + + ret = UPNP_GetSpecificPortMappingEntry(urls->controlURL, + data->first.servicetype, + externalport, proto, NULL, + lanaddr, lanport, description, + enabled, duration); + if(ret != UPNPCOMMAND_SUCCESS) { + traceEvent(TRACE_WARNING, "UPNP_GetSpecificPortMappingEntry() failed, code %d (%s)", ret, strupnperror(ret)); + errorcode = -1; + goto end; + } + +end: + FreeUPNPUrls(urls); + + return errorcode; +} +#endif // unused code + + +static int n2n_upnp_set_port_mapping (const uint16_t port) { + + int errorcode = 0; + struct UPNPUrls urls; + struct IGDdatas data; + char lanaddr[N2N_NETMASK_STR_SIZE] = {'\0'}; + char lanport[6] = {'\0'}; + char externaladdr[N2N_NETMASK_STR_SIZE] = {'\0'}; + char externalport[6] = {'\0'}; + int ret = 0; + + if(port == 0) { + traceEvent(TRACE_ERROR, "invalid port"); + errorcode = -1; + return errorcode; + } + snprintf(lanport, sizeof(lanport), "%d", port); + memcpy(externalport, lanport, sizeof(externalport)); + + ret = n2n_UPNP_GetValidIGD(&urls, &data, lanaddr, externaladdr); + if(ret != 0) { + errorcode = -1; + return errorcode; + } + + // TCP port + ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, + externalport, lanport, lanaddr, "n2n-vpn", + "TCP", NULL, "0"); + if(ret != UPNPCOMMAND_SUCCESS) { + traceEvent(TRACE_WARNING, "UPnP local TCP port %s mapping failed, code %d (%s)", lanport, ret, strupnperror(ret)); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "UPnP added TCP port mapping: %s:%s -> %s:%s", externaladdr, externalport, lanaddr, lanport); + + // UDP port + ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, + externalport, lanport, lanaddr, "n2n-vpn", + "UDP", NULL, "0"); + if(ret != UPNPCOMMAND_SUCCESS) { + traceEvent(TRACE_WARNING, "UPnP local UDP port %s mapping failed, code %d (%s)", lanport, ret, strupnperror(ret)); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "UPnP added UDP port mapping: %s:%s -> %s:%s", externaladdr, externalport, lanaddr, lanport); + + FreeUPNPUrls(&urls); + + return errorcode; +} + + +static int n2n_upnp_del_port_mapping (const uint16_t port) { + + int errorcode = 0; + struct UPNPUrls urls; + struct IGDdatas data; + char lanaddr[N2N_NETMASK_STR_SIZE] = {'\0'}; + // char lanport[6] = {'\0'}; + char externaladdr[N2N_NETMASK_STR_SIZE] = {'\0'}; + char externalport[6] = {'\0'}; + int ret = 0; + + if(port == 0) { + traceEvent(TRACE_ERROR, "invalid port"); + errorcode = -1; + return errorcode; + } + snprintf(externalport, sizeof(externalport), "%d", port); + + ret = n2n_UPNP_GetValidIGD(&urls, &data, lanaddr, externaladdr); + if(ret != 0) { + errorcode = -1; + return errorcode; + } + + // TCP port + ret = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, externalport, "TCP", NULL); + if(ret != UPNPCOMMAND_SUCCESS) { + traceEvent(TRACE_WARNING, "UPnP failed to delete TCP port mapping for %s:%s, code %d (%s)", externaladdr, externalport, ret, strupnperror(ret)); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "UPnP deleted TCP port mapping for %s:%s", externaladdr, externalport); + + // UDP port + ret = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, externalport, "UDP", NULL); + if(ret != UPNPCOMMAND_SUCCESS) { + traceEvent(TRACE_WARNING, "UPnP failed to delete UDP port mapping for %s:%s, code %d (%s)", externaladdr, externalport, ret, strupnperror(ret)); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "UPnP deleted UDP port mapping for %s:%s", externaladdr, externalport); + + FreeUPNPUrls(&urls); + + return errorcode; +} + +#endif // HAVE_MINIUPNP + + +// ---------------------------------------------------------------------------------------------------- + + +#ifdef HAVE_NATPMP + +static int n2n_natpmp_initialization (natpmp_t *natpmp, char *lanaddr, char *externaladdr) { + + int errorcode = 0; + natpmpresp_t response; + int ret = 0; + int forcegw = 0; + in_addr_t gateway = 0; + struct in_addr gateway_in_use; + struct timeval timeout; + fd_set fds; + + ret = initnatpmp(natpmp, forcegw, gateway); + if(ret != 0) { + traceEvent(TRACE_WARNING, "NAT-PMP failed to initialize, code %d", ret); + errorcode = -1; + return errorcode; + } + gateway_in_use.s_addr = natpmp->gateway; + traceEvent(TRACE_INFO, "NAT-PMP using gateway: %s", inet_ntoa(gateway_in_use)); + + ret = sendpublicaddressrequest(natpmp); + if(ret != 2) { + traceEvent(TRACE_WARNING, "NAT-PMP get external ip address failed, code %d", ret); + closenatpmp(natpmp); + errorcode = -1; + return errorcode; + } + + do + { + FD_ZERO(&fds); + FD_SET(natpmp->s, &fds); + getnatpmprequesttimeout(natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + ret = readnatpmpresponseorretry(natpmp, &response); + traceEvent(TRACE_INFO, "NAT-PMP read response returned %d (%s)", ret, ret == 0 ? "OK" : (ret == NATPMP_TRYAGAIN ? "TRY AGAIN" : "FAILED")); + } while (ret == NATPMP_TRYAGAIN); + + if(response.type != NATPMP_RESPTYPE_PUBLICADDRESS) { + traceEvent(TRACE_WARNING, "NAT-PMP invalid response type %u", response.type); + closenatpmp(natpmp); + errorcode = -1; + return errorcode; + } + snprintf(externaladdr, N2N_NETMASK_STR_SIZE, "%s", inet_ntoa(response.pnu.publicaddress.addr)); + snprintf(lanaddr, N2N_NETMASK_STR_SIZE, "localhost"); + + return errorcode; +} + + +static int n2n_natpmp_port_mapping_request (natpmp_t *natpmp, + const uint16_t port, + const int protocol /* NATPMP_PROTOCOL_TCP or NATPMP_PROTOCOL_UDP */, + const int method /* set:1 del:0 */) { + + int errorcode = 0; + natpmpresp_t response; + int ret = 0; + uint16_t lanport = 0; + uint16_t externalport = 0; + struct timeval timeout; + fd_set fds; + + if(port == 0) { + traceEvent(TRACE_ERROR, "invalid port"); + errorcode = -1; + return errorcode; + } + lanport = port; + externalport = port; + + ret = sendnewportmappingrequest(natpmp, protocol, lanport, externalport, (method ? 31104000 /* lifetime 360 days*/ : 0)); + if(ret != 12) { + traceEvent(TRACE_WARNING, "NAT-PMP new port mapping request failed, code %d", ret); + errorcode = -1; + return errorcode; + } + + do + { + FD_ZERO(&fds); + FD_SET(natpmp->s, &fds); + getnatpmprequesttimeout(natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + ret = readnatpmpresponseorretry(natpmp, &response); + traceEvent(TRACE_INFO, "NAT-PMP read response returned %d (%s)", ret, ret == 0 ? "OK" : (ret == NATPMP_TRYAGAIN ? "TRY AGAIN" : "FAILED")); + } while (ret == NATPMP_TRYAGAIN); + + if(!((response.type == NATPMP_RESPTYPE_TCPPORTMAPPING) || (response.type == NATPMP_RESPTYPE_UDPPORTMAPPING))) { + traceEvent(TRACE_WARNING, "NAT-PMP invalid response type %u", response.type); + errorcode = -1; + return errorcode; + } + + return errorcode; +} + + +static int n2n_natpmp_set_port_mapping (const uint16_t port) { + + int errorcode = 0; + natpmp_t natpmp; + int ret = 0; + char lanaddr[N2N_NETMASK_STR_SIZE] = {'\0'}; + uint16_t lanport = 0; + char externaladdr[N2N_NETMASK_STR_SIZE] = {'\0'}; + uint16_t externalport = 0; + + lanport = port; + externalport = port; + + ret = n2n_natpmp_initialization(&natpmp, lanaddr, externaladdr); + if(ret != 0) { + errorcode = -1; + return errorcode; + } + + // TCP port + ret = n2n_natpmp_port_mapping_request(&natpmp, port, NATPMP_PROTOCOL_TCP, 1); + if(ret != 0) { + traceEvent(TRACE_WARNING, "NAT-PMP local TCP port %hu mapping failed", lanport); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "NAT-PMP added TCP port mapping: %s:%hu -> %s:%hu", externaladdr, externalport, lanaddr, lanport); + + // UDP port + ret = n2n_natpmp_port_mapping_request(&natpmp, port, NATPMP_PROTOCOL_UDP, 1); + if(ret != 0) { + traceEvent(TRACE_WARNING, "NAT-PMP local UDP port %hu mapping failed", lanport); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "NAT-PMP added UDP port mapping: %s:%hu -> %s:%hu", externaladdr, externalport, lanaddr, lanport); + + closenatpmp(&natpmp); + + return errorcode; +} + + +static int n2n_natpmp_del_port_mapping (const uint16_t port) { + + int errorcode = 0; + natpmp_t natpmp; + int ret = 0; + char lanaddr[N2N_NETMASK_STR_SIZE] = {'\0'}; + // uint16_t lanport = 0; + char externaladdr[N2N_NETMASK_STR_SIZE] = {'\0'}; + uint16_t externalport = 0; + + // lanport = port; + externalport = port; + + ret = n2n_natpmp_initialization(&natpmp, lanaddr, externaladdr); + if(ret != 0) { + errorcode = -1; + return errorcode; + } + + // TCP port + ret = n2n_natpmp_port_mapping_request(&natpmp, port, NATPMP_PROTOCOL_TCP, 0); + if(ret != 0) { + traceEvent(TRACE_WARNING, "NAT-PMP failed to delete TCP port mapping for %s:%hu", externaladdr, externalport); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "NAT-PMP deleted TCP port mapping for %s:%hu", externaladdr, externalport); + + // UDP port + ret = n2n_natpmp_port_mapping_request(&natpmp, port, NATPMP_PROTOCOL_UDP, 0); + if(ret != 0) { + traceEvent(TRACE_WARNING, "NAT-PMP failed to delete UDP port mapping for %s:%hu", externaladdr, externalport); + errorcode = -1; + } else + traceEvent(TRACE_NORMAL, "NAT-PMP deleted UDP port mapping for %s:%hu", externaladdr, externalport); + + closenatpmp(&natpmp); + + return errorcode; +} + +#endif // HAVE_NATPMP + + +// ---------------------------------------------------------------------------------------------------- + + +static void n2n_set_port_mapping (const uint16_t port) { + +#ifdef HAVE_NATPMP + // since the NAT-PMP protocol is more concise than UPnP, NAT-PMP is preferred. + if(n2n_natpmp_set_port_mapping(port)) +#endif // HAVE_NATPMP + { +#ifdef HAVE_MINIUPNP + n2n_upnp_set_port_mapping(port); +#endif // HAVE_MINIUPNP + } +} + + +static void n2n_del_port_mapping (const uint16_t port) { + +#ifdef HAVE_NATPMP + if(n2n_natpmp_del_port_mapping(port)) +#endif // HAVE_NATPMP + { +#ifdef HAVE_MINIUPNP + n2n_upnp_del_port_mapping(port); +#endif // HAVE_MINIUPNP + } +} + + +// static +// ---------------------------------------------------------------------------------------------------- +// public + + +#ifdef HAVE_PTHREAD /* future management port subscriptions will deprecate the following temporary code */ +void n2n_chg_port_mapping (struct n2n_edge *eee, uint16_t port) { + // write a port change request to param struct, it will be handled in the thread + pthread_mutex_lock(&eee->port_map_parameter->access); + eee->port_map_parameter->new_port = port; + pthread_mutex_unlock(&eee->port_map_parameter->access); + +} +#endif + + +N2N_THREAD_RETURN_DATATYPE port_map_thread(N2N_THREAD_PARAMETER_DATATYPE p) { + +#ifdef HAVE_PTHREAD /* future management port subscriptions will deprecate the following temporary code */ + n2n_port_map_parameter_t *param = (n2n_port_map_parameter_t*)p; + + while(1) { + sleep(2); + pthread_mutex_lock(¶m->access); + if(param->mapped_port != param->new_port) { + if(param->mapped_port) + n2n_del_port_mapping(param->mapped_port); + if(param->new_port) + n2n_set_port_mapping(param->new_port); + param->mapped_port = param->new_port; + } + pthread_mutex_unlock(¶m->access); + } +#endif + +#if 0 /* to be used with future management port subscriptions */ + n2n_port_map_parameter_t *param = (n2n_port_map_parameter_t*)p; + SOCKET socket_fd; + fd_set socket_mask; + struct timeval wait_time; + int ret = 0; + char udp_buf[N2N_PKT_BUF_SIZE]; + ssize_t msg_len; + uint32_t addr_tmp = htonl(INADDR_LOOPBACK); + n2n_sock_t sock_tmp; + struct sockaddr_in sock; + socklen_t sock_len; + + // open a new socket ... + socket_fd = open_socket(0 /* no specific port */, INADDR_LOOPBACK, 0 /* UDP */); + if(socket_fd < 0) { + traceEvent(TRACE_ERROR, "port_map_thread failed to open a socket to management port"); + return 0; + } + // ... and connect to local mgmt port + sock_tmp.family = AF_INET; + memcpy(&sock_tmp.addr.v4, &addr_tmp, IPV4_SIZE); + sock_tmp.port = param->mgmt_port; + sock_len = sizeof(sock); + fill_sockaddr((struct sockaddr*)&sock, sock_len, &sock_tmp); + connect(socket_fd, (struct sockaddr*)&sock, sock_len); + + // prepare a subscription request in 'udp_buf' of length 'msg_len' + // !!! dummy + udp_buf[0] = '\n'; + msg_len = 1; + send(socket_fd, udp_buf, msg_len, 0 /*flags*/); + // note: 'msg_len' and 'sock' get re-used hereafter + + while(1) { + FD_ZERO(&socket_mask); + FD_SET(socket_fd, &socket_mask); + + wait_time.tv_sec = SOCKET_TIMEOUT_INTERVAL_SECS; + wait_time.tv_usec = 0; + + ret = select(socket_fd + 1, &socket_mask, NULL, NULL, &wait_time); + + if(ret > 0) { + if(FD_ISSET(socket_fd, &socket_mask)) { + // get the data + sock_len = sizeof(sock); + msg_len = recv(socket_fd, udp_buf, N2N_PKT_BUF_SIZE, 0 /*flags*/); + + // check message format, first message could be the still buffered answer to the subscription request + // !!! + if(1 /* !!! correct message format */) { + // delete an eventually previous port mapping + if(param->mapped_port) + n2n_del_port_mapping(param->mapped_port); + // extract port from message and set accordingly if valid + param->mapped_port = 0; // !!! + if(param->mapped_port) + n2n_set_port_mapping(param->mapped_port); + } + } + } + } +#endif + + return 0; /* should never happen */ +} + + +int port_map_create_thread (n2n_port_map_parameter_t **param, uint16_t mgmt_port) { + +#ifdef HAVE_PTHREAD + int ret; + + // create parameter structure + *param = (n2n_port_map_parameter_t*)calloc(1, sizeof(n2n_port_map_parameter_t)); + if(*param) { + // initialize + (*param)->mgmt_port = mgmt_port; + } else { + traceEvent(TRACE_WARNING, "port_map_create_thread was unable to create parameter structure"); + return -1; + } + + // create thread + ret = pthread_create(&((*param)->id), NULL, port_map_thread, (void *)*param); + if(ret) { + traceEvent(TRACE_WARNING, "port_map_create_thread failed to create port mapping thread with error number %d", ret); + return -1; + } + +#endif + + return 0; +} + + +void port_map_cancel_thread (n2n_port_map_parameter_t *param) { + +#ifdef HAVE_PTHREAD + pthread_cancel(param->id); + if(param->mapped_port) + n2n_del_port_mapping(param->mapped_port); + free(param); +#endif +} diff --git a/thirdparty/libnatpmp b/thirdparty/libnatpmp new file mode 160000 index 0000000..4536032 --- /dev/null +++ b/thirdparty/libnatpmp @@ -0,0 +1 @@ +Subproject commit 4536032ae32268a45c073a4d5e91bbab4534773a diff --git a/thirdparty/miniupnp b/thirdparty/miniupnp new file mode 160000 index 0000000..77876ae --- /dev/null +++ b/thirdparty/miniupnp @@ -0,0 +1 @@ +Subproject commit 77876aea5fd926ef25dce9af6b264c7b53d2b134