A Python port of the Invisible Internet Project (I2P)
at main 147 lines 5.7 kB view raw
1"""Full NTCP2 connection test: handshake + data exchange.""" 2import asyncio 3import base64 4import hashlib 5import logging 6import random 7import sys 8import time 9 10sys.path.insert(0, "src") 11 12from i2p_data.router import RouterInfo 13from i2p_netdb.reseed import ReseedClient 14from i2p_router.identity import ( 15 RouterKeyBundle, 16 create_full_router_identity, 17) 18from i2p_router.peer_connector import extract_ntcp2_address 19from i2p_transport.ntcp2_real_server import NTCP2RealConnector 20from i2p_transport.ntcp2_blocks import ( 21 BLOCK_DATETIME, BLOCK_I2NP, BLOCK_ROUTERINFO, BLOCK_TERMINATION, 22 BLOCK_PADDING, BLOCK_OPTIONS, 23) 24 25logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s") 26logger = logging.getLogger("full") 27 28BLOCK_NAMES = { 29 BLOCK_DATETIME: "DateTime", 30 BLOCK_I2NP: "I2NP", 31 BLOCK_ROUTERINFO: "RouterInfo", 32 BLOCK_TERMINATION: "Termination", 33 BLOCK_PADDING: "Padding", 34 BLOCK_OPTIONS: "Options", 35} 36 37 38async def main(): 39 bundle = RouterKeyBundle.generate() 40 identity, ri = create_full_router_identity(bundle, "0.0.0.0", 9000) 41 our_ri_bytes = ri.to_bytes() 42 43 logger.info("Our RI: %d bytes, verify=%s", len(our_ri_bytes), ri.verify()) 44 45 # Reseed 46 client = ReseedClient(target_count=20, min_servers=1, timeout=15) 47 ri_list = await client.reseed() 48 logger.info("Got %d peer RIs from reseed", len(ri_list)) 49 50 connector_keypair = (bundle.ntcp2_private, bundle.ntcp2_public) 51 random.shuffle(ri_list) 52 53 connected = 0 54 attempts = 0 55 for peer_bytes in ri_list: 56 if connected >= 3 or attempts >= 10: 57 break 58 try: 59 peer_ri = RouterInfo.from_bytes(peer_bytes) 60 params = extract_ntcp2_address(peer_ri) 61 if params is None: 62 continue 63 64 host, port, peer_static_pub, peer_iv = params 65 if len(peer_static_pub) != 32 or len(peer_iv) != 16: 66 continue 67 68 peer_identity_bytes = peer_ri.identity.to_bytes() 69 peer_hash = hashlib.sha256(peer_identity_bytes).digest() 70 71 attempts += 1 72 logger.info("Connecting to %s:%d (v%s, caps=%s)...", 73 host, port, 74 peer_ri.options.get("router.version", "?"), 75 peer_ri.options.get("caps", "?")) 76 77 connector = NTCP2RealConnector() 78 try: 79 conn = await asyncio.wait_for( 80 connector.connect( 81 host=host, 82 port=port, 83 our_static_key=connector_keypair, 84 our_ri_bytes=our_ri_bytes, 85 peer_static_pub=peer_static_pub, 86 peer_ri_hash=peer_hash, 87 peer_iv=peer_iv, 88 ), 89 timeout=10, 90 ) 91 connected += 1 92 logger.info("CONNECTED to %s:%d!", host, port) 93 94 # Read frames 95 frames_read = 0 96 try: 97 while frames_read < 5: 98 blocks = await asyncio.wait_for(conn.recv_frame(), timeout=10) 99 frames_read += 1 100 for block in blocks: 101 name = BLOCK_NAMES.get(block.block_type, f"Unknown({block.block_type})") 102 logger.info(" Frame %d: %s (%d bytes)", 103 frames_read, name, len(block.data)) 104 if block.block_type == BLOCK_TERMINATION: 105 reason = block.data[-1] if block.data else -1 106 logger.info(" -> Termination reason=%d", reason) 107 elif block.block_type == BLOCK_I2NP and len(block.data) >= 11: 108 msg_type = block.data[0] 109 logger.info(" -> I2NP msg_type=%d", msg_type) 110 elif block.block_type == BLOCK_ROUTERINFO and len(block.data) > 1: 111 flag = block.data[0] 112 ri_data = block.data[1:] 113 try: 114 peer_ri2 = RouterInfo.from_bytes(ri_data) 115 logger.info(" -> RI: %d addrs, v%s, verify=%s", 116 len(peer_ri2.addresses), 117 peer_ri2.options.get("router.version", "?"), 118 peer_ri2.verify()) 119 except Exception as e: 120 logger.info(" -> RI parse error: %s", e) 121 elif block.block_type == BLOCK_DATETIME: 122 import struct 123 ts = struct.unpack("!I", block.data[:4])[0] 124 logger.info(" -> DateTime: %d", ts) 125 except asyncio.TimeoutError: 126 logger.info(" No more frames within timeout (read %d frames)", frames_read) 127 except Exception as e: 128 logger.info(" Error reading frame: %s", e) 129 130 try: 131 await conn.close() 132 except Exception: 133 pass 134 135 except asyncio.TimeoutError: 136 logger.info("Connection timed out") 137 except Exception as e: 138 logger.info("Connection failed: %s", e) 139 140 except Exception as e: 141 continue 142 143 logger.info("=== DONE: %d/%d connections successful ===", connected, attempts) 144 145 146if __name__ == "__main__": 147 asyncio.run(main())