Files
Hippolyzer/hippolyzer/lib/proxy/lludp_proxy.py

178 lines
7.3 KiB
Python
Raw Normal View History

import asyncio
2021-04-30 17:30:24 +00:00
import logging
import weakref
from typing import Optional, Tuple
from hippolyzer.lib.base.message.message_dot_xml import MessageDotXML
from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.base.network.transport import UDPPacket
from hippolyzer.lib.base.message.message import Message
2021-04-30 17:30:24 +00:00
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
from hippolyzer.lib.proxy.socks_proxy import SOCKS5Server, UDPProxyProtocol
LOG = logging.getLogger(__name__)
class SLSOCKS5Server(SOCKS5Server):
def __init__(self, session_manager=None):
super().__init__()
self.session_manager = weakref.proxy(session_manager)
def _udp_protocol_creator(self, source_addr):
return lambda: InterceptingLLUDPProxyProtocol(source_addr, self.session_manager)
class InterceptingLLUDPProxyProtocol(UDPProxyProtocol):
def __init__(self, source_addr: Tuple[str, int], session_manager: SessionManager):
2021-04-30 17:30:24 +00:00
super().__init__(source_addr)
self.session_manager: SessionManager = session_manager
2021-04-30 17:30:24 +00:00
self.serializer = UDPMessageSerializer()
self.deserializer = UDPMessageDeserializer(
settings=self.session_manager.settings,
2021-04-30 17:30:24 +00:00
)
self.message_xml = MessageDotXML()
self.session: Optional[Session] = None
loop = asyncio.get_event_loop_policy().get_event_loop()
self.resend_task = loop.create_task(self.attempt_resends())
async def attempt_resends(self):
while True:
await asyncio.sleep(0.1)
if self.session is None:
continue
for region in self.session.regions:
if not region.circuit or not region.circuit.is_alive:
continue
region.circuit.resend_unacked()
2021-04-30 17:30:24 +00:00
def _ensure_message_allowed(self, msg: Message):
2021-04-30 17:30:24 +00:00
if not self.message_xml.validate_udp_msg(msg.name):
LOG.warning(
f"Received {msg.name!r} over UDP, when it should come over the event queue. Discarding."
)
raise PermissionError(f"UDPBanned message {msg.name}")
def handle_proxied_packet(self, packet: UDPPacket):
2021-04-30 17:30:24 +00:00
region: Optional[ProxiedRegion] = None
# Try to do an initial region lookup so we have it for handle_proxied_packet()
if self.session:
region = self.session.region_by_circuit_addr(packet.far_addr)
# the proxied packet handler is allowed to mutate `packet.data` before
# the message gets parsed.
2021-04-30 17:30:24 +00:00
if AddonManager.handle_proxied_packet(self.session_manager, packet,
self.session, region):
2021-04-30 17:30:24 +00:00
return
message = self.deserializer.deserialize(packet.data)
message.direction = packet.direction
message.sender = packet.src_addr
message.meta.update(packet.meta)
2021-04-30 17:30:24 +00:00
assert message is not None
# Check for UDP bans on inbound messages
if packet.incoming:
self._ensure_message_allowed(message)
if not self.session:
# This proxy instance isn't tied to a session yet
# First message should be "UseCircuitCode" and should let us
# claim a pending session
if message.name == "UseCircuitCode" and packet.outgoing:
session_id = message["CircuitCode"][0]["SessionID"]
self.session = self.session_manager.claim_session(session_id)
if not self.session:
LOG.error(f"Wasn't able to claim session {session_id!r}! Generally this means that the "
f"login HTTP request was not intercepted for some reason. Have you configured "
f"the viewer to send all HTTP traffic through this proxy?")
return
else:
LOG.warning(
"Received unexpected message %s on %r before circuit open" % (
message.name, packet.far_addr
)
)
return
if message.name == "UseCircuitCode" and packet.outgoing:
# This will create a circuit, replace a circuit, or do nothing if
# circuit is already alive.
if not self.session.open_circuit(packet.src_addr, packet.dst_addr, self.transport):
LOG.warning("Couldn't open circuit to %r, did we have a region???" % (
packet.dst_addr,
))
return
region = self.session.region_by_circuit_addr(packet.far_addr)
if not region:
LOG.error("No circuit for %r, dropping packet!" % (packet.far_addr,))
return
# Process any ACKs for messages we injected first
region.circuit.collect_acks(message)
2021-04-30 17:30:24 +00:00
if message.name == "AgentMovementComplete":
self.session.main_region = region
if region.handle is None:
region.handle = message["Data"]["RegionHandle"]
LOG.info(f"Setting main region to {region!r}, had circuit addr {packet.far_addr!r}")
AddonManager.handle_region_changed(self.session, region)
if message.name == "RegionHandshake":
region.cache_id = message["RegionInfo"]["CacheID"]
self.session.objects.track_region_objects(region.handle)
if self.session_manager.settings.USE_VIEWER_OBJECT_CACHE:
try:
region.objects.load_cache()
except:
LOG.exception("Failed to load region cache, skipping")
2021-04-30 17:30:24 +00:00
try:
self.session.message_handler.handle(message)
except:
LOG.exception("Failed in session message handler")
2021-04-30 17:30:24 +00:00
try:
region.message_handler.handle(message)
except:
LOG.exception("Failed in region message handler")
message_logger = self.session_manager.message_logger
handled = AddonManager.handle_lludp_message(
self.session, region, message
)
# This message is owned by an async handler, drop it so it doesn't get
# sent with the normal flow.
if message.queued:
region.circuit.drop_message(message)
# Shouldn't mutate the message past this point, so log it now.
if message_logger:
message_logger.log_lludp_message(self.session, region, message)
2021-04-30 17:30:24 +00:00
if handled:
return
if message.name in ("CloseCircuit", "DisableSimulator"):
region.mark_dead()
elif message.name == "RegionHandshake":
region.name = str(message["RegionInfo"][0]["SimName"])
elif message.name == "AgentDataUpdate" and self.session:
self.session.active_group = message["AgentData"]["ActiveGroupID"]
2021-04-30 17:30:24 +00:00
# Send the message if it wasn't explicitly dropped or sent before
if not message.finalized:
2021-12-09 05:30:12 +00:00
region.circuit.send(message)
2021-04-30 17:30:24 +00:00
def close(self):
super().close()
if self.session:
AddonManager.handle_session_closed(self.session)
self.session_manager.close_session(self.session)
self.session = None
self.resend_task.cancel()