Skip to content

Commit

Permalink
merge bitcoin#22211: relay I2P addresses even if not reachable (by us)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwvg committed Apr 12, 2024
1 parent 7e08db5 commit fe66202
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/netaddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ class CNetAddr
*/
bool IsRelayable() const
{
return IsIPv4() || IsIPv6() || IsTor();
return IsIPv4() || IsIPv6() || IsTor() || IsI2P();
}

/**
Expand Down
30 changes: 18 additions & 12 deletions test/functional/p2p_addrv2_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal

I2P_ADDR = "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p"

ADDRS = []


class AddrReceiver(P2PInterface):
addrv2_received_and_checked = False
Expand All @@ -23,11 +27,8 @@ def __init__(self):
super().__init__(support_addrv2 = True)

def on_addrv2(self, message):
for addr in message.addrs:
assert_equal(addr.nServices, 1)
assert addr.ip.startswith('123.123.123.')
assert 8333 <= addr.port < 8343
self.addrv2_received_and_checked = True
if ADDRS == message.addrs:
self.addrv2_received_and_checked = True

def wait_for_addrv2(self):
self.wait_until(lambda: "addrv2" in self.last_message)
Expand All @@ -39,18 +40,21 @@ def set_test_params(self):
self.num_nodes = 1

def run_test(self):
ADDRS = []
for i in range(10):
addr = CAddress()
addr.time = int(self.mocktime) + i
addr.nServices = NODE_NETWORK
addr.ip = "123.123.123.{}".format(i % 256)
# Add one I2P address at an arbitrary position.
if i == 5:
addr.net = addr.NET_I2P
addr.ip = I2P_ADDR
else:
addr.ip = f"123.123.123.{i % 256}"
addr.port = 8333 + i
ADDRS.append(addr)

self.log.info('Create connection that sends addrv2 messages')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())

msg = msg_addrv2()

self.log.info('Send too-large addrv2 message')
Expand All @@ -63,22 +67,24 @@ def run_test(self):
self.log.info('Check that addrv2 message content is relayed and added to addrman')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())

msg.addrs = ADDRS
with self.nodes[0].assert_debug_log([
'Added 10 addresses from 127.0.0.1: 0 tried',
'received: addrv2 (131 bytes) peer=1',
# The I2P address is not added to node's own addrman because it has no
# I2P reachability (thus 10 - 1 = 9).
'Added 9 addresses from 127.0.0.1: 0 tried',
'received: addrv2 (159 bytes) peer=1',
]):
addr_source.send_and_ping(msg)

# Wait until "Added ..." before bumping mocktime to make sure addv2 is (almost) fully processed
with self.nodes[0].assert_debug_log([
'sending addrv2 (131 bytes) peer=2',
'sending addrv2 (159 bytes) peer=2',
]):
self.bump_mocktime(30 * 60)
addr_receiver.wait_for_addrv2()

assert addr_receiver.addrv2_received_and_checked
assert_equal(len(self.nodes[0].getnodeaddresses(count=0, network="i2p")), 0)

self.nodes[0].disconnect_p2ps()

Expand Down
31 changes: 24 additions & 7 deletions test/functional/test_framework/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Classes use __slots__ to ensure extraneous attributes aren't accidentally added
by tests, compromising their intended effect.
"""

from base64 import b32decode, b32encode
import copy
from collections import namedtuple
import hashlib
Expand Down Expand Up @@ -251,22 +251,30 @@ class CAddress:

# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
NET_IPV4 = 1
NET_I2P = 5

ADDRV2_NET_NAME = {
NET_IPV4: "IPv4"
NET_IPV4: "IPv4",
NET_I2P: "I2P"
}

ADDRV2_ADDRESS_LENGTH = {
NET_IPV4: 4
NET_IPV4: 4,
NET_I2P: 32
}

I2P_PAD = "===="

def __init__(self):
self.time = 0
self.nServices = 1
self.net = self.NET_IPV4
self.ip = "0.0.0.0"
self.port = 0

def __eq__(self, other):
return self.net == other.net and self.ip == other.ip and self.nServices == other.nServices and self.port == other.port and self.time == other.time

def deserialize(self, f, *, with_time=True):
"""Deserialize from addrv1 format (pre-BIP155)"""
if with_time:
Expand Down Expand Up @@ -299,24 +307,33 @@ def deserialize_v2(self, f):
self.nServices = deser_compact_size(f)

self.net = struct.unpack("B", f.read(1))[0]
assert self.net == self.NET_IPV4
assert self.net in (self.NET_IPV4, self.NET_I2P)

address_length = deser_compact_size(f)
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]

self.ip = socket.inet_ntoa(f.read(4))
addr_bytes = f.read(address_length)
if self.net == self.NET_IPV4:
self.ip = socket.inet_ntoa(addr_bytes)
else:
self.ip = b32encode(addr_bytes)[0:-len(self.I2P_PAD)].decode("ascii").lower() + ".b32.i2p"

self.port = struct.unpack(">H", f.read(2))[0]

def serialize_v2(self):
"""Serialize in addrv2 format (BIP155)"""
assert self.net == self.NET_IPV4
assert self.net in (self.NET_IPV4, self.NET_I2P)
r = b""
r += struct.pack("<I", self.time)
r += ser_compact_size(self.nServices)
r += struct.pack("B", self.net)
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
r += socket.inet_aton(self.ip)
if self.net == self.NET_IPV4:
r += socket.inet_aton(self.ip)
else:
sfx = ".b32.i2p"
assert self.ip.endswith(sfx)
r += b32decode(self.ip[0:-len(sfx)] + self.I2P_PAD, True)
r += struct.pack(">H", self.port)
return r

Expand Down

0 comments on commit fe66202

Please sign in to comment.