mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-24 18:23:26 -03:00
Merge bitcoin/bitcoin#30043: net: Replace libnatpmp with built-in PCP+NATPMP implementation
5c7cacf649
ci: Remove natpmp build option and libnatpmp dependency (laanwj)7e7ec984da
doc: Remove mention of natpmp build options (laanwj)061c3e32a2
depends: Drop natpmp and associated option from depends (laanwj)20a18bf6aa
build: Drop libnatpmp from build system (laanwj)7b04709862
qt: Changes for built-in PCP+NAT-PMP (laanwj)52f8ef66c6
net: Replace libnatpmp with built-in NATPMP+PCP implementation in mapport (laanwj)97c97177cd
net: Add PCP and NATPMP implementation (laanwj)d72df63d16
net: Use GetLocalAddresses in Discover (laanwj)e02030432b
net: Add netif utility (laanwj)754e425438
crypto: Add missing WriteBE16 function (laanwj) Pull request description: Continues #30005. Closes #17012.. This PR adds PCP (Port Control Protocol) from [RFC6887](https://datatracker.ietf.org/doc/html/rfc6887). This adds, in addition to the existing IPv4 port mapping (which now uses PCP, with fallback to NAT-PMP), support for IPv6 pinholing-that is, opening a port on the firewall to make it reachable. PCP, like NAT-PMP is a simple UDP-based protocol, and the implementation is self-contained, so this gets rid of lthe libnatpnp dependency without adding a new one. It should otherwise be a drop-in replacement. NAT-PMP fallback is implemented so this will not make router support worse. For now it is disabled by default, though in the future (not in this PR) we could consider enable it by default to increase the number of connectable nodes without adding significant attack surface. To test: ```bash bitcoind -regtest -natpmp=1 -debug=net ``` (most of the changes in this PR are, ironically, removing the libnatpmp dependency and associated build system and build docs) ## TODO - [x] Default gateway discovery on Linux / FreeBSD - [x] Default gateway discovery on Windows - [x] Default gateway discovery on MacOS - [x] Either solve FreeBSD compile issue (probably upstream issue) or remove FreeBSD support ## Things to consider for follow-up PRs - https://github.com/bitcoin/bitcoin/pull/30043#discussion_r1658764974 avoid unreachable nets (not given to -onlynet=) - https://github.com/bitcoin/bitcoin/pull/30043#discussion_r1658949236 could announce an addr:port where we do not listen (no -bind) - https://github.com/bitcoin/bitcoin/pull/30043#discussion_r1684368824 could announce the wrong port because it uses GetListenPort() - https://github.com/bitcoin/bitcoin/pull/30043#discussion_r1679709347 if we requested one port but another was assigned, then which one to use in the renewal? - https://github.com/bitcoin/bitcoin/pull/30043#discussion_r1772017020 Use `GetAdapterAddresses` to discover local addresses for Windows ACKs for top commit: Sjors: ACK5c7cacf649
achow101: ACK5c7cacf649
vasild: ACK5c7cacf649
Tree-SHA512: e35b69e56d5f5449a3d48a799f9b7b65107c65eeb3e245c2c1e9d42221e469ca5ead90afae423160601cd664dd553a51c859e04f4492f335b064aae3bf23e3bc
This commit is contained in:
commit
c33eb2360e
38 changed files with 1055 additions and 289 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -67,12 +67,12 @@ jobs:
|
|||
echo "TEST_BASE=$(git rev-list -n$((${{ env.MAX_COUNT }} + 1)) --reverse HEAD $EXCLUDE_MERGE_BASE_ANCESTORS | head -1)" >> "$GITHUB_ENV"
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang ccache build-essential cmake pkg-config python3-zmq libevent-dev libboost-dev libsqlite3-dev libdb++-dev systemtap-sdt-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev qtbase5-dev qttools5-dev qttools5-dev-tools qtwayland5 libqrencode-dev -y
|
||||
sudo apt-get install clang ccache build-essential cmake pkg-config python3-zmq libevent-dev libboost-dev libsqlite3-dev libdb++-dev systemtap-sdt-dev libminiupnpc-dev libzmq3-dev qtbase5-dev qttools5-dev qttools5-dev-tools qtwayland5 libqrencode-dev -y
|
||||
- name: Compile and run tests
|
||||
run: |
|
||||
# Run tests on commits after the last merge commit and before the PR head commit
|
||||
# Use clang++, because it is a bit faster and uses less memory than g++
|
||||
git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_NATPMP=ON -DWITH_MINIUPNPC=ON -DWITH_USDT=ON && cmake --build build -j $(nproc) && ctest --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 ))" ${{ env.TEST_BASE }}
|
||||
git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_MINIUPNPC=ON -DWITH_USDT=ON && cmake --build build -j $(nproc) && ctest --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 ))" ${{ env.TEST_BASE }}
|
||||
|
||||
macos-native-arm64:
|
||||
name: 'macOS 14 native, arm64, no depends, sqlite only, gui'
|
||||
|
@ -105,7 +105,7 @@ jobs:
|
|||
run: |
|
||||
# A workaround for "The `brew link` step did not complete successfully" error.
|
||||
brew install --quiet python@3 || brew link --overwrite python@3
|
||||
brew install --quiet ninja pkg-config gnu-getopt ccache boost libevent miniupnpc libnatpmp zeromq qt@5 qrencode
|
||||
brew install --quiet ninja pkg-config gnu-getopt ccache boost libevent miniupnpc zeromq qt@5 qrencode
|
||||
|
||||
- name: Set Ccache directory
|
||||
run: echo "CCACHE_DIR=${RUNNER_TEMP}/ccache_dir" >> "$GITHUB_ENV"
|
||||
|
|
|
@ -121,11 +121,6 @@ option(REDUCE_EXPORTS "Attempt to reduce exported symbols in the resulting execu
|
|||
option(WERROR "Treat compiler warnings as errors." OFF)
|
||||
option(WITH_CCACHE "Attempt to use ccache for compiling." ON)
|
||||
|
||||
option(WITH_NATPMP "Enable NAT-PMP." OFF)
|
||||
if(WITH_NATPMP)
|
||||
find_package(NATPMP MODULE REQUIRED)
|
||||
endif()
|
||||
|
||||
option(WITH_MINIUPNPC "Enable UPnP." OFF)
|
||||
if(WITH_MINIUPNPC)
|
||||
find_package(MiniUPnPc MODULE REQUIRED)
|
||||
|
@ -239,7 +234,6 @@ if(BUILD_FOR_FUZZING)
|
|||
set(BUILD_WALLET_TOOL OFF)
|
||||
set(BUILD_GUI OFF)
|
||||
set(ENABLE_EXTERNAL_SIGNER OFF)
|
||||
set(WITH_NATPMP OFF)
|
||||
set(WITH_MINIUPNPC OFF)
|
||||
set(WITH_ZMQ OFF)
|
||||
set(BUILD_TESTS OFF)
|
||||
|
@ -621,9 +615,7 @@ if(ENABLE_WALLET)
|
|||
message(" - legacy wallets (Berkeley DB) ..... ${WITH_BDB}")
|
||||
endif()
|
||||
message(" external signer ..................... ${ENABLE_EXTERNAL_SIGNER}")
|
||||
message(" port mapping:")
|
||||
message(" - using NAT-PMP .................... ${WITH_NATPMP}")
|
||||
message(" - using UPnP ....................... ${WITH_MINIUPNPC}")
|
||||
message(" port mapping using UPnP ............. ${WITH_MINIUPNPC}")
|
||||
message(" ZeroMQ .............................. ${WITH_ZMQ}")
|
||||
message(" USDT tracing ........................ ${WITH_USDT}")
|
||||
message(" QR code (GUI) ....................... ${WITH_QRENCODE}")
|
||||
|
|
|
@ -86,7 +86,6 @@
|
|||
"WITH_BDB": "ON",
|
||||
"WITH_MINIUPNPC": "ON",
|
||||
"WITH_MULTIPROCESS": "ON",
|
||||
"WITH_NATPMP": "ON",
|
||||
"WITH_QRENCODE": "ON",
|
||||
"WITH_SQLITE": "ON",
|
||||
"WITH_USDT": "ON",
|
||||
|
|
|
@ -11,7 +11,7 @@ export LC_ALL=C.UTF-8
|
|||
export PIP_PACKAGES="--break-system-packages zmq"
|
||||
export GOAL="install"
|
||||
export CMAKE_GENERATOR="Ninja"
|
||||
export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DWITH_MINIUPNPC=ON -DWITH_NATPMP=ON -DREDUCE_EXPORTS=ON"
|
||||
export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DWITH_MINIUPNPC=ON -DREDUCE_EXPORTS=ON"
|
||||
export CI_OS_NAME="macos"
|
||||
export NO_DEPENDS=1
|
||||
export OSX_SDK=""
|
||||
|
|
|
@ -19,7 +19,7 @@ else
|
|||
fi
|
||||
|
||||
export CONTAINER_NAME=ci_native_asan
|
||||
export PACKAGES="systemtap-sdt-dev clang-18 llvm-18 libclang-rt-18-dev python3-zmq qtbase5-dev qttools5-dev qttools5-dev-tools libevent-dev libboost-dev libdb5.3++-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE}"
|
||||
export PACKAGES="systemtap-sdt-dev clang-18 llvm-18 libclang-rt-18-dev python3-zmq qtbase5-dev qttools5-dev qttools5-dev-tools libevent-dev libboost-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE}"
|
||||
export NO_DEPENDS=1
|
||||
export GOAL="install"
|
||||
export BITCOIN_CONFIG="\
|
||||
|
|
|
@ -10,7 +10,7 @@ export CONTAINER_NAME=ci_native_previous_releases
|
|||
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:22.04"
|
||||
# Use minimum supported python3.9 (or best effort 3.10) and gcc-11, see doc/dependencies.md
|
||||
export PACKAGES="gcc-11 g++-11 python3-zmq"
|
||||
export DEP_OPTS="NO_UPNP=1 NO_NATPMP=1 DEBUG=1 CC=gcc-11 CXX=g++-11"
|
||||
export DEP_OPTS="NO_UPNP=1 DEBUG=1 CC=gcc-11 CXX=g++-11"
|
||||
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_UNIT_TESTS_SEQUENTIAL="true"
|
||||
export RUN_UNIT_TESTS="false"
|
||||
|
|
|
@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8
|
|||
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
|
||||
export CONTAINER_NAME=ci_native_tidy
|
||||
export TIDY_LLVM_V="18"
|
||||
export PACKAGES="clang-${TIDY_LLVM_V} libclang-${TIDY_LLVM_V}-dev llvm-${TIDY_LLVM_V}-dev libomp-${TIDY_LLVM_V}-dev clang-tidy-${TIDY_LLVM_V} jq libevent-dev libboost-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev systemtap-sdt-dev qtbase5-dev qttools5-dev qttools5-dev-tools libqrencode-dev libsqlite3-dev libdb++-dev"
|
||||
export PACKAGES="clang-${TIDY_LLVM_V} libclang-${TIDY_LLVM_V}-dev llvm-${TIDY_LLVM_V}-dev libomp-${TIDY_LLVM_V}-dev clang-tidy-${TIDY_LLVM_V} jq libevent-dev libboost-dev libminiupnpc-dev libzmq3-dev systemtap-sdt-dev qtbase5-dev qttools5-dev qttools5-dev-tools libqrencode-dev libsqlite3-dev libdb++-dev"
|
||||
export NO_DEPENDS=1
|
||||
export RUN_UNIT_TESTS=false
|
||||
export RUN_FUNCTIONAL_TESTS=false
|
||||
|
@ -18,7 +18,7 @@ export RUN_CHECK_DEPS=true
|
|||
export RUN_TIDY=true
|
||||
export GOAL="install"
|
||||
export BITCOIN_CONFIG="\
|
||||
-DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DWITH_NATPMP=ON -DWITH_MINIUPNPC=ON -DWITH_USDT=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF \
|
||||
-DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DWITH_MINIUPNPC=ON -DWITH_USDT=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF \
|
||||
-DENABLE_HARDENING=OFF \
|
||||
-DCMAKE_C_COMPILER=clang-${TIDY_LLVM_V} \
|
||||
-DCMAKE_CXX_COMPILER=clang++-${TIDY_LLVM_V} \
|
||||
|
|
|
@ -8,14 +8,14 @@ export LC_ALL=C.UTF-8
|
|||
|
||||
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
|
||||
export CONTAINER_NAME=ci_native_valgrind
|
||||
export PACKAGES="valgrind clang-16 llvm-16 libclang-rt-16-dev python3-zmq libevent-dev libboost-dev libdb5.3++-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev libsqlite3-dev"
|
||||
export PACKAGES="valgrind clang-16 llvm-16 libclang-rt-16-dev python3-zmq libevent-dev libboost-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libsqlite3-dev"
|
||||
export USE_VALGRIND=1
|
||||
export NO_DEPENDS=1
|
||||
export TEST_RUNNER_EXTRA="--exclude feature_init,rpc_bind,feature_bind_extra" # feature_init excluded for now, see https://github.com/bitcoin/bitcoin/issues/30011 ; bind tests excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
|
||||
export GOAL="install"
|
||||
# TODO enable GUI
|
||||
export BITCOIN_CONFIG="\
|
||||
-DWITH_ZMQ=ON -DWITH_BDB=ON -DWITH_NATPMP=ON -DWITH_MINIUPNPC=ON -DWARN_INCOMPATIBLE_BDB=OFF -DBUILD_GUI=OFF \
|
||||
-DWITH_ZMQ=ON -DWITH_BDB=ON -DWITH_MINIUPNPC=ON -DWARN_INCOMPATIBLE_BDB=OFF -DBUILD_GUI=OFF \
|
||||
-DCMAKE_C_COMPILER=clang-16 \
|
||||
-DCMAKE_CXX_COMPILER=clang++-16 \
|
||||
"
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright (c) 2023-present The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://opensource.org/license/mit/.
|
||||
|
||||
find_path(NATPMP_INCLUDE_DIR
|
||||
NAMES natpmp.h
|
||||
)
|
||||
|
||||
find_library(NATPMP_LIBRARY
|
||||
NAMES natpmp
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(NATPMP
|
||||
REQUIRED_VARS NATPMP_LIBRARY NATPMP_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if(NATPMP_FOUND AND NOT TARGET NATPMP::NATPMP)
|
||||
add_library(NATPMP::NATPMP UNKNOWN IMPORTED)
|
||||
set_target_properties(NATPMP::NATPMP PROPERTIES
|
||||
IMPORTED_LOCATION "${NATPMP_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${NATPMP_INCLUDE_DIR}"
|
||||
)
|
||||
set_property(TARGET NATPMP::NATPMP PROPERTY
|
||||
INTERFACE_COMPILE_DEFINITIONS USE_NATPMP=1 $<$<PLATFORM_ID:Windows>:NATPMP_STATICLIB>
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
NATPMP_INCLUDE_DIR
|
||||
NATPMP_LIBRARY
|
||||
)
|
|
@ -42,7 +42,6 @@ NO_WALLET ?=
|
|||
NO_ZMQ ?=
|
||||
NO_UPNP ?=
|
||||
NO_USDT ?=
|
||||
NO_NATPMP ?=
|
||||
MULTIPROCESS ?=
|
||||
LTO ?=
|
||||
NO_HARDEN ?=
|
||||
|
@ -159,13 +158,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)
|
||||
usdt_packages_$(NO_USDT) = $(usdt_$(host_os)_packages)
|
||||
|
||||
packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(boost_packages_) $(libevent_packages_) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) $(natpmp_packages_) $(usdt_packages_)
|
||||
packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(boost_packages_) $(libevent_packages_) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) $(usdt_packages_)
|
||||
native_packages += $($(host_arch)_$(host_os)_native_packages) $($(host_os)_native_packages)
|
||||
|
||||
ifneq ($(zmq_packages_),)
|
||||
|
@ -233,7 +231,6 @@ $(host_prefix)/toolchain.cmake : toolchain.cmake.in $(host_prefix)/.stamp_$(fina
|
|||
-e 's|@bdb_packages@|$(bdb_packages_)|' \
|
||||
-e 's|@sqlite_packages@|$(sqlite_packages_)|' \
|
||||
-e 's|@upnp_packages@|$(upnp_packages_)|' \
|
||||
-e 's|@natpmp_packages@|$(natpmp_packages_)|' \
|
||||
-e 's|@usdt_packages@|$(usdt_packages_)|' \
|
||||
-e 's|@no_harden@|$(NO_HARDEN)|' \
|
||||
-e 's|@multiprocess@|$(MULTIPROCESS)|' \
|
||||
|
|
|
@ -113,7 +113,6 @@ 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
|
||||
- `NO_USDT`: Don't download/build/cache packages needed for enabling USDT tracepoints
|
||||
- `MULTIPROCESS`: Build libmultiprocess (experimental)
|
||||
- `DEBUG`: Disable some optimizations and enable more runtime checking
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package=libnatpmp
|
||||
$(package)_version=f2433bec24ca3d3f22a8a7840728a3ac177f94ba
|
||||
$(package)_download_path=https://github.com/miniupnp/libnatpmp/archive
|
||||
$(package)_file_name=$($(package)_version).tar.gz
|
||||
$(package)_sha256_hash=ef84979950dfb3556705b63c9cd6c95501b75e887fba466234b187f3c9029669
|
||||
$(package)_build_subdir=build
|
||||
|
||||
define $(package)_config_cmds
|
||||
$($(package)_cmake) -S .. -B .
|
||||
endef
|
||||
|
||||
define $(package)_build_cmds
|
||||
$(MAKE) natpmp
|
||||
endef
|
||||
|
||||
define $(package)_stage_cmds
|
||||
mkdir -p $($(package)_staging_prefix_dir)/include $($(package)_staging_prefix_dir)/lib && \
|
||||
install ../natpmp.h ../natpmp_declspec.h $($(package)_staging_prefix_dir)/include && \
|
||||
install libnatpmp.a $($(package)_staging_prefix_dir)/lib
|
||||
endef
|
|
@ -18,7 +18,6 @@ sqlite_packages=sqlite
|
|||
zmq_packages=zeromq
|
||||
|
||||
upnp_packages=miniupnpc
|
||||
natpmp_packages=libnatpmp
|
||||
|
||||
multiprocess_packages = libmultiprocess capnp
|
||||
multiprocess_native_packages = native_libmultiprocess native_capnp
|
||||
|
|
|
@ -146,13 +146,6 @@ else()
|
|||
set(WITH_MINIUPNPC ON CACHE BOOL "")
|
||||
endif()
|
||||
|
||||
set(natpmp_packages @natpmp_packages@)
|
||||
if("${natpmp_packages}" STREQUAL "")
|
||||
set(WITH_NATPMP OFF CACHE BOOL "")
|
||||
else()
|
||||
set(WITH_NATPMP ON CACHE BOOL "")
|
||||
endif()
|
||||
|
||||
set(usdt_packages @usdt_packages@)
|
||||
if("${usdt_packages}" STREQUAL "")
|
||||
set(WITH_USDT OFF CACHE BOOL "")
|
||||
|
|
|
@ -42,7 +42,7 @@ from ports. However, you can build DB 4.8 yourself [using depends](/depends).
|
|||
|
||||
```bash
|
||||
pkg install gmake
|
||||
gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_NATPMP=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1
|
||||
gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1
|
||||
```
|
||||
|
||||
When the build is complete, the Berkeley DB installation location will be displayed:
|
||||
|
|
|
@ -44,7 +44,7 @@ from ports. However you can build it yourself, [using depends](/depends).
|
|||
Refer to [depends/README.md](/depends/README.md) for detailed instructions.
|
||||
|
||||
```bash
|
||||
gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_NATPMP=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1
|
||||
gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1
|
||||
...
|
||||
to: /path/to/bitcoin/depends/*-unknown-openbsd*
|
||||
```
|
||||
|
|
|
@ -135,17 +135,6 @@ Skip if you do not need this functionality.
|
|||
brew install miniupnpc
|
||||
```
|
||||
|
||||
###### libnatpmp
|
||||
|
||||
libnatpmp may be used for NAT-PMP port mapping.
|
||||
Skip if you do not need this functionality.
|
||||
|
||||
``` bash
|
||||
brew install libnatpmp
|
||||
```
|
||||
|
||||
Check out the [further configuration](#further-configuration) section for more information.
|
||||
|
||||
---
|
||||
|
||||
#### ZMQ Dependencies
|
||||
|
|
|
@ -60,9 +60,9 @@ executables, which are based on BerkeleyDB 4.8. Otherwise, you can build Berkele
|
|||
|
||||
To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode)
|
||||
|
||||
Optional port mapping libraries (see: `-DWITH_MINIUPNPC=ON` and `-DWITH_NATPMP=ON`):
|
||||
Optional port mapping library (see: `-DWITH_MINIUPNPC=ON`):
|
||||
|
||||
sudo apt install libminiupnpc-dev libnatpmp-dev
|
||||
sudo apt install libminiupnpc-dev
|
||||
|
||||
ZMQ dependencies (provides ZMQ API):
|
||||
|
||||
|
@ -112,9 +112,9 @@ are based on Berkeley DB 4.8. Otherwise, you can build Berkeley DB [yourself](#b
|
|||
|
||||
To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode)
|
||||
|
||||
Optional port mapping libraries (see: `-DWITH_MINIUPNPC=ON` and `-DWITH_NATPMP=ON`):
|
||||
Optional port mapping library (see: `-DWITH_MINIUPNPC=ON`):
|
||||
|
||||
sudo dnf install miniupnpc-devel libnatpmp-devel
|
||||
sudo dnf install miniupnpc-devel
|
||||
|
||||
ZMQ dependencies (provides ZMQ API):
|
||||
|
||||
|
@ -153,7 +153,7 @@ The legacy wallet uses Berkeley DB. To ensure backwards compatibility it is
|
|||
recommended to use Berkeley DB 4.8. If you have to build it yourself, and don't
|
||||
want to use any other libraries built in depends, you can do:
|
||||
```bash
|
||||
make -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_NATPMP=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1
|
||||
make -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1
|
||||
...
|
||||
to: /path/to/bitcoin/depends/x86_64-pc-linux-gnu
|
||||
```
|
||||
|
|
|
@ -34,7 +34,6 @@ You can find installation instructions in the `build-*.md` file for your platfor
|
|||
### Networking
|
||||
| Dependency | Releases | Version used | Minimum required | Runtime |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [libnatpmp](../depends/packages/libnatpmp.mk) | [link](https://github.com/miniupnp/libnatpmp/) | commit [f2433be...](https://github.com/bitcoin/bitcoin/pull/29708) | | No |
|
||||
| [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.7](https://github.com/bitcoin/bitcoin/pull/29707) | 2.1 | No |
|
||||
|
||||
### Notifications
|
||||
|
|
|
@ -292,7 +292,6 @@ target_link_libraries(bitcoin_node
|
|||
Boost::headers
|
||||
$<TARGET_NAME_IF_EXISTS:libevent::libevent>
|
||||
$<TARGET_NAME_IF_EXISTS:libevent::pthreads>
|
||||
$<TARGET_NAME_IF_EXISTS:NATPMP::NATPMP>
|
||||
$<TARGET_NAME_IF_EXISTS:MiniUPnPc::MiniUPnPc>
|
||||
$<TARGET_NAME_IF_EXISTS:bitcoin_zmq>
|
||||
$<TARGET_NAME_IF_EXISTS:USDT::headers>
|
||||
|
|
|
@ -70,6 +70,12 @@ uint64_t static inline ReadBE64(const unsigned char* ptr)
|
|||
return be64toh_internal(x);
|
||||
}
|
||||
|
||||
void static inline WriteBE16(unsigned char* ptr, uint16_t x)
|
||||
{
|
||||
uint16_t v = htobe16_internal(x);
|
||||
memcpy(ptr, &v, 2);
|
||||
}
|
||||
|
||||
void static inline WriteBE32(unsigned char* ptr, uint32_t x)
|
||||
{
|
||||
uint32_t v = htobe32_internal(x);
|
||||
|
|
|
@ -557,11 +557,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
|
|||
#else
|
||||
hidden_args.emplace_back("-upnp");
|
||||
#endif
|
||||
#ifdef USE_NATPMP
|
||||
argsman.AddArg("-natpmp", strprintf("Use NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
#else
|
||||
hidden_args.emplace_back("-natpmp");
|
||||
#endif // USE_NATPMP
|
||||
argsman.AddArg("-natpmp", strprintf("Use PCP or NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
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);
|
||||
|
@ -1842,7 +1838,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
|||
LogPrintf("nBestHeight = %d\n", chain_active_height);
|
||||
if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time});
|
||||
|
||||
// Map ports with UPnP or NAT-PMP.
|
||||
// Map ports with UPnP or NAT-PMP
|
||||
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP));
|
||||
|
||||
CConnman::Options connOptions;
|
||||
|
|
|
@ -121,7 +121,7 @@ public:
|
|||
virtual void resetSettings() = 0;
|
||||
|
||||
//! Map port.
|
||||
virtual void mapPort(bool use_upnp, bool use_natpmp) = 0;
|
||||
virtual void mapPort(bool use_upnp, bool use_pcp) = 0;
|
||||
|
||||
//! Get proxy.
|
||||
virtual bool getProxy(Network net, Proxy& proxy_info) = 0;
|
||||
|
|
220
src/mapport.cpp
220
src/mapport.cpp
|
@ -12,14 +12,12 @@
|
|||
#include <net.h>
|
||||
#include <netaddress.h>
|
||||
#include <netbase.h>
|
||||
#include <random.h>
|
||||
#include <util/netif.h>
|
||||
#include <util/pcp.h>
|
||||
#include <util/thread.h>
|
||||
#include <util/threadinterrupt.h>
|
||||
|
||||
#ifdef USE_NATPMP
|
||||
#include <compat/compat.h>
|
||||
#include <natpmp.h>
|
||||
#endif // USE_NATPMP
|
||||
|
||||
#ifdef USE_UPNP
|
||||
#include <miniupnpc/miniupnpc.h>
|
||||
#include <miniupnpc/upnpcommands.h>
|
||||
|
@ -36,7 +34,6 @@ static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed"
|
|||
#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};
|
||||
|
@ -46,104 +43,96 @@ 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)
|
||||
static bool ProcessPCP()
|
||||
{
|
||||
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;
|
||||
}
|
||||
// The same nonce is used for all mappings, this is allowed by the spec, and simplifies keeping track of them.
|
||||
PCPMappingNonce pcp_nonce;
|
||||
GetRandBytes(pcp_nonce);
|
||||
|
||||
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.ToStringAddrPort());
|
||||
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();
|
||||
bool no_resources = false;
|
||||
const uint16_t private_port = GetListenPort();
|
||||
// Multiply the reannounce period by two, as we'll try to renew approximately halfway.
|
||||
const uint32_t requested_lifetime = std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count();
|
||||
uint32_t actual_lifetime = 0;
|
||||
std::chrono::milliseconds sleep_time;
|
||||
|
||||
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);
|
||||
// Local functor to handle result from PCP/NATPMP mapping.
|
||||
auto handle_mapping = [&](std::variant<MappingResult, MappingError> &res) -> void {
|
||||
if (MappingResult* mapping = std::get_if<MappingResult>(&res)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Info, "portmap: Added mapping %s\n", mapping->ToString());
|
||||
AddLocal(mapping->external, LOCAL_MAPPED);
|
||||
ret = true;
|
||||
actual_lifetime = std::min(actual_lifetime, mapping->lifetime);
|
||||
} else if (MappingError *err = std::get_if<MappingError>(&res)) {
|
||||
// Detailed error will already have been logged internally in respective Portmap function.
|
||||
if (*err == MappingError::NO_RESOURCES) {
|
||||
no_resources = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
do {
|
||||
actual_lifetime = requested_lifetime;
|
||||
no_resources = false; // Set to true if there was any "no resources" error.
|
||||
ret = false; // Set to true if any mapping succeeds.
|
||||
|
||||
// IPv4
|
||||
std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4);
|
||||
if (!gateway4) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv4 default gateway\n");
|
||||
} else {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv4]: %s\n", gateway4->ToStringAddr());
|
||||
|
||||
// Open a port mapping on whatever local address we have toward the gateway.
|
||||
struct in_addr inaddr_any;
|
||||
inaddr_any.s_addr = htonl(INADDR_ANY);
|
||||
auto res = PCPRequestPortMap(pcp_nonce, *gateway4, CNetAddr(inaddr_any), private_port, requested_lifetime);
|
||||
MappingError* pcp_err = std::get_if<MappingError>(&res);
|
||||
if (pcp_err && *pcp_err == MappingError::UNSUPP_VERSION) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Got unsupported PCP version response, falling back to NAT-PMP\n");
|
||||
res = NATPMPRequestPortMap(*gateway4, private_port, requested_lifetime);
|
||||
}
|
||||
handle_mapping(res);
|
||||
}
|
||||
|
||||
// IPv6
|
||||
std::optional<CNetAddr> gateway6 = QueryDefaultGateway(NET_IPV6);
|
||||
if (!gateway6) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv6 default gateway\n");
|
||||
} else {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv6]: %s\n", gateway6->ToStringAddr());
|
||||
|
||||
// Try to open pinholes for all routable local IPv6 addresses.
|
||||
for (const auto &addr: GetLocalAddresses()) {
|
||||
if (!addr.IsRoutable() || !addr.IsIPv6()) continue;
|
||||
auto res = PCPRequestPortMap(pcp_nonce, *gateway6, addr, private_port, requested_lifetime);
|
||||
handle_mapping(res);
|
||||
}
|
||||
}
|
||||
|
||||
// Log message if we got NO_RESOURCES.
|
||||
if (no_resources) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: At least one mapping failed because of a NO_RESOURCES error. This usually indicates that the port is already used on the router. If this is the only instance of bitcoin running on the network, this will resolve itself automatically. Otherwise, you might want to choose a different P2P port to prevent this conflict.\n");
|
||||
}
|
||||
|
||||
// Sanity-check returned lifetime.
|
||||
if (actual_lifetime < 30) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: Got impossibly short mapping lifetime of %d seconds\n", actual_lifetime);
|
||||
return false;
|
||||
}
|
||||
// RFC6887 11.2.1 recommends that clients send their first renewal packet at a time chosen with uniform random
|
||||
// distribution in the range 1/2 to 5/8 of expiration time.
|
||||
std::chrono::seconds sleep_time_min(actual_lifetime / 2);
|
||||
std::chrono::seconds sleep_time_max(actual_lifetime * 5 / 8);
|
||||
sleep_time = sleep_time_min + FastRandomContext().randrange<std::chrono::milliseconds>(sleep_time_max - sleep_time_min);
|
||||
} while (ret && g_mapport_interrupt.sleep_for(sleep_time));
|
||||
|
||||
// We don't delete the mappings when the thread is interrupted because this would add additional complexity, so
|
||||
// we rather just choose a fairly short expiry time.
|
||||
|
||||
closenatpmp(&natpmp);
|
||||
return ret;
|
||||
}
|
||||
#endif // USE_NATPMP
|
||||
|
||||
#ifdef USE_UPNP
|
||||
static bool ProcessUpnp()
|
||||
|
@ -223,8 +212,15 @@ static void ThreadMapPort()
|
|||
do {
|
||||
ok = false;
|
||||
|
||||
#ifdef USE_UPNP
|
||||
// High priority protocol.
|
||||
if (g_mapport_enabled_protos & MapPortProtoFlag::PCP) {
|
||||
g_mapport_current_proto = MapPortProtoFlag::PCP;
|
||||
ok = ProcessPCP();
|
||||
if (ok) continue;
|
||||
}
|
||||
|
||||
#ifdef USE_UPNP
|
||||
// Low priority protocol.
|
||||
if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
|
||||
g_mapport_current_proto = MapPortProtoFlag::UPNP;
|
||||
ok = ProcessUpnp();
|
||||
|
@ -232,15 +228,6 @@ static void ThreadMapPort()
|
|||
}
|
||||
#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;
|
||||
|
@ -281,7 +268,7 @@ static void DispatchMapPort()
|
|||
|
||||
assert(g_mapport_thread.joinable());
|
||||
assert(!g_mapport_interrupt);
|
||||
// Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadNatpmp()
|
||||
// Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadPCP()
|
||||
// to force trying the next protocol in the ThreadMapPort() loop.
|
||||
g_mapport_interrupt();
|
||||
}
|
||||
|
@ -295,10 +282,10 @@ static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
|
|||
}
|
||||
}
|
||||
|
||||
void StartMapPort(bool use_upnp, bool use_natpmp)
|
||||
void StartMapPort(bool use_upnp, bool use_pcp)
|
||||
{
|
||||
MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
|
||||
MapPortProtoSetEnabled(MapPortProtoFlag::NAT_PMP, use_natpmp);
|
||||
MapPortProtoSetEnabled(MapPortProtoFlag::PCP, use_pcp);
|
||||
DispatchMapPort();
|
||||
}
|
||||
|
||||
|
@ -317,18 +304,3 @@ void StopMapPort()
|
|||
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)
|
||||
|
|
|
@ -12,10 +12,10 @@ static constexpr bool DEFAULT_NATPMP = false;
|
|||
enum MapPortProtoFlag : unsigned int {
|
||||
NONE = 0x00,
|
||||
UPNP = 0x01,
|
||||
NAT_PMP = 0x02,
|
||||
PCP = 0x02, // PCP with NAT-PMP fallback.
|
||||
};
|
||||
|
||||
void StartMapPort(bool use_upnp, bool use_natpmp);
|
||||
void StartMapPort(bool use_upnp, bool use_pcp);
|
||||
void InterruptMapPort();
|
||||
void StopMapPort();
|
||||
|
||||
|
|
43
src/net.cpp
43
src/net.cpp
|
@ -28,6 +28,7 @@
|
|||
#include <random.h>
|
||||
#include <scheduler.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/netif.h>
|
||||
#include <util/sock.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/thread.h>
|
||||
|
@ -3118,46 +3119,10 @@ void Discover()
|
|||
if (!fDiscover)
|
||||
return;
|
||||
|
||||
#ifdef WIN32
|
||||
// Get local host IP
|
||||
char pszHostName[256] = "";
|
||||
if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR)
|
||||
{
|
||||
const std::vector<CNetAddr> addresses{LookupHost(pszHostName, 0, true)};
|
||||
for (const CNetAddr& addr : addresses)
|
||||
{
|
||||
if (AddLocal(addr, LOCAL_IF))
|
||||
LogPrintf("%s: %s - %s\n", __func__, pszHostName, addr.ToStringAddr());
|
||||
}
|
||||
for (const CNetAddr &addr: GetLocalAddresses()) {
|
||||
if (AddLocal(addr, LOCAL_IF))
|
||||
LogPrintf("%s: %s\n", __func__, addr.ToStringAddr());
|
||||
}
|
||||
#elif (HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS)
|
||||
// Get local host ip
|
||||
struct ifaddrs* myaddrs;
|
||||
if (getifaddrs(&myaddrs) == 0)
|
||||
{
|
||||
for (struct ifaddrs* ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (ifa->ifa_addr == nullptr) continue;
|
||||
if ((ifa->ifa_flags & IFF_UP) == 0) continue;
|
||||
if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) continue;
|
||||
if (ifa->ifa_addr->sa_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr);
|
||||
CNetAddr addr(s4->sin_addr);
|
||||
if (AddLocal(addr, LOCAL_IF))
|
||||
LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name, addr.ToStringAddr());
|
||||
}
|
||||
else if (ifa->ifa_addr->sa_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr);
|
||||
CNetAddr addr(s6->sin6_addr);
|
||||
if (AddLocal(addr, LOCAL_IF))
|
||||
LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name, addr.ToStringAddr());
|
||||
}
|
||||
}
|
||||
freeifaddrs(myaddrs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CConnman::SetNetworkActive(bool active)
|
||||
|
|
|
@ -148,7 +148,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 or NAT-PMP
|
||||
LOCAL_MAPPED, // address reported by UPnP or PCP
|
||||
LOCAL_MANUAL, // address explicitly specified (-externalip=)
|
||||
|
||||
LOCAL_MAX
|
||||
|
|
|
@ -184,7 +184,7 @@ public:
|
|||
});
|
||||
args().WriteSettingsFile();
|
||||
}
|
||||
void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); }
|
||||
void mapPort(bool use_upnp, bool use_pcp) override { StartMapPort(use_upnp, use_pcp); }
|
||||
bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); }
|
||||
size_t getNodeCount(ConnectionDirection flags) override
|
||||
{
|
||||
|
|
|
@ -133,7 +133,6 @@ target_link_libraries(bitcoinqt
|
|||
bitcoin_cli
|
||||
leveldb
|
||||
Boost::headers
|
||||
$<TARGET_NAME_IF_EXISTS:NATPMP::NATPMP>
|
||||
$<TARGET_NAME_IF_EXISTS:MiniUPnPc::MiniUPnPc>
|
||||
$<TARGET_NAME_IF_EXISTS:PkgConfig::libqrencode>
|
||||
$<$<PLATFORM_ID:Darwin>:-framework\ AppKit>
|
||||
|
|
|
@ -328,10 +328,10 @@
|
|||
<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>
|
||||
<string>Automatically open the Bitcoin client port on the router. This only works when your router supports PCP or 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>
|
||||
<string>Map port using PCP or NA&T-PMP</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -108,9 +108,6 @@ OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet)
|
|||
#ifndef USE_UPNP
|
||||
ui->mapPortUpnp->setEnabled(false);
|
||||
#endif
|
||||
#ifndef USE_NATPMP
|
||||
ui->mapPortNatpmp->setEnabled(false);
|
||||
#endif
|
||||
|
||||
ui->proxyIp->setEnabled(false);
|
||||
ui->proxyPort->setEnabled(false);
|
||||
|
|
|
@ -414,11 +414,7 @@ QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) con
|
|||
return false;
|
||||
#endif // USE_UPNP
|
||||
case MapPortNatpmp:
|
||||
#ifdef USE_NATPMP
|
||||
return SettingToBool(setting(), DEFAULT_NATPMP);
|
||||
#else
|
||||
return false;
|
||||
#endif // USE_NATPMP
|
||||
case MinimizeOnClose:
|
||||
return fMinimizeOnClose;
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ FUZZ_TARGET(crypto_common)
|
|||
WriteLE64(writele64_arr.data(), random_u64);
|
||||
assert(ReadLE64(writele64_arr.data()) == random_u64);
|
||||
|
||||
std::array<uint8_t, 2> writebe16_arr;
|
||||
WriteBE16(writebe16_arr.data(), random_u16);
|
||||
assert(ReadBE16(writebe16_arr.data()) == random_u16);
|
||||
|
||||
std::array<uint8_t, 4> writebe32_arr;
|
||||
WriteBE32(writebe32_arr.data(), random_u32);
|
||||
assert(ReadBE32(writebe32_arr.data()) == random_u32);
|
||||
|
|
|
@ -15,6 +15,8 @@ add_library(bitcoin_util STATIC EXCLUDE_FROM_ALL
|
|||
fs_helpers.cpp
|
||||
hasher.cpp
|
||||
moneystr.cpp
|
||||
netif.cpp
|
||||
pcp.cpp
|
||||
rbf.cpp
|
||||
readwritefile.cpp
|
||||
serfloat.cpp
|
||||
|
@ -42,4 +44,5 @@ target_link_libraries(bitcoin_util
|
|||
bitcoin_clientversion
|
||||
bitcoin_crypto
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:iphlpapi>
|
||||
)
|
||||
|
|
303
src/util/netif.cpp
Normal file
303
src/util/netif.cpp
Normal file
|
@ -0,0 +1,303 @@
|
|||
// Copyright (c) 2024 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <config/bitcoin-config.h> // IWYU pragma: keep
|
||||
|
||||
#include <util/netif.h>
|
||||
|
||||
#include <logging.h>
|
||||
#include <netbase.h>
|
||||
#include <util/check.h>
|
||||
#include <util/sock.h>
|
||||
#include <util/syserror.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <linux/rtnetlink.h>
|
||||
#elif defined(__FreeBSD__)
|
||||
#include <osreldate.h>
|
||||
#if __FreeBSD_version >= 1400000
|
||||
// Workaround https://github.com/freebsd/freebsd-src/pull/1070.
|
||||
#define typeof __typeof
|
||||
#include <netlink/netlink.h>
|
||||
#include <netlink/netlink_route.h>
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
#include <iphlpapi.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <net/route.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// Linux and FreeBSD 14.0+. For FreeBSD 13.2 the code can be compiled but
|
||||
// running it requires loading a special kernel module, otherwise socket(AF_NETLINK,...)
|
||||
// will fail, so we skip that.
|
||||
#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD_version >= 1400000)
|
||||
|
||||
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
|
||||
{
|
||||
// Create a netlink socket.
|
||||
auto sock{CreateSock(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)};
|
||||
if (!sock) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "socket(AF_NETLINK): %s\n", NetworkErrorString(errno));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Send request.
|
||||
struct {
|
||||
nlmsghdr hdr; ///< Request header.
|
||||
rtmsg data; ///< Request data, a "route message".
|
||||
nlattr dst_hdr; ///< One attribute, conveying the route destination address.
|
||||
char dst_data[16]; ///< Route destination address. To query the default route we use 0.0.0.0/0 or [::]/0. For IPv4 the first 4 bytes are used.
|
||||
} request{};
|
||||
|
||||
// Whether to use the first 4 or 16 bytes from request.dst_data.
|
||||
const size_t dst_data_len = family == AF_INET ? 4 : 16;
|
||||
|
||||
request.hdr.nlmsg_type = RTM_GETROUTE;
|
||||
request.hdr.nlmsg_flags = NLM_F_REQUEST;
|
||||
#ifdef __linux__
|
||||
// Linux IPv4 / IPv6 - this must be present, otherwise no gateway is found
|
||||
// FreeBSD IPv4 - does not matter, the gateway is found with or without this
|
||||
// FreeBSD IPv6 - this must be absent, otherwise no gateway is found
|
||||
request.hdr.nlmsg_flags |= NLM_F_DUMP;
|
||||
#endif
|
||||
request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg) + sizeof(nlattr) + dst_data_len);
|
||||
request.hdr.nlmsg_seq = 0; // Sequence number, used to match which reply is to which request. Irrelevant for us because we send just one request.
|
||||
request.data.rtm_family = family;
|
||||
request.data.rtm_dst_len = 0; // Prefix length.
|
||||
#ifdef __FreeBSD__
|
||||
// Linux IPv4 / IPv6 this must be absent, otherwise no gateway is found
|
||||
// FreeBSD IPv4 - does not matter, the gateway is found with or without this
|
||||
// FreeBSD IPv6 - this must be present, otherwise no gateway is found
|
||||
request.data.rtm_flags = RTM_F_PREFIX;
|
||||
#endif
|
||||
request.dst_hdr.nla_type = RTA_DST;
|
||||
request.dst_hdr.nla_len = sizeof(nlattr) + dst_data_len;
|
||||
|
||||
if (sock->Send(&request, request.hdr.nlmsg_len, 0) != static_cast<ssize_t>(request.hdr.nlmsg_len)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "send() to netlink socket: %s\n", NetworkErrorString(errno));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Receive response.
|
||||
char response[4096];
|
||||
int64_t recv_result;
|
||||
do {
|
||||
recv_result = sock->Recv(response, sizeof(response), 0);
|
||||
} while (recv_result < 0 && (errno == EINTR || errno == EAGAIN));
|
||||
if (recv_result < 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "recv() from netlink socket: %s\n", NetworkErrorString(errno));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (nlmsghdr* hdr = (nlmsghdr*)response; NLMSG_OK(hdr, recv_result); hdr = NLMSG_NEXT(hdr, recv_result)) {
|
||||
rtmsg* r = (rtmsg*)NLMSG_DATA(hdr);
|
||||
int remaining_len = RTM_PAYLOAD(hdr);
|
||||
|
||||
// Iterate over the attributes.
|
||||
rtattr *rta_gateway = nullptr;
|
||||
int scope_id = 0;
|
||||
for (rtattr* attr = RTM_RTA(r); RTA_OK(attr, remaining_len); attr = RTA_NEXT(attr, remaining_len)) {
|
||||
if (attr->rta_type == RTA_GATEWAY) {
|
||||
rta_gateway = attr;
|
||||
} else if (attr->rta_type == RTA_OIF && sizeof(int) == RTA_PAYLOAD(attr)) {
|
||||
std::memcpy(&scope_id, RTA_DATA(attr), sizeof(scope_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Found gateway?
|
||||
if (rta_gateway != nullptr) {
|
||||
if (family == AF_INET && sizeof(in_addr) == RTA_PAYLOAD(rta_gateway)) {
|
||||
in_addr gw;
|
||||
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
|
||||
return CNetAddr(gw);
|
||||
} else if (family == AF_INET6 && sizeof(in6_addr) == RTA_PAYLOAD(rta_gateway)) {
|
||||
in6_addr gw;
|
||||
std::memcpy(&gw, RTA_DATA(rta_gateway), sizeof(gw));
|
||||
return CNetAddr(gw, scope_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#elif defined(WIN32)
|
||||
|
||||
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
|
||||
{
|
||||
NET_LUID interface_luid = {};
|
||||
SOCKADDR_INET destination_address = {};
|
||||
MIB_IPFORWARD_ROW2 best_route = {};
|
||||
SOCKADDR_INET best_source_address = {};
|
||||
DWORD best_if_idx = 0;
|
||||
DWORD status = 0;
|
||||
|
||||
// Pass empty destination address of the requested type (:: or 0.0.0.0) to get interface of default route.
|
||||
destination_address.si_family = family;
|
||||
status = GetBestInterfaceEx((sockaddr*)&destination_address, &best_if_idx);
|
||||
if (status != NO_ERROR) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get best interface for default route: %s\n", NetworkErrorString(status));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Get best route to default gateway.
|
||||
// Leave interface_luid at all-zeros to use interface index instead.
|
||||
status = GetBestRoute2(&interface_luid, best_if_idx, nullptr, &destination_address, 0, &best_route, &best_source_address);
|
||||
if (status != NO_ERROR) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get best route for default route for interface index %d: %s\n",
|
||||
best_if_idx, NetworkErrorString(status));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Assume(best_route.NextHop.si_family == family);
|
||||
if (family == AF_INET) {
|
||||
return CNetAddr(best_route.NextHop.Ipv4.sin_addr);
|
||||
} else if(family == AF_INET6) {
|
||||
return CNetAddr(best_route.NextHop.Ipv6.sin6_addr, best_route.InterfaceIndex);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#define ROUNDUP32(a) \
|
||||
((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))
|
||||
|
||||
std::optional<CNetAddr> FromSockAddr(const struct sockaddr* addr)
|
||||
{
|
||||
// Check valid length. Note that sa_len is not part of POSIX, and exists on MacOS and some BSDs only, so we can't
|
||||
// do this check in SetSockAddr.
|
||||
if (!(addr->sa_family == AF_INET && addr->sa_len == sizeof(struct sockaddr_in)) &&
|
||||
!(addr->sa_family == AF_INET6 && addr->sa_len == sizeof(struct sockaddr_in6))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Fill in a CService from the sockaddr, then drop the port part.
|
||||
CService service;
|
||||
if (service.SetSockAddr(addr)) {
|
||||
return (CNetAddr)service;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
//! MacOS: Get default gateway from route table. See route(4) for the format.
|
||||
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
|
||||
{
|
||||
// net.route.0.inet[6].flags.gateway
|
||||
int mib[] = {CTL_NET, PF_ROUTE, 0, family, NET_RT_FLAGS, RTF_GATEWAY};
|
||||
// The size of the available data is determined by calling sysctl() with oldp=nullptr. See sysctl(3).
|
||||
size_t l = 0;
|
||||
if (sysctl(/*name=*/mib, /*namelen=*/sizeof(mib) / sizeof(int), /*oldp=*/nullptr, /*oldlenp=*/&l, /*newp=*/nullptr, /*newlen=*/0) < 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get sysctl length of routing table: %s\n", SysErrorString(errno));
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<std::byte> buf(l);
|
||||
if (sysctl(/*name=*/mib, /*namelen=*/sizeof(mib) / sizeof(int), /*oldp=*/buf.data(), /*oldlenp=*/&l, /*newp=*/nullptr, /*newlen=*/0) < 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Could not get sysctl data of routing table: %s\n", SysErrorString(errno));
|
||||
return std::nullopt;
|
||||
}
|
||||
// Iterate over messages (each message is a routing table entry).
|
||||
for (size_t msg_pos = 0; msg_pos < buf.size(); ) {
|
||||
if ((msg_pos + sizeof(rt_msghdr)) > buf.size()) return std::nullopt;
|
||||
const struct rt_msghdr* rt = (const struct rt_msghdr*)(buf.data() + msg_pos);
|
||||
const size_t next_msg_pos = msg_pos + rt->rtm_msglen;
|
||||
if (rt->rtm_msglen < sizeof(rt_msghdr) || next_msg_pos > buf.size()) return std::nullopt;
|
||||
// Iterate over addresses within message, get destination and gateway (if present).
|
||||
// Address data starts after header.
|
||||
size_t sa_pos = msg_pos + sizeof(struct rt_msghdr);
|
||||
std::optional<CNetAddr> dst, gateway;
|
||||
for (int i = 0; i < RTAX_MAX; i++) {
|
||||
if (rt->rtm_addrs & (1 << i)) {
|
||||
// 2 is just sa_len + sa_family, the theoretical minimum size of a socket address.
|
||||
if ((sa_pos + 2) > next_msg_pos) return std::nullopt;
|
||||
const struct sockaddr* sa = (const struct sockaddr*)(buf.data() + sa_pos);
|
||||
if ((sa_pos + sa->sa_len) > next_msg_pos) return std::nullopt;
|
||||
if (i == RTAX_DST) {
|
||||
dst = FromSockAddr(sa);
|
||||
} else if (i == RTAX_GATEWAY) {
|
||||
gateway = FromSockAddr(sa);
|
||||
}
|
||||
// Skip sockaddr entries for bit flags we're not interested in,
|
||||
// move cursor.
|
||||
sa_pos += ROUNDUP32(sa->sa_len);
|
||||
}
|
||||
}
|
||||
// Found default gateway?
|
||||
if (dst && gateway && dst->IsBindAny()) { // Route to 0.0.0.0 or :: ?
|
||||
return *gateway;
|
||||
}
|
||||
// Skip to next message.
|
||||
msg_pos = next_msg_pos;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Dummy implementation.
|
||||
std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
std::optional<CNetAddr> QueryDefaultGateway(Network network)
|
||||
{
|
||||
Assume(network == NET_IPV4 || network == NET_IPV6);
|
||||
|
||||
sa_family_t family;
|
||||
if (network == NET_IPV4) {
|
||||
family = AF_INET;
|
||||
} else if(network == NET_IPV6) {
|
||||
family = AF_INET6;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<CNetAddr> ret = QueryDefaultGatewayImpl(family);
|
||||
|
||||
// It's possible for the default gateway to be 0.0.0.0 or ::0 on at least Windows
|
||||
// for some routing strategies. If so, return as if no default gateway was found.
|
||||
if (ret && !ret->IsBindAny()) {
|
||||
return ret;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CNetAddr> GetLocalAddresses()
|
||||
{
|
||||
std::vector<CNetAddr> addresses;
|
||||
#ifdef WIN32
|
||||
char pszHostName[256] = "";
|
||||
if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR) {
|
||||
addresses = LookupHost(pszHostName, 0, true);
|
||||
}
|
||||
#elif (HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS)
|
||||
struct ifaddrs* myaddrs;
|
||||
if (getifaddrs(&myaddrs) == 0) {
|
||||
for (struct ifaddrs* ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (ifa->ifa_addr == nullptr) continue;
|
||||
if ((ifa->ifa_flags & IFF_UP) == 0) continue;
|
||||
if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) continue;
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) {
|
||||
struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr);
|
||||
addresses.emplace_back(s4->sin_addr);
|
||||
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr);
|
||||
addresses.emplace_back(s6->sin6_addr);
|
||||
}
|
||||
}
|
||||
freeifaddrs(myaddrs);
|
||||
}
|
||||
#endif
|
||||
return addresses;
|
||||
}
|
19
src/util/netif.h
Normal file
19
src/util/netif.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2024 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_UTIL_NETIF_H
|
||||
#define BITCOIN_UTIL_NETIF_H
|
||||
|
||||
#include <netaddress.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
//! Query the OS for the default gateway for `network`. This only makes sense for NET_IPV4 and NET_IPV6.
|
||||
//! Returns std::nullopt if it cannot be found, or there is no support for this OS.
|
||||
std::optional<CNetAddr> QueryDefaultGateway(Network network);
|
||||
|
||||
//! Return all local non-loopback IPv4 and IPv6 network addresses.
|
||||
std::vector<CNetAddr> GetLocalAddresses();
|
||||
|
||||
#endif // BITCOIN_UTIL_NETIF_H
|
524
src/util/pcp.cpp
Normal file
524
src/util/pcp.cpp
Normal file
|
@ -0,0 +1,524 @@
|
|||
// Copyright (c) 2024 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <util/pcp.h>
|
||||
|
||||
#include <crypto/common.h>
|
||||
#include <logging.h>
|
||||
#include <netaddress.h>
|
||||
#include <netbase.h>
|
||||
#include <random.h>
|
||||
#include <span.h>
|
||||
#include <util/check.h>
|
||||
#include <util/netif.h>
|
||||
#include <util/readwritefile.h>
|
||||
#include <util/sock.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// RFC6886 NAT-PMP and RFC6887 Port Control Protocol (PCP) implementation.
|
||||
// NAT-PMP and PCP use network byte order (big-endian).
|
||||
|
||||
// NAT-PMP (v0) protocol constants.
|
||||
//! NAT-PMP uses a fixed server port number (RFC6887 section 1.1).
|
||||
constexpr uint16_t NATPMP_SERVER_PORT = 5351;
|
||||
//! Version byte for NATPMP (RFC6886 1.1)
|
||||
constexpr uint8_t NATPMP_VERSION = 0;
|
||||
//! Request opcode base (RFC6886 3).
|
||||
constexpr uint8_t NATPMP_REQUEST = 0x00;
|
||||
//! Response opcode base (RFC6886 3).
|
||||
constexpr uint8_t NATPMP_RESPONSE = 0x80;
|
||||
//! Get external address (RFC6886 3.2)
|
||||
constexpr uint8_t NATPMP_OP_GETEXTERNAL = 0x00;
|
||||
//! Map TCP port (RFC6886 3.3)
|
||||
constexpr uint8_t NATPMP_OP_MAP_TCP = 0x02;
|
||||
//! Shared request header size in bytes.
|
||||
constexpr size_t NATPMP_REQUEST_HDR_SIZE = 2;
|
||||
//! Shared response header (minimum) size in bytes.
|
||||
constexpr size_t NATPMP_RESPONSE_HDR_SIZE = 8;
|
||||
//! GETEXTERNAL request size in bytes, including header (RFC6886 3.2).
|
||||
constexpr size_t NATPMP_GETEXTERNAL_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 0;
|
||||
//! GETEXTERNAL response size in bytes, including header (RFC6886 3.2).
|
||||
constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 4;
|
||||
//! MAP request size in bytes, including header (RFC6886 3.3).
|
||||
constexpr size_t NATPMP_MAP_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 10;
|
||||
//! MAP response size in bytes, including header (RFC6886 3.3).
|
||||
constexpr size_t NATPMP_MAP_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 8;
|
||||
|
||||
// Shared header offsets (RFC6886 3.2, 3.3), relative to start of packet.
|
||||
//! Offset of version field in packets.
|
||||
constexpr size_t NATPMP_HDR_VERSION_OFS = 0;
|
||||
//! Offset of opcode field in packets
|
||||
constexpr size_t NATPMP_HDR_OP_OFS = 1;
|
||||
//! Offset of result code in packets. Result codes are 16 bit in NAT-PMP instead of 8 bit in PCP.
|
||||
constexpr size_t NATPMP_RESPONSE_HDR_RESULT_OFS = 2;
|
||||
|
||||
// GETEXTERNAL response offsets (RFC6886 3.2), relative to start of packet.
|
||||
//! Returned external address
|
||||
constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_IP_OFS = 8;
|
||||
|
||||
// MAP request offsets (RFC6886 3.3), relative to start of packet.
|
||||
//! Internal port to be mapped.
|
||||
constexpr size_t NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS = 4;
|
||||
//! Suggested external port for mapping.
|
||||
constexpr size_t NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS = 6;
|
||||
//! Requested port mapping lifetime in seconds.
|
||||
constexpr size_t NATPMP_MAP_REQUEST_LIFETIME_OFS = 8;
|
||||
|
||||
// MAP response offsets (RFC6886 3.3), relative to start of packet.
|
||||
//! Internal port for mapping (will match internal port of request).
|
||||
constexpr size_t NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS = 8;
|
||||
//! External port for mapping.
|
||||
constexpr size_t NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS = 10;
|
||||
//! Created port mapping lifetime in seconds.
|
||||
constexpr size_t NATPMP_MAP_RESPONSE_LIFETIME_OFS = 12;
|
||||
|
||||
// Relevant NETPMP result codes (RFC6886 3.5).
|
||||
//! Result code representing success status.
|
||||
constexpr uint8_t NATPMP_RESULT_SUCCESS = 0;
|
||||
//! Result code representing unsupported version.
|
||||
constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1;
|
||||
//! Result code representing lack of resources.
|
||||
constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4;
|
||||
|
||||
//! Mapping of NATPMP result code to string (RFC6886 3.5). Result codes <=2 match PCP.
|
||||
const std::map<uint8_t, std::string> NATPMP_RESULT_STR{
|
||||
{0, "SUCCESS"},
|
||||
{1, "UNSUPP_VERSION"},
|
||||
{2, "NOT_AUTHORIZED"},
|
||||
{3, "NETWORK_FAILURE"},
|
||||
{4, "NO_RESOURCES"},
|
||||
{5, "UNSUPP_OPCODE"},
|
||||
};
|
||||
|
||||
// PCP (v2) protocol constants.
|
||||
//! Maximum packet size in bytes (RFC6887 section 7).
|
||||
constexpr size_t PCP_MAX_SIZE = 1100;
|
||||
//! PCP uses a fixed server port number (RFC6887 section 19.1). Shared with NAT-PMP.
|
||||
constexpr uint16_t PCP_SERVER_PORT = NATPMP_SERVER_PORT;
|
||||
//! Version byte. 0 is NAT-PMP (RFC6886), 1 is forbidden, 2 for PCP (RFC6887).
|
||||
constexpr uint8_t PCP_VERSION = 2;
|
||||
//! PCP Request Header. See RFC6887 section 7.1. Shared with NAT-PMP.
|
||||
constexpr uint8_t PCP_REQUEST = NATPMP_REQUEST; // R = 0
|
||||
//! PCP Response Header. See RFC6887 section 7.2. Shared with NAT-PMP.
|
||||
constexpr uint8_t PCP_RESPONSE = NATPMP_RESPONSE; // R = 1
|
||||
//! Map opcode. See RFC6887 section 19.2
|
||||
constexpr uint8_t PCP_OP_MAP = 0x01;
|
||||
//! TCP protocol number (IANA).
|
||||
constexpr uint16_t PCP_PROTOCOL_TCP = 6;
|
||||
//! Request and response header size in bytes (RFC6887 section 7.1).
|
||||
constexpr size_t PCP_HDR_SIZE = 24;
|
||||
//! Map request and response size in bytes (RFC6887 section 11.1).
|
||||
constexpr size_t PCP_MAP_SIZE = 36;
|
||||
|
||||
// Header offsets shared between request and responses (RFC6887 7.1, 7.2), relative to start of packet.
|
||||
//! Version field (1 byte).
|
||||
constexpr size_t PCP_HDR_VERSION_OFS = NATPMP_HDR_VERSION_OFS;
|
||||
//! Opcode field (1 byte).
|
||||
constexpr size_t PCP_HDR_OP_OFS = NATPMP_HDR_OP_OFS;
|
||||
//! Requested lifetime (request), granted lifetime (response) (4 bytes).
|
||||
constexpr size_t PCP_HDR_LIFETIME_OFS = 4;
|
||||
|
||||
// Request header offsets (RFC6887 7.1), relative to start of packet.
|
||||
//! PCP client's IP address (16 bytes).
|
||||
constexpr size_t PCP_REQUEST_HDR_IP_OFS = 8;
|
||||
|
||||
// Response header offsets (RFC6887 7.2), relative to start of packet.
|
||||
//! Result code (1 byte).
|
||||
constexpr size_t PCP_RESPONSE_HDR_RESULT_OFS = 3;
|
||||
|
||||
// MAP request/response offsets (RFC6887 11.1), relative to start of opcode-specific data.
|
||||
//! Mapping nonce (12 bytes).
|
||||
constexpr size_t PCP_MAP_NONCE_OFS = 0;
|
||||
//! Protocol (1 byte).
|
||||
constexpr size_t PCP_MAP_PROTOCOL_OFS = 12;
|
||||
//! Internal port for mapping (2 bytes).
|
||||
constexpr size_t PCP_MAP_INTERNAL_PORT_OFS = 16;
|
||||
//! Suggested external port (request), assigned external port (response) (2 bytes).
|
||||
constexpr size_t PCP_MAP_EXTERNAL_PORT_OFS = 18;
|
||||
//! Suggested external IP (request), assigned external IP (response) (16 bytes).
|
||||
constexpr size_t PCP_MAP_EXTERNAL_IP_OFS = 20;
|
||||
|
||||
//! Result code representing success (RFC6887 7.4), shared with NAT-PMP.
|
||||
constexpr uint8_t PCP_RESULT_SUCCESS = NATPMP_RESULT_SUCCESS;
|
||||
//! Result code representing lack of resources (RFC6887 7.4).
|
||||
constexpr uint8_t PCP_RESULT_NO_RESOURCES = 8;
|
||||
|
||||
//! Mapping of PCP result code to string (RFC6887 7.4). Result codes <=2 match NAT-PMP.
|
||||
const std::map<uint8_t, std::string> PCP_RESULT_STR{
|
||||
{0, "SUCCESS"},
|
||||
{1, "UNSUPP_VERSION"},
|
||||
{2, "NOT_AUTHORIZED"},
|
||||
{3, "MALFORMED_REQUEST"},
|
||||
{4, "UNSUPP_OPCODE"},
|
||||
{5, "UNSUPP_OPTION"},
|
||||
{6, "MALFORMED_OPTION"},
|
||||
{7, "NETWORK_FAILURE"},
|
||||
{8, "NO_RESOURCES"},
|
||||
{9, "UNSUPP_PROTOCOL"},
|
||||
{10, "USER_EX_QUOTA"},
|
||||
{11, "CANNOT_PROVIDE_EXTERNAL"},
|
||||
{12, "ADDRESS_MISMATCH"},
|
||||
{13, "EXCESSIVE_REMOTE_PEER"},
|
||||
};
|
||||
|
||||
//! Return human-readable string from NATPMP result code.
|
||||
std::string NATPMPResultString(uint8_t result_code)
|
||||
{
|
||||
auto result_i = NATPMP_RESULT_STR.find(result_code);
|
||||
return strprintf("%s (code %d)", result_i == NATPMP_RESULT_STR.end() ? "(unknown)" : result_i->second, result_code);
|
||||
}
|
||||
|
||||
//! Return human-readable string from PCP result code.
|
||||
std::string PCPResultString(uint8_t result_code)
|
||||
{
|
||||
auto result_i = PCP_RESULT_STR.find(result_code);
|
||||
return strprintf("%s (code %d)", result_i == PCP_RESULT_STR.end() ? "(unknown)" : result_i->second, result_code);
|
||||
}
|
||||
|
||||
//! Wrap address in IPv6 according to RFC6887. wrapped_addr needs to be able to store 16 bytes.
|
||||
[[nodiscard]] bool PCPWrapAddress(Span<uint8_t> wrapped_addr, const CNetAddr &addr)
|
||||
{
|
||||
Assume(wrapped_addr.size() == ADDR_IPV6_SIZE);
|
||||
if (addr.IsIPv4()) {
|
||||
struct in_addr addr4;
|
||||
if (!addr.GetInAddr(&addr4)) return false;
|
||||
// Section 5: "When the address field holds an IPv4 address, an IPv4-mapped IPv6 address [RFC4291] is used (::ffff:0:0/96)."
|
||||
std::memcpy(wrapped_addr.data(), IPV4_IN_IPV6_PREFIX.data(), IPV4_IN_IPV6_PREFIX.size());
|
||||
std::memcpy(wrapped_addr.data() + IPV4_IN_IPV6_PREFIX.size(), &addr4, ADDR_IPV4_SIZE);
|
||||
return true;
|
||||
} else if (addr.IsIPv6()) {
|
||||
struct in6_addr addr6;
|
||||
if (!addr.GetIn6Addr(&addr6)) return false;
|
||||
std::memcpy(wrapped_addr.data(), &addr6, ADDR_IPV6_SIZE);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//! Unwrap PCP-encoded address according to RFC6887.
|
||||
CNetAddr PCPUnwrapAddress(Span<const uint8_t> wrapped_addr)
|
||||
{
|
||||
Assume(wrapped_addr.size() == ADDR_IPV6_SIZE);
|
||||
if (util::HasPrefix(wrapped_addr, IPV4_IN_IPV6_PREFIX)) {
|
||||
struct in_addr addr4;
|
||||
std::memcpy(&addr4, wrapped_addr.data() + IPV4_IN_IPV6_PREFIX.size(), ADDR_IPV4_SIZE);
|
||||
return CNetAddr(addr4);
|
||||
} else {
|
||||
struct in6_addr addr6;
|
||||
std::memcpy(&addr6, wrapped_addr.data(), ADDR_IPV6_SIZE);
|
||||
return CNetAddr(addr6);
|
||||
}
|
||||
}
|
||||
|
||||
//! PCP or NAT-PMP send-receive loop.
|
||||
std::optional<std::vector<uint8_t>> PCPSendRecv(Sock &sock, const std::string &protocol, Span<const uint8_t> request, int num_tries,
|
||||
std::chrono::milliseconds timeout_per_try,
|
||||
std::function<bool(Span<const uint8_t>)> check_packet)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
// UDP is a potentially lossy protocol, so we try to send again a few times.
|
||||
uint8_t response[PCP_MAX_SIZE];
|
||||
bool got_response = false;
|
||||
int recvsz = 0;
|
||||
for (int ntry = 0; !got_response && ntry < num_tries; ++ntry) {
|
||||
if (ntry > 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "%s: Retrying (%d)\n", protocol, ntry);
|
||||
}
|
||||
// Dispatch packet to gateway.
|
||||
if (sock.Send(request.data(), request.size(), 0) != static_cast<ssize_t>(request.size())) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "%s: Could not send request: %s\n", protocol, NetworkErrorString(WSAGetLastError()));
|
||||
return std::nullopt; // Network-level error, probably no use retrying.
|
||||
}
|
||||
|
||||
// Wait for response(s) until we get a valid response, a network error, or time out.
|
||||
auto cur_time = time_point_cast<milliseconds>(steady_clock::now());
|
||||
auto deadline = cur_time + timeout_per_try;
|
||||
while ((cur_time = time_point_cast<milliseconds>(steady_clock::now())) < deadline) {
|
||||
Sock::Event occurred = 0;
|
||||
if (!sock.Wait(deadline - cur_time, Sock::RECV, &occurred)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "%s: Could not wait on socket: %s\n", protocol, NetworkErrorString(WSAGetLastError()));
|
||||
return std::nullopt; // Network-level error, probably no use retrying.
|
||||
}
|
||||
if (!occurred) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "%s: Timeout\n", protocol);
|
||||
break; // Retry.
|
||||
}
|
||||
|
||||
// Receive response.
|
||||
recvsz = sock.Recv(response, sizeof(response), MSG_DONTWAIT);
|
||||
if (recvsz < 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "%s: Could not receive response: %s\n", protocol, NetworkErrorString(WSAGetLastError()));
|
||||
return std::nullopt; // Network-level error, probably no use retrying.
|
||||
}
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "%s: Received response of %d bytes: %s\n", protocol, recvsz, HexStr(Span(response, recvsz)));
|
||||
|
||||
if (check_packet(Span<uint8_t>(response, recvsz))) {
|
||||
got_response = true; // Got expected response, break from receive loop as well as from retry loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!got_response) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "%s: Giving up after %d tries\n", protocol, num_tries);
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::vector<uint8_t>(response, response + recvsz);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::variant<MappingResult, MappingError> NATPMPRequestPortMap(const CNetAddr &gateway, uint16_t port, uint32_t lifetime, int num_tries, std::chrono::milliseconds timeout_per_try)
|
||||
{
|
||||
struct sockaddr_storage dest_addr;
|
||||
socklen_t dest_addrlen = sizeof(struct sockaddr_storage);
|
||||
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "natpmp: Requesting port mapping port %d from gateway %s\n", port, gateway.ToStringAddr());
|
||||
|
||||
// Validate gateway, make sure it's IPv4. NAT-PMP does not support IPv6.
|
||||
if (!CService(gateway, PCP_SERVER_PORT).GetSockAddr((struct sockaddr*)&dest_addr, &dest_addrlen)) return MappingError::NETWORK_ERROR;
|
||||
if (dest_addr.ss_family != AF_INET) return MappingError::NETWORK_ERROR;
|
||||
|
||||
// Create IPv4 UDP socket
|
||||
auto sock{CreateSock(AF_INET, SOCK_DGRAM, IPPROTO_UDP)};
|
||||
if (!sock) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Could not create UDP socket: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Associate UDP socket to gateway.
|
||||
if (sock->Connect((struct sockaddr*)&dest_addr, dest_addrlen) != 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Could not connect to gateway: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Use getsockname to get the address toward the default gateway (the internal address).
|
||||
struct sockaddr_in internal;
|
||||
socklen_t internal_addrlen = sizeof(struct sockaddr_in);
|
||||
if (sock->GetSockName((struct sockaddr*)&internal, &internal_addrlen) != 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Could not get sock name: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Request external IP address (RFC6886 section 3.2).
|
||||
std::vector<uint8_t> request(NATPMP_GETEXTERNAL_REQUEST_SIZE);
|
||||
request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
|
||||
request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_GETEXTERNAL;
|
||||
|
||||
auto recv_res = PCPSendRecv(*sock, "natpmp", request, num_tries, timeout_per_try,
|
||||
[&](const Span<const uint8_t> response) -> bool {
|
||||
if (response.size() < NATPMP_GETEXTERNAL_RESPONSE_SIZE) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Response too small\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
if (response[NATPMP_HDR_VERSION_OFS] != NATPMP_VERSION || response[NATPMP_HDR_OP_OFS] != (NATPMP_RESPONSE | NATPMP_OP_GETEXTERNAL)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Response to wrong command\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
struct in_addr external_addr;
|
||||
if (recv_res) {
|
||||
const std::span<const uint8_t> response = *recv_res;
|
||||
|
||||
Assume(response.size() >= NATPMP_GETEXTERNAL_RESPONSE_SIZE);
|
||||
uint16_t result_code = ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS);
|
||||
if (result_code != NATPMP_RESULT_SUCCESS) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Getting external address failed with result %s\n", NATPMPResultString(result_code));
|
||||
return MappingError::PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
std::memcpy(&external_addr, response.data() + NATPMP_GETEXTERNAL_RESPONSE_IP_OFS, ADDR_IPV4_SIZE);
|
||||
} else {
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Create TCP mapping request (RFC6886 section 3.3).
|
||||
request = std::vector<uint8_t>(NATPMP_MAP_REQUEST_SIZE);
|
||||
request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
|
||||
request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_MAP_TCP;
|
||||
WriteBE16(request.data() + NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS, port);
|
||||
WriteBE16(request.data() + NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS, port);
|
||||
WriteBE32(request.data() + NATPMP_MAP_REQUEST_LIFETIME_OFS, lifetime);
|
||||
|
||||
recv_res = PCPSendRecv(*sock, "natpmp", request, num_tries, timeout_per_try,
|
||||
[&](const Span<const uint8_t> response) -> bool {
|
||||
if (response.size() < NATPMP_MAP_RESPONSE_SIZE) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Response too small\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
if (response[0] != NATPMP_VERSION || response[1] != (NATPMP_RESPONSE | NATPMP_OP_MAP_TCP)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Response to wrong command\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
uint16_t internal_port = ReadBE16(response.data() + NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS);
|
||||
if (internal_port != port) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Response port doesn't match request\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (recv_res) {
|
||||
const std::span<uint8_t> response = *recv_res;
|
||||
|
||||
Assume(response.size() >= NATPMP_MAP_RESPONSE_SIZE);
|
||||
uint16_t result_code = ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS);
|
||||
if (result_code != NATPMP_RESULT_SUCCESS) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
|
||||
if (result_code == NATPMP_RESULT_NO_RESOURCES) {
|
||||
return MappingError::NO_RESOURCES;
|
||||
}
|
||||
return MappingError::PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
uint32_t lifetime_ret = ReadBE32(response.data() + NATPMP_MAP_RESPONSE_LIFETIME_OFS);
|
||||
uint16_t external_port = ReadBE16(response.data() + NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS);
|
||||
return MappingResult(NATPMP_VERSION, CService(internal.sin_addr, port), CService(external_addr, external_port), lifetime_ret);
|
||||
} else {
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<MappingResult, MappingError> PCPRequestPortMap(const PCPMappingNonce &nonce, const CNetAddr &gateway, const CNetAddr &bind, uint16_t port, uint32_t lifetime, int num_tries, std::chrono::milliseconds timeout_per_try)
|
||||
{
|
||||
struct sockaddr_storage dest_addr, bind_addr;
|
||||
socklen_t dest_addrlen = sizeof(struct sockaddr_storage), bind_addrlen = sizeof(struct sockaddr_storage);
|
||||
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "pcp: Requesting port mapping for addr %s port %d from gateway %s\n", bind.ToStringAddr(), port, gateway.ToStringAddr());
|
||||
|
||||
// Validate addresses, make sure they're the same network family.
|
||||
if (!CService(gateway, PCP_SERVER_PORT).GetSockAddr((struct sockaddr*)&dest_addr, &dest_addrlen)) return MappingError::NETWORK_ERROR;
|
||||
if (!CService(bind, 0).GetSockAddr((struct sockaddr*)&bind_addr, &bind_addrlen)) return MappingError::NETWORK_ERROR;
|
||||
if (dest_addr.ss_family != bind_addr.ss_family) return MappingError::NETWORK_ERROR;
|
||||
|
||||
// Create UDP socket (IPv4 or IPv6 based on provided gateway).
|
||||
auto sock{CreateSock(dest_addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)};
|
||||
if (!sock) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Could not create UDP socket: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Make sure that we send from requested destination address, anything else will be
|
||||
// rejected by a security-conscious router.
|
||||
if (sock->Bind((struct sockaddr*)&bind_addr, bind_addrlen) != 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Could not bind to address: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Associate UDP socket to gateway.
|
||||
if (sock->Connect((struct sockaddr*)&dest_addr, dest_addrlen) != 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Could not connect to gateway: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
|
||||
// Use getsockname to get the address toward the default gateway (the internal address),
|
||||
// in case we don't know what address to map
|
||||
// (this is only needed if bind is INADDR_ANY, but it doesn't hurt as an extra check).
|
||||
struct sockaddr_storage internal_addr;
|
||||
socklen_t internal_addrlen = sizeof(struct sockaddr_storage);
|
||||
if (sock->GetSockName((struct sockaddr*)&internal_addr, &internal_addrlen) != 0) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Could not get sock name: %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
CService internal;
|
||||
if (!internal.SetSockAddr((struct sockaddr*)&internal_addr)) return MappingError::NETWORK_ERROR;
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "pcp: Internal address after connect: %s\n", internal.ToStringAddr());
|
||||
|
||||
// Build request packet. Make sure the packet is zeroed so that reserved fields are zero
|
||||
// as required by the spec (and not potentially leak data).
|
||||
// Make sure there's space for the request header and MAP specific request data.
|
||||
std::vector<uint8_t> request(PCP_HDR_SIZE + PCP_MAP_SIZE);
|
||||
// Fill in request header, See RFC6887 Figure 2.
|
||||
size_t ofs = 0;
|
||||
request[ofs + PCP_HDR_VERSION_OFS] = PCP_VERSION;
|
||||
request[ofs + PCP_HDR_OP_OFS] = PCP_REQUEST | PCP_OP_MAP;
|
||||
WriteBE32(request.data() + ofs + PCP_HDR_LIFETIME_OFS, lifetime);
|
||||
if (!PCPWrapAddress(Span(request).subspan(ofs + PCP_REQUEST_HDR_IP_OFS, ADDR_IPV6_SIZE), internal)) return MappingError::NETWORK_ERROR;
|
||||
|
||||
ofs += PCP_HDR_SIZE;
|
||||
|
||||
// Fill in MAP request packet, See RFC6887 Figure 9.
|
||||
// Randomize mapping nonce (this is repeated in the response, to be able to
|
||||
// correlate requests and responses, and used to authenticate changes to the mapping).
|
||||
std::memcpy(request.data() + ofs + PCP_MAP_NONCE_OFS, nonce.data(), PCP_MAP_NONCE_SIZE);
|
||||
request[ofs + PCP_MAP_PROTOCOL_OFS] = PCP_PROTOCOL_TCP;
|
||||
WriteBE16(request.data() + ofs + PCP_MAP_INTERNAL_PORT_OFS, port);
|
||||
WriteBE16(request.data() + ofs + PCP_MAP_EXTERNAL_PORT_OFS, port);
|
||||
if (!PCPWrapAddress(Span(request).subspan(ofs + PCP_MAP_EXTERNAL_IP_OFS, ADDR_IPV6_SIZE), bind)) return MappingError::NETWORK_ERROR;
|
||||
|
||||
ofs += PCP_MAP_SIZE;
|
||||
Assume(ofs == request.size());
|
||||
|
||||
// Receive loop.
|
||||
bool is_natpmp = false;
|
||||
auto recv_res = PCPSendRecv(*sock, "pcp", request, num_tries, timeout_per_try,
|
||||
[&](const Span<const uint8_t> response) -> bool {
|
||||
// Unsupported version according to RFC6887 appendix A and RFC6886 section 3.5, can fall back to NAT-PMP.
|
||||
if (response.size() == NATPMP_RESPONSE_HDR_SIZE && response[PCP_HDR_VERSION_OFS] == NATPMP_VERSION && response[PCP_RESPONSE_HDR_RESULT_OFS] == NATPMP_RESULT_UNSUPP_VERSION) {
|
||||
is_natpmp = true;
|
||||
return true; // Let it through to caller.
|
||||
}
|
||||
if (response.size() < (PCP_HDR_SIZE + PCP_MAP_SIZE)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Response too small\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
if (response[PCP_HDR_VERSION_OFS] != PCP_VERSION || response[PCP_HDR_OP_OFS] != (PCP_RESPONSE | PCP_OP_MAP)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Response to wrong command\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
// Handle MAP opcode response. See RFC6887 Figure 10.
|
||||
// Check that returned mapping nonce matches our request.
|
||||
if (!std::ranges::equal(response.subspan(PCP_HDR_SIZE + PCP_MAP_NONCE_OFS, PCP_MAP_NONCE_SIZE), nonce)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Mapping nonce mismatch\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
uint8_t protocol = response[PCP_HDR_SIZE + 12];
|
||||
uint16_t internal_port = ReadBE16(response.data() + PCP_HDR_SIZE + 16);
|
||||
if (protocol != PCP_PROTOCOL_TCP || internal_port != port) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Response protocol or port doesn't match request\n");
|
||||
return false; // Wasn't response to what we expected, try receiving next packet.
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!recv_res) {
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
if (is_natpmp) {
|
||||
return MappingError::UNSUPP_VERSION;
|
||||
}
|
||||
|
||||
const std::span<const uint8_t> response = *recv_res;
|
||||
// If we get here, we got a valid MAP response to our request.
|
||||
// Check to see if we got the result we expected.
|
||||
Assume(response.size() >= (PCP_HDR_SIZE + PCP_MAP_SIZE));
|
||||
uint8_t result_code = response[PCP_RESPONSE_HDR_RESULT_OFS];
|
||||
uint32_t lifetime_ret = ReadBE32(response.data() + PCP_HDR_LIFETIME_OFS);
|
||||
uint16_t external_port = ReadBE16(response.data() + PCP_HDR_SIZE + PCP_MAP_EXTERNAL_PORT_OFS);
|
||||
CNetAddr external_addr{PCPUnwrapAddress(response.subspan(PCP_HDR_SIZE + PCP_MAP_EXTERNAL_IP_OFS, ADDR_IPV6_SIZE))};
|
||||
if (result_code != PCP_RESULT_SUCCESS) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "pcp: Mapping failed with result %s\n", PCPResultString(result_code));
|
||||
if (result_code == PCP_RESULT_NO_RESOURCES) {
|
||||
return MappingError::NO_RESOURCES;
|
||||
}
|
||||
return MappingError::PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
return MappingResult(PCP_VERSION, CService(internal, port), CService(external_addr, external_port), lifetime_ret);
|
||||
}
|
||||
|
||||
std::string MappingResult::ToString()
|
||||
{
|
||||
Assume(version == NATPMP_VERSION || version == PCP_VERSION);
|
||||
return strprintf("%s:%s -> %s (for %ds)",
|
||||
version == NATPMP_VERSION ? "natpmp" : "pcp",
|
||||
external.ToStringAddrPort(),
|
||||
internal.ToStringAddrPort(),
|
||||
lifetime
|
||||
);
|
||||
}
|
68
src/util/pcp.h
Normal file
68
src/util/pcp.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) 2024 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_UTIL_PCP_H
|
||||
#define BITCOIN_UTIL_PCP_H
|
||||
|
||||
#include <netaddress.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
// RFC6886 NAT-PMP and RFC6887 Port Control Protocol (PCP) implementation.
|
||||
// NAT-PMP and PCP use network byte order (big-endian).
|
||||
|
||||
//! Mapping nonce size in bytes (see RFC6887 section 11.1).
|
||||
constexpr size_t PCP_MAP_NONCE_SIZE = 12;
|
||||
|
||||
//! PCP mapping nonce. Arbitrary data chosen by the client to identify a mapping.
|
||||
typedef std::array<uint8_t, PCP_MAP_NONCE_SIZE> PCPMappingNonce;
|
||||
|
||||
//! Unsuccessful response to a port mapping.
|
||||
enum class MappingError {
|
||||
NETWORK_ERROR, ///< Any kind of network-level error.
|
||||
PROTOCOL_ERROR, ///< Any kind of protocol-level error, except unsupported version or no resources.
|
||||
UNSUPP_VERSION, ///< Unsupported protocol version.
|
||||
NO_RESOURCES, ///< No resources available (port probably already mapped).
|
||||
};
|
||||
|
||||
//! Successful response to a port mapping.
|
||||
struct MappingResult {
|
||||
MappingResult(uint8_t version, const CService &internal_in, const CService &external_in, uint32_t lifetime_in):
|
||||
version(version), internal(internal_in), external(external_in), lifetime(lifetime_in) {}
|
||||
//! Protocol version, one of NATPMP_VERSION or PCP_VERSION.
|
||||
uint8_t version;
|
||||
//! Internal host:port.
|
||||
CService internal;
|
||||
//! External host:port.
|
||||
CService external;
|
||||
//! Granted lifetime of binding (seconds).
|
||||
uint32_t lifetime;
|
||||
|
||||
//! Format mapping as string for logging.
|
||||
std::string ToString();
|
||||
};
|
||||
|
||||
//! Try to open a port using RFC 6886 NAT-PMP. IPv4 only.
|
||||
//!
|
||||
//! * gateway: Destination address for PCP requests (usually the default gateway).
|
||||
//! * port: Internal port, and desired external port.
|
||||
//! * lifetime: Requested lifetime in seconds for mapping. The server may assign as shorter or longer lifetime. A lifetime of 0 deletes the mapping.
|
||||
//! * num_tries: Number of tries in case of no response.
|
||||
//!
|
||||
//! Returns the external_ip:external_port of the mapping if successful, otherwise a MappingError.
|
||||
std::variant<MappingResult, MappingError> NATPMPRequestPortMap(const CNetAddr &gateway, uint16_t port, uint32_t lifetime, int num_tries = 3, std::chrono::milliseconds timeout_per_try = std::chrono::milliseconds(1000));
|
||||
|
||||
//! Try to open a port using RFC 6887 Port Control Protocol (PCP). Handles IPv4 and IPv6.
|
||||
//!
|
||||
//! * nonce: Mapping cookie. Keep this the same over renewals.
|
||||
//! * gateway: Destination address for PCP requests (usually the default gateway).
|
||||
//! * bind: Specific local bind address for IPv6 pinholing. Set this as INADDR_ANY for IPv4.
|
||||
//! * port: Internal port, and desired external port.
|
||||
//! * lifetime: Requested lifetime in seconds for mapping. The server may assign as shorter or longer lifetime. A lifetime of 0 deletes the mapping.
|
||||
//! * num_tries: Number of tries in case of no response.
|
||||
//!
|
||||
//! Returns the external_ip:external_port of the mapping if successful, otherwise a MappingError.
|
||||
std::variant<MappingResult, MappingError> PCPRequestPortMap(const PCPMappingNonce &nonce, const CNetAddr &gateway, const CNetAddr &bind, uint16_t port, uint32_t lifetime, int num_tries = 3, std::chrono::milliseconds timeout_per_try = std::chrono::milliseconds(1000));
|
||||
|
||||
#endif // BITCOIN_UTIL_PCP_H
|
Loading…
Add table
Reference in a new issue