Bitcoin ABC 0.33.3
P2P Digital Currency
mapport.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2020 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <mapport.h>
6
7#include <clientversion.h>
8#include <common/netif.h>
9#include <common/pcp.h>
10#include <common/system.h>
11#include <logging.h>
12#include <net.h>
13#include <netaddress.h>
14#include <netbase.h>
15#include <random.h>
16#include <util/thread.h>
18
19#include <atomic>
20#include <cassert>
21#include <chrono>
22#include <functional>
23#include <string>
24#include <thread>
25
27static std::thread g_mapport_thread;
28
29using namespace std::chrono_literals;
30static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
31static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
32
33static void ProcessPCP() {
34 // The same nonce is used for all mappings, this is allowed by the spec, and
35 // simplifies keeping track of them.
36 PCPMappingNonce pcp_nonce;
37 GetRandBytes(pcp_nonce);
38
39 bool ret = false;
40 bool no_resources = false;
41 const uint16_t private_port = GetListenPort();
42 // Multiply the reannounce period by two, as we'll try to renew
43 // approximately halfway.
44 const uint32_t requested_lifetime =
45 std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count();
46 uint32_t actual_lifetime = 0;
47 std::chrono::milliseconds sleep_time;
48
49 // Local functor to handle result from PCP/NATPMP mapping.
50 auto handle_mapping =
51 [&](std::variant<MappingResult, MappingError> &res) -> void {
52 if (MappingResult *mapping = std::get_if<MappingResult>(&res)) {
54 "portmap: Added mapping %s\n", mapping->ToString());
55 AddLocal(mapping->external, LOCAL_MAPPED);
56 ret = true;
57 actual_lifetime = std::min(actual_lifetime, mapping->lifetime);
58 } else if (MappingError *err = std::get_if<MappingError>(&res)) {
59 // Detailed error will already have been logged internally in
60 // respective Portmap function.
61 if (*err == MappingError::NO_RESOURCES) {
62 no_resources = true;
63 }
64 }
65 };
66
67 do {
68 actual_lifetime = requested_lifetime;
69 // Set to true if there was any "no resources" error.
70 no_resources = false;
71 // Set to true if any mapping succeeds.
72 ret = false;
73
74 // IPv4
75 std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4);
76 if (!gateway4) {
79 "portmap: Could not determine IPv4 default gateway\n");
80 } else {
82 "portmap: gateway [IPv4]: %s\n",
83 gateway4->ToStringAddr());
84
85 // Open a port mapping on whatever local address we have toward the
86 // gateway.
87 struct in_addr inaddr_any;
88 inaddr_any.s_addr = htonl(INADDR_ANY);
89 auto res = PCPRequestPortMap(
90 pcp_nonce, *gateway4, CNetAddr(inaddr_any), private_port,
91 requested_lifetime, g_mapport_interrupt);
92 MappingError *pcp_err = std::get_if<MappingError>(&res);
93 if (pcp_err && *pcp_err == MappingError::UNSUPP_VERSION) {
95 "portmap: Got unsupported PCP version response, "
96 "falling back to NAT-PMP\n");
97 res = NATPMPRequestPortMap(*gateway4, private_port,
98 requested_lifetime,
100 }
101 handle_mapping(res);
102 }
103
104 // IPv6
105 std::optional<CNetAddr> gateway6 = QueryDefaultGateway(NET_IPV6);
106 if (!gateway6) {
109 "portmap: Could not determine IPv6 default gateway\n");
110 } else {
112 "portmap: gateway [IPv6]: %s\n",
113 gateway6->ToStringAddr());
114
115 // Try to open pinholes for all routable local IPv6 addresses.
116 for (const auto &addr : GetLocalAddresses()) {
117 if (!addr.IsRoutable() || !addr.IsIPv6()) {
118 continue;
119 }
120 auto res =
121 PCPRequestPortMap(pcp_nonce, *gateway6, addr, private_port,
122 requested_lifetime, g_mapport_interrupt);
123 handle_mapping(res);
124 }
125 }
126
127 // Log message if we got NO_RESOURCES.
128 if (no_resources) {
131 "portmap: At least one mapping failed because of a "
132 "NO_RESOURCES error. This usually indicates that the port is "
133 "already used on the router. If this is the only instance of "
134 "bitcoin running on the network, this will resolve itself "
135 "automatically. Otherwise, you might want to choose a "
136 "different P2P port to prevent this conflict.\n");
137 }
138
139 // Sanity-check returned lifetime.
140 if (actual_lifetime < 30) {
142 "portmap: Got impossibly short mapping lifetime of "
143 "%d seconds\n",
144 actual_lifetime);
145 return;
146 }
147 // RFC6887 11.2.1 recommends that clients send their first renewal
148 // packet at a time chosen with uniform random distribution in the range
149 // 1/2 to 5/8 of expiration time.
150 std::chrono::seconds sleep_time_min(actual_lifetime / 2);
151 std::chrono::seconds sleep_time_max(actual_lifetime * 5 / 8);
152 sleep_time = sleep_time_min +
153 FastRandomContext().randrange<std::chrono::milliseconds>(
154 sleep_time_max - sleep_time_min);
155 } while (ret && g_mapport_interrupt.sleep_for(sleep_time));
156
157 // We don't delete the mappings when the thread is interrupted because this
158 // would add additional complexity, so we rather just choose a fairly short
159 // expiry time.
160}
161
162static void ThreadMapPort() {
163 do {
164 ProcessPCP();
166}
167
169 if (!g_mapport_thread.joinable()) {
172 std::thread(&util::TraceThread, "mapport", &ThreadMapPort);
173 }
174}
175
176void StartMapPort(bool enable) {
177 if (enable) {
179 } else {
181 StopMapPort();
182 }
183}
184
186 if (g_mapport_thread.joinable()) {
188 }
189}
190
192 if (g_mapport_thread.joinable()) {
193 g_mapport_thread.join();
195 }
196}
Network address.
Definition: netaddress.h:114
A helper class for interruptible sleeps.
bool sleep_for(Clock::duration rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut)
Fast randomness source.
Definition: random.h:411
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:266
#define LogPrintLevel(category, level,...)
Definition: logging.h:437
static void ThreadMapPort()
Definition: mapport.cpp:162
static std::thread g_mapport_thread
Definition: mapport.cpp:27
static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD
Definition: mapport.cpp:30
static CThreadInterrupt g_mapport_interrupt
Definition: mapport.cpp:26
static void ProcessPCP()
Definition: mapport.cpp:33
void StopMapPort()
Definition: mapport.cpp:191
static constexpr auto PORT_MAPPING_RETRY_PERIOD
Definition: mapport.cpp:31
void InterruptMapPort()
Definition: mapport.cpp:185
void StartMapPort(bool enable)
Definition: mapport.cpp:176
void StartThreadMapPort()
Definition: mapport.cpp:168
@ NET
Definition: logging.h:69
void TraceThread(std::string_view thread_name, std::function< void()> thread_func)
A wrapper for do-something-once thread functions.
Definition: thread.cpp:14
uint16_t GetListenPort()
Definition: net.cpp:140
bool AddLocal(const CService &addr, int nScore)
Definition: net.cpp:281
@ LOCAL_MAPPED
Definition: net.h:166
@ NET_IPV6
IPv6.
Definition: netaddress.h:46
@ NET_IPV4
IPv4.
Definition: netaddress.h:43
std::vector< CNetAddr > GetLocalAddresses()
Return all local non-loopback IPv4 and IPv6 network addresses.
Definition: netif.cpp:386
std::optional< CNetAddr > QueryDefaultGateway(Network network)
Query the OS for the default gateway for network.
Definition: netif.cpp:363
std::variant< MappingResult, MappingError > NATPMPRequestPortMap(const CNetAddr &gateway, uint16_t port, uint32_t lifetime, CThreadInterrupt &interrupt, int num_tries, std::chrono::milliseconds timeout_per_try)
Try to open a port using RFC 6886 NAT-PMP.
Definition: pcp.cpp:318
std::variant< MappingResult, MappingError > PCPRequestPortMap(const PCPMappingNonce &nonce, const CNetAddr &gateway, const CNetAddr &bind, uint16_t port, uint32_t lifetime, CThreadInterrupt &interrupt, int num_tries, std::chrono::milliseconds timeout_per_try)
Try to open a port using RFC 6887 Port Control Protocol (PCP).
Definition: pcp.cpp:494
std::array< uint8_t, PCP_MAP_NONCE_SIZE > PCPMappingNonce
PCP mapping nonce.
Definition: pcp.h:24
MappingError
Unsuccessful response to a port mapping.
Definition: pcp.h:27
@ NO_RESOURCES
No resources available (port probably already mapped).
@ UNSUPP_VERSION
Unsupported protocol version.
void GetRandBytes(Span< uint8_t > bytes) noexcept
================== BASE RANDOMNESS GENERATION FUNCTIONS ====================
Definition: random.cpp:690
Successful response to a port mapping.
Definition: pcp.h:40
assert(!tx.IsCoinBase())