From aedd02ef2750329019d5698b14b17d67c5a563ad Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Thu, 9 Sep 2021 18:19:17 +0200 Subject: [PATCH 01/13] net: make it possible to connect to CJDNS addresses Connecting to CJDNS addresses works without a proxy, just like connecting to an IPv6 address. Thus adapt `CService::GetSockAddr()` to retrieve the `struct sockaddr*` even for `CService::IsCJDNS()` objects. --- src/netaddress.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/netaddress.cpp b/src/netaddress.cpp index f9fff5a6d5..c7ec2b8659 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -663,7 +663,7 @@ bool CNetAddr::GetInAddr(struct in_addr* pipv4Addr) const } /** - * Try to get our IPv6 address. + * Try to get our IPv6 (or CJDNS) address. * * @param[out] pipv6Addr The in6_addr struct to which to copy. * @@ -674,7 +674,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()); @@ -993,7 +993,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); From de01e312b333b65b09c8dc72f0cea6295ab8e43f Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Tue, 21 Sep 2021 12:26:51 +0200 Subject: [PATCH 02/13] net: use -proxy for connecting to the CJDNS network If `-proxy` is given, then also use it for connecting to the CJDNS network. --- src/init.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/init.cpp b/src/init.cpp index e554a18fbf..b5b647e7c1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1315,6 +1315,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) 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 } From 78f456c57677e6a3a839426e211078ddf0b3e194 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Wed, 22 Sep 2021 17:06:31 +0200 Subject: [PATCH 03/13] net: recognize CJDNS from ParseNetwork() This allows to use "cjdns" as an argument to the `getnodeaddresses` RPC and to the `-onlynet=` parameter. --- src/netbase.cpp | 3 +++ src/test/netbase_tests.cpp | 2 ++ test/functional/rpc_net.py | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/netbase.cpp b/src/netbase.cpp index 64d17189a6..56f124579a 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -96,6 +96,9 @@ enum Network ParseNetwork(const std::string& net_in) { if (net == "i2p") { return NET_I2P; } + if (net == "cjdns") { + return NET_CJDNS; + } return NET_UNROUTABLE; } diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 687d2f6747..b6d7496cc7 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -339,11 +339,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/rpc_net.py b/test/functional/rpc_net.py index 0f3bbce54c..103c064cf0 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -228,8 +228,8 @@ class NetTest(BitcoinTestFramework): assert_equal(res[0]["port"], 8333) assert_equal(res[0]["services"], P2P_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 e9d90d3c11cee8ea70056f69afaa548cee898f40 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Thu, 9 Sep 2021 17:40:11 +0200 Subject: [PATCH 04/13] net: introduce a new config option to enable CJDNS CJDNS is set up in the host OS, outside of the application. When the routing is configured properly then connecting to fc00::/8 results in connecting to the CJDNS network. Introduce an option so that Bitcoin Core knows whether this is the case. --- src/init.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/init.cpp b/src/init.cpp index b5b647e7c1..b0335183d6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -422,6 +422,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-asmap=", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), 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, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), signetBaseParams->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); @@ -1294,6 +1295,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } + 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); From e6890fcb440245c9a24ded0b7af46267453433f1 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Tue, 21 Sep 2021 17:03:47 +0200 Subject: [PATCH 05/13] net: don't skip CJDNS from GetNetworksInfo() --- src/rpc/net.cpp | 2 +- test/functional/feature_proxy.py | 6 +++++- test/functional/interface_bitcoin_cli.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index a9bee33c5d..e33f1ce4a3 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -566,7 +566,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/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 2fb5e328f5..50e2233342 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -50,9 +50,10 @@ NET_IPV4 = "ipv4" 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): @@ -214,6 +215,7 @@ class ProxyTest(BitcoinTestFramework): 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 +242,7 @@ class ProxyTest(BitcoinTestFramework): 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 +256,7 @@ class ProxyTest(BitcoinTestFramework): 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) if __name__ == '__main__': diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index c28186cde7..ae665958b9 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -136,7 +136,7 @@ class TestBitcoinCli(BitcoinTestFramework): 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 bitcoin-cli getwalletinfo return expected wallet info") From 6387f397b323b0fb4ca303fe418550f5465147c6 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 13 Sep 2021 13:02:05 +0200 Subject: [PATCH 06/13] net: recognize CJDNS addresses as such In some cases addresses come from an external source as a string or as a `struct sockaddr_in6`, without a tag to tell whether it is a private IPv6 or a CJDNS address. In those cases interpret the address as a CJDNS address instead of an IPv6 address if `-cjdnsreachable` is set and the seemingly-IPv6-address belongs to `fc00::/8`. Those external sources are: * `-externalip=` * `-bind=` * UPnP * `getifaddrs(3)` (called through `-discover`) * `addnode` * `connect` * incoming connections (returned by `accept(2)`) --- src/net.cpp | 34 +++++++++++++++++++++++++++++----- src/netaddress.h | 1 + 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 8cf59f0b0d..82e55d4189 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -230,9 +230,27 @@ std::optional GetLocalAddrForPeer(CNode *pnode) return std::nullopt; } -// learn a new local address -bool AddLocal(const CService& addr, int nScore) +/** + * 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) +{ + CService addr{MaybeFlipIPv6toCJDNS(addr_)}; + if (!addr.IsRoutable()) return false; @@ -409,7 +427,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; @@ -1092,9 +1111,11 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { 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); @@ -2460,7 +2481,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.h b/src/netaddress.h index 57eb8bc72f..f074c1d3ec 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -550,6 +550,7 @@ public: } friend class CServiceHash; + friend CService MaybeFlipIPv6toCJDNS(const CService& service); }; class CServiceHash From 508eb258fd569cabda6fe15699f911fd627e0c56 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Tue, 21 Sep 2021 12:12:50 +0200 Subject: [PATCH 07/13] test: remove default argument of feature_proxy.py:node_test() The default bool argument makes it harder to read because the last but one argument is also bool. Pass all of them as named arguments to increase readability. Another bool argument will be added to indicate whether to test CJDNS. Co-authored-by: Jon Atack --- test/functional/feature_proxy.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 50e2233342..b8ba039999 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -114,7 +114,7 @@ class ProxyTest(BitcoinTestFramework): 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): rv = [] addr = "15.61.23.23:1234" self.log.debug(f"Test: outgoing IPv4 connection through node for address {addr}") @@ -180,20 +180,28 @@ class ProxyTest(BitcoinTestFramework): 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) # -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) # -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) # 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) def networks_dict(d): r = {} From 9b43b3b257a00f777538fcc6e2550702055a1488 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Tue, 21 Sep 2021 16:56:13 +0200 Subject: [PATCH 08/13] test: extend feature_proxy.py to test CJDNS --- test/functional/feature_proxy.py | 53 +++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index b8ba039999..70b9e019c1 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -12,6 +12,7 @@ Test plan: - `-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 IPv4 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 """ @@ -58,7 +60,7 @@ 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): @@ -102,7 +104,9 @@ class ProxyTest(BitcoinTestFramework): ['-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'] @@ -114,7 +118,7 @@ class ProxyTest(BitcoinTestFramework): if peer["addr"] == addr: assert_equal(peer["network"], network) - def node_test(self, node, *, proxies, auth, test_onion): + 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}") @@ -162,6 +166,21 @@ class ProxyTest(BitcoinTestFramework): 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") @@ -182,17 +201,17 @@ class ProxyTest(BitcoinTestFramework): # basic -proxy self.node_test(self.nodes[0], proxies=[self.serv1, self.serv1, self.serv1, self.serv1], - auth=False, test_onion=True) + auth=False, test_onion=True, test_cjdns=False) # -proxy plus -onion self.node_test(self.nodes[1], proxies=[self.serv1, self.serv1, self.serv2, self.serv1], - auth=False, test_onion=True) + auth=False, test_onion=True, test_cjdns=False) # -proxy plus -onion, -proxyrandomize rv = self.node_test(self.nodes[2], proxies=[self.serv2, self.serv2, self.serv2, self.serv2], - auth=True, test_onion=True) + 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)) @@ -201,7 +220,12 @@ class ProxyTest(BitcoinTestFramework): # proxy on IPv6 localhost self.node_test(self.nodes[3], proxies=[self.serv3, self.serv3, self.serv3, self.serv3], - auth=False, test_onion=False) + 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 = {} @@ -266,6 +290,21 @@ class ProxyTest(BitcoinTestFramework): 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__': ProxyTest().main() From c2d751abbae3811adaf856b1dd1b71b33e54d315 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Fri, 10 Sep 2021 10:28:10 +0200 Subject: [PATCH 09/13] net: take CJDNS into account in CNetAddr::GetReachabilityFrom() This way `GetLocal()` will pick our CJDNS address for a CJDNS peer. --- src/netaddress.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/netaddress.cpp b/src/netaddress.cpp index c7ec2b8659..e87a27979d 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -892,6 +892,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; From d96f8d304c872b21070245c1b6aacc8b1f5da697 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Thu, 9 Sep 2021 15:55:45 +0200 Subject: [PATCH 10/13] net: don't skip CJDNS from GetNetworkNames() --- src/netbase.cpp | 2 +- test/functional/rpc_net.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/netbase.cpp b/src/netbase.cpp index 56f124579a..6191f25cd9 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -123,7 +123,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/test/functional/rpc_net.py b/test/functional/rpc_net.py index 103c064cf0..0857f4e0ca 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -106,7 +106,7 @@ class NetTest(BitcoinTestFramework): assert_equal(peer_info[1][1]['connection_type'], 'inbound') # 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") def test_getnettotals(self): self.log.info("Test getnettotals") @@ -157,7 +157,7 @@ class NetTest(BitcoinTestFramework): 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") def test_getaddednodeinfo(self): self.log.info("Test getaddednodeinfo") From 29ff79c0a2a95abf50b78dd2be6ead2abeeaec9f Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Thu, 9 Sep 2021 17:51:12 +0200 Subject: [PATCH 11/13] net: relay CJDNS addresses even if we are not connected to CJDNS This will help with propagation, so that multi-homed nodes can learn CJDNS addresses outside of the CJDNS network. --- src/netaddress.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netaddress.h b/src/netaddress.h index f074c1d3ec..b0b1c5ca9e 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -224,7 +224,7 @@ public: */ bool IsRelayable() const { - return IsIPv4() || IsIPv6() || IsTor() || IsI2P(); + return IsIPv4() || IsIPv6() || IsTor() || IsI2P() || IsCJDNS(); } /** From f9c28330a0e77ed077f342e4669e855b3e6b20a1 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Thu, 9 Sep 2021 15:46:12 +0200 Subject: [PATCH 12/13] net: take the first 4 random bits from CJDNS addresses in GetGroup() CJDNS addresses start with constant 8 bits, so in order to account for the first 4 random ones, we must take the first 12. Otherwise the entire CJDNS network will belong to one group. --- src/netaddress.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/netaddress.cpp b/src/netaddress.cpp index e87a27979d..7f1dd698b0 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -794,8 +794,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; From 420695c1933e2b9c6e594fcd8885f1c261e435cf Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Thu, 9 Sep 2021 15:30:44 +0200 Subject: [PATCH 13/13] contrib: recognize CJDNS seeds as such An IPv6 address from fc00::/8 could be either from the CJDNS network or from a private-unroutable-reserved segment of IPv6. A seed node with such an address must be from the CJDNS network, otherwise other peers will not be able to connect to it. --- contrib/seeds/generate-seeds.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index dbecba7d1d..44345e3987 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -61,7 +61,7 @@ def name_to_bip155(addr): raise ValueError(f'Invalid I2P {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(':') @@ -77,7 +77,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)