From f9d1a9a00df131dfc81b69dc8665e2105abaefd8 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 27 May 2024 09:24:31 +0000 Subject: [PATCH 01/10] merge bitcoin#23077: Full CJDNS support --- contrib/seeds/generate-seeds.py | 11 +++- src/init.cpp | 10 ++++ src/net.cpp | 32 +++++++++-- src/netaddress.cpp | 17 ++++-- src/netaddress.h | 4 +- src/netbase.cpp | 5 +- src/rpc/net.cpp | 2 +- src/test/netbase_tests.cpp | 2 + test/functional/feature_proxy.py | 67 +++++++++++++++++++++--- test/functional/interface_bitcoin_cli.py | 2 +- test/functional/rpc_net.py | 8 +-- 11 files changed, 135 insertions(+), 25 deletions(-) diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index 75cb6fad6e80..c35827ed5d78 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -54,7 +54,7 @@ def name_to_bip155(addr): raise ValueError('Invalid onion %s' % vchAddr) elif '.' in addr: # IPv4 return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.')))) - elif ':' in addr: # IPv6 + elif ':' in addr: # IPv6 or CJDNS sub = [[], []] # prefix, suffix x = 0 addr = addr.split(':') @@ -70,7 +70,14 @@ def name_to_bip155(addr): sub[x].append(val & 0xff) nullbytes = 16 - len(sub[0]) - len(sub[1]) assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)) - return (BIP155Network.IPV6, bytes(sub[0] + ([0] * nullbytes) + sub[1])) + addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1]) + if addr_bytes[0] == 0xfc: + # Assume that seeds with fc00::/8 addresses belong to CJDNS, + # not to the publicly unroutable "Unique Local Unicast" network, see + # RFC4193: https://datatracker.ietf.org/doc/html/rfc4193#section-8 + return (BIP155Network.CJDNS, addr_bytes) + else: + return (BIP155Network.IPV6, addr_bytes) else: raise ValueError('Could not parse address %s' % addr) diff --git a/src/init.cpp b/src/init.cpp index d600e54a856f..e0a12d117ff7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -566,6 +566,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-cjdnsreachable", "If set then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-connect=", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1785,6 +1786,14 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc } } + if (!args.IsArgSet("-cjdnsreachable")) { + SetReachable(NET_CJDNS, false); + } + // Now IsReachable(NET_CJDNS) is true if: + // 1. -cjdnsreachable is given and + // 2.1. -onlynet is not given or + // 2.2. -onlynet=cjdns is given + // Check for host lookup allowed before parsing any network related parameters fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); @@ -1806,6 +1815,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); + SetProxy(NET_CJDNS, addrProxy); SetNameProxy(addrProxy); SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later } diff --git a/src/net.cpp b/src/net.cpp index 2e5c297cacb6..baeef8781ca2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -255,9 +255,27 @@ std::optional GetLocalAddrForPeer(CNode *pnode) return std::nullopt; } +/** + * If an IPv6 address belongs to the address range used by the CJDNS network and + * the CJDNS network is reachable (-cjdnsreachable config is set), then change + * the type from NET_IPV6 to NET_CJDNS. + * @param[in] service Address to potentially convert. + * @return a copy of `service` either unmodified or changed to CJDNS. + */ +CService MaybeFlipIPv6toCJDNS(const CService& service) +{ + CService ret{service}; + if (ret.m_net == NET_IPV6 && ret.m_addr[0] == 0xfc && IsReachable(NET_CJDNS)) { + ret.m_net = NET_CJDNS; + } + return ret; +} + // learn a new local address -bool AddLocal(const CService& addr, int nScore) +bool AddLocal(const CService& addr_, int nScore) { + CService addr{MaybeFlipIPv6toCJDNS(addr_)}; + if (!addr.IsRoutable() && Params().RequireRoutableExternalIP()) return false; @@ -454,7 +472,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo if (pszDest) { std::vector resolved; if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { - addrConnect = CAddress(resolved[GetRand(resolved.size())], NODE_NONE); + const CService rnd{resolved[GetRand(resolved.size())]}; + addrConnect = CAddress{MaybeFlipIPv6toCJDNS(rnd), NODE_NONE}; if (!addrConnect.IsValid()) { LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest); return nullptr; @@ -1210,9 +1229,11 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket, CMasternodeSy if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { LogPrintf("Warning: Unknown socket family\n"); + } else { + addr = CAddress{MaybeFlipIPv6toCJDNS(addr), NODE_NONE}; } - const CAddress addr_bind = GetBindAddress(hSocket); + const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(hSocket)), NODE_NONE}; NetPermissionFlags permissionFlags = NetPermissionFlags::None; hListenSocket.AddSocketPermissionFlags(permissionFlags); @@ -3303,7 +3324,10 @@ NodeId CConnman::GetNewNodeId() } -bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) { +bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlags permissions) +{ + const CService addr{MaybeFlipIPv6toCJDNS(addr_)}; + if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) { return false; } diff --git a/src/netaddress.cpp b/src/netaddress.cpp index f09fefba7d65..aaa036f9e29e 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -676,7 +676,7 @@ bool CNetAddr::GetInAddr(struct in_addr* pipv4Addr) const */ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const { - if (!IsIPv6()) { + if (!IsIPv6() && !IsCJDNS()) { return false; } assert(sizeof(*pipv6Addr) == m_addr.size()); @@ -796,8 +796,14 @@ std::vector CNetAddr::GetGroup(const std::vector &asmap) co vchRet.push_back((ipv4 >> 24) & 0xFF); vchRet.push_back((ipv4 >> 16) & 0xFF); return vchRet; - } else if (IsTor() || IsI2P() || IsCJDNS()) { + } else if (IsTor() || IsI2P()) { nBits = 4; + } else if (IsCJDNS()) { + // Treat in the same way as Tor and I2P because the address in all of + // them is "random" bytes (derived from a public key). However in CJDNS + // the first byte is a constant 0xfc, so the random bytes come after it. + // Thus skip the constant 8 bits at the start. + nBits = 12; } else if (IsHeNet()) { // for he.net, use /36 groups nBits = 36; @@ -894,6 +900,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_I2P: return REACH_PRIVATE; default: return REACH_DEFAULT; } + case NET_CJDNS: + switch (ourNet) { + case NET_CJDNS: return REACH_PRIVATE; + default: return REACH_DEFAULT; + } case NET_TEREDO: switch(ourNet) { default: return REACH_DEFAULT; @@ -995,7 +1006,7 @@ bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const paddrin->sin_port = htons(port); return true; } - if (IsIPv6()) { + if (IsIPv6() || IsCJDNS()) { if (*addrlen < (socklen_t)sizeof(struct sockaddr_in6)) return false; *addrlen = sizeof(struct sockaddr_in6); diff --git a/src/netaddress.h b/src/netaddress.h index 7ca6e5730478..00cf1e214799 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -232,7 +232,7 @@ class CNetAddr */ bool IsRelayable() const { - return IsIPv4() || IsIPv6() || IsTor() || IsI2P(); + return IsIPv4() || IsIPv6() || IsTor() || IsI2P() || IsCJDNS(); } /** @@ -581,6 +581,8 @@ class CService : public CNetAddr READWRITEAS(CNetAddr, obj); READWRITE(Using>(obj.port)); } + + friend CService MaybeFlipIPv6toCJDNS(const CService& service); }; bool SanityCheckASMap(const std::vector& asmap); diff --git a/src/netbase.cpp b/src/netbase.cpp index 473c8a5a757d..74ab7b6318d9 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -95,6 +95,9 @@ enum Network ParseNetwork(const std::string& net_in) { if (net == "i2p") { return NET_I2P; } + if (net == "cjdns") { + return NET_CJDNS; + } return NET_UNROUTABLE; } @@ -119,7 +122,7 @@ std::vector GetNetworkNames(bool append_unroutable) std::vector names; for (int n = 0; n < NET_MAX; ++n) { const enum Network network{static_cast(n)}; - if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; + if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue; names.emplace_back(GetNetworkName(network)); } if (append_unroutable) { diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index e9f6d46f48bb..a1a1d4003510 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -585,7 +585,7 @@ static UniValue GetNetworksInfo() UniValue networks(UniValue::VARR); for (int n = 0; n < NET_MAX; ++n) { enum Network network = static_cast(n); - if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; + if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue; proxyType proxy; UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index cae1e3c06730..98aae4744d69 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -441,11 +441,13 @@ BOOST_AUTO_TEST_CASE(netbase_parsenetwork) BOOST_CHECK_EQUAL(ParseNetwork("ipv6"), NET_IPV6); BOOST_CHECK_EQUAL(ParseNetwork("onion"), NET_ONION); BOOST_CHECK_EQUAL(ParseNetwork("tor"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("cjdns"), NET_CJDNS); BOOST_CHECK_EQUAL(ParseNetwork("IPv4"), NET_IPV4); BOOST_CHECK_EQUAL(ParseNetwork("IPv6"), NET_IPV6); BOOST_CHECK_EQUAL(ParseNetwork("ONION"), NET_ONION); BOOST_CHECK_EQUAL(ParseNetwork("TOR"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("CJDNS"), NET_CJDNS); BOOST_CHECK_EQUAL(ParseNetwork(":)"), NET_UNROUTABLE); BOOST_CHECK_EQUAL(ParseNetwork("tÖr"), NET_UNROUTABLE); diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index a04a836c0ea4..5276b8fd3687 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -12,6 +12,7 @@ - `-proxy` (proxy everything) - `-onion` (proxy just onions) - `-proxyrandomize` Circuit randomization + - `-cjdnsreachable` - Proxy configurations to test on proxy side, - support no authentication (other proxy) - support no authentication + user/pass authentication (Tor) @@ -26,6 +27,7 @@ addnode connect to IPv6 addnode connect to onion addnode connect to generic DNS name +addnode connect to a CJDNS address - Test getnetworkinfo for each node """ @@ -50,14 +52,15 @@ NET_IPV6 = "ipv6" NET_ONION = "onion" NET_I2P = "i2p" +NET_CJDNS = "cjdns" # Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo() -NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P}) +NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P, NET_CJDNS}) class ProxyTest(BitcoinTestFramework): def set_test_params(self): - self.num_nodes = 4 + self.num_nodes = 5 self.setup_clean_chain = True def setup_nodes(self): @@ -101,7 +104,9 @@ def setup_nodes(self): ['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}',f'-onion={self.conf2.addr[0]}:{self.conf2.addr[1]}', f'-i2psam={self.i2p_sam[0]}:{self.i2p_sam[1]}', '-i2pacceptincoming=0', '-proxyrandomize=0'], ['-listen', f'-proxy={self.conf2.addr[0]}:{self.conf2.addr[1]}','-proxyrandomize=1'], - [] + [], + ['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}','-proxyrandomize=1', + '-cjdnsreachable'] ] if self.have_ipv6: args[3] = ['-listen', f'-proxy=[{self.conf3.addr[0]}]:{self.conf3.addr[1]}','-proxyrandomize=0', '-noonion'] @@ -113,7 +118,7 @@ def network_test(self, node, addr, network): if peer["addr"] == addr: assert_equal(peer["network"], network) - def node_test(self, node, proxies, auth, test_onion=True): + def node_test(self, node, *, proxies, auth, test_onion, test_cjdns): rv = [] addr = "15.61.23.23:1234" self.log.debug(f"Test: outgoing IPv4 connection through node for address {addr}") @@ -161,6 +166,21 @@ def node_test(self, node, proxies, auth, test_onion=True): rv.append(cmd) self.network_test(node, addr, network=NET_ONION) + if test_cjdns: + addr = "[fc00:1:2:3:4:5:6:7]:8888" + self.log.debug(f"Test: outgoing CJDNS connection through node for address {addr}") + node.addnode(addr, "onetry") + cmd = proxies[1].queue.get() + assert isinstance(cmd, Socks5Command) + assert_equal(cmd.atyp, AddressType.DOMAINNAME) + assert_equal(cmd.addr, b"fc00:1:2:3:4:5:6:7") + assert_equal(cmd.port, 8888) + if not auth: + assert_equal(cmd.username, None) + assert_equal(cmd.password, None) + rv.append(cmd) + self.network_test(node, addr, network=NET_CJDNS) + addr = "node.noumenon:8333" self.log.debug(f"Test: outgoing DNS name connection through node for address {addr}") node.addnode(addr, "onetry") @@ -179,20 +199,33 @@ def node_test(self, node, proxies, auth, test_onion=True): def run_test(self): # basic -proxy - self.node_test(self.nodes[0], [self.serv1, self.serv1, self.serv1, self.serv1], False) + self.node_test(self.nodes[0], + proxies=[self.serv1, self.serv1, self.serv1, self.serv1], + auth=False, test_onion=True, test_cjdns=False) # -proxy plus -onion - self.node_test(self.nodes[1], [self.serv1, self.serv1, self.serv2, self.serv1], False) + self.node_test(self.nodes[1], + proxies=[self.serv1, self.serv1, self.serv2, self.serv1], + auth=False, test_onion=True, test_cjdns=False) # -proxy plus -onion, -proxyrandomize - rv = self.node_test(self.nodes[2], [self.serv2, self.serv2, self.serv2, self.serv2], True) + rv = self.node_test(self.nodes[2], + proxies=[self.serv2, self.serv2, self.serv2, self.serv2], + auth=True, test_onion=True, test_cjdns=False) # Check that credentials as used for -proxyrandomize connections are unique credentials = set((x.username,x.password) for x in rv) assert_equal(len(credentials), len(rv)) if self.have_ipv6: # proxy on IPv6 localhost - self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False) + self.node_test(self.nodes[3], + proxies=[self.serv3, self.serv3, self.serv3, self.serv3], + auth=False, test_onion=False, test_cjdns=False) + + # -proxy=unauth -proxyrandomize=1 -cjdnsreachable + self.node_test(self.nodes[4], + proxies=[self.serv1, self.serv1, self.serv1, self.serv1], + auth=False, test_onion=True, test_cjdns=True) def networks_dict(d): r = {} @@ -214,6 +247,7 @@ def networks_dict(d): assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize) assert_equal(n0['onion']['reachable'], True) assert_equal(n0['i2p']['reachable'], False) + assert_equal(n0['cjdns']['reachable'], False) n1 = networks_dict(self.nodes[1].getnetworkinfo()) assert_equal(NETWORKS, n1.keys()) @@ -240,6 +274,7 @@ def networks_dict(d): assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize) assert_equal(n2['onion']['reachable'], True) assert_equal(n2['i2p']['reachable'], False) + assert_equal(n2['cjdns']['reachable'], False) if self.have_ipv6: n3 = networks_dict(self.nodes[3].getnetworkinfo()) @@ -253,6 +288,22 @@ def networks_dict(d): assert_equal(n3[net]['proxy_randomize_credentials'], False) assert_equal(n3['onion']['reachable'], False) assert_equal(n3['i2p']['reachable'], False) + assert_equal(n3['cjdns']['reachable'], False) + + n4 = networks_dict(self.nodes[4].getnetworkinfo()) + assert_equal(NETWORKS, n4.keys()) + for net in NETWORKS: + if net == NET_I2P: + expected_proxy = '' + expected_randomize = False + else: + expected_proxy = '%s:%i' % (self.conf1.addr) + expected_randomize = True + assert_equal(n4[net]['proxy'], expected_proxy) + assert_equal(n4[net]['proxy_randomize_credentials'], expected_randomize) + assert_equal(n4['onion']['reachable'], True) + assert_equal(n4['i2p']['reachable'], False) + assert_equal(n4['cjdns']['reachable'], True) if __name__ == '__main__': diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 239dae2d3e2e..d7c9ff6dc3a7 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -141,7 +141,7 @@ def run_test(self): network_info = self.nodes[0].getnetworkinfo() cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) - assert_equal(cli_get_info["Proxies"], "127.0.0.1:9050 (ipv4, ipv6, onion), 127.0.0.1:7656 (i2p)") + assert_equal(cli_get_info["Proxies"], "127.0.0.1:9050 (ipv4, ipv6, onion, cjdns), 127.0.0.1:7656 (i2p)") if self.is_wallet_compiled(): self.log.info("Test -getinfo and dash-cli getwalletinfo return expected wallet info") diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index ca77cc429d47..8c8a95f9c23c 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -99,7 +99,7 @@ def test_getpeerinfo(self): assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"]) # Check dynamically generated networks list in getpeerinfo help output. - assert "(ipv4, ipv6, onion, i2p, not_publicly_routable)" in self.nodes[0].help("getpeerinfo") + assert "(ipv4, ipv6, onion, i2p, cjdns, not_publicly_routable)" in self.nodes[0].help("getpeerinfo") # This part is slightly different comparing to the Bitcoin implementation. This is expected because we create connections on network setup a bit differently too. # We also create more connection during the test itself to test mn specific stats assert_equal(peer_info[0][0]['connection_type'], 'inbound') @@ -167,7 +167,7 @@ def test_getnetworkinfo(self): assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"]) # Check dynamically generated networks list in getnetworkinfo help output. - assert "(ipv4, ipv6, onion, i2p)" in self.nodes[0].help("getnetworkinfo") + assert "(ipv4, ipv6, onion, i2p, cjdns)" in self.nodes[0].help("getnetworkinfo") self.log.info('Test extended connections info') # Connect nodes both ways. @@ -252,8 +252,8 @@ def test_getnodeaddresses(self): assert_equal(res[0]["port"], 8333) assert_equal(res[0]["services"], services) - # Test for the absence of onion and I2P addresses. - for network in ["onion", "i2p"]: + # Test for the absence of onion, I2P and CJDNS addresses. + for network in ["onion", "i2p", "cjdns"]: assert_equal(self.nodes[0].getnodeaddresses(0, network), []) # Test invalid arguments. From d52724d0396e59b6f2e82ff03ebf1c81fba45420 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 30 Aug 2021 14:33:29 +0200 Subject: [PATCH 02/10] merge bitcoin#22834: respect -onlynet= when making outbound connections --- doc/i2p.md | 6 +----- doc/release-notes-22834.md | 8 +++++++ doc/tor.md | 6 +----- src/init.cpp | 37 +++++++++++++++++++------------- src/interfaces/node.h | 4 ++-- src/net.cpp | 4 ++-- src/netbase.cpp | 14 ++++++------ src/netbase.h | 16 +++++++------- src/node/interfaces.cpp | 2 +- src/qt/clientmodel.cpp | 2 +- src/qt/optionsdialog.cpp | 4 ++-- src/rpc/net.cpp | 2 +- src/torcontrol.cpp | 19 ++++++++++++++-- test/functional/feature_proxy.py | 2 +- 14 files changed, 74 insertions(+), 52 deletions(-) create mode 100644 doc/release-notes-22834.md diff --git a/doc/i2p.md b/doc/i2p.md index 9ce825ff5f3b..d779054a5667 100644 --- a/doc/i2p.md +++ b/doc/i2p.md @@ -60,11 +60,7 @@ logging` for more information. Make outgoing connections only to I2P addresses. Incoming connections are not affected by this option. It can be specified multiple times to allow multiple -network types, e.g. onlynet=ipv4, onlynet=ipv6, onlynet=onion, onlynet=i2p. - -Warning: if you use -onlynet with values other than onion, and the -onion or --proxy option is set, then outgoing onion connections will still be made; use --noonion or -onion=0 to disable outbound onion connections in this case. +network types, e.g. onlynet=onion, onlynet=i2p. I2P support was added to Dash Core in version 20.0 and there may be fewer I2P peers than Tor or IP ones. Therefore, using I2P alone without other networks may diff --git a/doc/release-notes-22834.md b/doc/release-notes-22834.md new file mode 100644 index 000000000000..23fa5d6a8022 --- /dev/null +++ b/doc/release-notes-22834.md @@ -0,0 +1,8 @@ +Updated settings +---------------- + +- If `-proxy=` is given together with `-noonion` then the provided proxy will + not be set as a proxy for reaching the Tor network. So it will not be + possible to open manual connections to the Tor network for example with the + `addnode` RPC. To mimic the old behavior use `-proxy=` together with + `-onlynet=` listing all relevant networks except `onion`. \ No newline at end of file diff --git a/doc/tor.md b/doc/tor.md index fdfb4c59eb8f..c044e0b278b2 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -51,11 +51,7 @@ outgoing connections, but more is possible. -onlynet=onion Make outgoing connections only to .onion addresses. Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple network types, e.g. - onlynet=ipv4, onlynet=ipv6, onlynet=onion, onlynet=i2p. - Warning: if you use -onlynet with values other than onion, and - the -onion or -proxy option is set, then outgoing onion - connections will still be made; use -noonion or -onion=0 to - disable outbound onion connections in this case. + onlynet=onion, onlynet=i2p. An example how to start the client if the Tor proxy is running on local host on port 9050 and only allows .onion nodes to connect: diff --git a/src/init.cpp b/src/init.cpp index e0a12d117ff7..981dbb072fd4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -584,7 +584,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-onion=", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-onlynet=", "Make outgoing connections only through network (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks. Warning: if it is used with non-onion networks and the -onion or -proxy option is set, then outbound onion connections will still be made; use -noonion or -onion=0 to disable outbound onion connections in this case.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-onlynet=", "Make automatic outgoing connections only through network (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peertimeout=", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1797,27 +1797,27 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc // Check for host lookup allowed before parsing any network related parameters fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); + Proxy onion_proxy; + bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE); // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default std::string proxyArg = args.GetArg("-proxy", ""); - SetReachable(NET_ONION, false); if (proxyArg != "" && proxyArg != "0") { CService proxyAddr; if (!Lookup(proxyArg, proxyAddr, 9050, fNameLookup)) { return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); } - proxyType addrProxy = proxyType(proxyAddr, proxyRandomize); + Proxy addrProxy = Proxy(proxyAddr, proxyRandomize); if (!addrProxy.IsValid()) return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); - SetProxy(NET_ONION, addrProxy); SetProxy(NET_CJDNS, addrProxy); SetNameProxy(addrProxy); - SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later + onion_proxy = addrProxy; } // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses @@ -1826,18 +1826,26 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc std::string onionArg = args.GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 - SetReachable(NET_ONION, false); + onion_proxy = Proxy{}; } else { - CService onionProxy; - if (!Lookup(onionArg, onionProxy, 9050, fNameLookup)) { + CService addr; + if (!Lookup(onionArg, addr, 9050, fNameLookup) || !addr.IsValid()) { return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); } - proxyType addrOnion = proxyType(onionProxy, proxyRandomize); - if (!addrOnion.IsValid()) - return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); - SetProxy(NET_ONION, addrOnion); - SetReachable(NET_ONION, true); + onion_proxy = Proxy{addr, proxyRandomize}; + } + } + + if (onion_proxy.IsValid()) { + SetProxy(NET_ONION, onion_proxy); + } else { + if (args.IsArgSet("-onlynet") && IsReachable(NET_ONION)) { + return InitError( + _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is not provided (no -proxy= and no -onion= given) or " + "it is explicitly forbidden (-onion=0)")); } + SetReachable(NET_ONION, false); } for (const std::string& strAddr : args.GetArgs("-externalip")) { @@ -2539,8 +2547,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) { return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg)); } - SetReachable(NET_I2P, true); - SetProxy(NET_I2P, proxyType{addr}); + SetProxy(NET_I2P, Proxy{addr}); } else { SetReachable(NET_I2P, false); } diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 374e4d0a95d0..46b6f643171a 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -32,7 +32,7 @@ class CNodeStats; class Coin; class RPCTimerInterface; class UniValue; -class proxyType; +class Proxy; struct bilingual_str; enum class SynchronizationState; struct CNodeStateStats; @@ -174,7 +174,7 @@ class Node virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; //! Get proxy. - virtual bool getProxy(Network net, proxyType& proxy_info) = 0; + virtual bool getProxy(Network net, Proxy& proxy_info) = 0; //! Get number of connections. virtual size_t getNodeCount(ConnectionDirection flags) = 0; diff --git a/src/net.cpp b/src/net.cpp index baeef8781ca2..c267e2dbfda0 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -492,7 +492,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo // Connect bool connected = false; std::unique_ptr sock; - proxyType proxy; + Proxy proxy; CAddress addr_bind; assert(!addr_bind.IsValid()); std::unique_ptr i2p_transient_session; @@ -3397,7 +3397,7 @@ bool CConnman::Start(CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_met return false; } - proxyType i2p_sam; + Proxy i2p_sam; if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) { m_i2p_sam_session = std::make_unique(GetDataDir() / "i2p_private_key", i2p_sam.proxy, &interruptNet); diff --git a/src/netbase.cpp b/src/netbase.cpp index 74ab7b6318d9..544f575da03d 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -30,8 +30,8 @@ // Settings static Mutex g_proxyinfo_mutex; -static proxyType proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); -static proxyType nameProxy GUARDED_BY(g_proxyinfo_mutex); +static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); +static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex); int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; bool fNameLookup = DEFAULT_NAME_LOOKUP; @@ -604,7 +604,7 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT return true; } -bool SetProxy(enum Network net, const proxyType &addrProxy) { +bool SetProxy(enum Network net, const Proxy &addrProxy) { assert(net >= 0 && net < NET_MAX); if (!addrProxy.IsValid()) return false; @@ -613,7 +613,7 @@ bool SetProxy(enum Network net, const proxyType &addrProxy) { return true; } -bool GetProxy(enum Network net, proxyType &proxyInfoOut) { +bool GetProxy(enum Network net, Proxy &proxyInfoOut) { assert(net >= 0 && net < NET_MAX); LOCK(g_proxyinfo_mutex); if (!proxyInfo[net].IsValid()) @@ -622,7 +622,7 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) { return true; } -bool SetNameProxy(const proxyType &addrProxy) { +bool SetNameProxy(const Proxy &addrProxy) { if (!addrProxy.IsValid()) return false; LOCK(g_proxyinfo_mutex); @@ -630,7 +630,7 @@ bool SetNameProxy(const proxyType &addrProxy) { return true; } -bool GetNameProxy(proxyType &nameProxyOut) { +bool GetNameProxy(Proxy &nameProxyOut) { LOCK(g_proxyinfo_mutex); if(!nameProxy.IsValid()) return false; @@ -652,7 +652,7 @@ bool IsProxy(const CNetAddr &addr) { return false; } -bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed) +bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed) { // first connect to proxy server if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) { diff --git a/src/netbase.h b/src/netbase.h index f40f0a5b3126..3d98f9cb7328 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -49,11 +49,11 @@ static inline bool operator&(ConnectionDirection a, ConnectionDirection b) { return (underlying(a) & underlying(b)); } -class proxyType +class Proxy { public: - proxyType(): randomize_credentials(false) {} - explicit proxyType(const CService &_proxy, bool _randomize_credentials=false): proxy(_proxy), randomize_credentials(_randomize_credentials) {} + Proxy(): randomize_credentials(false) {} + explicit Proxy(const CService &_proxy, bool _randomize_credentials=false): proxy(_proxy), randomize_credentials(_randomize_credentials) {} bool IsValid() const { return proxy.IsValid(); } @@ -77,8 +77,8 @@ enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); /** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */ std::vector GetNetworkNames(bool append_unroutable = false); -bool SetProxy(enum Network net, const proxyType &addrProxy); -bool GetProxy(enum Network net, proxyType &proxyInfoOut); +bool SetProxy(enum Network net, const Proxy &addrProxy); +bool GetProxy(enum Network net, Proxy &proxyInfoOut); bool IsProxy(const CNetAddr &addr); /** * Set the name proxy to use for all connections to nodes specified by a @@ -96,9 +96,9 @@ bool IsProxy(const CNetAddr &addr); * server in common use (most notably Tor) actually implements UDP * support, and a DNS resolver is beyond the scope of this project. */ -bool SetNameProxy(const proxyType &addrProxy); +bool SetNameProxy(const Proxy &addrProxy); bool HaveNameProxy(); -bool GetNameProxy(proxyType &nameProxyOut); +bool GetNameProxy(Proxy &nameProxyOut); using DNSLookupFn = std::function(const std::string&, bool)>; extern DNSLookupFn g_dns_lookup; @@ -223,7 +223,7 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT * * @returns Whether or not the operation succeeded. */ -bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); +bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); /** Enable non-blocking mode for a socket */ bool SetSocketNonBlocking(const SOCKET& hSocket); diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index b4be01cde2e2..c3f725291bb9 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -341,7 +341,7 @@ class NodeImpl : public Node } bool shutdownRequested() override { return ShutdownRequested(); } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } - bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } + bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(ConnectionDirection flags) override { return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 9bca270f4659..a20453b36060 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -381,7 +381,7 @@ void ClientModel::unsubscribeFromCoreSignals() bool ClientModel::getProxyInfo(std::string& ip_port) const { - proxyType ipv4, ipv6; + Proxy ipv4, ipv6; if (m_node.getProxy((Network) 1, ipv4) && m_node.getProxy((Network) 2, ipv6)) { ip_port = ipv4.proxy.ToStringIPPort(); return true; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index d5dec22d6bf6..1431342697ba 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -485,7 +485,7 @@ void OptionsDialog::updateProxyValidationState() void OptionsDialog::updateDefaultProxyNets() { - proxyType proxy; + Proxy proxy; std::string strProxy; QString strDefaultProxyGUI; @@ -567,7 +567,7 @@ QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) cons Q_UNUSED(pos); // Validate the proxy CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT)); - proxyType addrProxy = proxyType(serv, true); + Proxy addrProxy = Proxy(serv, true); if (addrProxy.IsValid()) return QValidator::Acceptable; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index a1a1d4003510..1143612a045c 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -586,7 +586,7 @@ static UniValue GetNetworksInfo() for (int n = 0; n < NET_MAX; ++n) { enum Network network = static_cast(n); if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue; - proxyType proxy; + Proxy proxy; UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); obj.pushKV("name", GetNetworkName(network)); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 0f98a2f2ed61..4463e1d72889 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -374,9 +374,24 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& // if -onion isn't set to something else. if (gArgs.GetArg("-onion", "") == "") { CService resolved(LookupNumeric("127.0.0.1", 9050)); - proxyType addrOnion = proxyType(resolved, true); + Proxy addrOnion = Proxy(resolved, true); SetProxy(NET_ONION, addrOnion); - SetReachable(NET_ONION, true); + + const auto onlynets = gArgs.GetArgs("-onlynet"); + + const bool onion_allowed_by_onlynet{ + !gArgs.IsArgSet("-onlynet") || + std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) { + return ParseNetwork(n) == NET_ONION; + })}; + + if (onion_allowed_by_onlynet) { + // If NET_ONION is reachable, then the below is a noop. + // + // If NET_ONION is not reachable, then none of -proxy or -onion was given. + // Since we are here, then -torcontrol and -torpassword were given. + SetReachable(NET_ONION, true); + } } // Finally - now create the service diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 5276b8fd3687..d5f3777cad2a 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -280,7 +280,7 @@ def networks_dict(d): n3 = networks_dict(self.nodes[3].getnetworkinfo()) assert_equal(NETWORKS, n3.keys()) for net in NETWORKS: - if net == NET_I2P: + if net == NET_I2P or net == NET_ONION: expected_proxy = '' else: expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}' From 5436b6a82d02e60f3eac900d95b2d8f22243ee90 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 20 May 2024 19:50:15 +0000 Subject: [PATCH 03/10] merge bitcoin#24165: extend inbound eviction protection by network to CJDNS peers --- src/net.cpp | 12 +- src/net.h | 2 + src/test/net_peer_eviction_tests.cpp | 163 ++++++++++++++++++++++++--- 3 files changed, 156 insertions(+), 21 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index c267e2dbfda0..7b77dd93279c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1026,17 +1026,17 @@ void ProtectEvictionCandidatesByRatio(std::vector& evicti // Protect the half of the remaining nodes which have been connected the longest. // This replicates the non-eviction implicit behavior, and precludes attacks that start later. // To favorise the diversity of our peer connections, reserve up to half of these protected - // spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall. - // This helps protect these higher-latency peers that tend to be otherwise + // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime + // overall. This helps protect these higher-latency peers that tend to be otherwise // disadvantaged under our eviction criteria. const size_t initial_size = eviction_candidates.size(); const size_t total_protect_size{initial_size / 2}; - // Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier - // array members have first opportunity to recover unused slots from the previous iteration. + // Disadvantaged networks to protect. In the case of equal counts, earlier array members + // have the first opportunity to recover unused slots from the previous iteration. struct Net { bool is_local; Network id; size_t count; }; - std::array networks{ - {{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}}; + std::array networks{ + {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}}; // Count and store the number of eviction candidates per network. for (Net& n : networks) { diff --git a/src/net.h b/src/net.h index b6f510f05c54..ca1c9adf9028 100644 --- a/src/net.h +++ b/src/net.h @@ -1661,6 +1661,8 @@ size_t GetRequestedObjectCount(NodeId nodeId) EXCLUSIVE_LOCKS_REQUIRED(cs_main); * * - I2P peers * + * - CJDNS peers + * * This helps protect these privacy network peers, which tend to be otherwise * disadvantaged under our eviction criteria for their higher min ping times * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 602d8635879d..4957969d234d 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) // Test protection of onion, localhost, and I2P peers... // Expect 1/4 onion peers to be protected from eviction, - // if no localhost or I2P peers. + // if no localhost, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = false; @@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 onion peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest nTimeConnected), if no localhost or I2P peers. + // sorted by longest uptime (lowest nTimeConnected), if no localhost, I2P or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 localhost peers to be protected from eviction, - // if no onion or I2P peers. + // if no onion, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); @@ -146,7 +146,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 localhost peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest nTimeConnected), if no onion or I2P peers. + // sorted by longest uptime (lowest nTimeConnected), if no onion, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; @@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 I2P peers to be protected from eviction, - // if no onion or localhost peers. + // if no onion, localhost, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = false; @@ -168,8 +168,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /* unprotected_peer_ids */ {}, random_context)); - // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest nTimeConnected), if no onion or localhost peers. + // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted + // by longest uptime (lowest nTimeConnected), if no onion, localhost, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; @@ -180,6 +180,29 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11}, random_context)); + // Expect 1/4 CJDNS peers to be protected from eviction, + // if no onion, localhost, or I2P peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_local = false; + c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4; + }, + /*protected_peer_ids=*/{2, 7, 10}, + /*unprotected_peer_ids=*/{}, + random_context)); + + // Expect 1/4 CJDNS peers and 1/4 of the other peers to be protected, sorted + // by longest uptime (lowest nTimeConnected), if no onion, localhost, or I2P peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = false; + c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6; + }, + /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10}, + /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, + random_context)); + // Tests with 2 networks... // Combined test: expect having 1 localhost and 1 onion peer out of 4 to @@ -311,16 +334,16 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( 4, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_local = (c.id == 3); - if (c.id == 4) { + c.m_is_local = (c.id == 2); + if (c.id == 3) { c.m_network = NET_I2P; - } else if (c.id == 2) { + } else if (c.id == 1) { c.m_network = NET_ONION; } else { c.m_network = NET_IPV6; } }, - /* protected_peer_ids */ {0, 4}, + /* protected_peer_ids */ {0, 3}, /* unprotected_peer_ids */ {1, 2}, random_context)); @@ -438,15 +461,15 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23}, random_context)); - // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of - // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted - // by longest uptime. + // Combined test: expect having 8 localhost, 4 CJDNS, and 3 onion peers out + // of 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, + // sorted by longest uptime. BOOST_CHECK(IsProtected( 24, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; c.m_is_local = (c.id > 15); if (c.id > 10 && c.id < 15) { - c.m_network = NET_I2P; + c.m_network = NET_CJDNS; } else if (c.id > 6 && c.id < 10) { c.m_network = NET_ONION; } else { @@ -456,6 +479,116 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17}, /* unprotected_peer_ids */ {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, random_context)); + + // Tests with 4 networks... + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 5 to protect 1 CJDNS, 0 I2P, 0 localhost, 0 onion and 1 other peer + // (2 total), sorted by longest uptime; stable sort breaks tie with array + // order of CJDNS first. + BOOST_CHECK(IsProtected( + 5, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 3); + if (c.id == 4) { + c.m_network = NET_CJDNS; + } else if (c.id == 1) { + c.m_network = NET_I2P; + } else if (c.id == 2) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2, 3}, + random_context)); + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 7 to protect 1 CJDNS, 0, I2P, 0 localhost, 0 onion and 2 other + // peers (3 total) sorted by longest uptime; stable sort breaks tie with + // array order of CJDNS first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 4); + if (c.id == 6) { + c.m_network = NET_CJDNS; + } else if (c.id == 5) { + c.m_network = NET_I2P; + } else if (c.id == 3) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /*protected_peer_ids=*/{0, 1, 6}, + /*unprotected_peer_ids=*/{2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 8 to protect 1 CJDNS, 1 I2P, 0 localhost, 0 onion and 2 other + // peers (4 total) sorted by longest uptime; stable sort breaks tie with + // array order of CJDNS first. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 3); + if (c.id == 5) { + c.m_network = NET_CJDNS; + } else if (c.id == 6) { + c.m_network = NET_I2P; + } else if (c.id == 3) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /*protected_peer_ids=*/{0, 1, 5, 6}, + /*unprotected_peer_ids=*/{2, 3, 4, 7}, + random_context)); + + // Combined test: expect having 2 CJDNS, 2 I2P, 4 localhost, and 2 onion + // peers out of 16 to protect 1 CJDNS, 1 I2P, 1 localhost, 1 onion (4/16 + // total), plus 4 others for 8 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id > 5); + if (c.id == 11 || c.id == 15) { + c.m_network = NET_CJDNS; + } else if (c.id == 10 || c.id == 14) { + c.m_network = NET_I2P; + } else if (c.id == 8 || c.id == 9) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11}, + /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 6 CJDNS, 1 I2P, 1 localhost, and 4 onion + // peers out of 24 to protect 2 CJDNS, 1 I2P, 1 localhost, and 2 onions (6 + // total), plus 6 others for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 13); + if (c.id > 17) { + c.m_network = NET_CJDNS; + } else if (c.id == 17) { + c.m_network = NET_I2P; + } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19}, + /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23}, + random_context)); } // Returns true if any of the node ids in node_ids are selected for eviction. From 554bd24186a50e58ae7f08b45291400e521def57 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 1 Mar 2022 21:13:22 +0100 Subject: [PATCH 04/10] partial bitcoin#24468: improve -onlynet help and related tor/i2p documentation includes: - a1db99adea36dbee1ec97ca1851edad12137feea (excl. changes to doc/ p2p-bad-ports.md) --- doc/i2p.md | 6 +++--- doc/tor.md | 8 ++++---- src/init.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/i2p.md b/doc/i2p.md index d779054a5667..3056427bcac9 100644 --- a/doc/i2p.md +++ b/doc/i2p.md @@ -58,9 +58,9 @@ logging` for more information. -onlynet=i2p ``` -Make outgoing connections only to I2P addresses. Incoming connections are not -affected by this option. It can be specified multiple times to allow multiple -network types, e.g. onlynet=onion, onlynet=i2p. +Make automatic outbound connections only to I2P addresses. Inbound and manual +connections are not affected by this option. It can be specified multiple times +to allow multiple networks, e.g. onlynet=onion, onlynet=i2p. I2P support was added to Dash Core in version 20.0 and there may be fewer I2P peers than Tor or IP ones. Therefore, using I2P alone without other networks may diff --git a/doc/tor.md b/doc/tor.md index c044e0b278b2..5411036029da 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -48,10 +48,10 @@ outgoing connections, but more is possible. -seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with other P2P nodes. - -onlynet=onion Make outgoing connections only to .onion addresses. Incoming - connections are not affected by this option. This option can be - specified multiple times to allow multiple network types, e.g. - onlynet=onion, onlynet=i2p. + -onlynet=onion Make automatic outbound connections only to .onion addresses. + Inbound and manual connections are not affected by this option. + It can be specified multiple times to allow multiple networks, + e.g. onlynet=onion, onlynet=i2p. An example how to start the client if the Tor proxy is running on local host on port 9050 and only allows .onion nodes to connect: diff --git a/src/init.cpp b/src/init.cpp index 981dbb072fd4..b65e7b329db1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -584,7 +584,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-onion=", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-onlynet=", "Make automatic outgoing connections only through network (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-onlynet=", "Make automatic outbound connections only to network (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peertimeout=", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); From c736ebf56687b625ff267bed8c1ef4b22759649e Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 21 May 2024 08:37:29 +0000 Subject: [PATCH 05/10] merge bitcoin#24555: create initial doc/cjdns.md for CJDNS how-to documentation --- doc/cjdns.md | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/i2p.md | 10 +++--- doc/tor.md | 11 +++--- 3 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 doc/cjdns.md diff --git a/doc/cjdns.md b/doc/cjdns.md new file mode 100644 index 000000000000..32ea9f9c8af6 --- /dev/null +++ b/doc/cjdns.md @@ -0,0 +1,95 @@ +# CJDNS support in Dash Core + +It is possible to run Dash Core over CJDNS, an encrypted IPv6 network that +uses public-key cryptography for address allocation and a distributed hash table +for routing. + +## What is CJDNS? + +CJDNS is like a distributed, shared VPN with multiple entry points where every +participant can reach any other participant. All participants use addresses from +the `fc00::/8` network (reserved IPv6 range). Installation and configuration is +done outside of Dash Core, similarly to a VPN (either in the host/OS or on +the network router). + +Compared to IPv4/IPv6, CJDNS provides end-to-end encryption and protects nodes +from traffic analysis and filtering. + +Used with Tor and I2P, CJDNS is a complementary option that can enhance network +redundancy and robustness for both the Dash network and individual nodes. + +Each network has different characteristics. For instance, Tor is widely used but +somewhat centralized. I2P connections have a source address and I2P is slow. +CJDNS is fast but does not hide the sender and the recipient from intermediate +routers. + +## Installing CJDNS and connecting to the network + +To install and set up CJDNS, follow the instructions at +https://github.com/cjdelisle/cjdns#cjdns. + +Don't skip steps +["2. Find a friend"](https://github.com/cjdelisle/cjdns#2-find-a-friend) and +["3. Connect your node to your friend's +node"](https://github.com/cjdelisle/cjdns#3-connect-your-node-to-your-friends-node). +You need to be connected to the CJDNS network before it will work with your +Dash Core node. + +Typically, CJDNS might be launched from its directory with +`sudo ./cjdroute < cjdroute.conf` and it sheds permissions after setting up the +[TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface. You may also [launch it as an +unprivileged user](https://github.com/cjdelisle/cjdns/blob/master/doc/non-root-user.md) +with some additional setup. + +The network connection can be checked by running `./tools/peerStats` from the +CJDNS directory. + +## Run Dash Core with CJDNS + +Once you are connected to the CJDNS network, the following Dash Core +configuration option makes CJDNS peers automatically reachable: + +``` +-cjdnsreachable +``` + +When enabled, this option tells Dash Core that it is running in an +environment where a connection to an `fc00::/8` address will be to the CJDNS +network instead of to an [RFC4193](https://datatracker.ietf.org/doc/html/rfc4193) +IPv6 local network. This helps Dash Core perform better address management: + - Your node can consider incoming `fc00::/8` connections to be from the CJDNS + network rather than from an IPv6 private one. + - If one of your node's local addresses is `fc00::/8`, then it can choose to + gossip that address to peers. + +## Additional configuration options related to CJDNS + +``` +-onlynet=cjdns +``` + +Make automatic outbound connections only to CJDNS addresses. Inbound and manual +connections are not affected by this option. It can be specified multiple times +to allow multiple networks, e.g. onlynet=cjdns, onlynet=i2p, onlynet=onion. + +CJDNS support was added to Dash Core in version 21.0 and there may be fewer +CJDNS peers than Tor or IP ones. You can use `dash-cli -addrinfo` to see the +number of CJDNS addresses known to your node. + +In general, a node can be run with both an onion service and CJDNS (or any/all +of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if one of +the networks has issues. There are a number of ways to configure this; see +[doc/tor.md](https://github.com/dashpay/dash/blob/master/doc/tor.md) for +details. + +## CJDNS-related information in Dash Core + +There are several ways to see your CJDNS address in Dash Core: +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` + +To see which CJDNS peers your node is connected to, use `dash-cli -netinfo 4` +or the `getpeerinfo` RPC (i.e. `dash-cli getpeerinfo`). + +To see which CJDNS addresses your node knows, use the `getnodeaddresses 0 cjdns` +RPC. diff --git a/doc/i2p.md b/doc/i2p.md index 3056427bcac9..6e9dfd15d274 100644 --- a/doc/i2p.md +++ b/doc/i2p.md @@ -73,8 +73,8 @@ phase when syncing up a new node can be very slow. This phase can be sped up by using other networks, for instance `onlynet=onion`, at the same time. In general, a node can be run with both onion and I2P hidden services (or -any/all of IPv4/IPv6/onion/I2P), which can provide a potential fallback if one -of the networks has issues. +any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if +one of the networks has issues. ## Persistent vs transient I2P addresses @@ -102,9 +102,9 @@ listening should only be turned off if really needed. There are several ways to see your I2P address in Dash Core if accepting incoming I2P connections (`-i2pacceptincoming`): -- in the debug log (grep for `AddLocal`, the I2P address ends in `.b32.i2p`) -- in the output of the `getnetworkinfo` RPC in the "localaddresses" section -- in the output of `dash-cli -netinfo` peer connections dashboard +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` +- in the debug log (grep for `AddLocal`; the I2P address ends in `.b32.i2p`) To see which I2P peers your node is connected to, use `dash-cli -netinfo 4` or the `getpeerinfo` RPC (e.g. `dash-cli getpeerinfo`). diff --git a/doc/tor.md b/doc/tor.md index 5411036029da..ba45f60d9ee3 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -11,9 +11,9 @@ for how to properly configure Tor. ## How to see information about your Tor configuration via Dash Core There are several ways to see your local onion address in Dash Core: -- in the debug log (grep for "tor:" or "AddLocal") -- in the output of RPC `getnetworkinfo` in the "localaddresses" section -- in the output of the CLI `-netinfo` peer connections dashboard +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` +- in the debug log (grep for "AddLocal"; the Tor address ends in `.onion`) You may set the `-debug=tor` config logging option to have additional information in the debug log about your Tor configuration. @@ -22,6 +22,9 @@ CLI `-addrinfo` returns the number of addresses known to your node per network. This can be useful to see how many onion peers your node knows, e.g. for `-onlynet=onion`. +To fetch a number of onion addresses that your node knows, for example seven +addresses, use the `getnodeaddresses 7 onion` RPC. + ## 1. Run Dash Core behind a Tor proxy The first step is running Dash Core behind a Tor proxy. This will already anonymize all @@ -51,7 +54,7 @@ outgoing connections, but more is possible. -onlynet=onion Make automatic outbound connections only to .onion addresses. Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks, - e.g. onlynet=onion, onlynet=i2p. + e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns. An example how to start the client if the Tor proxy is running on local host on port 9050 and only allows .onion nodes to connect: From 7cb74798294e3f12ddfafadfa041333f76ce7263 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:12:32 +0100 Subject: [PATCH 06/10] merge bitcoin#24663: add links to doc/cjdns.md --- doc/README.md | 1 + src/init.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index a24300151829..c3a6d7e99e65 100644 --- a/doc/README.md +++ b/doc/README.md @@ -69,6 +69,7 @@ The Dash Core repo's [root README](/README.md) contains relevant information on ### Miscellaneous - [Assets Attribution](assets-attribution.md) - [dash.conf Configuration File](dash-conf.md) +- [CJDNS Support](cjdns.md) - [Files](files.md) - [Fuzz-testing](fuzzing.md) - [I2P Support](i2p.md) diff --git a/src/init.cpp b/src/init.cpp index b65e7b329db1..8415ceb9a801 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -566,7 +566,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); - argsman.AddArg("-cjdnsreachable", "If set then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-connect=", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); From fb1416f7cb749fd42cd751e8a7c660567fd4e542 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:46:25 +0100 Subject: [PATCH 07/10] merge bitcoin#24205: improve network reachability test coverage and safety --- src/init.cpp | 1 + src/test/net_tests.cpp | 5 ++++ test/functional/feature_proxy.py | 40 ++++++++++++++++++++++++++++---- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 8415ceb9a801..2c3b41f161eb 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1781,6 +1781,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc } for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; + assert(IsReachable(net)); if (!nets.count(net)) SetReachable(net, false); } diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 09f2000d0ff1..ca43f733277b 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -765,26 +765,31 @@ BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) BOOST_CHECK(IsReachable(NET_IPV6)); BOOST_CHECK(IsReachable(NET_ONION)); BOOST_CHECK(IsReachable(NET_I2P)); + BOOST_CHECK(IsReachable(NET_CJDNS)); SetReachable(NET_IPV4, false); SetReachable(NET_IPV6, false); SetReachable(NET_ONION, false); SetReachable(NET_I2P, false); + SetReachable(NET_CJDNS, false); BOOST_CHECK(!IsReachable(NET_IPV4)); BOOST_CHECK(!IsReachable(NET_IPV6)); BOOST_CHECK(!IsReachable(NET_ONION)); BOOST_CHECK(!IsReachable(NET_I2P)); + BOOST_CHECK(!IsReachable(NET_CJDNS)); SetReachable(NET_IPV4, true); SetReachable(NET_IPV6, true); SetReachable(NET_ONION, true); SetReachable(NET_I2P, true); + SetReachable(NET_CJDNS, true); BOOST_CHECK(IsReachable(NET_IPV4)); BOOST_CHECK(IsReachable(NET_IPV6)); BOOST_CHECK(IsReachable(NET_ONION)); BOOST_CHECK(IsReachable(NET_I2P)); + BOOST_CHECK(IsReachable(NET_CJDNS)); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index d5f3777cad2a..7d6dc7ff2202 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -30,6 +30,11 @@ addnode connect to a CJDNS address - Test getnetworkinfo for each node + +- Test passing invalid -proxy +- Test passing invalid -onion +- Test passing -onlynet=onion without -proxy or -onion +- Test passing -onlynet=onion with -onion=0 and with -noonion """ import socket @@ -263,12 +268,13 @@ def networks_dict(d): n2 = networks_dict(self.nodes[2].getnetworkinfo()) assert_equal(NETWORKS, n2.keys()) + proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}' for net in NETWORKS: if net == NET_I2P: expected_proxy = '' expected_randomize = False else: - expected_proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}' + expected_proxy = proxy expected_randomize = True assert_equal(n2[net]['proxy'], expected_proxy) assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize) @@ -279,11 +285,9 @@ def networks_dict(d): if self.have_ipv6: n3 = networks_dict(self.nodes[3].getnetworkinfo()) assert_equal(NETWORKS, n3.keys()) + proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}' for net in NETWORKS: - if net == NET_I2P or net == NET_ONION: - expected_proxy = '' - else: - expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}' + expected_proxy = '' if net == NET_I2P or net == NET_ONION else proxy assert_equal(n3[net]['proxy'], expected_proxy) assert_equal(n3[net]['proxy_randomize_credentials'], False) assert_equal(n3['onion']['reachable'], False) @@ -305,6 +309,32 @@ def networks_dict(d): assert_equal(n4['i2p']['reachable'], False) assert_equal(n4['cjdns']['reachable'], True) + self.stop_node(1) + + self.log.info("Test passing invalid -proxy raises expected init error") + self.nodes[1].extra_args = ["-proxy=abc:def"] + msg = "Error: Invalid -proxy address or hostname: 'abc:def'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -onion raises expected init error") + self.nodes[1].extra_args = ["-onion=xyz:abc"] + msg = "Error: Invalid -onion address or hostname: 'xyz:abc'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + msg = ( + "Error: Outbound connections restricted to Tor (-onlynet=onion) but " + "the proxy for reaching the Tor network is not provided (no -proxy= " + "and no -onion= given) or it is explicitly forbidden (-onion=0)" + ) + self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error") + self.nodes[1].extra_args = ["-onlynet=onion"] + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error") + for arg in ["-onion=0", "-noonion"]: + self.nodes[1].extra_args = ["-onlynet=onion", arg] + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + if __name__ == '__main__': ProxyTest().main() From 77efd3611283838574f9852ccabb1e8cec3e57b8 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 26 Mar 2022 21:50:34 -0300 Subject: [PATCH 08/10] merge bitcoin#24687: Check an invalid -i2psam will raise an init error --- test/functional/feature_proxy.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 7d6dc7ff2202..06521791d83d 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -33,6 +33,7 @@ - Test passing invalid -proxy - Test passing invalid -onion +- Test passing invalid -i2psam - Test passing -onlynet=onion without -proxy or -onion - Test passing -onlynet=onion with -onion=0 and with -noonion """ @@ -321,6 +322,11 @@ def networks_dict(d): msg = "Error: Invalid -onion address or hostname: 'xyz:abc'" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + self.log.info("Test passing invalid -i2psam raises expected init error") + self.nodes[1].extra_args = ["-i2psam=def:xyz"] + msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + msg = ( "Error: Outbound connections restricted to Tor (-onlynet=onion) but " "the proxy for reaching the Tor network is not provided (no -proxy= " From e67ed92d3d8bd864070feeb65848e5dac66035d5 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 19 May 2022 10:42:56 -0300 Subject: [PATCH 09/10] merge bitcoin#25173: add coverage for unknown network in -onlynet --- test/functional/feature_proxy.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 06521791d83d..dc3f6d123de7 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -36,6 +36,7 @@ - Test passing invalid -i2psam - Test passing -onlynet=onion without -proxy or -onion - Test passing -onlynet=onion with -onion=0 and with -noonion +- Test passing unknown -onlynet """ import socket @@ -341,6 +342,11 @@ def networks_dict(d): self.nodes[1].extra_args = ["-onlynet=onion", arg] self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + self.log.info("Test passing unknown network to -onlynet raises expected init error") + self.nodes[1].extra_args = ["-onlynet=abc"] + msg = "Error: Unknown network specified in -onlynet: 'abc'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + if __name__ == '__main__': ProxyTest().main() From 32f8fda7d6fa9eaa76a7a9f5a19d38e87e2f8937 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Apr 2022 10:45:10 +0200 Subject: [PATCH 10/10] merge bitcoin#24991: allow startup with -onlynet=onion -listenonion=1 --- src/init.cpp | 16 +++++++++++++--- test/functional/feature_proxy.py | 21 ++++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 2c3b41f161eb..f3776a958c77 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1821,6 +1821,8 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc onion_proxy = addrProxy; } + const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && IsReachable(NET_ONION)}; + // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses // -noonion (or -onion=0) disables connecting to .onion entirely // An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none) @@ -1828,6 +1830,11 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 onion_proxy = Proxy{}; + if (onlynet_used_with_onion) { + return InitError( + _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is explicitly forbidden: -onion=0")); + } } else { CService addr; if (!Lookup(onionArg, addr, 9050, fNameLookup) || !addr.IsValid()) { @@ -1840,11 +1847,14 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc if (onion_proxy.IsValid()) { SetProxy(NET_ONION, onion_proxy); } else { - if (args.IsArgSet("-onlynet") && IsReachable(NET_ONION)) { + // If -listenonion is set, then we will (try to) connect to the Tor control port + // later from the torcontrol thread and may retrieve the onion proxy from there. + const bool listenonion_disabled{!args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)}; + if (onlynet_used_with_onion && listenonion_disabled) { return InitError( _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " - "reaching the Tor network is not provided (no -proxy= and no -onion= given) or " - "it is explicitly forbidden (-onion=0)")); + "reaching the Tor network is not provided: none of -proxy, -onion or " + "-listenonion is given")); } SetReachable(NET_ONION, false); } diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index dc3f6d123de7..cd531466abe8 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -328,20 +328,27 @@ def networks_dict(d): msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error") msg = ( "Error: Outbound connections restricted to Tor (-onlynet=onion) but " - "the proxy for reaching the Tor network is not provided (no -proxy= " - "and no -onion= given) or it is explicitly forbidden (-onion=0)" + "the proxy for reaching the Tor network is explicitly forbidden: -onion=0" ) - self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error") - self.nodes[1].extra_args = ["-onlynet=onion"] - self.nodes[1].assert_start_raises_init_error(expected_msg=msg) - - self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error") for arg in ["-onion=0", "-noonion"]: self.nodes[1].extra_args = ["-onlynet=onion", arg] self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + self.log.info("Test passing -onlynet=onion without -proxy, -onion or -listenonion raises expected init error") + self.nodes[1].extra_args = ["-onlynet=onion", "-listenonion=0"] + msg = ( + "Error: Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given" + ) + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing -onlynet=onion without -proxy or -onion but with -listenonion=1 is ok") + self.start_node(1, extra_args=["-onlynet=onion", "-listenonion=1"]) + self.stop_node(1) + self.log.info("Test passing unknown network to -onlynet raises expected init error") self.nodes[1].extra_args = ["-onlynet=abc"] msg = "Error: Unknown network specified in -onlynet: 'abc'"