Merge #20685: Add I2P support using I2P SAM

a701fcf01f net: Do not skip the I2P network from GetNetworkNames() (Vasil Dimov)
0181e24439 net: recognize I2P from ParseNetwork() so that -onlynet=i2p works (Vasil Dimov)
b905363fa8 net: accept incoming I2P connections from CConnman (Vasil Dimov)
0635233a1e net: make outgoing I2P connections from CConnman (Vasil Dimov)
9559bd1404 net: add I2P to the reachability map (Vasil Dimov)
76c35c60f3 init: introduce I2P connectivity options (Vasil Dimov)
c22daa2ecf net: implement the necessary parts of the I2P SAM protocol (Vasil Dimov)
5bac7e45e1 net: extend Sock with a method to check whether connected (Vasil Dimov)
42c779f503 net: extend Sock with methods for robust send & read until terminator (Vasil Dimov)
ea1845315a net: extend Sock::Wait() to report a timeout (Vasil Dimov)
78fdfbea66 net: dedup MSG_NOSIGNAL and MSG_DONTWAIT definitions (Vasil Dimov)
34bcfab562 net: move the constant maxWait out of InterruptibleRecv() (Vasil Dimov)
cff65c4a27 net: extend CNetAddr::SetSpecial() to support I2P (Vasil Dimov)
f6c267db3b net: avoid unnecessary GetBindAddress() call (Vasil Dimov)
7c224fdac4 net: isolate the protocol-agnostic part of CConnman::AcceptConnection() (Vasil Dimov)
1f75a653dd net: get the bind address earlier in CConnman::AcceptConnection() (Vasil Dimov)
25605895af net: check for invalid socket earlier in CConnman::AcceptConnection() (Vasil Dimov)
545bc5f81d util: fix WriteBinaryFile() claiming success even if error occurred (Vasil Dimov)
8b6e4b3b23 util: fix ReadBinaryFile() returning partial contents (Vasil Dimov)
4cba2fdafa util: extract {Read,Write}BinaryFile() to its own files (Vasil Dimov)

Pull request description:

  Add I2P support by using the [I2P SAM](https://geti2p.net/en/docs/api/samv3) protocol. Unlike Tor, for incoming connections we get the I2P address of the peer (and they also receive ours when we are the connection initiator).

  Two new options are added:

  ```
    -i2psam=<ip:port>
         I2P SAM proxy to reach I2P peers and accept I2P connections (default:
         none)

    -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. Notice that listening for incoming
         I2P connections is done through the SAM proxy, not by binding to
         a local address and port (default: true)
  ```

  # Overview of the changes

  ## Make `ReadBinary()` and `WriteBinary()` reusable

  We would need to dump the I2P private key to a file and read it back later. Move those two functions out of `torcontrol.cpp`.

  ```
  util: extract {Read,Write}BinaryFile() to its own files
  util: fix ReadBinaryFile() returning partial contents
  util: fix WriteBinaryFile() claiming success even if error occurred
  ```

  ## Split `CConnman::AcceptConnection()`

  Most of `CConnman::AcceptConnection()` is agnostic of how the socket was accepted. The other part of it deals with the details of the `accept(2)` system call. Split those so that the protocol-agnostic part can be reused if we accept a socket by other means.

  ```
  net: check for invalid socket earlier in CConnman::AcceptConnection()
  net: get the bind address earlier in CConnman::AcceptConnection()
  net: isolate the protocol-agnostic part of CConnman::AcceptConnection()
  net: avoid unnecessary GetBindAddress() call
  ```

  ## Implement the I2P [SAM](https://geti2p.net/en/docs/api/samv3) protocol (not all of it)

  Just the parts that would enable us to make outgoing and accept incoming I2P connections.

  ```
  net: extend CNetAddr::SetSpecial() to support I2P
  net: move the constant maxWait out of InterruptibleRecv()
  net: dedup MSG_NOSIGNAL and MSG_DONTWAIT definitions
  net: extend Sock::Wait() to report a timeout
  net: extend Sock with methods for robust send & read until terminator
  net: extend Sock with a method to check whether connected
  net: implement the necessary parts of the I2P SAM protocol
  ```

  ## Use I2P SAM to connect to and accept connections from I2P peers

  Profit from all of the preceding commits.

  ```
  init: introduce I2P connectivity options
  net: add I2P to the reachability map
  net: make outgoing I2P connections from CConnman
  net: accept incoming I2P connections from CConnman
  net: recognize I2P from ParseNetwork() so that -onlynet=i2p works
  net: Do not skip the I2P network from GetNetworkNames()
  ```

ACKs for top commit:
  laanwj:
    re-ACK a701fcf01f
  jonatack:
    re-ACK a701fcf01f reviewed diff per `git range-diff ad89812 2a7bb34 a701fcf`, debug built and launched bitcoind with i2pd v2.35 running a dual I2P+Torv3 service with the I2P config settings listed below (did not test `onlynet=i2p`); operation appears nominal (same as it has been these past weeks), and tested the bitcoind help outputs grepping for `-i i2p` and the rpc getpeerinfo and getnetworkinfo helps

Tree-SHA512: de42090c9c0bf23b43b5839f5b4fc4b3a2657bde1e45c796b5f3c7bf83cb8ec6ca4278f8a89e45108ece92f9b573cafea3b42a06bc09076b40a196c909b6610e
This commit is contained in:
Wladimir J. van der Laan
2021-03-02 11:46:33 +01:00
22 changed files with 1304 additions and 113 deletions

View File

@@ -49,9 +49,10 @@ NET_UNROUTABLE = "not_publicly_routable"
NET_IPV4 = "ipv4"
NET_IPV6 = "ipv6"
NET_ONION = "onion"
NET_I2P = "i2p"
# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo()
NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION})
NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P})
class ProxyTest(BitcoinTestFramework):
@@ -90,11 +91,15 @@ class ProxyTest(BitcoinTestFramework):
self.serv3 = Socks5Server(self.conf3)
self.serv3.start()
# We will not try to connect to this.
self.i2p_sam = ('127.0.0.1', 7656)
# Note: proxies are not used to connect to local nodes. This is because the proxy to
# use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost.
args = [
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),
'-i2psam=%s:%i' % (self.i2p_sam), '-i2pacceptincoming=0', '-proxyrandomize=0'],
['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
[]
]
@@ -199,9 +204,16 @@ class ProxyTest(BitcoinTestFramework):
n0 = networks_dict(self.nodes[0].getnetworkinfo())
assert_equal(NETWORKS, n0.keys())
for net in NETWORKS:
assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n0[net]['proxy_randomize_credentials'], True)
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
expected_proxy = '%s:%i' % (self.conf1.addr)
expected_randomize = True
assert_equal(n0[net]['proxy'], expected_proxy)
assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n0['onion']['reachable'], True)
assert_equal(n0['i2p']['reachable'], False)
n1 = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(NETWORKS, n1.keys())
@@ -211,21 +223,36 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n1['onion']['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['reachable'], True)
assert_equal(n1['i2p']['proxy'], '%s:%i' % (self.i2p_sam))
assert_equal(n1['i2p']['proxy_randomize_credentials'], False)
assert_equal(n1['i2p']['reachable'], True)
n2 = networks_dict(self.nodes[2].getnetworkinfo())
assert_equal(NETWORKS, n2.keys())
for net in NETWORKS:
assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n2[net]['proxy_randomize_credentials'], True)
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
expected_proxy = '%s:%i' % (self.conf2.addr)
expected_randomize = True
assert_equal(n2[net]['proxy'], expected_proxy)
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n2['onion']['reachable'], True)
assert_equal(n2['i2p']['reachable'], False)
if self.have_ipv6:
n3 = networks_dict(self.nodes[3].getnetworkinfo())
assert_equal(NETWORKS, n3.keys())
for net in NETWORKS:
assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
if net == NET_I2P:
expected_proxy = ''
else:
expected_proxy = '[%s]:%i' % (self.conf3.addr)
assert_equal(n3[net]['proxy'], expected_proxy)
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
assert_equal(n3['i2p']['reachable'], False)
if __name__ == '__main__':

View File

@@ -105,7 +105,7 @@ class NetTest(BitcoinTestFramework):
assert_equal(peer_info[1][1]['connection_type'], 'inbound')
# Check dynamically generated networks list in getpeerinfo help output.
assert "(ipv4, ipv6, onion, not_publicly_routable)" in self.nodes[0].help("getpeerinfo")
assert "(ipv4, ipv6, onion, i2p, not_publicly_routable)" in self.nodes[0].help("getpeerinfo")
def test_getnettotals(self):
self.log.info("Test getnettotals")
@@ -156,7 +156,7 @@ class NetTest(BitcoinTestFramework):
assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"])
# Check dynamically generated networks list in getnetworkinfo help output.
assert "(ipv4, ipv6, onion)" in self.nodes[0].help("getnetworkinfo")
assert "(ipv4, ipv6, onion, i2p)" in self.nodes[0].help("getnetworkinfo")
def test_getaddednodeinfo(self):
self.log.info("Test getaddednodeinfo")