A Python port of the Invisible Internet Project (I2P)
at main 148 lines 4.5 kB view raw
1"""Config advisor — maps probe results to recommended RouterConfig values. 2 3Takes LocalProbeResult, NatProbeResult, and NetworkProbeResult and produces 4a ConfigRecommendation with explanations and warnings. 5""" 6 7from __future__ import annotations 8 9import socket 10from dataclasses import dataclass, field 11 12from i2p_apps.setup.local_probe import LocalProbeResult 13from i2p_apps.setup.stun_probe import NatProbeResult 14from i2p_apps.setup.network_probe import NetworkProbeResult 15 16GB = 1_073_741_824 # 1 GiB in bytes 17 18 19@dataclass 20class ConfigRecommendation: 21 """Recommended config values with explanations.""" 22 23 bandwidth_limit_kbps: int = 0 24 share_percentage: int = 50 25 inbound_tunnel_count: int = 5 26 outbound_tunnel_count: int = 5 27 floodfill: bool = False 28 nat_type: str = "unknown" 29 external_ip: str | None = None 30 upnp_enabled: bool = True 31 listen_port: int = 9700 32 warnings: list[str] = field(default_factory=list) 33 notes: list[str] = field(default_factory=list) 34 35 def to_router_config_overrides(self) -> dict: 36 """Return dict of RouterConfig field overrides.""" 37 return { 38 "bandwidth_limit_kbps": self.bandwidth_limit_kbps, 39 "share_percentage": self.share_percentage, 40 "inbound_tunnel_count": self.inbound_tunnel_count, 41 "outbound_tunnel_count": self.outbound_tunnel_count, 42 "floodfill": self.floodfill, 43 "nat_type": self.nat_type, 44 "external_ip": self.external_ip, 45 "upnp_enabled": self.upnp_enabled, 46 "listen_port": self.listen_port, 47 } 48 49 50def _find_available_port(preferred: int) -> int: 51 """Check if preferred port is available, return it or next available.""" 52 try: 53 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 54 s.bind(("0.0.0.0", preferred)) 55 return preferred 56 except OSError: 57 # Port in use, try next 58 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 59 s.bind(("0.0.0.0", 0)) 60 return s.getsockname()[1] 61 62 63def advise( 64 local: LocalProbeResult, 65 nat: NatProbeResult | None = None, 66 network: NetworkProbeResult | None = None, 67) -> ConfigRecommendation: 68 """Map probe results to recommended config values.""" 69 warnings: list[str] = [] 70 notes: list[str] = [] 71 72 # CPU → tunnel count + floodfill eligibility 73 crypto = local.crypto_ops_per_sec 74 if crypto > 50000: 75 tunnels = 20 76 floodfill_eligible = True 77 elif crypto > 10000: 78 tunnels = 10 79 floodfill_eligible = True 80 elif crypto > 2000: 81 tunnels = 5 82 floodfill_eligible = False 83 else: 84 tunnels = 2 85 floodfill_eligible = False 86 87 # Bandwidth → share percentage + limit 88 share = 50 89 limit = 0 90 if network and network.bandwidth: 91 bw = network.bandwidth.download_mbps 92 if bw >= 100: 93 share = 80 94 limit = 80_000 95 elif bw >= 10: 96 share = 60 97 limit = int(bw * 600) 98 elif bw >= 1: 99 share = 40 100 limit = int(bw * 400) 101 else: 102 share = 30 103 limit = int(bw * 300) 104 105 # NAT → UPnP + warnings 106 upnp = True 107 nat_type = "unknown" 108 external_ip = None 109 110 if nat: 111 nat_type = nat.nat_type 112 external_ip = nat.external_ip 113 114 if nat.nat_type == "symmetric": 115 warnings.append( 116 "Symmetric NAT detected — inbound connections will not work. " 117 "SSU2 relay mode recommended." 118 ) 119 upnp = False 120 elif nat.nat_type == "cone": 121 upnp = True 122 elif not nat.nat_present: 123 upnp = False 124 notes.append("Open internet detected — no NAT traversal needed.") 125 126 # Floodfill: requires good CPU + good bandwidth + non-symmetric NAT 127 floodfill = ( 128 floodfill_eligible 129 and share >= 60 130 and (nat is None or nat.nat_type != "symmetric") 131 ) 132 133 # Port selection 134 listen_port = _find_available_port(9700) 135 136 return ConfigRecommendation( 137 bandwidth_limit_kbps=limit, 138 share_percentage=share, 139 inbound_tunnel_count=tunnels, 140 outbound_tunnel_count=tunnels, 141 floodfill=floodfill, 142 nat_type=nat_type, 143 external_ip=external_ip, 144 upnp_enabled=upnp, 145 listen_port=listen_port, 146 warnings=warnings, 147 notes=notes, 148 )