A Python port of the Invisible Internet Project (I2P)
at main 107 lines 3.5 kB view raw
1"""Extract NTCP2 connection info from a Java I2P router's router.info file. 2 3Reads the binary RouterInfo, parses it, and writes the NTCP2 host, port, 4and static public key to a JSON file. 5 6Usage: 7 python extract_java_ri.py --ri-file /shared/router.info \ 8 --output /shared/java_ntcp2_info.json 9""" 10 11import argparse 12import base64 13import json 14import sys 15import traceback 16 17sys.path.insert(0, "/app/src") 18 19from i2p_data.router import RouterInfo 20 21 22def extract_ntcp2_info(ri_path, output_path): 23 """Parse router.info and extract NTCP2 address details.""" 24 try: 25 with open(ri_path, "rb") as f: 26 data = f.read() 27 28 print(f"RouterInfo file: {len(data)} bytes", flush=True) 29 30 ri = RouterInfo.from_bytes(data) 31 print(f"Parsed RouterInfo: {ri}", flush=True) 32 print(f" Published: {ri.published}", flush=True) 33 print(f" Addresses: {len(ri.addresses)}", flush=True) 34 print(f" Options: {ri.options}", flush=True) 35 36 # Find NTCP2 address 37 ntcp2_addr = None 38 for addr in ri.addresses: 39 print(f" Address: {addr} options={addr.options}", flush=True) 40 if addr.transport == "NTCP2": 41 ntcp2_addr = addr 42 break 43 44 if ntcp2_addr is None: 45 # Some newer Java routers use "NTCP" for NTCP2 46 for addr in ri.addresses: 47 if "NTCP" in addr.transport.upper(): 48 ntcp2_addr = addr 49 break 50 51 if ntcp2_addr is None: 52 result = { 53 "status": "error", 54 "error": "No NTCP2 address found in RouterInfo", 55 "transports": [a.transport for a in ri.addresses], 56 } 57 else: 58 # The "s" option contains the X25519 static key (base64) 59 static_key_b64 = ntcp2_addr.options.get("s", "") 60 host = ntcp2_addr.get_host() or "127.0.0.1" 61 port = ntcp2_addr.get_port() or 12345 62 63 if static_key_b64: 64 # I2P uses modified base64: ~ instead of /, - instead of + 65 std_b64 = static_key_b64.replace("~", "/").replace("-", "+") 66 # Ensure proper padding 67 padding = 4 - len(std_b64) % 4 68 if padding != 4: 69 std_b64 += "=" * padding 70 static_key = base64.b64decode(std_b64) 71 else: 72 static_key = b"" 73 74 result = { 75 "status": "ok", 76 "host": host, 77 "port": port, 78 "static_key_b64": static_key_b64, 79 "static_key_hex": static_key.hex() if static_key else "", 80 "static_key_len": len(static_key), 81 "transport": ntcp2_addr.transport, 82 "all_options": ntcp2_addr.options, 83 "signature_valid": ri.verify(), 84 } 85 86 with open(output_path, "w") as f: 87 json.dump(result, f, indent=2) 88 print(f"Result: {json.dumps(result, indent=2)}", flush=True) 89 90 except Exception as e: 91 result = {"status": "error", "error": str(e)} 92 traceback.print_exc() 93 with open(output_path, "w") as f: 94 json.dump(result, f) 95 print(f"Error: {e}", flush=True) 96 97 98def main(): 99 parser = argparse.ArgumentParser() 100 parser.add_argument("--ri-file", required=True) 101 parser.add_argument("--output", required=True) 102 args = parser.parse_args() 103 extract_ntcp2_info(args.ri_file, args.output) 104 105 106if __name__ == "__main__": 107 main()