diff options
author | Wladimir J. van der Laan <laanwj@protonmail.com> | 2021-01-07 19:33:14 +0100 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@protonmail.com> | 2021-01-07 19:41:55 +0100 |
commit | d7e2401c629be3db6ceef551ee0f74deb56c02d1 (patch) | |
tree | 2404b5124be0cf9d6daa53efb7ea1a7a929e1e88 | |
parent | b6a71b80d28c79796b557cdb6bae05abb34d1225 (diff) | |
parent | a191e23b8e7f0e19fc0359825eb7ca0d47966fa9 (diff) |
Merge #18077: net: Add NAT-PMP port forwarding support
a191e23b8e7f0e19fc0359825eb7ca0d47966fa9 doc: Add release notes (Hennadii Stepanov)
ae749d12ddbaf592fbdb65d98ca35a0ff5566992 doc: Add libnatpmp stuff (Hennadii Stepanov)
e28f9be87a0f3c59a9184d602fe7947526df6a97 ci: Add libnatpmp-dev package to some builds (Hennadii Stepanov)
5a0185b6c9c838290103314916190a0330ed9a82 gui: Add NAT-PMP network option (Hennadii Stepanov)
a39f7336a3b493d46a4486c4c94fdca1b3151370 net: Add -natpmp command line option (Hennadii Stepanov)
28acffd9d53ec437e908abb8c84497a4f41b91ed net: Add NAT-PMP to port mapping loop (Hennadii Stepanov)
a8d9f275d0ca64797cc89627f8003b48b3efef63 net: Add libnatpmp support (Hennadii Stepanov)
58e8364dcdc4e57b0caac09f8402e6535301de9b gui: Apply port mapping changes on dialog exit (Hennadii Stepanov)
cf151cc68c95a8943e43e3fa4061e176262779e7 scripted-diff: Rename UPnP stuff (Hennadii Stepanov)
4e91b1e24d96e0cdccdd2a3ed034413f3ba6bae6 net: Add flags for port mapping protocols (Hennadii Stepanov)
8b50d1b5bb29b7d1ea0245ba75a8df3144e312dc net: Keep trying to use UPnP when -upnp=1 (Hennadii Stepanov)
28e2961fd6a2a9101fc08fb748430989291aaf7e refactor: Replace magic number with named constant (Hennadii Stepanov)
02ccf69dd6b772423acb343d16ef2bdbb3e3da03 refactor: Move port mapping code to its own module (Hennadii Stepanov)
Pull request description:
Close #11902
This PR is an alternative to:
- #12288
- #15717
To compile with NAT-PMP support on Ubuntu [`libnatpmp-dev`](https://packages.ubuntu.com/source/bionic/libnatpmp) should be available.
Log excerpt:
```
2020-02-05T20:12:28Z [mapport] NAT-PMP: public address = 95.164.65.194
2020-02-05T20:12:28Z [mapport] AddLocal(95.164.65.194:18333,3)
2020-02-05T20:12:28Z [mapport] NAT-PMP: port mapping successful.
```
See: [`libnatpmp`](https://miniupnp.tuxfamily.org/libnatpmp.html)
---
Some follow-ups are out of this PR's scope:
- mention NAT-PMP library in the version message
- ~integrate NAT-PMP into the GUI~ (already [added](https://github.com/bitcoin/bitcoin/pull/18077#issuecomment-589405068))
ACKs for top commit:
laanwj:
Tested and code review ACK a191e23b8e7f0e19fc0359825eb7ca0d47966fa9
Tree-SHA512: 10e19267c21bf30f20ff1abfc882d526049f0e790b95e12f109dc2bed7c0aef45de03eaf967f4e667e7509be04f1873a5c508087393d947205f3aab2ad6d7cf1
33 files changed, 547 insertions, 173 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index e7a50d931a..b915cfabc7 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -170,7 +170,7 @@ task: task: name: 'macOS 10.15 native [gui] [no depends]' macos_brew_addon_script: - - brew install boost libevent berkeley-db4 qt miniupnpc ccache zeromq qrencode sqlite libtool automake pkg-config gnu-getopt + - brew install boost libevent berkeley-db4 qt miniupnpc libnatpmp ccache zeromq qrencode sqlite libtool automake pkg-config gnu-getopt << : *GLOBAL_TASK_TEMPLATE osx_instance: # Use latest image, but hardcode version to avoid silent upgrades (and breaks) diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh index 191b8049b0..f682486088 100644 --- a/ci/test/00_setup_env_native_asan.sh +++ b/ci/test/00_setup_env_native_asan.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_asan -export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev libsqlite3-dev" +export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev libqrencode-dev libsqlite3-dev" export DOCKER_NAME_TAG=ubuntu:20.04 export NO_DEPENDS=1 export GOAL="install" 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/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh index bfaea13a25..f0c153158b 100644 --- a/ci/test/00_setup_env_native_valgrind.sh +++ b/ci/test/00_setup_env_native_valgrind.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_valgrind -export PACKAGES="valgrind clang llvm python3-zmq libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libsqlite3-dev" +export PACKAGES="valgrind clang llvm python3-zmq libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev libsqlite3-dev" export USE_VALGRIND=1 export NO_DEPENDS=1 export TEST_RUNNER_EXTRA="--exclude rpc_bind" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547 diff --git a/configure.ac b/configure.ac index 0a8d5ef8f8..0fa99cfc47 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], @@ -1206,6 +1218,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]]) @@ -1303,6 +1316,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 @@ -1561,6 +1581,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 @@ -1707,6 +1752,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) @@ -1794,6 +1840,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/README.md b/depends/README.md index 085e0e6054..9bc9b6714b 100644 --- a/depends/README.md +++ b/depends/README.md @@ -94,6 +94,7 @@ The following can be set when running make: `make FOO=bar` - `NO_BDB`: Don't download/build/cache BerkeleyDB - `NO_SQLITE`: Don't download/build/cache SQLite - `NO_UPNP`: Don't download/build/cache packages needed for enabling UPnP +- `NO_NATPMP`: Don't download/build/cache packages needed for enabling NAT-PMP</dd> - `ALLOW_HOST_PACKAGES`: Packages that are missed in dependencies (due to `NO_*` option or build script logic) are searched for among the host system packages using `pkg-config`. It allows building with packages of other (newer) versions 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/doc/build-osx.md b/doc/build-osx.md index c1d101fde1..b46bf61c4b 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -19,7 +19,7 @@ Then install [Homebrew](https://brew.sh). ## Dependencies ```shell -brew install automake libtool boost miniupnpc pkg-config python qt libevent qrencode +brew install automake libtool boost miniupnpc libnatpmp pkg-config python qt libevent qrencode ``` If you run into issues, check [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting). diff --git a/doc/build-unix.md b/doc/build-unix.md index cfe3328b45..5c24886dbf 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -41,6 +41,7 @@ Optional dependencies: Library | Purpose | Description ------------|------------------|---------------------- miniupnpc | UPnP Support | Firewall-jumping support + libnatpmp | NAT-PMP Support | Firewall-jumping support libdb4.8 | Berkeley DB | Optional, wallet storage (only needed when wallet enabled) qt | GUI | GUI toolkit (only needed when GUI enabled) libqrencode | QR codes in GUI | Optional for generating QR codes (only needed when GUI enabled) @@ -99,9 +100,9 @@ SQLite is required for the wallet: To build Bitcoin Core without wallet, see [*Disable-wallet mode*](/doc/build-unix.md#disable-wallet-mode) -Optional (see `--with-miniupnpc` and `--enable-upnp-default`): +Optional port mapping libraries (see: `--with-miniupnpc`, and `--enable-upnp-default`, `--with-natpmp`, `--enable-natpmp-default`): - sudo apt-get install libminiupnpc-dev + sudo apt install libminiupnpc-dev libnatpmp-dev ZMQ dependencies (provides ZMQ API): @@ -133,9 +134,9 @@ Build requirements: sudo dnf install gcc-c++ libtool make autoconf automake libevent-devel boost-devel libdb4-devel libdb4-cxx-devel python3 -Optional (see `--with-miniupnpc` and `--enable-upnp-default`): +Optional port mapping libraries (see: `--with-miniupnpc`, and `--enable-upnp-default`, `--with-natpmp`, `--enable-natpmp-default`): - sudo dnf install miniupnpc-devel + sudo dnf install miniupnpc-devel libnatpmp-devel ZMQ dependencies (provides ZMQ API): @@ -158,18 +159,27 @@ Notes The release is built with GCC and then "strip bitcoind" to strip the debug symbols, which reduces the executable size by about 90%. - miniupnpc --------- [miniupnpc](https://miniupnp.tuxfamily.org) may be used for UPnP port mapping. It can be downloaded from [here]( https://miniupnp.tuxfamily.org/files/). UPnP support is compiled in and -turned off by default. See the configure options for upnp behavior desired: +turned off by default. See the configure options for UPnP behavior desired: - --without-miniupnpc No UPnP support miniupnp not required + --without-miniupnpc No UPnP support, miniupnp not required --disable-upnp-default (the default) UPnP support turned off by default at runtime --enable-upnp-default UPnP support turned on by default at runtime +libnatpmp +--------- + +[libnatpmp](https://miniupnp.tuxfamily.org/libnatpmp.html) may be used for NAT-PMP port mapping. It can be downloaded +from [here](https://miniupnp.tuxfamily.org/files/). NAT-PMP support is compiled in and +turned off by default. See the configure options for NAT-PMP behavior desired: + + --without-natpmp No NAT-PMP support, libnatpmp not required + --disable-natpmp-default (the default) NAT-PMP support turned off by default at runtime + --enable-natpmp-default NAT-PMP support turned on by default at runtime Berkeley DB ----------- diff --git a/doc/dependencies.md b/doc/dependencies.md index 76e8910871..6b2b904632 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -14,6 +14,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | GCC | | [7+](https://gcc.gnu.org/) (C++17 support) | | | | | HarfBuzz-NG | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | libevent | [2.1.11-stable](https://github.com/libevent/libevent/releases) | [2.0.21](https://github.com/bitcoin/bitcoin/pull/18676) | No | | | +| libnatpmp | [20150609](https://miniupnp.tuxfamily.org/files) | | No | | | | libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | librsvg | | | | | | | MiniUPnPc | [2.0.20180203](https://miniupnp.tuxfamily.org/files) | | No | | | @@ -32,7 +33,8 @@ Controlling dependencies Some dependencies are not needed in all configurations. The following are some factors that affect the dependency list. #### Options passed to `./configure` -* MiniUPnPc is not needed with `--with-miniupnpc=no`. +* MiniUPnPc is not needed with `--without-miniupnpc`. +* libnatpmp is not needed with `--without-natpmp`. * Berkeley DB is not needed with `--disable-wallet` or `--without-bdb`. * SQLite is not needed with `--disable-wallet` or `--without-sqlite`. * Qt is not needed with `--without-gui`. diff --git a/doc/productivity.md b/doc/productivity.md index 555f0afe3c..a01c6f545d 100644 --- a/doc/productivity.md +++ b/doc/productivity.md @@ -51,6 +51,7 @@ After running `./autogen.sh`, which generates the `./configure` file, use `./con ```sh --without-miniupnpc +--without-natpmp --disable-bench --disable-wallet --without-gui diff --git a/doc/release-notes-18077.md b/doc/release-notes-18077.md new file mode 100644 index 0000000000..4034a4c19c --- /dev/null +++ b/doc/release-notes-18077.md @@ -0,0 +1,10 @@ +P2P and network changes +----------------------- + +- Added NAT-PMP port mapping support via [`libnatpmp`](https://miniupnp.tuxfamily.org/libnatpmp.html) + +Command-line options +-------------------- + +- The `-natpmp` option has been added to use NAT-PMP to map the listening port. If both UPnP +and NAT-PMP are enabled, a successful allocation from UPnP prevails over one from NAT-PMP. diff --git a/doc/tor.md b/doc/tor.md index 86e5d9ddf3..1ba7137b8e 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -99,7 +99,7 @@ as well, use `discover` instead: ./bitcoind ... -discover -and open port 8333 on your firewall (or use -upnp). +and open port 8333 on your firewall (or use port mapping, i.e., `-upnp` or `-natpmp`). If you only want to use Tor to reach .onion addresses, but not use it as a proxy for normal IPv4/IPv6 communication, use: diff --git a/src/Makefile.am b/src/Makefile.am index 02797b19bc..1a0791dccd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -152,6 +152,7 @@ BITCOIN_CORE_H = \ key_io.h \ logging.h \ logging/timer.h \ + mapport.h \ memusage.h \ merkleblock.h \ miner.h \ @@ -299,6 +300,7 @@ libbitcoin_server_a_SOURCES = \ index/blockfilterindex.cpp \ index/txindex.cpp \ init.cpp \ + mapport.cpp \ miner.cpp \ net.cpp \ net_processing.cpp \ @@ -598,7 +600,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 bdaddb4f0d..e9f9b73abe 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/init.cpp b/src/init.cpp index 42e9925f98..9a63fe0e03 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -26,6 +26,7 @@ #include <interfaces/chain.h> #include <interfaces/node.h> #include <key.h> +#include <mapport.h> #include <miner.h> #include <net.h> #include <net_permissions.h> @@ -468,6 +469,11 @@ void SetupServerArgs(NodeContext& node) #else hidden_args.emplace_back("-upnp"); #endif +#ifdef USE_NATPMP + argsman.AddArg("-natpmp", strprintf("Use NAT-PMP to map the listening port (default: %s)", DEFAULT_NATPMP ? "1 when listening and no -proxy" : "0"), ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION); +#else + hidden_args.emplace_back("-natpmp"); +#endif // USE_NATPMP argsman.AddArg("-whitebind=<[permissions@]addr>", "Bind to the given address and add permission flags to the peers connecting to it. " "Use [host]:port notation for IPv6. Allowed permissions: " + Join(NET_PERMISSIONS_DOC, ", ") + ". " "Specify multiple permissions separated by commas (default: download,noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -812,10 +818,13 @@ void InitParameterInteraction(ArgsManager& args) // to protect privacy, do not listen by default if a default proxy server is specified if (args.SoftSetBoolArg("-listen", false)) LogPrintf("%s: parameter interaction: -proxy set -> setting -listen=0\n", __func__); - // to protect privacy, do not use UPNP when a proxy is set. The user may still specify -listen=1 + // to protect privacy, do not map ports when a proxy is set. The user may still specify -listen=1 // to listen locally, so don't rely on this happening through -listen below. if (args.SoftSetBoolArg("-upnp", false)) LogPrintf("%s: parameter interaction: -proxy set -> setting -upnp=0\n", __func__); + if (args.SoftSetBoolArg("-natpmp", false)) { + LogPrintf("%s: parameter interaction: -proxy set -> setting -natpmp=0\n", __func__); + } // to protect privacy, do not discover addresses by default if (args.SoftSetBoolArg("-discover", false)) LogPrintf("%s: parameter interaction: -proxy set -> setting -discover=0\n", __func__); @@ -825,6 +834,9 @@ void InitParameterInteraction(ArgsManager& args) // do not map ports or try to retrieve public IP when not listening (pointless) if (args.SoftSetBoolArg("-upnp", false)) LogPrintf("%s: parameter interaction: -listen=0 -> setting -upnp=0\n", __func__); + if (args.SoftSetBoolArg("-natpmp", false)) { + LogPrintf("%s: parameter interaction: -listen=0 -> setting -natpmp=0\n", __func__); + } if (args.SoftSetBoolArg("-discover", false)) LogPrintf("%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__); if (args.SoftSetBoolArg("-listenonion", false)) @@ -1898,10 +1910,8 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA Discover(); - // Map ports with UPnP - if (args.GetBoolArg("-upnp", DEFAULT_UPNP)) { - StartMapPort(); - } + // Map ports with UPnP or NAT-PMP. + StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), gArgs.GetBoolArg("-natpmp", DEFAULT_NATPMP)); CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 97549aeb0e..15f7ef6256 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -82,7 +82,7 @@ public: virtual bool shutdownRequested() = 0; //! Map port. - virtual void mapPort(bool use_upnp) = 0; + virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; //! Get proxy. virtual bool getProxy(Network net, proxyType& proxy_info) = 0; diff --git a/src/mapport.cpp b/src/mapport.cpp new file mode 100644 index 0000000000..2df4ce45d2 --- /dev/null +++ b/src/mapport.cpp @@ -0,0 +1,336 @@ +// Copyright (c) 2011-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <mapport.h> + +#include <clientversion.h> +#include <logging.h> +#include <net.h> +#include <netaddress.h> +#include <netbase.h> +#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> +#include <miniupnpc/upnperrors.h> +// 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 // USE_UPNP + +#include <atomic> +#include <cassert> +#include <chrono> +#include <functional> +#include <string> +#include <thread> + +#if defined(USE_NATPMP) || defined(USE_UPNP) +static CThreadInterrupt g_mapport_interrupt; +static std::thread g_mapport_thread; +static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE}; +static std::atomic<MapPortProtoFlag> g_mapport_current_proto{MapPortProtoFlag::NONE}; + +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; + std::string port = strprintf("%u", GetListenPort()); + const char * multicastif = nullptr; + const char * minissdpdpath = nullptr; + struct UPNPDev * devlist = nullptr; + char lanaddr[64]; + + int error = 0; +#if MINIUPNPC_API_VERSION < 14 + devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); +#else + devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); +#endif + + struct UPNPUrls urls; + struct IGDdatas data; + int r; + + r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); + if (r == 1) + { + if (fDiscover) { + char externalIPAddress[40]; + r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); + if (r != UPNPCOMMAND_SUCCESS) { + LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); + } else { + if (externalIPAddress[0]) { + CNetAddr resolved; + if (LookupHost(externalIPAddress, resolved, false)) { + LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); + AddLocal(resolved, LOCAL_MAPPED); + } + } else { + LogPrintf("UPnP: GetExternalIPAddress failed.\n"); + } + } + } + + std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); + + do { + r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); + + if (r != UPNPCOMMAND_SUCCESS) { + ret = false; + LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); + break; + } else { + ret = true; + LogPrintf("UPnP Port Mapping successful.\n"); + } + } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); + g_mapport_interrupt.reset(); + + r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); + LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); + freeUPNPDevlist(devlist); devlist = nullptr; + FreeUPNPUrls(&urls); + } else { + LogPrintf("No valid UPnP IGDs found\n"); + freeUPNPDevlist(devlist); devlist = nullptr; + if (r != 0) + FreeUPNPUrls(&urls); + } + + return ret; +} +#endif // USE_UPNP + +static void ThreadMapPort() +{ + bool ok; + do { + ok = false; + +#ifdef USE_UPNP + // High priority protocol. + if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) { + g_mapport_current_proto = MapPortProtoFlag::UPNP; + ok = ProcessUpnp(); + if (ok) continue; + } +#endif // USE_UPNP + +#ifdef USE_NATPMP + // Low priority protocol. + if (g_mapport_enabled_protos & MapPortProtoFlag::NAT_PMP) { + g_mapport_current_proto = MapPortProtoFlag::NAT_PMP; + ok = ProcessNatpmp(); + if (ok) continue; + } +#endif // USE_NATPMP + + g_mapport_current_proto = MapPortProtoFlag::NONE; + if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) { + return; + } + + } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD)); +} + +void StartThreadMapPort() +{ + if (!g_mapport_thread.joinable()) { + assert(!g_mapport_interrupt); + g_mapport_thread = std::thread(std::bind(&TraceThread<void (*)()>, "mapport", &ThreadMapPort)); + } +} + +static void DispatchMapPort() +{ + if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) { + return; + } + + if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) { + StartThreadMapPort(); + return; + } + + if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) { + InterruptMapPort(); + StopMapPort(); + return; + } + + if (g_mapport_enabled_protos & g_mapport_current_proto) { + // Enabling another protocol does not cause switching from the currently used one. + return; + } + + assert(g_mapport_thread.joinable()); + assert(!g_mapport_interrupt); + // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp() + // to force trying the next protocol in the ThreadMapPort() loop. + g_mapport_interrupt(); +} + +static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled) +{ + if (enabled) { + g_mapport_enabled_protos |= proto; + } else { + g_mapport_enabled_protos &= ~proto; + } +} + +void StartMapPort(bool use_upnp, bool use_natpmp) +{ + MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp); + MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp); + DispatchMapPort(); +} + +void InterruptMapPort() +{ + g_mapport_enabled_protos = MapPortProtoFlag::NONE; + if (g_mapport_thread.joinable()) { + g_mapport_interrupt(); + } +} + +void StopMapPort() +{ + if (g_mapport_thread.joinable()) { + g_mapport_thread.join(); + g_mapport_interrupt.reset(); + } +} + +#else // #if defined(USE_NATPMP) || defined(USE_UPNP) +void StartMapPort(bool use_upnp, bool use_natpmp) +{ + // Intentionally left blank. +} +void InterruptMapPort() +{ + // Intentionally left blank. +} +void StopMapPort() +{ + // Intentionally left blank. +} +#endif // #if defined(USE_NATPMP) || defined(USE_UPNP) diff --git a/src/mapport.h b/src/mapport.h new file mode 100644 index 0000000000..279d65167f --- /dev/null +++ b/src/mapport.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_MAPPORT_H +#define BITCOIN_MAPPORT_H + +#ifdef USE_UPNP +static constexpr bool DEFAULT_UPNP = USE_UPNP; +#else +static constexpr bool DEFAULT_UPNP = false; +#endif // USE_UPNP + +#ifdef USE_NATPMP +static constexpr bool DEFAULT_NATPMP = USE_NATPMP; +#else +static constexpr bool DEFAULT_NATPMP = false; +#endif // USE_NATPMP + +enum MapPortProtoFlag : unsigned int { + NONE = 0x00, + UPNP = 0x01, + NAT_PMP = 0x02, +}; + +void StartMapPort(bool use_upnp, bool use_natpmp); +void InterruptMapPort(); +void StopMapPort(); + +#endif // BITCOIN_MAPPORT_H diff --git a/src/net.cpp b/src/net.cpp index ad4af4abb2..6e5a271945 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -33,15 +33,6 @@ #include <poll.h> #endif -#ifdef USE_UPNP -#include <miniupnpc/miniupnpc.h> -#include <miniupnpc/upnpcommands.h> -#include <miniupnpc/upnperrors.h> -// 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 - #include <algorithm> #include <cstdint> #include <unordered_map> @@ -1539,121 +1530,6 @@ void CConnman::WakeMessageHandler() condMsgProc.notify_one(); } - - - - - -#ifdef USE_UPNP -static CThreadInterrupt g_upnp_interrupt; -static std::thread g_upnp_thread; -static void ThreadMapPort() -{ - std::string port = strprintf("%u", GetListenPort()); - const char * multicastif = nullptr; - const char * minissdpdpath = nullptr; - struct UPNPDev * devlist = nullptr; - char lanaddr[64]; - - int error = 0; -#if MINIUPNPC_API_VERSION < 14 - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); -#else - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); -#endif - - struct UPNPUrls urls; - struct IGDdatas data; - int r; - - r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); - if (r == 1) - { - if (fDiscover) { - char externalIPAddress[40]; - r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); - if (r != UPNPCOMMAND_SUCCESS) { - LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); - } else { - if (externalIPAddress[0]) { - CNetAddr resolved; - if (LookupHost(externalIPAddress, resolved, false)) { - LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); - AddLocal(resolved, LOCAL_UPNP); - } - } else { - LogPrintf("UPnP: GetExternalIPAddress failed.\n"); - } - } - } - - std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); - - do { - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); - - if (r != UPNPCOMMAND_SUCCESS) { - LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); - } else { - LogPrintf("UPnP Port Mapping successful.\n"); - } - } while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); - - r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); - LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); - freeUPNPDevlist(devlist); devlist = nullptr; - FreeUPNPUrls(&urls); - } else { - LogPrintf("No valid UPnP IGDs found\n"); - freeUPNPDevlist(devlist); devlist = nullptr; - if (r != 0) - FreeUPNPUrls(&urls); - } -} - -void StartMapPort() -{ - if (!g_upnp_thread.joinable()) { - assert(!g_upnp_interrupt); - g_upnp_thread = std::thread((std::bind(&TraceThread<void (*)()>, "upnp", &ThreadMapPort))); - } -} - -void InterruptMapPort() -{ - if(g_upnp_thread.joinable()) { - g_upnp_interrupt(); - } -} - -void StopMapPort() -{ - if(g_upnp_thread.joinable()) { - g_upnp_thread.join(); - g_upnp_interrupt.reset(); - } -} - -#else -void StartMapPort() -{ - // Intentionally left blank. -} -void InterruptMapPort() -{ - // Intentionally left blank. -} -void StopMapPort() -{ - // Intentionally left blank. -} -#endif - - - - - - void CConnman::ThreadDNSAddressSeed() { FastRandomContext rng; @@ -67,12 +67,6 @@ static const int MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2; static const int MAX_FEELER_CONNECTIONS = 1; /** -listen default */ static const bool DEFAULT_LISTEN = true; -/** -upnp default */ -#ifdef USE_UPNP -static const bool DEFAULT_UPNP = USE_UPNP; -#else -static const bool DEFAULT_UPNP = false; -#endif /** The maximum number of peer connections to maintain. */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125; /** The default for -maxuploadtarget. 0 = Unlimited */ @@ -181,9 +175,6 @@ enum class ConnectionType { }; void Discover(); -void StartMapPort(); -void InterruptMapPort(); -void StopMapPort(); uint16_t GetListenPort(); enum @@ -191,7 +182,7 @@ enum LOCAL_NONE, // unknown LOCAL_IF, // address a local interface listens on LOCAL_BIND, // address explicit bound to - LOCAL_UPNP, // address reported by UPnP + LOCAL_MAPPED, // address reported by UPnP or NAT-PMP LOCAL_MANUAL, // address explicitly specified (-externalip=) LOCAL_MAX diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 317a5c7cbe..e07eaa33d8 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -12,6 +12,7 @@ #include <interfaces/handler.h> #include <interfaces/node.h> #include <interfaces/wallet.h> +#include <mapport.h> #include <net.h> #include <net_processing.h> #include <netaddress.h> @@ -93,15 +94,7 @@ public: } } bool shutdownRequested() override { return ShutdownRequested(); } - void mapPort(bool use_upnp) override - { - if (use_upnp) { - StartMapPort(); - } else { - InterruptMapPort(); - StopMapPort(); - } - } + void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 88944a58a6..8181cc47e2 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -260,6 +260,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="mapPortNatpmp"> + <property name="toolTip"> + <string>Automatically open the Bitcoin client port on the router. This only works when your router supports NAT-PMP and it is enabled. The external port could be random.</string> + </property> + <property name="text"> + <string>Map port using NA&T-PMP</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="allowIncoming"> <property name="toolTip"> <string>Accept connections from outside.</string> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 468008693a..416b9c83c9 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -24,6 +24,7 @@ #include <QIntValidator> #include <QLocale> #include <QMessageBox> +#include <QSettings> #include <QSystemTrayIcon> #include <QTimer> @@ -50,6 +51,13 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifndef USE_UPNP ui->mapPortUpnp->setEnabled(false); #endif +#ifndef USE_NATPMP + ui->mapPortNatpmp->setEnabled(false); +#endif + connect(this, &QDialog::accepted, [this](){ + QSettings settings; + model->node().mapPort(settings.value("fUseUPnP").toBool(), settings.value("fUseNatpmp").toBool()); + }); ui->proxyIp->setEnabled(false); ui->proxyPort->setEnabled(false); @@ -214,6 +222,7 @@ void OptionsDialog::setMapper() /* Network */ mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP); + mapper->addMapping(ui->mapPortNatpmp, OptionsModel::MapPortNatpmp); mapper->addMapping(ui->allowIncoming, OptionsModel::Listen); mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 152de6decb..1e0391a35c 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -13,11 +13,12 @@ #include <qt/guiutil.h> #include <interfaces/node.h> -#include <validation.h> // For DEFAULT_SCRIPTCHECK_THREADS +#include <mapport.h> #include <net.h> #include <netbase.h> -#include <txdb.h> // for -dbcache defaults +#include <txdb.h> // for -dbcache defaults #include <util/string.h> +#include <validation.h> // For DEFAULT_SCRIPTCHECK_THREADS #include <QDebug> #include <QSettings> @@ -123,6 +124,13 @@ void OptionsModel::Init(bool resetSettings) if (!gArgs.SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) addOverriddenOption("-upnp"); + if (!settings.contains("fUseNatpmp")) { + settings.setValue("fUseNatpmp", DEFAULT_NATPMP); + } + if (!gArgs.SoftSetBoolArg("-natpmp", settings.value("fUseNatpmp").toBool())) { + addOverriddenOption("-natpmp"); + } + if (!settings.contains("fListen")) settings.setValue("fListen", DEFAULT_LISTEN); if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool())) @@ -282,7 +290,13 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return settings.value("fUseUPnP"); #else return false; -#endif +#endif // USE_UPNP + case MapPortNatpmp: +#ifdef USE_NATPMP + return settings.value("fUseNatpmp"); +#else + return false; +#endif // USE_NATPMP case MinimizeOnClose: return fMinimizeOnClose; @@ -354,7 +368,9 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in break; case MapPortUPnP: // core option - can be changed on-the-fly settings.setValue("fUseUPnP", value.toBool()); - node().mapPort(value.toBool()); + break; + case MapPortNatpmp: // core option - can be changed on-the-fly + settings.setValue("fUseNatpmp", value.toBool()); break; case MinimizeOnClose: fMinimizeOnClose = value.toBool(); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index f1929a5e06..f7171951a1 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -48,6 +48,7 @@ public: ShowTrayIcon, // bool MinimizeToTray, // bool MapPortUPnP, // bool + MapPortNatpmp, // bool MinimizeOnClose, // bool ProxyUse, // bool ProxyIP, // QString diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 62ff5c6e33..b3eb2d61a7 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -362,6 +362,7 @@ def initialize_datadir(dirname, n, chain): f.write("listenonion=0\n") f.write("printtoconsole=0\n") f.write("upnp=0\n") + f.write("natpmp=0\n") f.write("shrinkdebugfile=0\n") os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) |