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

166 lines
6.9 KiB
Python
Raw Normal View History

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
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):
message: Optional[Message] = None
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)
deserialize_exc = None
try:
message = self.deserializer.deserialize(packet.data)
message.direction = packet.direction
message.sender = packet.src_addr
2021-04-30 17:30:24 +00:00
except Exception as e:
# Hang onto this since handle_proxied_packet doesn't need a parseable
# message. If that hook doesn't handle the packet then re-raise.
deserialize_exc = e
if AddonManager.handle_proxied_packet(self.session_manager, packet,
self.session, region, message):
# Swallow any error raised by above message deserialization, it was handled.
return
if deserialize_exc is not None:
# handle_proxied_packet() didn't deal with the error, so it's fatal.
raise deserialize_exc
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
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 and not message.dropped:
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"])
if not message.dropped:
region.circuit.send_message(message)
def close(self):
super().close()
if self.session:
AddonManager.handle_session_closed(self.session)
self.session_manager.close_session(self.session)
self.session = None