Skip to content

Commit 76c35c6

Browse files
committed
init: introduce I2P connectivity options
Introduce two new options to reach the I2P network: * `-i2psam=<ip:port>` point to the I2P SAM proxy. If this is set then the I2P network is considered reachable and we can make outgoing connections to I2P peers via that proxy. We listen for and accept incoming connections from I2P peers if the below is set in addition to `-i2psam=<ip:port>` * `-i2pacceptincoming` if this is set together with `-i2psam=<ip:port>` then we accept incoming I2P connections via the I2P SAM proxy.
1 parent c22daa2 commit 76c35c6

File tree

6 files changed

+76
-8
lines changed

6 files changed

+76
-8
lines changed

doc/files.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Subdirectory | File(s) | Description
6464
`./` | `ip_asn.map` | IP addresses to Autonomous System Numbers (ASNs) mapping used for bucketing of the peers; path can be specified with the `-asmap` option
6565
`./` | `mempool.dat` | Dump of the mempool's transactions
6666
`./` | `onion_v3_private_key` | Cached Tor onion service private key for `-listenonion` option
67+
`./` | `i2p_private_key` | Private key that corresponds to our I2P address. When `-i2psam=` is specified the contents of this file is used to identify ourselves for making outgoing connections to I2P peers and possibly accepting incoming ones. Automatically generated if it does not exist.
6768
`./` | `peers.dat` | Peer IP address database (custom format)
6869
`./` | `settings.json` | Read-write settings set through GUI or RPC interfaces, augmenting manual settings from [bitcoin.conf](bitcoin-conf.md). File is created automatically if read-write settings storage is not disabled with `-nosettings` option. Path can be specified with `-settings` option
6970
`./` | `.cookie` | Session RPC authentication cookie; if used, created at start and deleted on shutdown; can be specified by `-rpccookiefile` option

src/init.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,8 @@ void SetupServerArgs(NodeContext& node)
447447
argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
448448
argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
449449
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);
450+
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
451+
argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
450452
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 ipv4 or ipv6 but not onion 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);
451453
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
452454
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -847,6 +849,9 @@ void InitParameterInteraction(ArgsManager& args)
847849
LogPrintf("%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__);
848850
if (args.SoftSetBoolArg("-listenonion", false))
849851
LogPrintf("%s: parameter interaction: -listen=0 -> setting -listenonion=0\n", __func__);
852+
if (args.SoftSetBoolArg("-i2pacceptincoming", false)) {
853+
LogPrintf("%s: parameter interaction: -listen=0 -> setting -i2pacceptincoming=0\n", __func__);
854+
}
850855
}
851856

852857
if (args.IsArgSet("-externalip")) {
@@ -1990,6 +1995,21 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
19901995
connOptions.m_specified_outgoing = connect;
19911996
}
19921997
}
1998+
1999+
const std::string& i2psam_arg = args.GetArg("-i2psam", "");
2000+
if (!i2psam_arg.empty()) {
2001+
CService addr;
2002+
if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) {
2003+
return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg));
2004+
}
2005+
SetReachable(NET_I2P, true);
2006+
SetProxy(NET_I2P, proxyType{addr});
2007+
} else {
2008+
SetReachable(NET_I2P, false);
2009+
}
2010+
2011+
connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", true);
2012+
19932013
if (!node.connman->Start(*node.scheduler, connOptions)) {
19942014
return false;
19952015
}

src/net.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,12 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
23742374
return false;
23752375
}
23762376

2377+
proxyType i2p_sam;
2378+
if (GetProxy(NET_I2P, i2p_sam)) {
2379+
m_i2p_sam_session = std::make_unique<i2p::sam::Session>(GetDataDir() / "i2p_private_key",
2380+
i2p_sam.proxy, &interruptNet);
2381+
}
2382+
23772383
for (const auto& strDest : connOptions.vSeedNodes) {
23782384
AddAddrFetch(strDest);
23792385
}

src/net.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <compat.h>
1515
#include <crypto/siphash.h>
1616
#include <hash.h>
17+
#include <i2p.h>
1718
#include <net_permissions.h>
1819
#include <netaddress.h>
1920
#include <optional.h>
@@ -831,6 +832,7 @@ class CConnman
831832
std::vector<std::string> m_specified_outgoing;
832833
std::vector<std::string> m_added_nodes;
833834
std::vector<bool> m_asmap;
835+
bool m_i2p_accept_incoming;
834836
};
835837

836838
void Init(const Options& connOptions) {
@@ -1221,8 +1223,20 @@ class CConnman
12211223
Mutex mutexMsgProc;
12221224
std::atomic<bool> flagInterruptMsgProc{false};
12231225

1226+
/**
1227+
* This is signaled when network activity should cease.
1228+
* A pointer to it is saved in `m_i2p_sam_session`, so make sure that
1229+
* the lifetime of `interruptNet` is not shorter than
1230+
* the lifetime of `m_i2p_sam_session`.
1231+
*/
12241232
CThreadInterrupt interruptNet;
12251233

1234+
/**
1235+
* I2P SAM session.
1236+
* Used to accept incoming and make outgoing I2P connections.
1237+
*/
1238+
std::unique_ptr<i2p::sam::Session> m_i2p_sam_session;
1239+
12261240
std::thread threadDNSAddressSeed;
12271241
std::thread threadSocketHandler;
12281242
std::thread threadOpenAddedConnections;

src/rpc/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ static UniValue GetNetworksInfo()
546546
UniValue networks(UniValue::VARR);
547547
for (int n = 0; n < NET_MAX; ++n) {
548548
enum Network network = static_cast<enum Network>(n);
549-
if (network == NET_UNROUTABLE || network == NET_I2P || network == NET_CJDNS || network == NET_INTERNAL) continue;
549+
if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue;
550550
proxyType proxy;
551551
UniValue obj(UniValue::VOBJ);
552552
GetProxy(network, proxy);

test/functional/feature_proxy.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@
4949
NET_IPV4 = "ipv4"
5050
NET_IPV6 = "ipv6"
5151
NET_ONION = "onion"
52+
NET_I2P = "i2p"
5253

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

5657

5758
class ProxyTest(BitcoinTestFramework):
@@ -90,11 +91,15 @@ def setup_nodes(self):
9091
self.serv3 = Socks5Server(self.conf3)
9192
self.serv3.start()
9293

94+
# We will not try to connect to this.
95+
self.i2p_sam = ('127.0.0.1', 7656)
96+
9397
# Note: proxies are not used to connect to local nodes. This is because the proxy to
9498
# use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost.
9599
args = [
96100
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
97-
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
101+
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),
102+
'-i2psam=%s:%i' % (self.i2p_sam), '-i2pacceptincoming=0', '-proxyrandomize=0'],
98103
['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
99104
[]
100105
]
@@ -199,9 +204,16 @@ def networks_dict(d):
199204
n0 = networks_dict(self.nodes[0].getnetworkinfo())
200205
assert_equal(NETWORKS, n0.keys())
201206
for net in NETWORKS:
202-
assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
203-
assert_equal(n0[net]['proxy_randomize_credentials'], True)
207+
if net == NET_I2P:
208+
expected_proxy = ''
209+
expected_randomize = False
210+
else:
211+
expected_proxy = '%s:%i' % (self.conf1.addr)
212+
expected_randomize = True
213+
assert_equal(n0[net]['proxy'], expected_proxy)
214+
assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize)
204215
assert_equal(n0['onion']['reachable'], True)
216+
assert_equal(n0['i2p']['reachable'], False)
205217

206218
n1 = networks_dict(self.nodes[1].getnetworkinfo())
207219
assert_equal(NETWORKS, n1.keys())
@@ -211,21 +223,36 @@ def networks_dict(d):
211223
assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
212224
assert_equal(n1['onion']['proxy_randomize_credentials'], False)
213225
assert_equal(n1['onion']['reachable'], True)
226+
assert_equal(n1['i2p']['proxy'], '%s:%i' % (self.i2p_sam))
227+
assert_equal(n1['i2p']['proxy_randomize_credentials'], False)
228+
assert_equal(n1['i2p']['reachable'], True)
214229

215230
n2 = networks_dict(self.nodes[2].getnetworkinfo())
216231
assert_equal(NETWORKS, n2.keys())
217232
for net in NETWORKS:
218-
assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
219-
assert_equal(n2[net]['proxy_randomize_credentials'], True)
233+
if net == NET_I2P:
234+
expected_proxy = ''
235+
expected_randomize = False
236+
else:
237+
expected_proxy = '%s:%i' % (self.conf2.addr)
238+
expected_randomize = True
239+
assert_equal(n2[net]['proxy'], expected_proxy)
240+
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
220241
assert_equal(n2['onion']['reachable'], True)
242+
assert_equal(n2['i2p']['reachable'], False)
221243

222244
if self.have_ipv6:
223245
n3 = networks_dict(self.nodes[3].getnetworkinfo())
224246
assert_equal(NETWORKS, n3.keys())
225247
for net in NETWORKS:
226-
assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
248+
if net == NET_I2P:
249+
expected_proxy = ''
250+
else:
251+
expected_proxy = '[%s]:%i' % (self.conf3.addr)
252+
assert_equal(n3[net]['proxy'], expected_proxy)
227253
assert_equal(n3[net]['proxy_randomize_credentials'], False)
228254
assert_equal(n3['onion']['reachable'], False)
255+
assert_equal(n3['i2p']['reachable'], False)
229256

230257

231258
if __name__ == '__main__':

0 commit comments

Comments
 (0)