A Python port of the Invisible Internet Project (I2P)
at main 117 lines 3.8 kB view raw
1"""NTCP2 connector script for podman integration tests. 2 3Waits for the listener's key file, connects to it, performs the 4Noise_XK handshake, exchanges frames, writes results to a JSON file. 5 6Usage: 7 python router_connector.py --host 127.0.0.1 --port 6000 \ 8 --key-file /shared/listener_key.bin \ 9 --result-file /shared/connector_result.json 10""" 11 12import argparse 13import asyncio 14import json 15import os 16import struct 17import sys 18import time 19import traceback 20 21sys.path.insert(0, "/app/src") 22 23from i2p_crypto.x25519 import X25519DH 24from i2p_transport.ntcp2 import NTCP2Frame, FrameType 25from i2p_transport.ntcp2_connection import NTCP2Connection 26from i2p_transport.ntcp2_handshake import NTCP2Handshake 27 28 29async def _send_hs(writer, msg): 30 writer.write(struct.pack("!H", len(msg)) + msg) 31 await writer.drain() 32 33 34async def _recv_hs(reader): 35 lb = await reader.readexactly(2) 36 length = struct.unpack("!H", lb)[0] 37 return await reader.readexactly(length) 38 39 40def wait_for_key_file(path, timeout=30): 41 """Block until the listener's key file appears.""" 42 deadline = time.time() + timeout 43 while time.time() < deadline: 44 if os.path.exists(path) and os.path.getsize(path) == 32: 45 with open(path, "rb") as f: 46 return f.read() 47 time.sleep(0.2) 48 raise TimeoutError(f"Key file {path} not found after {timeout}s") 49 50 51async def run_connector(host, port, key_file, result_file): 52 result = {"status": "error", "error": "unknown"} 53 54 try: 55 peer_static_pub = wait_for_key_file(key_file) 56 print(f"Got listener key, connecting to {host}:{port}", flush=True) 57 58 static = X25519DH.generate_keypair() 59 reader, writer = await asyncio.wait_for( 60 asyncio.open_connection(host, port), timeout=10.0 61 ) 62 63 hs = NTCP2Handshake( 64 our_static=static, peer_static_pub=peer_static_pub, initiator=True 65 ) 66 msg1 = hs.create_message_1() 67 await _send_hs(writer, msg1) 68 msg2 = await asyncio.wait_for(_recv_hs(reader), timeout=10.0) 69 msg3 = hs.process_message_2(msg2) 70 await _send_hs(writer, msg3) 71 72 send_cipher, recv_cipher = hs.split() 73 conn = NTCP2Connection( 74 reader=reader, writer=writer, 75 cipher_send=send_cipher, cipher_recv=recv_cipher, 76 remote_hash=peer_static_pub, 77 ) 78 print("Handshake complete", flush=True) 79 80 # Send a frame to the listener 81 frame = NTCP2Frame(FrameType.I2NP, b"hello from connector") 82 await conn.send_frame(frame) 83 84 # Receive the listener's reply 85 received = await asyncio.wait_for(conn.recv_frame(), timeout=10.0) 86 print(f"Received: type={received.frame_type}, payload={received.payload!r}", flush=True) 87 88 result = { 89 "status": "ok", 90 "handshake": "complete", 91 "received_type": received.frame_type.value, 92 "received_payload": received.payload.decode("utf-8", errors="replace"), 93 "sent_frame": True, 94 } 95 96 conn._writer.close() 97 except Exception as e: 98 result = {"status": "error", "error": str(e)} 99 traceback.print_exc() 100 finally: 101 with open(result_file, "w") as f: 102 json.dump(result, f) 103 print(f"Result: {result}", flush=True) 104 105 106def main(): 107 parser = argparse.ArgumentParser() 108 parser.add_argument("--host", default="127.0.0.1") 109 parser.add_argument("--port", type=int, required=True) 110 parser.add_argument("--key-file", required=True) 111 parser.add_argument("--result-file", required=True) 112 args = parser.parse_args() 113 asyncio.run(run_connector(args.host, args.port, args.key_file, args.result_file)) 114 115 116if __name__ == "__main__": 117 main()