Merge bitcoin/bitcoin#27452: test: cover addrv2 anchors by adding TorV3 to CAddress in messages.py

ba8ab4fc54 test: cover addrv2 support in anchors.dat with a TorV3 address (Matthew Zipkin)
b4bee4bbf4 test: add keep_alive option to socks5 proxy in test_framework (Matthew Zipkin)
5aaf988ccc test: cover TorV3 address in p2p_addrv2_relay (Matthew Zipkin)
80f64a3d40 test: add support for all networks in CAddress in messages.py (brunoerg)

Pull request description:

  Closes https://github.com/bitcoin/bitcoin/issues/27140

  Adds test coverage for https://github.com/bitcoin/bitcoin/pull/20516 to ensure that https://github.com/bitcoin/bitcoin/issues/20511 is completed and may be closed.

  This PR adds a test case to `feature_anchors.py` where an onion v3 address is set as a blocks-only relay peer and then shutdown, ensuring that the address is saved to anchors.dat in addrv2 format. We then ensure that bitcoin attempts to reconnect to that anchor address on restart.

  To compute the addrv2 serialization of the onion v3 address, I added logic to `CAddress` in `messages.py`. This new logic is covered by extending `p2p_addrv2_relay.py` to include an onion v3 address. Future work will be adding coverage for ipv6, torv2 and cjdns in these modules and also `feature_proxy.py`

  Also includes de/serialization unit test for `CAddress` in test framework.

ACKs for top commit:
  jonatack:
    ACK ba8ab4fc54
  brunoerg:
    crACK ba8ab4fc54
  willcl-ark:
    ACK ba8ab4fc54

Tree-SHA512: 7220e30d7cb975903d9ac575a7215a08e8f784c24c5741561affcbde12fb92cbf8704cb42e66494b788ba6ed4bb255fb0cc327e4f2190fae50c0ed9f336c0ff0
This commit is contained in:
fanquake
2023-08-02 11:54:28 +01:00
5 changed files with 143 additions and 17 deletions

View File

@@ -27,6 +27,7 @@ import random
import socket
import struct
import time
import unittest
from test_framework.siphash import siphash256
from test_framework.util import assert_equal
@@ -77,6 +78,10 @@ def sha256(s):
return hashlib.sha256(s).digest()
def sha3(s):
return hashlib.sha3_256(s).digest()
def hash256(s):
return sha256(sha256(s))
@@ -229,16 +234,25 @@ class CAddress:
# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
NET_IPV4 = 1
NET_IPV6 = 2
NET_TORV3 = 4
NET_I2P = 5
NET_CJDNS = 6
ADDRV2_NET_NAME = {
NET_IPV4: "IPv4",
NET_I2P: "I2P"
NET_IPV6: "IPv6",
NET_TORV3: "TorV3",
NET_I2P: "I2P",
NET_CJDNS: "CJDNS"
}
ADDRV2_ADDRESS_LENGTH = {
NET_IPV4: 4,
NET_I2P: 32
NET_IPV6: 16,
NET_TORV3: 32,
NET_I2P: 32,
NET_CJDNS: 16
}
I2P_PAD = "===="
@@ -285,7 +299,7 @@ class CAddress:
self.nServices = deser_compact_size(f)
self.net = struct.unpack("B", f.read(1))[0]
assert self.net in (self.NET_IPV4, self.NET_I2P)
assert self.net in self.ADDRV2_NET_NAME
address_length = deser_compact_size(f)
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
@@ -293,14 +307,25 @@ class CAddress:
addr_bytes = f.read(address_length)
if self.net == self.NET_IPV4:
self.ip = socket.inet_ntoa(addr_bytes)
else:
elif self.net == self.NET_IPV6:
self.ip = socket.inet_ntop(socket.AF_INET6, addr_bytes)
elif self.net == self.NET_TORV3:
prefix = b".onion checksum"
version = bytes([3])
checksum = sha3(prefix + addr_bytes + version)[:2]
self.ip = b32encode(addr_bytes + checksum + version).decode("ascii").lower() + ".onion"
elif self.net == self.NET_I2P:
self.ip = b32encode(addr_bytes)[0:-len(self.I2P_PAD)].decode("ascii").lower() + ".b32.i2p"
elif self.net == self.NET_CJDNS:
self.ip = socket.inet_ntop(socket.AF_INET6, addr_bytes)
else:
raise Exception(f"Address type not supported")
self.port = struct.unpack(">H", f.read(2))[0]
def serialize_v2(self):
"""Serialize in addrv2 format (BIP155)"""
assert self.net in (self.NET_IPV4, self.NET_I2P)
assert self.net in self.ADDRV2_NET_NAME
r = b""
r += struct.pack("<I", self.time)
r += ser_compact_size(self.nServices)
@@ -308,10 +333,20 @@ class CAddress:
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
if self.net == self.NET_IPV4:
r += socket.inet_aton(self.ip)
else:
elif self.net == self.NET_IPV6:
r += socket.inet_pton(socket.AF_INET6, self.ip)
elif self.net == self.NET_TORV3:
sfx = ".onion"
assert self.ip.endswith(sfx)
r += b32decode(self.ip[0:-len(sfx)], True)[0:32]
elif self.net == self.NET_I2P:
sfx = ".b32.i2p"
assert self.ip.endswith(sfx)
r += b32decode(self.ip[0:-len(sfx)] + self.I2P_PAD, True)
elif self.net == self.NET_CJDNS:
r += socket.inet_pton(socket.AF_INET6, self.ip)
else:
raise Exception(f"Address type not supported")
r += struct.pack(">H", self.port)
return r
@@ -1852,3 +1887,19 @@ class msg_sendtxrcncl:
def __repr__(self):
return "msg_sendtxrcncl(version=%lu, salt=%lu)" %\
(self.version, self.salt)
class TestFrameworkScript(unittest.TestCase):
def test_addrv2_encode_decode(self):
def check_addrv2(ip, net):
addr = CAddress()
addr.net, addr.ip = net, ip
ser = addr.serialize_v2()
actual = CAddress()
actual.deserialize_v2(BytesIO(ser))
self.assertEqual(actual, addr)
check_addrv2("1.65.195.98", CAddress.NET_IPV4)
check_addrv2("2001:41f0::62:6974:636f:696e", CAddress.NET_IPV6)
check_addrv2("2bqghnldu6mcug4pikzprwhtjjnsyederctvci6klcwzepnjd46ikjyd.onion", CAddress.NET_TORV3)
check_addrv2("255fhcp6ajvftnyo7bwz3an3t4a4brhopm3bamyh2iu5r3gnr2rq.b32.i2p", CAddress.NET_I2P)
check_addrv2("fc32:17ea:e415:c3bf:9808:149d:b5a2:c9aa", CAddress.NET_CJDNS)

View File

@@ -40,6 +40,7 @@ class Socks5Configuration():
self.af = socket.AF_INET # Bind address family
self.unauth = False # Support unauthenticated
self.auth = False # Support authentication
self.keep_alive = False # Do not automatically close connections
class Socks5Command():
"""Information about an incoming socks5 command."""
@@ -115,13 +116,14 @@ class Socks5Connection():
cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
self.serv.queue.put(cmdin)
logger.info('Proxy: %s', cmdin)
logger.debug('Proxy: %s', cmdin)
# Fall through to disconnect
except Exception as e:
logger.exception("socks5 request handling failed.")
self.serv.queue.put(e)
finally:
self.conn.close()
if not self.serv.keep_alive:
self.conn.close()
class Socks5Server():
def __init__(self, conf):
@@ -133,6 +135,7 @@ class Socks5Server():
self.running = False
self.thread = None
self.queue = queue.Queue() # report connections and exceptions to client
self.keep_alive = conf.keep_alive
def run(self):
while self.running:
@@ -157,4 +160,3 @@ class Socks5Server():
s.connect(self.conf.addr)
s.close()
self.thread.join()