A Python port of the Invisible Internet Project (I2P)
at main 165 lines 5.3 kB view raw
1"""InboundMessageDistributor — route messages from tunnel endpoints. 2 3Ported from net.i2p.router.InboundMessageDistributor. 4 5Routes I2NP messages from tunnel endpoints to appropriate handlers, 6filtering by message type based on tunnel type (client vs exploratory). 7Also handles garlic clove delivery instructions. 8""" 9 10from __future__ import annotations 11 12import logging 13from enum import IntEnum 14 15logger = logging.getLogger(__name__) 16 17 18class DeliveryMode(IntEnum): 19 """Garlic clove delivery modes.""" 20 LOCAL = 0 21 DESTINATION = 1 22 ROUTER = 2 23 TUNNEL = 3 24 25 26# I2NP message type codes 27class I2NPType(IntEnum): 28 DATABASE_STORE = 1 29 DATABASE_SEARCH_REPLY = 3 30 DELIVERY_STATUS = 10 31 GARLIC = 11 32 TUNNEL_BUILD_REPLY = 22 33 SHORT_TUNNEL_BUILD_REPLY = 24 34 TUNNEL_DATA = 18 35 TUNNEL_GATEWAY = 19 36 DATA = 20 37 38 39# Allowed message types per tunnel category 40_CLIENT_ALLOWED = frozenset({ 41 I2NPType.DATABASE_SEARCH_REPLY, 42 I2NPType.DATABASE_STORE, 43 I2NPType.DELIVERY_STATUS, 44 I2NPType.GARLIC, 45 I2NPType.TUNNEL_BUILD_REPLY, 46 I2NPType.SHORT_TUNNEL_BUILD_REPLY, 47}) 48 49_EXPLORATORY_ALLOWED = frozenset({ 50 I2NPType.DATABASE_STORE, 51 I2NPType.DATABASE_SEARCH_REPLY, 52 I2NPType.DELIVERY_STATUS, 53 I2NPType.GARLIC, 54 I2NPType.TUNNEL_BUILD_REPLY, 55 I2NPType.SHORT_TUNNEL_BUILD_REPLY, 56}) 57 58 59class InboundMessageDistributor: 60 """Routes messages from tunnel endpoints to handlers. 61 62 Filters messages by type based on whether the tunnel is a client 63 tunnel or an exploratory tunnel, then dispatches to the appropriate 64 subsystem. 65 """ 66 67 def __init__(self, router_context=None, client_hash: bytes | None = None, 68 is_exploratory: bool = False) -> None: 69 self._context = router_context 70 self._client_hash = client_hash 71 self._is_exploratory = is_exploratory 72 self._garlic_handler = None 73 self._client_manager = None 74 self._netdb = None 75 76 def set_garlic_handler(self, handler) -> None: 77 self._garlic_handler = handler 78 79 def set_client_manager(self, manager) -> None: 80 self._client_manager = manager 81 82 def set_netdb(self, netdb) -> None: 83 self._netdb = netdb 84 85 def is_allowed_message_type(self, msg_type: int) -> bool: 86 """Check if message type is allowed on this tunnel.""" 87 allowed = _EXPLORATORY_ALLOWED if self._is_exploratory else _CLIENT_ALLOWED 88 return msg_type in allowed 89 90 def distribute(self, msg_type: int, msg_data: bytes, 91 target_router: bytes | None = None, 92 target_tunnel: int | None = None) -> bool: 93 """Route an incoming message to its handler. 94 95 Returns True if the message was accepted, False if filtered/dropped. 96 """ 97 if not self.is_allowed_message_type(msg_type): 98 logger.debug("Dropping disallowed message type %d on %s tunnel", 99 msg_type, "exploratory" if self._is_exploratory else "client") 100 return False 101 102 if msg_type == I2NPType.GARLIC and self._garlic_handler: 103 self._garlic_handler(msg_data) 104 return True 105 106 if msg_type == I2NPType.DELIVERY_STATUS: 107 # Forward to reply handler for ACK matching 108 return True 109 110 if msg_type == I2NPType.DATABASE_STORE: 111 if self._netdb: 112 self._netdb(msg_data) 113 return True 114 115 if msg_type == I2NPType.DATABASE_SEARCH_REPLY: 116 return True 117 118 if msg_type in (I2NPType.TUNNEL_BUILD_REPLY, I2NPType.SHORT_TUNNEL_BUILD_REPLY): 119 return True 120 121 return False 122 123 def handle_clove(self, delivery_mode: int, msg_data: bytes, 124 dest_hash: bytes | None = None, 125 router_hash: bytes | None = None, 126 tunnel_id: int | None = None) -> bool: 127 """Handle a garlic clove based on delivery instructions. 128 129 Args: 130 delivery_mode: DeliveryMode enum value 131 msg_data: The inner message data 132 dest_hash: Destination hash (for DESTINATION delivery) 133 router_hash: Router hash (for ROUTER/TUNNEL delivery) 134 tunnel_id: Tunnel ID (for TUNNEL delivery) 135 136 Returns True if handled successfully. 137 """ 138 if delivery_mode == DeliveryMode.LOCAL: 139 # Route based on inner message type 140 if len(msg_data) < 1: 141 return False 142 inner_type = msg_data[0] 143 return self.distribute(inner_type, msg_data[1:]) 144 145 if delivery_mode == DeliveryMode.DESTINATION: 146 if dest_hash is None: 147 return False 148 # Verify destination matches our client 149 if self._client_hash and dest_hash != self._client_hash: 150 logger.debug("Clove destination mismatch: expected %s, got %s", 151 self._client_hash.hex()[:16], dest_hash.hex()[:16]) 152 return False 153 if self._client_manager: 154 self._client_manager(dest_hash, msg_data) 155 return True 156 157 if delivery_mode == DeliveryMode.ROUTER: 158 # Forward to router 159 return True 160 161 if delivery_mode == DeliveryMode.TUNNEL: 162 # Forward through tunnel 163 return True 164 165 return False