A Python port of the Invisible Internet Project (I2P)
1"""Diagnostic: connect to a single peer, dump all details."""
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_data.keys_and_cert import KeysAndCert
14from i2p_netdb.reseed import ReseedClient
15from i2p_router.identity import (
16 RouterKeyBundle,
17 create_full_router_identity,
18)
19from i2p_router.peer_connector import extract_ntcp2_address
20from i2p_transport.ntcp2_real_server import NTCP2RealConnector
21from i2p_transport.ntcp2_blocks import BLOCK_TERMINATION, decode_blocks
22
23logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(name)s %(message)s")
24logger = logging.getLogger("diag")
25
26
27async def main():
28 # 1. Generate keys
29 bundle = RouterKeyBundle.generate()
30 identity, ri = create_full_router_identity(bundle, "0.0.0.0", 9000)
31 our_ri_bytes = ri.to_bytes()
32
33 logger.info("=== OUR ROUTERINFO ===")
34 logger.info("RI total bytes: %d", len(our_ri_bytes))
35 logger.info("Identity bytes: %d", len(identity.to_bytes()))
36 logger.info("Signable bytes: %d", len(ri._signable_bytes()))
37 logger.info("Signature bytes: %d", len(ri.signature))
38 logger.info("Self-verify: %s", ri.verify())
39 logger.info("Options: %s", ri.options)
40 logger.info("Addresses: %s", [(a.transport, a.options) for a in ri.addresses])
41
42 # Roundtrip check
43 ri2 = RouterInfo.from_bytes(our_ri_bytes)
44 signable1 = ri._signable_bytes()
45 signable2 = ri2._signable_bytes()
46 logger.info("Roundtrip signable match: %s", signable1 == signable2)
47 logger.info("Roundtrip verify: %s", ri2.verify())
48
49 # Show identity hex
50 id_bytes = identity.to_bytes()
51 logger.info("Identity cert: %s", id_bytes[384:].hex())
52 logger.info("Signing pub: %s", bundle.signing_public.hex())
53 logger.info("Enc pub (X25519): %s", bundle.ntcp2_public.hex())
54
55 # Show full RI hex
56 logger.info("Full RI hex:\n%s", our_ri_bytes.hex())
57
58 # 2. Reseed to get peers
59 logger.info("=== RESEEDING ===")
60 client = ReseedClient(target_count=10, min_servers=1, timeout=15)
61 ri_list = await client.reseed()
62 logger.info("Got %d RIs from reseed", len(ri_list))
63
64 # 3. Try to connect to peers
65 connector_keypair = (bundle.ntcp2_private, bundle.ntcp2_public)
66 peer_ri_hash_for_connect = hashlib.sha256(identity.to_bytes()).digest()
67
68 random.shuffle(ri_list)
69 attempts = 0
70 for peer_bytes in ri_list:
71 if attempts >= 5:
72 break
73 try:
74 peer_ri = RouterInfo.from_bytes(peer_bytes)
75 params = extract_ntcp2_address(peer_ri)
76 if params is None:
77 continue
78
79 host, port, peer_static_pub, peer_iv = params
80 peer_identity_bytes = peer_ri.identity.to_bytes()
81 peer_hash = hashlib.sha256(peer_identity_bytes).digest()
82
83 attempts += 1
84 logger.info("=== CONNECTING TO %s:%d (hash=%s...) ===", host, port, peer_hash[:4].hex())
85 logger.info("Peer static pub: %s", peer_static_pub.hex())
86 logger.info("Peer IV: %s", peer_iv.hex())
87 logger.info("Peer RI hash: %s", peer_hash.hex())
88 logger.info("Peer RI valid: %s", peer_ri.verify())
89 logger.info("Peer options: %s", peer_ri.options)
90
91 connector = NTCP2RealConnector()
92 try:
93 conn = await asyncio.wait_for(
94 connector.connect(
95 host=host,
96 port=port,
97 our_static_key=connector_keypair,
98 our_ri_bytes=our_ri_bytes,
99 peer_static_pub=peer_static_pub,
100 peer_ri_hash=peer_hash,
101 peer_iv=peer_iv,
102 ),
103 timeout=15,
104 )
105 logger.info("CONNECTED! Reading first frame...")
106 try:
107 blocks = await asyncio.wait_for(conn.recv_frame(), timeout=5)
108 for block in blocks:
109 logger.info("Block type=%d len=%d data=%s",
110 block.block_type, len(block.data), block.data.hex())
111 if block.block_type == BLOCK_TERMINATION:
112 reason = block.data[-1] if block.data else -1
113 logger.info("TERMINATION reason=%d", reason)
114 except asyncio.TimeoutError:
115 logger.info("No frame received within 5s (peer might be OK!)")
116 except Exception as e:
117 logger.info("Error reading frame: %s", e)
118
119 await conn.close()
120
121 except asyncio.TimeoutError:
122 logger.info("Connection timed out")
123 except Exception as e:
124 logger.info("Connection failed: %s", e, exc_info=True)
125
126 except Exception as e:
127 logger.debug("Skip peer: %s", e)
128 continue
129
130 logger.info("=== DONE ===")
131
132
133if __name__ == "__main__":
134 asyncio.run(main())