Skip to content

Commit f9d1a9a

Browse files
committed
merge bitcoin#23077: Full CJDNS support
1 parent 7596a73 commit f9d1a9a

File tree

11 files changed

+135
-25
lines changed

11 files changed

+135
-25
lines changed

contrib/seeds/generate-seeds.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def name_to_bip155(addr):
5454
raise ValueError('Invalid onion %s' % vchAddr)
5555
elif '.' in addr: # IPv4
5656
return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.'))))
57-
elif ':' in addr: # IPv6
57+
elif ':' in addr: # IPv6 or CJDNS
5858
sub = [[], []] # prefix, suffix
5959
x = 0
6060
addr = addr.split(':')
@@ -70,7 +70,14 @@ def name_to_bip155(addr):
7070
sub[x].append(val & 0xff)
7171
nullbytes = 16 - len(sub[0]) - len(sub[1])
7272
assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0))
73-
return (BIP155Network.IPV6, bytes(sub[0] + ([0] * nullbytes) + sub[1]))
73+
addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1])
74+
if addr_bytes[0] == 0xfc:
75+
# Assume that seeds with fc00::/8 addresses belong to CJDNS,
76+
# not to the publicly unroutable "Unique Local Unicast" network, see
77+
# RFC4193: https://datatracker.ietf.org/doc/html/rfc4193#section-8
78+
return (BIP155Network.CJDNS, addr_bytes)
79+
else:
80+
return (BIP155Network.IPV6, addr_bytes)
7481
else:
7582
raise ValueError('Could not parse address %s' % addr)
7683

src/init.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ void SetupServerArgs(NodeContext& node)
566566
argsman.AddArg("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
567567
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
568568
argsman.AddArg("-bind=<addr>[:<port>][=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);
569+
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);
569570
argsman.AddArg("-connect=<ip>", "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);
570571
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
571572
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
17851786
}
17861787
}
17871788

1789+
if (!args.IsArgSet("-cjdnsreachable")) {
1790+
SetReachable(NET_CJDNS, false);
1791+
}
1792+
// Now IsReachable(NET_CJDNS) is true if:
1793+
// 1. -cjdnsreachable is given and
1794+
// 2.1. -onlynet is not given or
1795+
// 2.2. -onlynet=cjdns is given
1796+
17881797
// Check for host lookup allowed before parsing any network related parameters
17891798
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
17901799

@@ -1806,6 +1815,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
18061815
SetProxy(NET_IPV4, addrProxy);
18071816
SetProxy(NET_IPV6, addrProxy);
18081817
SetProxy(NET_ONION, addrProxy);
1818+
SetProxy(NET_CJDNS, addrProxy);
18091819
SetNameProxy(addrProxy);
18101820
SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later
18111821
}

src/net.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,27 @@ std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode)
255255
return std::nullopt;
256256
}
257257

258+
/**
259+
* If an IPv6 address belongs to the address range used by the CJDNS network and
260+
* the CJDNS network is reachable (-cjdnsreachable config is set), then change
261+
* the type from NET_IPV6 to NET_CJDNS.
262+
* @param[in] service Address to potentially convert.
263+
* @return a copy of `service` either unmodified or changed to CJDNS.
264+
*/
265+
CService MaybeFlipIPv6toCJDNS(const CService& service)
266+
{
267+
CService ret{service};
268+
if (ret.m_net == NET_IPV6 && ret.m_addr[0] == 0xfc && IsReachable(NET_CJDNS)) {
269+
ret.m_net = NET_CJDNS;
270+
}
271+
return ret;
272+
}
273+
258274
// learn a new local address
259-
bool AddLocal(const CService& addr, int nScore)
275+
bool AddLocal(const CService& addr_, int nScore)
260276
{
277+
CService addr{MaybeFlipIPv6toCJDNS(addr_)};
278+
261279
if (!addr.IsRoutable() && Params().RequireRoutableExternalIP())
262280
return false;
263281

@@ -454,7 +472,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
454472
if (pszDest) {
455473
std::vector<CService> resolved;
456474
if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) {
457-
addrConnect = CAddress(resolved[GetRand(resolved.size())], NODE_NONE);
475+
const CService rnd{resolved[GetRand(resolved.size())]};
476+
addrConnect = CAddress{MaybeFlipIPv6toCJDNS(rnd), NODE_NONE};
458477
if (!addrConnect.IsValid()) {
459478
LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest);
460479
return nullptr;
@@ -1210,9 +1229,11 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket, CMasternodeSy
12101229

12111230
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) {
12121231
LogPrintf("Warning: Unknown socket family\n");
1232+
} else {
1233+
addr = CAddress{MaybeFlipIPv6toCJDNS(addr), NODE_NONE};
12131234
}
12141235

1215-
const CAddress addr_bind = GetBindAddress(hSocket);
1236+
const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(hSocket)), NODE_NONE};
12161237

12171238
NetPermissionFlags permissionFlags = NetPermissionFlags::None;
12181239
hListenSocket.AddSocketPermissionFlags(permissionFlags);
@@ -3303,7 +3324,10 @@ NodeId CConnman::GetNewNodeId()
33033324
}
33043325

33053326

3306-
bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) {
3327+
bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlags permissions)
3328+
{
3329+
const CService addr{MaybeFlipIPv6toCJDNS(addr_)};
3330+
33073331
if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) {
33083332
return false;
33093333
}

src/netaddress.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ bool CNetAddr::GetInAddr(struct in_addr* pipv4Addr) const
676676
*/
677677
bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
678678
{
679-
if (!IsIPv6()) {
679+
if (!IsIPv6() && !IsCJDNS()) {
680680
return false;
681681
}
682682
assert(sizeof(*pipv6Addr) == m_addr.size());
@@ -796,8 +796,14 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
796796
vchRet.push_back((ipv4 >> 24) & 0xFF);
797797
vchRet.push_back((ipv4 >> 16) & 0xFF);
798798
return vchRet;
799-
} else if (IsTor() || IsI2P() || IsCJDNS()) {
799+
} else if (IsTor() || IsI2P()) {
800800
nBits = 4;
801+
} else if (IsCJDNS()) {
802+
// Treat in the same way as Tor and I2P because the address in all of
803+
// them is "random" bytes (derived from a public key). However in CJDNS
804+
// the first byte is a constant 0xfc, so the random bytes come after it.
805+
// Thus skip the constant 8 bits at the start.
806+
nBits = 12;
801807
} else if (IsHeNet()) {
802808
// for he.net, use /36 groups
803809
nBits = 36;
@@ -894,6 +900,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
894900
case NET_I2P: return REACH_PRIVATE;
895901
default: return REACH_DEFAULT;
896902
}
903+
case NET_CJDNS:
904+
switch (ourNet) {
905+
case NET_CJDNS: return REACH_PRIVATE;
906+
default: return REACH_DEFAULT;
907+
}
897908
case NET_TEREDO:
898909
switch(ourNet) {
899910
default: return REACH_DEFAULT;
@@ -995,7 +1006,7 @@ bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const
9951006
paddrin->sin_port = htons(port);
9961007
return true;
9971008
}
998-
if (IsIPv6()) {
1009+
if (IsIPv6() || IsCJDNS()) {
9991010
if (*addrlen < (socklen_t)sizeof(struct sockaddr_in6))
10001011
return false;
10011012
*addrlen = sizeof(struct sockaddr_in6);

src/netaddress.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class CNetAddr
232232
*/
233233
bool IsRelayable() const
234234
{
235-
return IsIPv4() || IsIPv6() || IsTor() || IsI2P();
235+
return IsIPv4() || IsIPv6() || IsTor() || IsI2P() || IsCJDNS();
236236
}
237237

238238
/**
@@ -581,6 +581,8 @@ class CService : public CNetAddr
581581
READWRITEAS(CNetAddr, obj);
582582
READWRITE(Using<BigEndianFormatter<2>>(obj.port));
583583
}
584+
585+
friend CService MaybeFlipIPv6toCJDNS(const CService& service);
584586
};
585587

586588
bool SanityCheckASMap(const std::vector<bool>& asmap);

src/netbase.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ enum Network ParseNetwork(const std::string& net_in) {
9595
if (net == "i2p") {
9696
return NET_I2P;
9797
}
98+
if (net == "cjdns") {
99+
return NET_CJDNS;
100+
}
98101
return NET_UNROUTABLE;
99102
}
100103

@@ -119,7 +122,7 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable)
119122
std::vector<std::string> names;
120123
for (int n = 0; n < NET_MAX; ++n) {
121124
const enum Network network{static_cast<Network>(n)};
122-
if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue;
125+
if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
123126
names.emplace_back(GetNetworkName(network));
124127
}
125128
if (append_unroutable) {

src/rpc/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ static UniValue GetNetworksInfo()
585585
UniValue networks(UniValue::VARR);
586586
for (int n = 0; n < NET_MAX; ++n) {
587587
enum Network network = static_cast<enum Network>(n);
588-
if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue;
588+
if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
589589
proxyType proxy;
590590
UniValue obj(UniValue::VOBJ);
591591
GetProxy(network, proxy);

src/test/netbase_tests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,13 @@ BOOST_AUTO_TEST_CASE(netbase_parsenetwork)
441441
BOOST_CHECK_EQUAL(ParseNetwork("ipv6"), NET_IPV6);
442442
BOOST_CHECK_EQUAL(ParseNetwork("onion"), NET_ONION);
443443
BOOST_CHECK_EQUAL(ParseNetwork("tor"), NET_ONION);
444+
BOOST_CHECK_EQUAL(ParseNetwork("cjdns"), NET_CJDNS);
444445

445446
BOOST_CHECK_EQUAL(ParseNetwork("IPv4"), NET_IPV4);
446447
BOOST_CHECK_EQUAL(ParseNetwork("IPv6"), NET_IPV6);
447448
BOOST_CHECK_EQUAL(ParseNetwork("ONION"), NET_ONION);
448449
BOOST_CHECK_EQUAL(ParseNetwork("TOR"), NET_ONION);
450+
BOOST_CHECK_EQUAL(ParseNetwork("CJDNS"), NET_CJDNS);
449451

450452
BOOST_CHECK_EQUAL(ParseNetwork(":)"), NET_UNROUTABLE);
451453
BOOST_CHECK_EQUAL(ParseNetwork("tÖr"), NET_UNROUTABLE);

test/functional/feature_proxy.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- `-proxy` (proxy everything)
1313
- `-onion` (proxy just onions)
1414
- `-proxyrandomize` Circuit randomization
15+
- `-cjdnsreachable`
1516
- Proxy configurations to test on proxy side,
1617
- support no authentication (other proxy)
1718
- support no authentication + user/pass authentication (Tor)
@@ -26,6 +27,7 @@
2627
addnode connect to IPv6
2728
addnode connect to onion
2829
addnode connect to generic DNS name
30+
addnode connect to a CJDNS address
2931
3032
- Test getnetworkinfo for each node
3133
"""
@@ -50,14 +52,15 @@
5052
NET_IPV6 = "ipv6"
5153
NET_ONION = "onion"
5254
NET_I2P = "i2p"
55+
NET_CJDNS = "cjdns"
5356

5457
# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo()
55-
NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P})
58+
NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P, NET_CJDNS})
5659

5760

5861
class ProxyTest(BitcoinTestFramework):
5962
def set_test_params(self):
60-
self.num_nodes = 4
63+
self.num_nodes = 5
6164
self.setup_clean_chain = True
6265

6366
def setup_nodes(self):
@@ -101,7 +104,9 @@ def setup_nodes(self):
101104
['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}',f'-onion={self.conf2.addr[0]}:{self.conf2.addr[1]}',
102105
f'-i2psam={self.i2p_sam[0]}:{self.i2p_sam[1]}', '-i2pacceptincoming=0', '-proxyrandomize=0'],
103106
['-listen', f'-proxy={self.conf2.addr[0]}:{self.conf2.addr[1]}','-proxyrandomize=1'],
104-
[]
107+
[],
108+
['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}','-proxyrandomize=1',
109+
'-cjdnsreachable']
105110
]
106111
if self.have_ipv6:
107112
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):
113118
if peer["addr"] == addr:
114119
assert_equal(peer["network"], network)
115120

116-
def node_test(self, node, proxies, auth, test_onion=True):
121+
def node_test(self, node, *, proxies, auth, test_onion, test_cjdns):
117122
rv = []
118123
addr = "15.61.23.23:1234"
119124
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):
161166
rv.append(cmd)
162167
self.network_test(node, addr, network=NET_ONION)
163168

169+
if test_cjdns:
170+
addr = "[fc00:1:2:3:4:5:6:7]:8888"
171+
self.log.debug(f"Test: outgoing CJDNS connection through node for address {addr}")
172+
node.addnode(addr, "onetry")
173+
cmd = proxies[1].queue.get()
174+
assert isinstance(cmd, Socks5Command)
175+
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
176+
assert_equal(cmd.addr, b"fc00:1:2:3:4:5:6:7")
177+
assert_equal(cmd.port, 8888)
178+
if not auth:
179+
assert_equal(cmd.username, None)
180+
assert_equal(cmd.password, None)
181+
rv.append(cmd)
182+
self.network_test(node, addr, network=NET_CJDNS)
183+
164184
addr = "node.noumenon:8333"
165185
self.log.debug(f"Test: outgoing DNS name connection through node for address {addr}")
166186
node.addnode(addr, "onetry")
@@ -179,20 +199,33 @@ def node_test(self, node, proxies, auth, test_onion=True):
179199

180200
def run_test(self):
181201
# basic -proxy
182-
self.node_test(self.nodes[0], [self.serv1, self.serv1, self.serv1, self.serv1], False)
202+
self.node_test(self.nodes[0],
203+
proxies=[self.serv1, self.serv1, self.serv1, self.serv1],
204+
auth=False, test_onion=True, test_cjdns=False)
183205

184206
# -proxy plus -onion
185-
self.node_test(self.nodes[1], [self.serv1, self.serv1, self.serv2, self.serv1], False)
207+
self.node_test(self.nodes[1],
208+
proxies=[self.serv1, self.serv1, self.serv2, self.serv1],
209+
auth=False, test_onion=True, test_cjdns=False)
186210

187211
# -proxy plus -onion, -proxyrandomize
188-
rv = self.node_test(self.nodes[2], [self.serv2, self.serv2, self.serv2, self.serv2], True)
212+
rv = self.node_test(self.nodes[2],
213+
proxies=[self.serv2, self.serv2, self.serv2, self.serv2],
214+
auth=True, test_onion=True, test_cjdns=False)
189215
# Check that credentials as used for -proxyrandomize connections are unique
190216
credentials = set((x.username,x.password) for x in rv)
191217
assert_equal(len(credentials), len(rv))
192218

193219
if self.have_ipv6:
194220
# proxy on IPv6 localhost
195-
self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False)
221+
self.node_test(self.nodes[3],
222+
proxies=[self.serv3, self.serv3, self.serv3, self.serv3],
223+
auth=False, test_onion=False, test_cjdns=False)
224+
225+
# -proxy=unauth -proxyrandomize=1 -cjdnsreachable
226+
self.node_test(self.nodes[4],
227+
proxies=[self.serv1, self.serv1, self.serv1, self.serv1],
228+
auth=False, test_onion=True, test_cjdns=True)
196229

197230
def networks_dict(d):
198231
r = {}
@@ -214,6 +247,7 @@ def networks_dict(d):
214247
assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize)
215248
assert_equal(n0['onion']['reachable'], True)
216249
assert_equal(n0['i2p']['reachable'], False)
250+
assert_equal(n0['cjdns']['reachable'], False)
217251

218252
n1 = networks_dict(self.nodes[1].getnetworkinfo())
219253
assert_equal(NETWORKS, n1.keys())
@@ -240,6 +274,7 @@ def networks_dict(d):
240274
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
241275
assert_equal(n2['onion']['reachable'], True)
242276
assert_equal(n2['i2p']['reachable'], False)
277+
assert_equal(n2['cjdns']['reachable'], False)
243278

244279
if self.have_ipv6:
245280
n3 = networks_dict(self.nodes[3].getnetworkinfo())
@@ -253,6 +288,22 @@ def networks_dict(d):
253288
assert_equal(n3[net]['proxy_randomize_credentials'], False)
254289
assert_equal(n3['onion']['reachable'], False)
255290
assert_equal(n3['i2p']['reachable'], False)
291+
assert_equal(n3['cjdns']['reachable'], False)
292+
293+
n4 = networks_dict(self.nodes[4].getnetworkinfo())
294+
assert_equal(NETWORKS, n4.keys())
295+
for net in NETWORKS:
296+
if net == NET_I2P:
297+
expected_proxy = ''
298+
expected_randomize = False
299+
else:
300+
expected_proxy = '%s:%i' % (self.conf1.addr)
301+
expected_randomize = True
302+
assert_equal(n4[net]['proxy'], expected_proxy)
303+
assert_equal(n4[net]['proxy_randomize_credentials'], expected_randomize)
304+
assert_equal(n4['onion']['reachable'], True)
305+
assert_equal(n4['i2p']['reachable'], False)
306+
assert_equal(n4['cjdns']['reachable'], True)
256307

257308

258309
if __name__ == '__main__':

test/functional/interface_bitcoin_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def run_test(self):
141141
network_info = self.nodes[0].getnetworkinfo()
142142
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
143143
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
144-
assert_equal(cli_get_info["Proxies"], "127.0.0.1:9050 (ipv4, ipv6, onion), 127.0.0.1:7656 (i2p)")
144+
assert_equal(cli_get_info["Proxies"], "127.0.0.1:9050 (ipv4, ipv6, onion, cjdns), 127.0.0.1:7656 (i2p)")
145145

146146
if self.is_wallet_compiled():
147147
self.log.info("Test -getinfo and dash-cli getwalletinfo return expected wallet info")

0 commit comments

Comments
 (0)