diff options
author | Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> | 2020-02-23 01:34:13 +0200 |
---|---|---|
committer | Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> | 2021-01-07 18:07:09 +0200 |
commit | a8d9f275d0ca64797cc89627f8003b48b3efef63 (patch) | |
tree | 69742a6269aad2f4b51d25410db8135726292e3e | |
parent | 58e8364dcdc4e57b0caac09f8402e6535301de9b (diff) |
net: Add libnatpmp support
-rw-r--r-- | ci/test/00_setup_env_native_qt5.sh | 2 | ||||
-rw-r--r-- | configure.ac | 47 | ||||
-rw-r--r-- | depends/Makefile | 6 | ||||
-rw-r--r-- | depends/config.site.in | 4 | ||||
-rw-r--r-- | depends/packages/libnatpmp.mk | 19 | ||||
-rw-r--r-- | depends/packages/packages.mk | 1 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/Makefile.bench.include | 2 | ||||
-rw-r--r-- | src/Makefile.qt.include | 2 | ||||
-rw-r--r-- | src/Makefile.qttest.include | 2 | ||||
-rw-r--r-- | src/Makefile.test.include | 2 | ||||
-rw-r--r-- | src/mapport.cpp | 116 | ||||
-rw-r--r-- | src/mapport.h | 1 | ||||
-rw-r--r-- | src/net.h | 2 |
14 files changed, 195 insertions, 13 deletions
diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh index dc6b2aecb5..567145fe47 100644 --- a/ci/test/00_setup_env_native_qt5.sh +++ b/ci/test/00_setup_env_native_qt5.sh @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_qt5 export DOCKER_NAME_TAG=ubuntu:18.04 # Check that bionic gcc-7 can compile our c++17 and run our functional tests in python3, see doc/dependencies.md export PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libdbus-1-dev libharfbuzz-dev" -export DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" +export DEP_OPTS="NO_QT=1 NO_UPNP=1 NO_NATPMP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" export TEST_RUNNER_EXTRA="--previous-releases --coverage --extended --exclude feature_dbcrash" # Run extended tests so that coverage does not fail, but exclude the very slow dbcrash export RUN_SECURITY_TESTS="true" export RUN_UNIT_TESTS_SEQUENTIAL="true" diff --git a/configure.ac b/configure.ac index c69b793d6e..352e7aaee7 100644 --- a/configure.ac +++ b/configure.ac @@ -143,6 +143,18 @@ AC_ARG_ENABLE([upnp-default], [use_upnp_default=$enableval], [use_upnp_default=no]) +AC_ARG_WITH([natpmp], + [AS_HELP_STRING([--with-natpmp], + [enable NAT-PMP (default is yes if libnatpmp is found)])], + [use_natpmp=$withval], + [use_natpmp=auto]) + +AC_ARG_ENABLE([natpmp-default], + [AS_HELP_STRING([--enable-natpmp-default], + [if NAT-PMP is enabled, turn it on at startup (default is no)])], + [use_natpmp_default=$enableval], + [use_natpmp_default=no]) + AC_ARG_ENABLE(tests, AS_HELP_STRING([--disable-tests],[do not compile tests (default is to compile)]), [use_tests=$enableval], @@ -1203,6 +1215,7 @@ if test "x$enable_fuzz" = "xyes"; then enable_wallet=no use_bench=no use_upnp=no + use_natpmp=no use_zmq=no AX_CHECK_PREPROC_FLAG([-DABORT_ON_FAILED_ASSUME],[[DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DABORT_ON_FAILED_ASSUME"]],,[[$CXXFLAG_WERROR]]) @@ -1300,6 +1313,13 @@ if test x$have_miniupnpc != xno; then fi fi +dnl Check for libnatpmp (optional). +if test "x$use_natpmp" != xno; then + AC_CHECK_HEADERS([natpmp.h], + [AC_CHECK_LIB([natpmp], [initnatpmp], [NATPMP_LIBS=-lnatpmp], [have_natpmp=no])], + [have_natpmp=no]) +fi + if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then use_boost=no else @@ -1558,6 +1578,31 @@ else fi fi +dnl Enable NAT-PMP support. +AC_MSG_CHECKING([whether to build with support for NAT-PMP]) +if test "x$have_natpmp" = xno; then + if test "x$use_natpmp" = xyes; then + AC_MSG_ERROR([NAT-PMP requested but cannot be built. Use --without-natpmp]) + fi + AC_MSG_RESULT([no]) + use_natpmp=no +else + if test "x$use_natpmp" != xno; then + AC_MSG_RESULT([yes]) + AC_MSG_CHECKING([whether to build with NAT-PMP enabled by default]) + use_natpmp=yes + natpmp_setting=0 + if test "x$use_natpmp_default" != xno; then + use_natpmp_default=yes + natpmp_setting=1 + fi + AC_MSG_RESULT($use_natpmp_default) + AC_DEFINE_UNQUOTED([USE_NATPMP], [$natpmp_setting], [NAT-PMP support not compiled if undefined, otherwise value (0 or 1) determines default state]) + else + AC_MSG_RESULT([no]) + fi +fi + dnl these are only used when qt is enabled BUILD_TEST_QT="" if test x$bitcoin_enable_qt != xno; then @@ -1704,6 +1749,7 @@ AC_SUBST(SQLITE_LIBS) AC_SUBST(TESTDEFS) AC_SUBST(MINIUPNPC_CPPFLAGS) AC_SUBST(MINIUPNPC_LIBS) +AC_SUBST(NATPMP_LIBS) AC_SUBST(EVENT_LIBS) AC_SUBST(EVENT_PTHREADS_LIBS) AC_SUBST(ZMQ_LIBS) @@ -1791,6 +1837,7 @@ else fi echo " with bench = $use_bench" echo " with upnp = $use_upnp" +echo " with natpmp = $use_natpmp" echo " use asm = $use_asm" echo " sanitizers = $use_sanitizers" echo " debug enabled = $enable_debug" diff --git a/depends/Makefile b/depends/Makefile index 1ad21f6821..0de98f8e3c 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -37,6 +37,7 @@ NO_QR ?= NO_WALLET ?= NO_ZMQ ?= NO_UPNP ?= +NO_NATPMP ?= MULTIPROCESS ?= FALLBACK_DOWNLOAD_PATH ?= https://bitcoincore.org/depends-sources @@ -139,10 +140,12 @@ sqlite_packages_$(NO_SQLITE) = $(sqlite_packages) wallet_packages_$(NO_WALLET) = $(bdb_packages_) $(sqlite_packages_) upnp_packages_$(NO_UPNP) = $(upnp_packages) +natpmp_packages_$(NO_NATPMP) = $(natpmp_packages) + zmq_packages_$(NO_ZMQ) = $(zmq_packages) multiprocess_packages_$(MULTIPROCESS) = $(multiprocess_packages) -packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) +packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) $(natpmp_packages_) native_packages += $($(host_arch)_$(host_os)_native_packages) $($(host_os)_native_packages) ifneq ($(zmq_packages_),) @@ -200,6 +203,7 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_ -e 's|@no_zmq@|$(NO_ZMQ)|' \ -e 's|@no_wallet@|$(NO_WALLET)|' \ -e 's|@no_upnp@|$(NO_UPNP)|' \ + -e 's|@no_natpmp@|$(NO_NATPMP)|' \ -e 's|@multiprocess@|$(MULTIPROCESS)|' \ -e 's|@debug@|$(DEBUG)|' \ $< > $@ diff --git a/depends/config.site.in b/depends/config.site.in index f4531830c8..bbfbd42460 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -46,6 +46,10 @@ if test -z $with_miniupnpc && test -n "@no_upnp@"; then with_miniupnpc=no fi +if test -z $with_natpmp && test -n "@no_natpmp@"; then + with_natpmp=no +fi + if test -z $with_gui && test -n "@no_qt@"; then with_gui=no fi diff --git a/depends/packages/libnatpmp.mk b/depends/packages/libnatpmp.mk new file mode 100644 index 0000000000..a24f201859 --- /dev/null +++ b/depends/packages/libnatpmp.mk @@ -0,0 +1,19 @@ +package=libnatpmp +$(package)_version=20150609 +$(package)_download_path=https://miniupnp.tuxfamily.org/files/ +$(package)_file_name=$(package)-$($(package)_version).tar.gz +$(package)_sha256_hash=e1aa9c4c4219bc06943d6b2130f664daee213fb262fcb94dd355815b8f4536b0 + +define $(package)_set_vars + $(package)_build_opts=CC="$($(package)_cc)" +endef + +define $(package)_build_cmds + $(MAKE) libnatpmp.a $($(package)_build_opts) +endef + +define $(package)_stage_cmds + mkdir -p $($(package)_staging_prefix_dir)/include $($(package)_staging_prefix_dir)/lib &&\ + install *.h $($(package)_staging_prefix_dir)/include &&\ + install libnatpmp.a $($(package)_staging_prefix_dir)/lib +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index d4fd23a47b..0f35ca0d2d 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -16,6 +16,7 @@ sqlite_packages=sqlite zmq_packages=zeromq upnp_packages=miniupnpc +natpmp_packages=libnatpmp multiprocess_packages = libmultiprocess capnp multiprocess_native_packages = native_libmultiprocess native_capnp diff --git a/src/Makefile.am b/src/Makefile.am index ec697515d9..d5975ccc79 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -598,7 +598,7 @@ bitcoin_bin_ldadd = \ $(LIBMEMENV) \ $(LIBSECP256K1) -bitcoin_bin_ldadd += $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(SQLITE_LIBS) +bitcoin_bin_ldadd += $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(SQLITE_LIBS) bitcoind_SOURCES = $(bitcoin_daemon_sources) bitcoind_CPPFLAGS = $(bitcoin_bin_cppflags) diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index beb3f8dfd2..56b8ca8ce6 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -74,7 +74,7 @@ bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp endif -bench_bench_bitcoin_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(SQLITE_LIBS) +bench_bench_bitcoin_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index f46310a603..3d41d203d3 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -320,7 +320,7 @@ if ENABLE_ZMQ bitcoin_qt_ldadd += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif bitcoin_qt_ldadd += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ - $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(SQLITE_LIBS) bitcoin_qt_ldflags = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) bitcoin_qt_libtoolflags = $(AM_LIBTOOLFLAGS) --tag CXX diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index c05dd38737..a6a857d952 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -55,7 +55,7 @@ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) \ $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ - $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(SQLITE_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) qt_test_test_bitcoin_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 3faa5ac968..4abf388bcc 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -167,7 +167,7 @@ test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_C $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -test_test_bitcoin_LDADD += $(BDB_LIBS) $(MINIUPNPC_LIBS) $(SQLITE_LIBS) +test_test_bitcoin_LDADD += $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) -static if ENABLE_ZMQ diff --git a/src/mapport.cpp b/src/mapport.cpp index 5f0aeb5e40..f3ede623a3 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -16,6 +16,11 @@ #include <threadinterrupt.h> #include <util/system.h> +#ifdef USE_NATPMP +#include <compat.h> +#include <natpmp.h> +#endif // USE_NATPMP + #ifdef USE_UPNP #include <miniupnpc/miniupnpc.h> #include <miniupnpc/upnpcommands.h> @@ -23,7 +28,7 @@ // The minimum supported miniUPnPc API version is set to 10. This keeps compatibility // with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); -#endif +#endif // USE_UPNP #include <atomic> #include <cassert> @@ -32,7 +37,7 @@ static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed" #include <string> #include <thread> -#ifdef USE_UPNP +#if defined(USE_NATPMP) || defined(USE_UPNP) static CThreadInterrupt g_mapport_interrupt; static std::thread g_mapport_thread; static std::atomic_uint g_mapport_target_proto{MapPortProtoFlag::NONE}; @@ -41,6 +46,106 @@ using namespace std::chrono_literals; static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min}; static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min}; +#ifdef USE_NATPMP +static uint16_t g_mapport_external_port = 0; +static bool NatpmpInit(natpmp_t* natpmp) +{ + const int r_init = initnatpmp(natpmp, /* detect gateway automatically */ 0, /* forced gateway - NOT APPLIED*/ 0); + if (r_init == 0) return true; + LogPrintf("natpmp: initnatpmp() failed with %d error.\n", r_init); + return false; +} + +static bool NatpmpDiscover(natpmp_t* natpmp, struct in_addr& external_ipv4_addr) +{ + const int r_send = sendpublicaddressrequest(natpmp); + if (r_send == 2 /* OK */) { + int r_read; + natpmpresp_t response; + do { + r_read = readnatpmpresponseorretry(natpmp, &response); + } while (r_read == NATPMP_TRYAGAIN); + + if (r_read == 0) { + external_ipv4_addr = response.pnu.publicaddress.addr; + return true; + } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) { + LogPrintf("natpmp: The gateway does not support NAT-PMP.\n"); + } else { + LogPrintf("natpmp: readnatpmpresponseorretry() for public address failed with %d error.\n", r_read); + } + } else { + LogPrintf("natpmp: sendpublicaddressrequest() failed with %d error.\n", r_send); + } + + return false; +} + +static bool NatpmpMapping(natpmp_t* natpmp, const struct in_addr& external_ipv4_addr, uint16_t private_port, bool& external_ip_discovered) +{ + const uint16_t suggested_external_port = g_mapport_external_port ? g_mapport_external_port : private_port; + const int r_send = sendnewportmappingrequest(natpmp, NATPMP_PROTOCOL_TCP, private_port, suggested_external_port, 3600 /*seconds*/); + if (r_send == 12 /* OK */) { + int r_read; + natpmpresp_t response; + do { + r_read = readnatpmpresponseorretry(natpmp, &response); + } while (r_read == NATPMP_TRYAGAIN); + + if (r_read == 0) { + auto pm = response.pnu.newportmapping; + if (private_port == pm.privateport && pm.lifetime > 0) { + g_mapport_external_port = pm.mappedpublicport; + const CService external{external_ipv4_addr, pm.mappedpublicport}; + if (!external_ip_discovered && fDiscover) { + AddLocal(external, LOCAL_MAPPED); + external_ip_discovered = true; + } + LogPrintf("natpmp: Port mapping successful. External address = %s\n", external.ToString()); + return true; + } else { + LogPrintf("natpmp: Port mapping failed.\n"); + } + } else if (r_read == NATPMP_ERR_NOGATEWAYSUPPORT) { + LogPrintf("natpmp: The gateway does not support NAT-PMP.\n"); + } else { + LogPrintf("natpmp: readnatpmpresponseorretry() for port mapping failed with %d error.\n", r_read); + } + } else { + LogPrintf("natpmp: sendnewportmappingrequest() failed with %d error.\n", r_send); + } + + return false; +} + +static bool ProcessNatpmp() +{ + bool ret = false; + natpmp_t natpmp; + struct in_addr external_ipv4_addr; + if (NatpmpInit(&natpmp) && NatpmpDiscover(&natpmp, external_ipv4_addr)) { + bool external_ip_discovered = false; + const uint16_t private_port = GetListenPort(); + do { + ret = NatpmpMapping(&natpmp, external_ipv4_addr, private_port, external_ip_discovered); + } while (ret && g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); + g_mapport_interrupt.reset(); + + const int r_send = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, private_port, g_mapport_external_port, /* remove a port mapping */ 0); + g_mapport_external_port = 0; + if (r_send == 12 /* OK */) { + LogPrintf("natpmp: Port mapping removed successfully.\n"); + } else { + LogPrintf("natpmp: sendnewportmappingrequest(0) failed with %d error.\n", r_send); + } + } + + closenatpmp(&natpmp); + return ret; +} +#endif // USE_NATPMP + +#ifdef USE_UPNP static bool ProcessUpnp() { bool ret = false; @@ -111,6 +216,7 @@ static bool ProcessUpnp() return ret; } +#endif // USE_UPNP static void ThreadMapPort() { @@ -123,7 +229,7 @@ void StartThreadMapPort() { if (!g_mapport_thread.joinable()) { assert(!g_mapport_interrupt); - g_mapport_thread = std::thread((std::bind(&TraceThread<void (*)()>, "mapport", &ThreadMapPort))); + g_mapport_thread = std::thread(std::bind(&TraceThread<void (*)()>, "mapport", &ThreadMapPort)); } } @@ -167,7 +273,7 @@ void StopMapPort() } } -#else +#else // #if defined(USE_NATPMP) || defined(USE_UPNP) void StartMapPort(bool use_upnp) { // Intentionally left blank. @@ -180,4 +286,4 @@ void StopMapPort() { // Intentionally left blank. } -#endif +#endif // #if defined(USE_NATPMP) || defined(USE_UPNP) diff --git a/src/mapport.h b/src/mapport.h index 6e33b6ef74..2cdde775d3 100644 --- a/src/mapport.h +++ b/src/mapport.h @@ -15,6 +15,7 @@ static const bool DEFAULT_UPNP = false; enum MapPortProtoFlag : unsigned int { NONE = 0x00, UPNP = 0x01, + NAT_PMP = 0x02, }; void StartMapPort(bool use_upnp); @@ -182,7 +182,7 @@ enum LOCAL_NONE, // unknown LOCAL_IF, // address a local interface listens on LOCAL_BIND, // address explicit bound to - LOCAL_MAPPED, // address reported by UPnP + LOCAL_MAPPED, // address reported by UPnP or NAT-PMP LOCAL_MANUAL, // address explicitly specified (-externalip=) LOCAL_MAX |