Merge #6031: backport: merge bitcoin#23077, #22834, #24165, #24555, #24663, #24205, #24687, #25173, #24991, partial bitcoin#24468 (cjdns support)

32f8fda7d6 merge bitcoin#24991: allow startup with -onlynet=onion -listenonion=1 (Kittywhiskers Van Gogh)
e67ed92d3d merge bitcoin#25173: add coverage for unknown network in -onlynet (Kittywhiskers Van Gogh)
77efd36112 merge bitcoin#24687: Check an invalid -i2psam will raise an init error (Kittywhiskers Van Gogh)
fb1416f7cb merge bitcoin#24205: improve network reachability test coverage and safety (Kittywhiskers Van Gogh)
7cb7479829 merge bitcoin#24663: add links to doc/cjdns.md (Kittywhiskers Van Gogh)
c736ebf566 merge bitcoin#24555: create initial doc/cjdns.md for CJDNS how-to documentation (Kittywhiskers Van Gogh)
554bd24186 partial bitcoin#24468: improve -onlynet help and related tor/i2p documentation (Kittywhiskers Van Gogh)
5436b6a82d merge bitcoin#24165: extend inbound eviction protection by network to CJDNS peers (Kittywhiskers Van Gogh)
d52724d039 merge bitcoin#22834: respect -onlynet= when making outbound connections (Kittywhiskers Van Gogh)
f9d1a9a00d merge bitcoin#23077: Full CJDNS support (Kittywhiskers Van Gogh)

Pull request description:

  ## Additional Information

  * Depends on https://github.com/dashpay/dash/pull/6034

  * Depends on https://github.com/dashpay/dash/pull/6035

  * 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`.

  * [bitcoin#24165](https://github.com/bitcoin/bitcoin/pull/24165) has been backported _before_ [bitcoin#23758](https://github.com/bitcoin/bitcoin/pull/23758) and to account for this, minor changes were made in `src/test/net_peer_eviction_tests.cpp` (using `nTimeConnected` instead of `m_connected`). When backporting  [bitcoin#23758](https://github.com/bitcoin/bitcoin/pull/23758), these changes will have to be reversed as they won't be covered by the cherry-pick diff.

  * CJDNS support has been labelled as being introduced in Dash Core 21.0, in line with the milestone designation of the PR. Should `develop` be used for a new minor/patch release, `doc/cjdns.md` will have to be modified to reflect the correct version number.

  ## Breaking changes

  No expected protocol or consensus changes.

  ## Checklist:

  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)**
  - [x] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 32f8fda7d6

Tree-SHA512: e23b22ca5edbe4c4abeab0bc07780303e68e7c4cc46b7697300b0837c5acd3a98649b6b03bd07a23c827bd85f64210173027b0b0eea31872c031fa4ed04eeb0c
This commit is contained in:
pasta 2024-06-10 11:10:49 -05:00
commit 825bba1312
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
25 changed files with 547 additions and 116 deletions

View File

@ -54,7 +54,7 @@ def name_to_bip155(addr):
raise ValueError('Invalid onion %s' % vchAddr) raise ValueError('Invalid onion %s' % vchAddr)
elif '.' in addr: # IPv4 elif '.' in addr: # IPv4
return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.')))) return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.'))))
elif ':' in addr: # IPv6 elif ':' in addr: # IPv6 or CJDNS
sub = [[], []] # prefix, suffix sub = [[], []] # prefix, suffix
x = 0 x = 0
addr = addr.split(':') addr = addr.split(':')
@ -70,7 +70,14 @@ def name_to_bip155(addr):
sub[x].append(val & 0xff) sub[x].append(val & 0xff)
nullbytes = 16 - len(sub[0]) - len(sub[1]) nullbytes = 16 - len(sub[0]) - len(sub[1])
assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)) 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: else:
raise ValueError('Could not parse address %s' % addr) raise ValueError('Could not parse address %s' % addr)

View File

@ -69,6 +69,7 @@ The Dash Core repo's [root README](/README.md) contains relevant information on
### Miscellaneous ### Miscellaneous
- [Assets Attribution](assets-attribution.md) - [Assets Attribution](assets-attribution.md)
- [dash.conf Configuration File](dash-conf.md) - [dash.conf Configuration File](dash-conf.md)
- [CJDNS Support](cjdns.md)
- [Files](files.md) - [Files](files.md)
- [Fuzz-testing](fuzzing.md) - [Fuzz-testing](fuzzing.md)
- [I2P Support](i2p.md) - [I2P Support](i2p.md)

95
doc/cjdns.md Normal file
View File

@ -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.

View File

@ -58,13 +58,9 @@ logging` for more information.
-onlynet=i2p -onlynet=i2p
``` ```
Make outgoing connections only to I2P addresses. Incoming connections are not Make automatic outbound connections only to I2P addresses. Inbound and manual
affected by this option. It can be specified multiple times to allow multiple connections are not affected by this option. It can be specified multiple times
network types, e.g. onlynet=ipv4, onlynet=ipv6, onlynet=onion, onlynet=i2p. to allow multiple networks, e.g. 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.
I2P support was added to Dash Core in version 20.0 and there may be fewer 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 peers than Tor or IP ones. Therefore, using I2P alone without other networks may
@ -77,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. 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 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 any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if
of the networks has issues. one of the networks has issues.
## Persistent vs transient I2P addresses ## Persistent vs transient I2P addresses
@ -106,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 There are several ways to see your I2P address in Dash Core if accepting
incoming I2P connections (`-i2pacceptincoming`): incoming I2P connections (`-i2pacceptincoming`):
- in the debug log (grep for `AddLocal`, the I2P address ends in `.b32.i2p`) - in the "Local addresses" output of CLI `-netinfo`
- in the output of the `getnetworkinfo` RPC in the "localaddresses" section - in the "localaddresses" output of RPC `getnetworkinfo`
- in the output of `dash-cli -netinfo` peer connections dashboard - 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` To see which I2P peers your node is connected to, use `dash-cli -netinfo 4`
or the `getpeerinfo` RPC (e.g. `dash-cli getpeerinfo`). or the `getpeerinfo` RPC (e.g. `dash-cli getpeerinfo`).

View File

@ -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`.

View File

@ -11,9 +11,9 @@ for how to properly configure Tor.
## How to see information about your Tor configuration via Dash Core ## How to see information about your Tor configuration via Dash Core
There are several ways to see your local onion address in 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 "Local addresses" output of CLI `-netinfo`
- in the output of RPC `getnetworkinfo` in the "localaddresses" section - in the "localaddresses" output of RPC `getnetworkinfo`
- in the output of the CLI `-netinfo` peer connections dashboard - 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 You may set the `-debug=tor` config logging option to have additional
information in the debug log about your Tor configuration. 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, network. This can be useful to see how many onion peers your node knows,
e.g. for `-onlynet=onion`. 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 ## 1. Run Dash Core behind a Tor proxy
The first step is running Dash Core behind a Tor proxy. This will already anonymize all The first step is running Dash Core behind a Tor proxy. This will already anonymize all
@ -50,14 +53,10 @@ outgoing connections, but more is possible.
-seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with -seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with
other P2P nodes. other P2P nodes.
-onlynet=onion Make outgoing connections only to .onion addresses. Incoming -onlynet=onion Make automatic outbound connections only to .onion addresses.
connections are not affected by this option. This option can be Inbound and manual connections are not affected by this option.
specified multiple times to allow multiple network types, e.g. It can be specified multiple times to allow multiple networks,
onlynet=ipv4, onlynet=ipv6, onlynet=onion, onlynet=i2p. e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns.
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.
An example how to start the client if the Tor proxy is running on local host on 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: port 9050 and only allows .onion nodes to connect:

View File

@ -567,6 +567,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("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
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); 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);
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=<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); 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);
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, 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); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@ -584,7 +585,7 @@ void SetupServerArgs(NodeContext& node)
argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=<ip:port>", "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=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=<ip:port>", "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("-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=<net>", "Make outgoing connections only through network <net> (" + 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=<net>", "Make automatic outbound connections only to network <net> (" + 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("-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("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peertimeout=<n>", 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); argsman.AddArg("-peertimeout=<n>", 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);
@ -1825,56 +1826,84 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
} }
for (int n = 0; n < NET_MAX; n++) { for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n; enum Network net = (enum Network)n;
assert(IsReachable(net));
if (!nets.count(net)) if (!nets.count(net))
SetReachable(net, false); SetReachable(net, false);
} }
} }
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 // Check for host lookup allowed before parsing any network related parameters
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
Proxy onion_proxy;
bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE); bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
// -proxy sets a proxy for all outgoing network traffic // -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 // -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", ""); std::string proxyArg = args.GetArg("-proxy", "");
SetReachable(NET_ONION, false);
if (proxyArg != "" && proxyArg != "0") { if (proxyArg != "" && proxyArg != "0") {
CService proxyAddr; CService proxyAddr;
if (!Lookup(proxyArg, proxyAddr, 9050, fNameLookup)) { if (!Lookup(proxyArg, proxyAddr, 9050, fNameLookup)) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
} }
proxyType addrProxy = proxyType(proxyAddr, proxyRandomize); Proxy addrProxy = Proxy(proxyAddr, proxyRandomize);
if (!addrProxy.IsValid()) if (!addrProxy.IsValid())
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV4, addrProxy);
SetProxy(NET_IPV6, addrProxy); SetProxy(NET_IPV6, addrProxy);
SetProxy(NET_ONION, addrProxy); SetProxy(NET_CJDNS, addrProxy);
SetNameProxy(addrProxy); SetNameProxy(addrProxy);
SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later 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 // -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 // -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) // An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none)
std::string onionArg = args.GetArg("-onion", ""); std::string onionArg = args.GetArg("-onion", "");
if (onionArg != "") { if (onionArg != "") {
if (onionArg == "0") { // Handle -noonion/-onion=0 if (onionArg == "0") { // Handle -noonion/-onion=0
SetReachable(NET_ONION, false); 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 { } else {
CService onionProxy; CService addr;
if (!Lookup(onionArg, onionProxy, 9050, fNameLookup)) { if (!Lookup(onionArg, addr, 9050, fNameLookup) || !addr.IsValid()) {
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
} }
proxyType addrOnion = proxyType(onionProxy, proxyRandomize); onion_proxy = Proxy{addr, proxyRandomize};
if (!addrOnion.IsValid())
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
SetProxy(NET_ONION, addrOnion);
SetReachable(NET_ONION, true);
} }
} }
if (onion_proxy.IsValid()) {
SetProxy(NET_ONION, onion_proxy);
} else {
// 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: none of -proxy, -onion or "
"-listenonion is given"));
}
SetReachable(NET_ONION, false);
}
for (const std::string& strAddr : args.GetArgs("-externalip")) { for (const std::string& strAddr : args.GetArgs("-externalip")) {
CService addrLocal; CService addrLocal;
if (Lookup(strAddr, addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid()) if (Lookup(strAddr, addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
@ -2560,8 +2589,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) { if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) {
return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg)); return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg));
} }
SetReachable(NET_I2P, true); SetProxy(NET_I2P, Proxy{addr});
SetProxy(NET_I2P, proxyType{addr});
} else { } else {
SetReachable(NET_I2P, false); SetReachable(NET_I2P, false);
} }

View File

@ -32,7 +32,7 @@ class CNodeStats;
class Coin; class Coin;
class RPCTimerInterface; class RPCTimerInterface;
class UniValue; class UniValue;
class proxyType; class Proxy;
struct bilingual_str; struct bilingual_str;
enum class SynchronizationState; enum class SynchronizationState;
struct CNodeStateStats; struct CNodeStateStats;
@ -174,7 +174,7 @@ public:
virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; virtual void mapPort(bool use_upnp, bool use_natpmp) = 0;
//! Get proxy. //! Get proxy.
virtual bool getProxy(Network net, proxyType& proxy_info) = 0; virtual bool getProxy(Network net, Proxy& proxy_info) = 0;
//! Get number of connections. //! Get number of connections.
virtual size_t getNodeCount(ConnectionDirection flags) = 0; virtual size_t getNodeCount(ConnectionDirection flags) = 0;

View File

@ -257,9 +257,27 @@ std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode)
return std::nullopt; 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() && Params().RequireRoutableExternalIP()) if (!addr.IsRoutable() && Params().RequireRoutableExternalIP())
return false; return false;
@ -456,7 +474,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
if (pszDest) { if (pszDest) {
std::vector<CService> resolved; std::vector<CService> resolved;
if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { 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()) { if (!addrConnect.IsValid()) {
LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest); LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest);
return nullptr; return nullptr;
@ -475,7 +494,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
// Connect // Connect
bool connected = false; bool connected = false;
std::unique_ptr<Sock> sock; std::unique_ptr<Sock> sock;
proxyType proxy; Proxy proxy;
CAddress addr_bind; CAddress addr_bind;
assert(!addr_bind.IsValid()); assert(!addr_bind.IsValid());
std::unique_ptr<i2p::sam::Session> i2p_transient_session; std::unique_ptr<i2p::sam::Session> i2p_transient_session;
@ -1008,17 +1027,17 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& evicti
// Protect the half of the remaining nodes which have been connected the longest. // 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. // 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 // 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. // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime
// This helps protect these higher-latency peers that tend to be otherwise // overall. This helps protect these higher-latency peers that tend to be otherwise
// disadvantaged under our eviction criteria. // disadvantaged under our eviction criteria.
const size_t initial_size = eviction_candidates.size(); const size_t initial_size = eviction_candidates.size();
const size_t total_protect_size{initial_size / 2}; const size_t total_protect_size{initial_size / 2};
// Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier // Disadvantaged networks to protect. In the case of equal counts, earlier array members
// array members have first opportunity to recover unused slots from the previous iteration. // have the first opportunity to recover unused slots from the previous iteration.
struct Net { bool is_local; Network id; size_t count; }; struct Net { bool is_local; Network id; size_t count; };
std::array<Net, 3> networks{ std::array<Net, 4> networks{
{{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}}; {{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. // Count and store the number of eviction candidates per network.
for (Net& n : networks) { for (Net& n : networks) {
@ -1211,9 +1230,11 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket, CMasternodeSy
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) {
LogPrintf("Warning: Unknown socket family\n"); 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; NetPermissionFlags permissionFlags = NetPermissionFlags::None;
hListenSocket.AddSocketPermissionFlags(permissionFlags); 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)) { if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) {
return false; return false;
} }
@ -3368,7 +3392,7 @@ bool CConnman::Start(CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_met
return false; return false;
} }
proxyType i2p_sam; Proxy i2p_sam;
if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) { if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) {
m_i2p_sam_session = std::make_unique<i2p::sam::Session>(GetDataDir() / "i2p_private_key", m_i2p_sam_session = std::make_unique<i2p::sam::Session>(GetDataDir() / "i2p_private_key",
i2p_sam.proxy, &interruptNet); i2p_sam.proxy, &interruptNet);

View File

@ -1657,6 +1657,8 @@ size_t GetRequestedObjectCount(NodeId nodeId) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
* *
* - I2P peers * - I2P peers
* *
* - CJDNS peers
*
* This helps protect these privacy network peers, which tend to be otherwise * This helps protect these privacy network peers, which tend to be otherwise
* disadvantaged under our eviction criteria for their higher min ping times * disadvantaged under our eviction criteria for their higher min ping times
* relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections.

View File

@ -680,7 +680,7 @@ bool CNetAddr::GetInAddr(struct in_addr* pipv4Addr) const
*/ */
bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
{ {
if (!IsIPv6()) { if (!IsIPv6() && !IsCJDNS()) {
return false; return false;
} }
assert(sizeof(*pipv6Addr) == m_addr.size()); assert(sizeof(*pipv6Addr) == m_addr.size());
@ -800,8 +800,14 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
vchRet.push_back((ipv4 >> 24) & 0xFF); vchRet.push_back((ipv4 >> 24) & 0xFF);
vchRet.push_back((ipv4 >> 16) & 0xFF); vchRet.push_back((ipv4 >> 16) & 0xFF);
return vchRet; return vchRet;
} else if (IsTor() || IsI2P() || IsCJDNS()) { } else if (IsTor() || IsI2P()) {
nBits = 4; 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()) { } else if (IsHeNet()) {
// for he.net, use /36 groups // for he.net, use /36 groups
nBits = 36; nBits = 36;
@ -898,6 +904,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
case NET_I2P: return REACH_PRIVATE; case NET_I2P: return REACH_PRIVATE;
default: return REACH_DEFAULT; default: return REACH_DEFAULT;
} }
case NET_CJDNS:
switch (ourNet) {
case NET_CJDNS: return REACH_PRIVATE;
default: return REACH_DEFAULT;
}
case NET_TEREDO: case NET_TEREDO:
switch(ourNet) { switch(ourNet) {
default: return REACH_DEFAULT; default: return REACH_DEFAULT;
@ -999,7 +1010,7 @@ bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const
paddrin->sin_port = htons(port); paddrin->sin_port = htons(port);
return true; return true;
} }
if (IsIPv6()) { if (IsIPv6() || IsCJDNS()) {
if (*addrlen < (socklen_t)sizeof(struct sockaddr_in6)) if (*addrlen < (socklen_t)sizeof(struct sockaddr_in6))
return false; return false;
*addrlen = sizeof(struct sockaddr_in6); *addrlen = sizeof(struct sockaddr_in6);

View File

@ -232,7 +232,7 @@ public:
*/ */
bool IsRelayable() const bool IsRelayable() const
{ {
return IsIPv4() || IsIPv6() || IsTor() || IsI2P(); return IsIPv4() || IsIPv6() || IsTor() || IsI2P() || IsCJDNS();
} }
/** /**
@ -581,6 +581,8 @@ public:
READWRITEAS(CNetAddr, obj); READWRITEAS(CNetAddr, obj);
READWRITE(Using<BigEndianFormatter<2>>(obj.port)); READWRITE(Using<BigEndianFormatter<2>>(obj.port));
} }
friend CService MaybeFlipIPv6toCJDNS(const CService& service);
}; };
#endif // BITCOIN_NETADDRESS_H #endif // BITCOIN_NETADDRESS_H

View File

@ -30,8 +30,8 @@
// Settings // Settings
static Mutex g_proxyinfo_mutex; static Mutex g_proxyinfo_mutex;
static proxyType proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
static proxyType nameProxy GUARDED_BY(g_proxyinfo_mutex); static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex);
int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
bool fNameLookup = DEFAULT_NAME_LOOKUP; bool fNameLookup = DEFAULT_NAME_LOOKUP;
@ -95,6 +95,9 @@ enum Network ParseNetwork(const std::string& net_in) {
if (net == "i2p") { if (net == "i2p") {
return NET_I2P; return NET_I2P;
} }
if (net == "cjdns") {
return NET_CJDNS;
}
return NET_UNROUTABLE; return NET_UNROUTABLE;
} }
@ -119,7 +122,7 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable)
std::vector<std::string> names; std::vector<std::string> names;
for (int n = 0; n < NET_MAX; ++n) { for (int n = 0; n < NET_MAX; ++n) {
const enum Network network{static_cast<Network>(n)}; const enum Network network{static_cast<Network>(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)); names.emplace_back(GetNetworkName(network));
} }
if (append_unroutable) { if (append_unroutable) {
@ -601,7 +604,7 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
return true; return true;
} }
bool SetProxy(enum Network net, const proxyType &addrProxy) { bool SetProxy(enum Network net, const Proxy &addrProxy) {
assert(net >= 0 && net < NET_MAX); assert(net >= 0 && net < NET_MAX);
if (!addrProxy.IsValid()) if (!addrProxy.IsValid())
return false; return false;
@ -610,7 +613,7 @@ bool SetProxy(enum Network net, const proxyType &addrProxy) {
return true; return true;
} }
bool GetProxy(enum Network net, proxyType &proxyInfoOut) { bool GetProxy(enum Network net, Proxy &proxyInfoOut) {
assert(net >= 0 && net < NET_MAX); assert(net >= 0 && net < NET_MAX);
LOCK(g_proxyinfo_mutex); LOCK(g_proxyinfo_mutex);
if (!proxyInfo[net].IsValid()) if (!proxyInfo[net].IsValid())
@ -619,7 +622,7 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) {
return true; return true;
} }
bool SetNameProxy(const proxyType &addrProxy) { bool SetNameProxy(const Proxy &addrProxy) {
if (!addrProxy.IsValid()) if (!addrProxy.IsValid())
return false; return false;
LOCK(g_proxyinfo_mutex); LOCK(g_proxyinfo_mutex);
@ -627,7 +630,7 @@ bool SetNameProxy(const proxyType &addrProxy) {
return true; return true;
} }
bool GetNameProxy(proxyType &nameProxyOut) { bool GetNameProxy(Proxy &nameProxyOut) {
LOCK(g_proxyinfo_mutex); LOCK(g_proxyinfo_mutex);
if(!nameProxy.IsValid()) if(!nameProxy.IsValid())
return false; return false;
@ -649,7 +652,7 @@ bool IsProxy(const CNetAddr &addr) {
return false; 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 // first connect to proxy server
if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) { if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) {

View File

@ -49,11 +49,11 @@ static inline bool operator&(ConnectionDirection a, ConnectionDirection b) {
return (underlying(a) & underlying(b)); return (underlying(a) & underlying(b));
} }
class proxyType class Proxy
{ {
public: public:
proxyType(): randomize_credentials(false) {} Proxy(): randomize_credentials(false) {}
explicit proxyType(const CService &_proxy, bool _randomize_credentials=false): proxy(_proxy), randomize_credentials(_randomize_credentials) {} explicit Proxy(const CService &_proxy, bool _randomize_credentials=false): proxy(_proxy), randomize_credentials(_randomize_credentials) {}
bool IsValid() const { return proxy.IsValid(); } bool IsValid() const { return proxy.IsValid(); }
@ -77,8 +77,8 @@ enum Network ParseNetwork(const std::string& net);
std::string GetNetworkName(enum Network net); std::string GetNetworkName(enum Network net);
/** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */ /** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */
std::vector<std::string> GetNetworkNames(bool append_unroutable = false); std::vector<std::string> GetNetworkNames(bool append_unroutable = false);
bool SetProxy(enum Network net, const proxyType &addrProxy); bool SetProxy(enum Network net, const Proxy &addrProxy);
bool GetProxy(enum Network net, proxyType &proxyInfoOut); bool GetProxy(enum Network net, Proxy &proxyInfoOut);
bool IsProxy(const CNetAddr &addr); bool IsProxy(const CNetAddr &addr);
/** /**
* Set the name proxy to use for all connections to nodes specified by a * 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 * server in common use (most notably Tor) actually implements UDP
* support, and a DNS resolver is beyond the scope of this project. * 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 HaveNameProxy();
bool GetNameProxy(proxyType &nameProxyOut); bool GetNameProxy(Proxy &nameProxyOut);
using DNSLookupFn = std::function<std::vector<CNetAddr>(const std::string&, bool)>; using DNSLookupFn = std::function<std::vector<CNetAddr>(const std::string&, bool)>;
extern DNSLookupFn g_dns_lookup; 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. * @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 */ /** Enable non-blocking mode for a socket */
bool SetSocketNonBlocking(const SOCKET& hSocket); bool SetSocketNonBlocking(const SOCKET& hSocket);

View File

@ -341,7 +341,7 @@ public:
} }
bool shutdownRequested() override { return ShutdownRequested(); } bool shutdownRequested() override { return ShutdownRequested(); }
void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } 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 size_t getNodeCount(ConnectionDirection flags) override
{ {
return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0;

View File

@ -381,7 +381,7 @@ void ClientModel::unsubscribeFromCoreSignals()
bool ClientModel::getProxyInfo(std::string& ip_port) const 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)) { if (m_node.getProxy((Network) 1, ipv4) && m_node.getProxy((Network) 2, ipv6)) {
ip_port = ipv4.proxy.ToStringIPPort(); ip_port = ipv4.proxy.ToStringIPPort();
return true; return true;

View File

@ -485,7 +485,7 @@ void OptionsDialog::updateProxyValidationState()
void OptionsDialog::updateDefaultProxyNets() void OptionsDialog::updateDefaultProxyNets()
{ {
proxyType proxy; Proxy proxy;
std::string strProxy; std::string strProxy;
QString strDefaultProxyGUI; QString strDefaultProxyGUI;
@ -567,7 +567,7 @@ QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) cons
Q_UNUSED(pos); Q_UNUSED(pos);
// Validate the proxy // Validate the proxy
CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT)); CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT));
proxyType addrProxy = proxyType(serv, true); Proxy addrProxy = Proxy(serv, true);
if (addrProxy.IsValid()) if (addrProxy.IsValid())
return QValidator::Acceptable; return QValidator::Acceptable;

View File

@ -585,8 +585,8 @@ static UniValue GetNetworksInfo()
UniValue networks(UniValue::VARR); UniValue networks(UniValue::VARR);
for (int n = 0; n < NET_MAX; ++n) { for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n); enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
proxyType proxy; Proxy proxy;
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy); GetProxy(network, proxy);
obj.pushKV("name", GetNetworkName(network)); obj.pushKV("name", GetNetworkName(network));

View File

@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
// Test protection of onion, localhost, and I2P peers... // Test protection of onion, localhost, and I2P peers...
// Expect 1/4 onion peers to be protected from eviction, // 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( BOOST_CHECK(IsProtected(
num_peers, [](NodeEvictionCandidate& c) { num_peers, [](NodeEvictionCandidate& c) {
c.m_is_local = false; c.m_is_local = false;
@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
random_context)); random_context));
// Expect 1/4 onion peers and 1/4 of the other peers to be protected, // 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( BOOST_CHECK(IsProtected(
num_peers, [](NodeEvictionCandidate& c) { num_peers, [](NodeEvictionCandidate& c) {
c.nTimeConnected = c.id; c.nTimeConnected = c.id;
@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
random_context)); random_context));
// Expect 1/4 localhost peers to be protected from eviction, // 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( BOOST_CHECK(IsProtected(
num_peers, [](NodeEvictionCandidate& c) { num_peers, [](NodeEvictionCandidate& c) {
c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); 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)); random_context));
// Expect 1/4 localhost peers and 1/4 of the other peers to be protected, // 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( BOOST_CHECK(IsProtected(
num_peers, [](NodeEvictionCandidate& c) { num_peers, [](NodeEvictionCandidate& c) {
c.nTimeConnected = c.id; c.nTimeConnected = c.id;
@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
random_context)); random_context));
// Expect 1/4 I2P peers to be protected from eviction, // 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( BOOST_CHECK(IsProtected(
num_peers, [](NodeEvictionCandidate& c) { num_peers, [](NodeEvictionCandidate& c) {
c.m_is_local = false; c.m_is_local = false;
@ -168,8 +168,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
/* unprotected_peer_ids */ {}, /* unprotected_peer_ids */ {},
random_context)); random_context));
// Expect 1/4 I2P peers and 1/4 of the other peers to be protected, // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted
// sorted by longest uptime (lowest nTimeConnected), if no onion or localhost peers. // by longest uptime (lowest nTimeConnected), if no onion, localhost, or CJDNS peers.
BOOST_CHECK(IsProtected( BOOST_CHECK(IsProtected(
num_peers, [](NodeEvictionCandidate& c) { num_peers, [](NodeEvictionCandidate& c) {
c.nTimeConnected = c.id; c.nTimeConnected = c.id;
@ -180,6 +180,29 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
/* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11}, /* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11},
random_context)); 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... // Tests with 2 networks...
// Combined test: expect having 1 localhost and 1 onion peer out of 4 to // 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( BOOST_CHECK(IsProtected(
4, [](NodeEvictionCandidate& c) { 4, [](NodeEvictionCandidate& c) {
c.nTimeConnected = c.id; c.nTimeConnected = c.id;
c.m_is_local = (c.id == 3); c.m_is_local = (c.id == 2);
if (c.id == 4) { if (c.id == 3) {
c.m_network = NET_I2P; c.m_network = NET_I2P;
} else if (c.id == 2) { } else if (c.id == 1) {
c.m_network = NET_ONION; c.m_network = NET_ONION;
} else { } else {
c.m_network = NET_IPV6; c.m_network = NET_IPV6;
} }
}, },
/* protected_peer_ids */ {0, 4}, /* protected_peer_ids */ {0, 3},
/* unprotected_peer_ids */ {1, 2}, /* unprotected_peer_ids */ {1, 2},
random_context)); 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}, /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
random_context)); random_context));
// Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of // Combined test: expect having 8 localhost, 4 CJDNS, and 3 onion peers out
// 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted // of 24 to protect 2 of each (6 total), plus 6 others for 12/24 total,
// by longest uptime. // sorted by longest uptime.
BOOST_CHECK(IsProtected( BOOST_CHECK(IsProtected(
24, [](NodeEvictionCandidate& c) { 24, [](NodeEvictionCandidate& c) {
c.nTimeConnected = c.id; c.nTimeConnected = c.id;
c.m_is_local = (c.id > 15); c.m_is_local = (c.id > 15);
if (c.id > 10 && 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) { } else if (c.id > 6 && c.id < 10) {
c.m_network = NET_ONION; c.m_network = NET_ONION;
} else { } 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}, /* 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}, /* unprotected_peer_ids */ {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23},
random_context)); 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. // Returns true if any of the node ids in node_ids are selected for eviction.

View File

@ -632,26 +632,31 @@ BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network)
BOOST_CHECK(IsReachable(NET_IPV6)); BOOST_CHECK(IsReachable(NET_IPV6));
BOOST_CHECK(IsReachable(NET_ONION)); BOOST_CHECK(IsReachable(NET_ONION));
BOOST_CHECK(IsReachable(NET_I2P)); BOOST_CHECK(IsReachable(NET_I2P));
BOOST_CHECK(IsReachable(NET_CJDNS));
SetReachable(NET_IPV4, false); SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false); SetReachable(NET_IPV6, false);
SetReachable(NET_ONION, false); SetReachable(NET_ONION, false);
SetReachable(NET_I2P, false); SetReachable(NET_I2P, false);
SetReachable(NET_CJDNS, false);
BOOST_CHECK(!IsReachable(NET_IPV4)); BOOST_CHECK(!IsReachable(NET_IPV4));
BOOST_CHECK(!IsReachable(NET_IPV6)); BOOST_CHECK(!IsReachable(NET_IPV6));
BOOST_CHECK(!IsReachable(NET_ONION)); BOOST_CHECK(!IsReachable(NET_ONION));
BOOST_CHECK(!IsReachable(NET_I2P)); BOOST_CHECK(!IsReachable(NET_I2P));
BOOST_CHECK(!IsReachable(NET_CJDNS));
SetReachable(NET_IPV4, true); SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true); SetReachable(NET_IPV6, true);
SetReachable(NET_ONION, true); SetReachable(NET_ONION, true);
SetReachable(NET_I2P, true); SetReachable(NET_I2P, true);
SetReachable(NET_CJDNS, true);
BOOST_CHECK(IsReachable(NET_IPV4)); BOOST_CHECK(IsReachable(NET_IPV4));
BOOST_CHECK(IsReachable(NET_IPV6)); BOOST_CHECK(IsReachable(NET_IPV6));
BOOST_CHECK(IsReachable(NET_ONION)); BOOST_CHECK(IsReachable(NET_ONION));
BOOST_CHECK(IsReachable(NET_I2P)); BOOST_CHECK(IsReachable(NET_I2P));
BOOST_CHECK(IsReachable(NET_CJDNS));
} }
BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal)

View File

@ -441,11 +441,13 @@ BOOST_AUTO_TEST_CASE(netbase_parsenetwork)
BOOST_CHECK_EQUAL(ParseNetwork("ipv6"), NET_IPV6); BOOST_CHECK_EQUAL(ParseNetwork("ipv6"), NET_IPV6);
BOOST_CHECK_EQUAL(ParseNetwork("onion"), NET_ONION); BOOST_CHECK_EQUAL(ParseNetwork("onion"), NET_ONION);
BOOST_CHECK_EQUAL(ParseNetwork("tor"), 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("IPv4"), NET_IPV4);
BOOST_CHECK_EQUAL(ParseNetwork("IPv6"), NET_IPV6); BOOST_CHECK_EQUAL(ParseNetwork("IPv6"), NET_IPV6);
BOOST_CHECK_EQUAL(ParseNetwork("ONION"), NET_ONION); BOOST_CHECK_EQUAL(ParseNetwork("ONION"), NET_ONION);
BOOST_CHECK_EQUAL(ParseNetwork("TOR"), 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(":)"), NET_UNROUTABLE);
BOOST_CHECK_EQUAL(ParseNetwork("tÖr"), NET_UNROUTABLE); BOOST_CHECK_EQUAL(ParseNetwork("tÖr"), NET_UNROUTABLE);

View File

@ -374,9 +374,24 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
// if -onion isn't set to something else. // if -onion isn't set to something else.
if (gArgs.GetArg("-onion", "") == "") { if (gArgs.GetArg("-onion", "") == "") {
CService resolved(LookupNumeric("127.0.0.1", 9050)); CService resolved(LookupNumeric("127.0.0.1", 9050));
proxyType addrOnion = proxyType(resolved, true); Proxy addrOnion = Proxy(resolved, true);
SetProxy(NET_ONION, addrOnion); 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 // Finally - now create the service

View File

@ -12,6 +12,7 @@ Test plan:
- `-proxy` (proxy everything) - `-proxy` (proxy everything)
- `-onion` (proxy just onions) - `-onion` (proxy just onions)
- `-proxyrandomize` Circuit randomization - `-proxyrandomize` Circuit randomization
- `-cjdnsreachable`
- Proxy configurations to test on proxy side, - Proxy configurations to test on proxy side,
- support no authentication (other proxy) - support no authentication (other proxy)
- support no authentication + user/pass authentication (Tor) - support no authentication + user/pass authentication (Tor)
@ -26,8 +27,16 @@ addnode connect to IPv4
addnode connect to IPv6 addnode connect to IPv6
addnode connect to onion addnode connect to onion
addnode connect to generic DNS name addnode connect to generic DNS name
addnode connect to a CJDNS address
- Test getnetworkinfo for each node - Test getnetworkinfo for each node
- 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
- Test passing unknown -onlynet
""" """
import socket import socket
@ -50,14 +59,15 @@ NET_IPV4 = "ipv4"
NET_IPV6 = "ipv6" NET_IPV6 = "ipv6"
NET_ONION = "onion" NET_ONION = "onion"
NET_I2P = "i2p" NET_I2P = "i2p"
NET_CJDNS = "cjdns"
# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo() # 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): class ProxyTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 4 self.num_nodes = 5
self.setup_clean_chain = True self.setup_clean_chain = True
def setup_nodes(self): def setup_nodes(self):
@ -101,7 +111,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]}', ['-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'], 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.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: if self.have_ipv6:
args[3] = ['-listen', f'-proxy=[{self.conf3.addr[0]}]:{self.conf3.addr[1]}','-proxyrandomize=0', '-noonion'] args[3] = ['-listen', f'-proxy=[{self.conf3.addr[0]}]:{self.conf3.addr[1]}','-proxyrandomize=0', '-noonion']
@ -113,7 +125,7 @@ class ProxyTest(BitcoinTestFramework):
if peer["addr"] == addr: if peer["addr"] == addr:
assert_equal(peer["network"], network) 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 = [] rv = []
addr = "15.61.23.23:1234" addr = "15.61.23.23:1234"
self.log.debug(f"Test: outgoing IPv4 connection through node for address {addr}") self.log.debug(f"Test: outgoing IPv4 connection through node for address {addr}")
@ -161,6 +173,21 @@ class ProxyTest(BitcoinTestFramework):
rv.append(cmd) rv.append(cmd)
self.network_test(node, addr, network=NET_ONION) 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" addr = "node.noumenon:8333"
self.log.debug(f"Test: outgoing DNS name connection through node for address {addr}") self.log.debug(f"Test: outgoing DNS name connection through node for address {addr}")
node.addnode(addr, "onetry") node.addnode(addr, "onetry")
@ -179,20 +206,33 @@ class ProxyTest(BitcoinTestFramework):
def run_test(self): def run_test(self):
# basic -proxy # 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 # -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 # -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 # Check that credentials as used for -proxyrandomize connections are unique
credentials = set((x.username,x.password) for x in rv) credentials = set((x.username,x.password) for x in rv)
assert_equal(len(credentials), len(rv)) assert_equal(len(credentials), len(rv))
if self.have_ipv6: if self.have_ipv6:
# proxy on IPv6 localhost # 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): def networks_dict(d):
r = {} r = {}
@ -214,6 +254,7 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize) assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n0['onion']['reachable'], True) assert_equal(n0['onion']['reachable'], True)
assert_equal(n0['i2p']['reachable'], False) assert_equal(n0['i2p']['reachable'], False)
assert_equal(n0['cjdns']['reachable'], False)
n1 = networks_dict(self.nodes[1].getnetworkinfo()) n1 = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(NETWORKS, n1.keys()) assert_equal(NETWORKS, n1.keys())
@ -229,30 +270,89 @@ class ProxyTest(BitcoinTestFramework):
n2 = networks_dict(self.nodes[2].getnetworkinfo()) n2 = networks_dict(self.nodes[2].getnetworkinfo())
assert_equal(NETWORKS, n2.keys()) assert_equal(NETWORKS, n2.keys())
proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}'
for net in NETWORKS: for net in NETWORKS:
if net == NET_I2P: if net == NET_I2P:
expected_proxy = '' expected_proxy = ''
expected_randomize = False expected_randomize = False
else: else:
expected_proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}' expected_proxy = proxy
expected_randomize = True expected_randomize = True
assert_equal(n2[net]['proxy'], expected_proxy) assert_equal(n2[net]['proxy'], expected_proxy)
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize) assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n2['onion']['reachable'], True) assert_equal(n2['onion']['reachable'], True)
assert_equal(n2['i2p']['reachable'], False) assert_equal(n2['i2p']['reachable'], False)
assert_equal(n2['cjdns']['reachable'], False)
if self.have_ipv6: if self.have_ipv6:
n3 = networks_dict(self.nodes[3].getnetworkinfo()) n3 = networks_dict(self.nodes[3].getnetworkinfo())
assert_equal(NETWORKS, n3.keys()) assert_equal(NETWORKS, n3.keys())
proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
for net in NETWORKS: for net in NETWORKS:
if net == NET_I2P: expected_proxy = '' if net == NET_I2P or net == NET_ONION else proxy
expected_proxy = ''
else:
expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
assert_equal(n3[net]['proxy'], expected_proxy) assert_equal(n3[net]['proxy'], expected_proxy)
assert_equal(n3[net]['proxy_randomize_credentials'], False) assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False) assert_equal(n3['onion']['reachable'], False)
assert_equal(n3['i2p']['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)
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)
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)
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 explicitly forbidden: -onion=0"
)
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'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -141,7 +141,7 @@ class TestBitcoinCli(BitcoinTestFramework):
network_info = self.nodes[0].getnetworkinfo() network_info = self.nodes[0].getnetworkinfo()
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) 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(): if self.is_wallet_compiled():
self.log.info("Test -getinfo and dash-cli getwalletinfo return expected wallet info") self.log.info("Test -getinfo and dash-cli getwalletinfo return expected wallet info")

View File

@ -99,7 +99,7 @@ class NetTest(DashTestFramework):
assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"]) assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"])
# Check dynamically generated networks list in getpeerinfo help output. # 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. # 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 # We also create more connection during the test itself to test mn specific stats
assert_equal(peer_info[0][0]['connection_type'], 'inbound') assert_equal(peer_info[0][0]['connection_type'], 'inbound')
@ -167,7 +167,7 @@ class NetTest(DashTestFramework):
assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"]) assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"])
# Check dynamically generated networks list in getnetworkinfo help output. # 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') self.log.info('Test extended connections info')
# Connect nodes both ways. # Connect nodes both ways.
@ -252,8 +252,8 @@ class NetTest(DashTestFramework):
assert_equal(res[0]["port"], 8333) assert_equal(res[0]["port"], 8333)
assert_equal(res[0]["services"], services) assert_equal(res[0]["services"], services)
# Test for the absence of onion and I2P addresses. # Test for the absence of onion, I2P and CJDNS addresses.
for network in ["onion", "i2p"]: for network in ["onion", "i2p", "cjdns"]:
assert_equal(self.nodes[0].getnodeaddresses(0, network), []) assert_equal(self.nodes[0].getnodeaddresses(0, network), [])
# Test invalid arguments. # Test invalid arguments.