Files
Hippolyzer/tests/proxy/integration/test_lludp.py
Salad Dais 75e9d03d70 Move tests out of the package
This will help us get better coverage info, and prevent packaging
test data with the sdist.
2021-05-01 00:34:46 +00:00

172 lines
8.8 KiB
Python

from __future__ import annotations
import random
import struct
from typing import *
import unittest
import lazy_object_proxy
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.objects import Object
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket, Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
from . import BaseIntegrationTest
class MockAddon(BaseAddon):
def __init__(self):
self.events = []
def handle_session_init(self, session: Session):
self.events.append(("session_init", session.id))
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
self.events.append(("lludp", session.id, region.circuit_addr, message.name))
def handle_object_updated(self, session: Session, region: ProxiedRegion,
obj: Object, updated_props: Set[str]):
self.events.append(("object_update", session.id, region.circuit_addr, obj.LocalID))
class LLUDPIntegrationTests(BaseIntegrationTest):
def setUp(self) -> None:
super().setUp()
self.addon = MockAddon()
AddonManager.init([], self.session_manager, [self.addon])
def _make_objectupdate_compressed(self, localid: Optional[int] = None):
if localid is None:
localid = random.getrandbits(32)
return b'\x00\x00\x00\x0c\xba\x00\r\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x03\xd0\x04\x00\x10' \
b'\xe6\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_' + struct.pack("<I", localid) + \
b'\t\x00\xcdG\x00\x00\x03\x00\x00\x00\x1cB\x00\x00\x1cB\xcd\xcc\xcc=\xedG,' \
b'B\x9e\xb1\x9eBff\xa0A\x00\x00\x00\x00\x00\x00\x00\x00[' \
b'\x8b\xf8\xbe\xc0\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x00\x00\x00\x00\x00' \
b'\x00\x00\x00\x00\x00\xa2=\x010\x00\x11\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed' \
b'\x15F_@ \x00\x00\x00\x00d\x96\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
b'\x00?\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x00\x00\x00\x003\x00ff\x86\xbf' \
b'\x00ff\x86?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x89UgG$\xcbC' \
b'\xed\x92\x0bG\xca\xed\x15F_\x10\x00\x00\x003\x00\x01\x01\x00\x00\x00\x00\xdb\x0f\xc9@\xa6' \
b'\x9b\xc4=\xd0\x04\x00\x10\xe6\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\\\x04\x00\x00\t' \
b'\x00\xd3G\x00\x00\x03\x00\x00\x00\x1cB\x00\x00\x1cB\xcd\xcc\xcc=\t\x08\x9cA\xf2\x03' \
b'\xa5Bff\xa0A\x00\x00\x00\x00\x00\x00\x00\x00[' \
b'\x8b\xf8\xbe\xc0\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x00\x00\x00\x00\x00' \
b'\x00\x00\x00\x00\x00\xa2=\x010\x00\x11\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed' \
b'\x15F_@ \x00\x00\x00\x00d\x96\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
b'\x00?\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x00\x00\x00\x003\x00ff\x86\xbf' \
b'\x00ff\x86?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x89UgG$\xcbC' \
b'\xed\x92\x0bG\xca\xed\x15F_\x10\x00\x00\x003\x00\x01\x01\x00\x00\x00\x00\xdb\x0f\xc9@\xa6' \
b'\x9b\xc4=\xd0\x04\x00\x10\xe6\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\xe2\x05\x00\x00' \
b'\t\x00\xbbG\x00\x00\x03\x00\x00\x00\x1cB\x00\x00\x1cB\xcd\xcc\xcc=\x0f5\x97AY\x98ZBff' \
b'\xa0A\x00\x00\x00\x00\x00\x00\x00\x00\xe6Y0\xbf\xc0\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG' \
b'\xca\xed\x15F_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa2=\x010\x00\x11\x00\x00\x00' \
b'\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_@ ' \
b'\x00\x00\x00\x00d\x96\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
b'?\x00\x00\x00\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x00\x00\x00\x003\x00ff\x86\xbf' \
b'\x00ff\x86?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x89UgG$\xcbC' \
b'\xed\x92\x0bG\xca\xed\x15F_\x10\x00\x00\x003\x00\x01\x01\x00\x00\x00\x00\xdb\x0f\xc9@\xa6' \
b'\x9b\xc4='
async def test_session_claiming(self):
# Need a UseCircuitCode to claim a pending session
msg = ProxiedMessage(
"UseCircuitCode",
Block("CircuitCode", Code=self.circuit_code, SessionID=self.session.id,
ID=self.session.agent_id),
packet_id=1,
)
datagram = self._msg_to_datagram(msg, self.client_addr, self.region_addr,
Direction.OUT, socks_header=True)
self.protocol.datagram_received(datagram, self.client_addr)
await self._wait_drained()
self.assertFalse(self.session.pending)
# session should now be tied to the protocol
self.assertEqual(self.session, self.protocol.session)
# Packet got forwarded through
self.assertEqual(len(self.transport.packets), 1)
async def test_bad_session_unclaimed(self):
# Need a UseCircuitCode to claim a pending session
msg = ProxiedMessage(
"UseCircuitCode",
Block("CircuitCode", Code=self.circuit_code, SessionID=UUID.random(),
ID=self.session.agent_id),
packet_id=1,
)
datagram = self._msg_to_datagram(msg, self.client_addr, self.region_addr,
Direction.OUT, socks_header=True)
self.protocol.datagram_received(datagram, source_addr=self.client_addr)
await self._wait_drained()
# Packet got dropped completely
self.assertTrue(self.session.pending)
self.assertIsNone(self.protocol.session)
self.assertEqual(len(self.transport.packets), 0)
async def test_bad_circuit_not_sent(self):
# Need a UseCircuitCode to claim a pending session
msg = ProxiedMessage(
"UseCircuitCode",
Block("CircuitCode", Code=self.circuit_code, SessionID=self.session.id,
ID=self.session.agent_id),
packet_id=1,
)
datagram = self._msg_to_datagram(msg, self.client_addr, (self.region_addr[0], 9),
Direction.OUT, socks_header=True)
self.protocol.datagram_received(datagram, source_addr=self.client_addr)
await self._wait_drained()
# The session claim will still work
self.assertFalse(self.session.pending)
self.assertEqual(self.session, self.protocol.session)
# Packet should not get sent, though and should not be handled
self.assertEqual(len(self.transport.packets), 0)
@unittest.skip("expensive")
async def test_benchmark(self):
num_times = 10_000
self._setup_circuit()
for _ in range(num_times):
self.protocol.datagram_received(self._make_objectupdate_compressed(), self.region_addr)
await self._wait_drained()
self.assertEqual(len(self.transport.packets), num_times)
async def test_forwarded_with_header(self):
self._setup_circuit()
obj_update = self._make_objectupdate_compressed()
self.protocol.datagram_received(obj_update, self.region_addr)
await self._wait_drained()
packets = self.transport.packets
self.assertEqual(len(packets), 1)
data, dst_addr = packets[0]
# Was being sent towards the client
self.assertEqual(dst_addr, self.client_addr)
# Which means it has a SOCKS header we need to ignore
data = data[ProxiedUDPPacket.HEADER_STRUCT.size:]
# The data should not have changed since we haven't injected
# or dropped anything
self.assertEqual(obj_update, data)
async def test_addon_hooks(self):
self._setup_circuit()
obj_update = self._make_objectupdate_compressed()
self.protocol.datagram_received(obj_update, self.region_addr)
await self._wait_drained()
expected_lludp_event = ("lludp", self.session.id, self.region_addr, "ObjectUpdateCompressed")
self.assertTrue(any(x == expected_lludp_event for x in self.addon.events))
async def test_object_added_with_tes(self):
self._setup_circuit()
obj_update = self._make_objectupdate_compressed(1234)
self.protocol.datagram_received(obj_update, self.region_addr)
await self._wait_drained()
obj = self.session.regions[0].objects.lookup_localid(1234)
self.assertIsInstance(obj.TextureEntry, lazy_object_proxy.Proxy)
self.assertEqual(obj.TextureEntry.Textures[None], UUID("89556747-24cb-43ed-920b-47caed15465f"))