You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
985 lines
32 KiB
985 lines
32 KiB
import importlib
|
|
import io
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from contextlib import redirect_stdout
|
|
|
|
|
|
class AuxiliaryToolTests(unittest.TestCase):
|
|
def test_report_receiver_bool_flag(self):
|
|
import report_receiver
|
|
|
|
self.assertTrue(report_receiver.bool_flag("1"))
|
|
self.assertTrue(report_receiver.bool_flag("true"))
|
|
self.assertTrue(report_receiver.bool_flag("yes"))
|
|
self.assertFalse(report_receiver.bool_flag("0"))
|
|
self.assertFalse(report_receiver.bool_flag(""))
|
|
self.assertFalse(report_receiver.bool_flag(None))
|
|
|
|
def test_ami_factory_builds_protocol_with_instance_state(self):
|
|
try:
|
|
import AMI
|
|
except ModuleNotFoundError as exc:
|
|
self.skipTest(str(exc))
|
|
|
|
factory = AMI.AMI.AMIClientFactory(
|
|
AMI.AMI.AMIClient,
|
|
b"user",
|
|
b"secret",
|
|
b"1234",
|
|
b"ilink 3 2350",
|
|
)
|
|
protocol = factory.buildProtocol(None)
|
|
|
|
self.assertEqual(protocol.username, b"user")
|
|
self.assertEqual(protocol.secret, b"secret")
|
|
self.assertEqual(protocol.nodenum, b"1234")
|
|
self.assertEqual(protocol.command, b"ilink 3 2350")
|
|
|
|
def test_report_sql_uses_factory_db_and_parameterized_insert(self):
|
|
self._install_mysql_stub()
|
|
try:
|
|
import report_sql
|
|
report_sql = importlib.reload(report_sql)
|
|
except ModuleNotFoundError as exc:
|
|
self.skipTest(str(exc))
|
|
|
|
fake_db = _FakeDB()
|
|
fake_reactor = object()
|
|
factory = report_sql.reportClientFactory(report_sql.reportClient, fake_db, fake_reactor)
|
|
with redirect_stdout(io.StringIO()):
|
|
client = factory.buildProtocol(None)
|
|
|
|
self.assertIs(client.db, fake_db)
|
|
self.assertIs(client.reactor, fake_reactor)
|
|
|
|
event = {
|
|
"type": "GROUP VOICE",
|
|
"event": "START",
|
|
"trx": "RX",
|
|
"system": "SYSTEM",
|
|
"streamid": "1234",
|
|
"peerid": "5678",
|
|
"subid": "9012",
|
|
"slot": "2",
|
|
"dstid": "2350",
|
|
"duration": "0",
|
|
"source_server": None,
|
|
"source_rptr": None,
|
|
}
|
|
with redirect_stdout(io.StringIO()):
|
|
client.send_mysql(event)
|
|
|
|
statement, params = fake_db.cursor_obj.executed
|
|
self.assertIn("%s", statement)
|
|
self.assertEqual(params[0], "GROUP VOICE")
|
|
self.assertEqual(params[8], "2350")
|
|
self.assertIsNone(params[10])
|
|
self.assertIsNone(params[11])
|
|
self.assertTrue(fake_db.committed)
|
|
self.assertTrue(fake_db.cursor_obj.closed)
|
|
|
|
def test_report_sql_parses_legacy_and_extended_events(self):
|
|
self._install_mysql_stub()
|
|
import report_sql
|
|
|
|
report_sql = importlib.reload(report_sql)
|
|
legacy = report_sql.parse_bridge_event(
|
|
"GROUP VOICE,END,RX,SYSTEM,1234,5678,9012,2,2350,4.25"
|
|
)
|
|
extended = report_sql.parse_bridge_event(
|
|
"GROUP VOICE,END,RX,SYSTEM,1234,5678,9012,2,2350,4.25,9991,1001"
|
|
)
|
|
start = report_sql.parse_bridge_event(
|
|
"GROUP VOICE,START,RX,SYSTEM,1234,5678,9012,2,2350,9991,1001"
|
|
)
|
|
|
|
self.assertEqual(legacy["duration"], "4.25")
|
|
self.assertIsNone(legacy["source_server"])
|
|
self.assertEqual(extended["source_server"], "9991")
|
|
self.assertEqual(extended["source_rptr"], "1001")
|
|
self.assertEqual(start["duration"], 0)
|
|
self.assertEqual(start["source_server"], "9991")
|
|
|
|
def test_report_sql_relay_caches_state_and_isolates_clients(self):
|
|
self._install_mysql_stub()
|
|
import report_sql
|
|
|
|
report_sql = importlib.reload(report_sql)
|
|
relay = report_sql.reportRelayFactory()
|
|
good = _FakeReportClient()
|
|
failed = _FakeReportClient(fail=True)
|
|
relay.clients.update((failed, good))
|
|
|
|
config = report_sql.REPORT_OPCODES["CONFIG_SND"] + b"config"
|
|
bridge = report_sql.REPORT_OPCODES["BRIDGE_SND"] + b"bridge"
|
|
event = report_sql.REPORT_OPCODES["BRDG_EVENT"] + b"event"
|
|
relay.relay(config)
|
|
relay.relay(bridge)
|
|
relay.relay(event)
|
|
|
|
self.assertEqual(good.messages, [config, bridge, event])
|
|
self.assertNotIn(failed, relay.clients)
|
|
newcomer = _FakeReportClient()
|
|
relay.add_client(newcomer)
|
|
self.assertEqual(newcomer.messages, [config, bridge])
|
|
|
|
def test_report_factory_isolates_failed_clients_and_targets_replies(self):
|
|
import hblink
|
|
|
|
factory = hblink.reportFactory({
|
|
"REPORTS": {"REPORT_CLIENTS": ["*"]},
|
|
"SYSTEMS": {"MASTER-A": {"MODE": "MASTER"}},
|
|
})
|
|
failed = _FakeReportClient(fail=True)
|
|
broadcast_client = _FakeReportClient()
|
|
request_client = _FakeReportClient()
|
|
factory.clients = [failed, broadcast_client]
|
|
|
|
factory.send_clients(b"event")
|
|
factory.send_config(request_client)
|
|
|
|
self.assertEqual(broadcast_client.messages, [b"event"])
|
|
self.assertNotIn(failed, factory.clients)
|
|
self.assertEqual(len(request_client.messages), 1)
|
|
self.assertEqual(request_client.messages[0][:1], hblink.REPORT_OPCODES["CONFIG_SND"])
|
|
self.assertEqual(broadcast_client.messages, [b"event"])
|
|
|
|
def test_report_sql_failed_reconnect_returns_without_spinning(self):
|
|
self._install_mysql_stub()
|
|
import report_sql
|
|
|
|
report_sql = importlib.reload(report_sql)
|
|
db = _FakeDisconnectedDB()
|
|
client = report_sql.reportClient(db, object())
|
|
with redirect_stdout(io.StringIO()):
|
|
client.send_mysql({})
|
|
|
|
self.assertEqual(db.reconnects, 1)
|
|
|
|
def test_proxy_environment_bool_parser(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
self.assertTrue(hotspot_proxy_v2.bool_from_env("1"))
|
|
self.assertTrue(hotspot_proxy_v2.bool_from_env("true"))
|
|
self.assertTrue(hotspot_proxy_v2.bool_from_env("yes"))
|
|
self.assertFalse(hotspot_proxy_v2.bool_from_env("0"))
|
|
self.assertFalse(hotspot_proxy_v2.bool_from_env(""))
|
|
self.assertFalse(hotspot_proxy_v2.bool_from_env(None))
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_default_destination_range_matches_generated_masters(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
default_ports = range(
|
|
hotspot_proxy_v2.DEFAULT_DESTPORT_START,
|
|
hotspot_proxy_v2.DEFAULT_DESTPORT_END + 1,
|
|
)
|
|
|
|
self.assertEqual(hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT, 100)
|
|
self.assertEqual(len(default_ports), hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_routes_login_and_master_dmrd(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
fake_reactor = _FakeReactor()
|
|
hotspot_proxy_v2.reactor = fake_reactor
|
|
transport = _FakeTransport()
|
|
peer_id = b"\x00\x00\x03\xe9"
|
|
conn_track = {54000: False}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"RPTL" + peer_id, ("198.51.100.10", 40000))
|
|
proxy.datagramReceived(_dmrd_packet(peer_id), ("127.0.0.1", 54000))
|
|
|
|
self.assertEqual(conn_track[54000], peer_id)
|
|
self.assertEqual(peer_track[peer_id]["shost"], "198.51.100.10")
|
|
self.assertEqual(transport.writes[0], (b"RPTL" + peer_id, ("127.0.0.1", 54000)))
|
|
self.assertEqual(transport.writes[-1], (_dmrd_packet(peer_id), ("198.51.100.10", 40000)))
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_ignores_short_client_dmrd(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
conn_track = {54000: False}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"DMRD", ("198.51.100.10", 40000))
|
|
|
|
self.assertEqual(conn_track[54000], False)
|
|
self.assertEqual(peer_track, {})
|
|
self.assertEqual(transport.writes, [])
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_ignores_unknown_master_rptack_port(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
{54000: False},
|
|
{},
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"RPTACK" + b"\x00\x00\x03\xe9", ("127.0.0.1", 59999))
|
|
|
|
self.assertEqual(transport.writes, [])
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_ignores_new_client_when_all_destination_ports_are_in_use(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
existing_peer = b"\x00\x00\x03\xe9"
|
|
new_peer = b"\x00\x00\x03\xea"
|
|
conn_track = {54000: existing_peer}
|
|
peer_track = {
|
|
existing_peer: {
|
|
"dport": 54000,
|
|
"sport": 40000,
|
|
"shost": "198.51.100.10",
|
|
"timer": _FakeTimer(),
|
|
}
|
|
}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"RPTL" + new_peer, ("198.51.100.11", 40001))
|
|
|
|
self.assertEqual(conn_track, {54000: existing_peer})
|
|
self.assertNotIn(new_peer, peer_track)
|
|
self.assertEqual(transport.writes, [])
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_allocates_each_default_destination_port_once_before_exhaustion(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
conn_track = {
|
|
port: False
|
|
for port in range(
|
|
hotspot_proxy_v2.DEFAULT_DESTPORT_START,
|
|
hotspot_proxy_v2.DEFAULT_DESTPORT_END + 1,
|
|
)
|
|
}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
hotspot_proxy_v2.DEFAULT_LISTEN_PORT,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
hotspot_proxy_v2.DEFAULT_DESTPORT_START,
|
|
hotspot_proxy_v2.DEFAULT_DESTPORT_END,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
peer_ids = []
|
|
for index in range(hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT):
|
|
peer_id = (1000 + index).to_bytes(4, "big")
|
|
peer_ids.append(peer_id)
|
|
proxy.datagramReceived(
|
|
b"RPTL" + peer_id,
|
|
("198.51.100.{}".format(index + 1), 40000 + index),
|
|
)
|
|
|
|
write_count_before_exhaustion = len(transport.writes)
|
|
proxy.datagramReceived(b"RPTL" + (9999).to_bytes(4, "big"), ("198.51.100.200", 40100))
|
|
|
|
self.assertEqual(len(peer_track), hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT)
|
|
self.assertEqual(set(conn_track.values()), set(peer_ids))
|
|
self.assertNotIn(False, conn_track.values())
|
|
self.assertEqual(len(transport.writes), write_count_before_exhaustion)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_updates_client_source_port_for_existing_peer(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
peer_id = b"\x00\x00\x03\xe9"
|
|
conn_track = {54000: False}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"RPTL" + peer_id, ("198.51.100.10", 40000))
|
|
proxy.datagramReceived(b"RPTPING" + peer_id, ("198.51.100.10", 40001))
|
|
proxy.datagramReceived(b"MSTPONG" + peer_id, ("127.0.0.1", 54000))
|
|
|
|
self.assertEqual(peer_track[peer_id]["sport"], 40001)
|
|
self.assertEqual(transport.writes[-1], (b"MSTPONG" + peer_id, ("198.51.100.10", 40001)))
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_reaper_releases_port_and_is_idempotent(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
transport = _FakeTransport()
|
|
peer_id = b"\x00\x00\x03\xe9"
|
|
conn_track = {54000: peer_id}
|
|
peer_track = {
|
|
peer_id: {
|
|
"dport": 54000,
|
|
"sport": 40000,
|
|
"shost": "198.51.100.10",
|
|
"timer": _FakeTimer(),
|
|
}
|
|
}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.reaper(peer_id)
|
|
write_count = len(transport.writes)
|
|
proxy.reaper(peer_id)
|
|
|
|
self.assertEqual(conn_track[54000], False)
|
|
self.assertNotIn(peer_id, peer_track)
|
|
self.assertEqual(transport.writes[0], (b"RPTCL" + peer_id, ("127.0.0.1", 54000)))
|
|
self.assertEqual(transport.writes[1:], [(b"MSTCL", ("198.51.100.10", 40000))] * 3)
|
|
self.assertEqual(len(transport.writes), write_count)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_blacklists_known_peer_from_proxy_control_packet(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
peer_id = b"\x00\x00\x03\xe9"
|
|
ip_blacklist = {}
|
|
peer_track = {
|
|
peer_id: {
|
|
"dport": 54000,
|
|
"sport": 40000,
|
|
"shost": "198.51.100.10",
|
|
"timer": _FakeTimer(),
|
|
}
|
|
}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
{54000: peer_id},
|
|
peer_track,
|
|
[],
|
|
ip_blacklist,
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"PRBL" + peer_id + b"12345.0", ("127.0.0.1", 54000))
|
|
|
|
self.assertEqual(ip_blacklist["198.51.100.10"], 12345.0)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_rptl_flood_blacklists_source_ip(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
peer_id = b"\x00\x00\x03\xe9"
|
|
ip_blacklist = {}
|
|
rptl_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
{54000: False},
|
|
{},
|
|
[],
|
|
ip_blacklist,
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
rptl_track,
|
|
)
|
|
proxy.transport = transport
|
|
|
|
for index in range(21):
|
|
proxy.datagramReceived(b"RPTL" + peer_id, ("198.51.100.10", 40000 + index))
|
|
write_count = len(transport.writes)
|
|
proxy.datagramReceived(b"RPTPING" + peer_id, ("198.51.100.10", 40022))
|
|
|
|
self.assertIn("198.51.100.10", ip_blacklist)
|
|
self.assertNotIn("198.51.100.10", rptl_track)
|
|
self.assertEqual(len(transport.writes), write_count)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_treats_packets_from_master_ip_as_master_side(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
{54000: False},
|
|
{},
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"RPTL" + b"\x00\x00\x03\xe9", ("127.0.0.1", 40000))
|
|
|
|
self.assertEqual(proxy.peerTrack, {})
|
|
self.assertEqual(transport.writes, [])
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_ignores_malformed_proxy_blacklist_packet(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
transport = _FakeTransport()
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
{54000: False},
|
|
{},
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = transport
|
|
|
|
proxy.datagramReceived(b"PRBL" + b"\x00\x00\x03\xe9" + b"not-a-float", ("127.0.0.1", 54000))
|
|
|
|
self.assertEqual(transport.writes, [])
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_interleaves_new_sessions_across_two_backend_ranges(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
ranges = ((54000, 54001), (54100, 54101))
|
|
conn_track = {
|
|
port: False
|
|
for limits in ranges
|
|
for port in range(limits[0], limits[1] + 1)
|
|
}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54101,
|
|
None,
|
|
{},
|
|
ranges,
|
|
)
|
|
proxy.transport = _FakeTransport()
|
|
|
|
peer_ids = [(1000 + index).to_bytes(4, "big") for index in range(4)]
|
|
for index, peer_id in enumerate(peer_ids):
|
|
proxy.datagramReceived(
|
|
b"RPTL" + peer_id,
|
|
("198.51.100.{}".format(index + 1), 40000 + index),
|
|
)
|
|
|
|
assignments = [peer_track[peer_id]["dport"] for peer_id in peer_ids]
|
|
self.assertEqual(
|
|
[
|
|
next(
|
|
range_index
|
|
for range_index, limits in enumerate(ranges)
|
|
if limits[0] <= port <= limits[1]
|
|
)
|
|
for port in assignments
|
|
],
|
|
[0, 1, 0, 1],
|
|
)
|
|
self.assertEqual(len(set(assignments)), 4)
|
|
|
|
proxy.datagramReceived(
|
|
b"RPTPING" + peer_ids[0],
|
|
("198.51.100.1", 41000),
|
|
)
|
|
self.assertEqual(peer_track[peer_ids[0]]["dport"], assignments[0])
|
|
self.assertEqual(
|
|
proxy.transport.writes[-1][1],
|
|
("127.0.0.1", assignments[0]),
|
|
)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_interleaves_more_than_two_backends_and_reuses_reaped_port(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
ranges = ((54000, 54001), (54100, 54101), (54200, 54201))
|
|
conn_track = {
|
|
port: False
|
|
for limits in ranges
|
|
for port in range(limits[0], limits[1] + 1)
|
|
}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
False,
|
|
54000,
|
|
54201,
|
|
None,
|
|
{},
|
|
ranges,
|
|
)
|
|
proxy.transport = _FakeTransport()
|
|
|
|
peer_ids = [(2000 + index).to_bytes(4, "big") for index in range(6)]
|
|
for index, peer_id in enumerate(peer_ids):
|
|
proxy.datagramReceived(
|
|
b"RPTL" + peer_id,
|
|
("203.0.113.{}".format(index + 1), 42000 + index),
|
|
)
|
|
|
|
assignments = [peer_track[peer_id]["dport"] for peer_id in peer_ids]
|
|
self.assertEqual(
|
|
[
|
|
next(
|
|
range_index
|
|
for range_index, limits in enumerate(ranges)
|
|
if limits[0] <= port <= limits[1]
|
|
)
|
|
for port in assignments
|
|
],
|
|
[0, 1, 2, 0, 1, 2],
|
|
)
|
|
self.assertEqual(len(set(assignments)), 6)
|
|
|
|
released_port = assignments[0]
|
|
proxy.reaper(peer_ids[0])
|
|
proxy.datagramReceived(
|
|
b"RPTL" + peer_ids[0],
|
|
("203.0.113.20", 43000),
|
|
)
|
|
self.assertEqual(peer_track[peer_ids[0]]["dport"], released_port)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_backend_ranges_reject_overlap(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
with self.assertRaises(ValueError):
|
|
hotspot_proxy_v2.backend_port_ranges(
|
|
[[54000, 54010], [54010, 54020]],
|
|
54000,
|
|
54099,
|
|
)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_logs_hdstack_ranges_assignment_and_removal(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
ranges = ((54000, 54000), (54100, 54100))
|
|
conn_track = {54000: False, 54100: False}
|
|
peer_track = {}
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
conn_track,
|
|
peer_track,
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
True,
|
|
54000,
|
|
54100,
|
|
None,
|
|
{},
|
|
ranges,
|
|
)
|
|
proxy.transport = _FakeTransport()
|
|
first_peer_id = (3001).to_bytes(4, "big")
|
|
second_peer_id = (3002).to_bytes(4, "big")
|
|
output = io.StringIO()
|
|
|
|
with redirect_stdout(output):
|
|
hotspot_proxy_v2.log_backend_port_ranges(ranges)
|
|
proxy.datagramReceived(
|
|
b"RPTL" + first_peer_id,
|
|
("198.51.100.30", 44000),
|
|
)
|
|
proxy.datagramReceived(
|
|
b"RPTL" + second_peer_id,
|
|
("198.51.100.31", 44001),
|
|
)
|
|
proxy.reaper(first_peer_id)
|
|
proxy.reaper(second_peer_id)
|
|
|
|
messages = output.getvalue()
|
|
self.assertIn(
|
|
"(PROXY)(HDSTACK) Backend:1 ports:54000-54000.",
|
|
messages,
|
|
)
|
|
self.assertIn(
|
|
"(PROXY)(HDSTACK) Backend:2 ports:54100-54100.",
|
|
messages,
|
|
)
|
|
self.assertIn("assigned to port:54000, backend:1.", messages)
|
|
self.assertIn("assigned to port:54100, backend:2.", messages)
|
|
self.assertIn("Removed from backend:1 port:54000.", messages)
|
|
self.assertIn("Removed from backend:2 port:54100.", messages)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def test_proxy_preserves_single_backend_logging(self):
|
|
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
|
|
try:
|
|
hotspot_proxy_v2.reactor = _FakeReactor()
|
|
proxy = hotspot_proxy_v2.Proxy(
|
|
"127.0.0.1",
|
|
62031,
|
|
{54000: False},
|
|
{},
|
|
[],
|
|
{},
|
|
30,
|
|
False,
|
|
True,
|
|
54000,
|
|
54000,
|
|
None,
|
|
{},
|
|
)
|
|
proxy.transport = _FakeTransport()
|
|
peer_id = (3001).to_bytes(4, "big")
|
|
output = io.StringIO()
|
|
|
|
with redirect_stdout(output):
|
|
hotspot_proxy_v2.log_backend_port_ranges(((54000, 54000),))
|
|
proxy.datagramReceived(
|
|
b"RPTL" + peer_id,
|
|
("198.51.100.30", 44000),
|
|
)
|
|
proxy.reaper(peer_id)
|
|
|
|
messages = output.getvalue()
|
|
self.assertNotIn("(PROXY)(HDSTACK)", messages)
|
|
self.assertNotIn("backend:", messages)
|
|
self.assertIn("assigned to port:54000.", messages)
|
|
self.assertIn("Port:44000 Removed.", messages)
|
|
finally:
|
|
self._restore_modules(saved_modules)
|
|
|
|
def _import_proxy_module(self):
|
|
saved_modules = self._install_proxy_stubs()
|
|
try:
|
|
import hotspot_proxy_v2
|
|
return importlib.reload(hotspot_proxy_v2), saved_modules
|
|
except Exception:
|
|
self._restore_modules(saved_modules)
|
|
raise
|
|
|
|
def _install_mysql_stub(self):
|
|
mysql_module = types.ModuleType("mysql")
|
|
connector_module = types.ModuleType("mysql.connector")
|
|
|
|
class ConnectorError(Exception):
|
|
pass
|
|
|
|
connector_module.Error = ConnectorError
|
|
connector_module.errorcode = types.SimpleNamespace(
|
|
ER_ACCESS_DENIED_ERROR=1045,
|
|
ER_BAD_DB_ERROR=1049,
|
|
)
|
|
mysql_module.connector = connector_module
|
|
sys.modules["mysql"] = mysql_module
|
|
sys.modules["mysql.connector"] = connector_module
|
|
|
|
def _install_proxy_stubs(self):
|
|
stubbed = [
|
|
"Pyro5",
|
|
"Pyro5.api",
|
|
"setproctitle",
|
|
"twisted",
|
|
"twisted.internet",
|
|
"twisted.internet.protocol",
|
|
"twisted.internet.task",
|
|
]
|
|
saved_modules = {name: sys.modules.get(name) for name in stubbed + ["hotspot_proxy_v2"]}
|
|
|
|
pyro5_module = types.ModuleType("Pyro5")
|
|
pyro5_api_module = types.ModuleType("Pyro5.api")
|
|
pyro5_api_module.Proxy = object
|
|
pyro5_module.api = pyro5_api_module
|
|
sys.modules["Pyro5"] = pyro5_module
|
|
sys.modules["Pyro5.api"] = pyro5_api_module
|
|
|
|
setproctitle_module = types.ModuleType("setproctitle")
|
|
setproctitle_module.setproctitle = lambda title: None
|
|
sys.modules["setproctitle"] = setproctitle_module
|
|
|
|
twisted_module = types.ModuleType("twisted")
|
|
twisted_internet_module = types.ModuleType("twisted.internet")
|
|
twisted_protocol_module = types.ModuleType("twisted.internet.protocol")
|
|
twisted_task_module = types.ModuleType("twisted.internet.task")
|
|
twisted_protocol_module.DatagramProtocol = object
|
|
twisted_task_module.LoopingCall = object
|
|
twisted_internet_module.protocol = twisted_protocol_module
|
|
twisted_internet_module.reactor = _FakeReactor()
|
|
twisted_internet_module.task = twisted_task_module
|
|
twisted_module.internet = twisted_internet_module
|
|
sys.modules["twisted"] = twisted_module
|
|
sys.modules["twisted.internet"] = twisted_internet_module
|
|
sys.modules["twisted.internet.protocol"] = twisted_protocol_module
|
|
sys.modules["twisted.internet.task"] = twisted_task_module
|
|
|
|
sys.modules.pop("hotspot_proxy_v2", None)
|
|
return saved_modules
|
|
|
|
def _restore_modules(self, saved_modules):
|
|
for name, module in saved_modules.items():
|
|
if module is None:
|
|
sys.modules.pop(name, None)
|
|
else:
|
|
sys.modules[name] = module
|
|
|
|
|
|
class _FakeCursor:
|
|
def __init__(self):
|
|
self.executed = None
|
|
self.closed = False
|
|
|
|
def execute(self, statement, params):
|
|
self.executed = (statement, params)
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
|
|
class _FakeDB:
|
|
def __init__(self):
|
|
self.cursor_obj = _FakeCursor()
|
|
self.committed = False
|
|
|
|
def is_connected(self):
|
|
return True
|
|
|
|
def cursor(self):
|
|
return self.cursor_obj
|
|
|
|
def commit(self):
|
|
self.committed = True
|
|
|
|
|
|
class _FakeReportClient:
|
|
def __init__(self, fail=False):
|
|
self.fail = fail
|
|
self.messages = []
|
|
|
|
def sendString(self, message):
|
|
if self.fail:
|
|
raise RuntimeError("closed")
|
|
self.messages.append(message)
|
|
|
|
|
|
class _FakeDisconnectedDB:
|
|
def __init__(self):
|
|
self.reconnects = 0
|
|
|
|
def is_connected(self):
|
|
return False
|
|
|
|
def reconnect(self, attempts, delay):
|
|
self.reconnects += 1
|
|
|
|
|
|
class _FakeTimer:
|
|
def __init__(self):
|
|
self.resets = []
|
|
self.cancelled = False
|
|
|
|
def reset(self, value=None):
|
|
self.resets.append(value)
|
|
|
|
def cancel(self):
|
|
self.cancelled = True
|
|
|
|
|
|
class _FakeReactor:
|
|
def __init__(self):
|
|
self.timers = []
|
|
self.thread_calls = []
|
|
|
|
def callLater(self, timeout, func, *args):
|
|
timer = _FakeTimer()
|
|
self.timers.append((timeout, func, args, timer))
|
|
return timer
|
|
|
|
def callInThread(self, func, *args):
|
|
self.thread_calls.append((func, args))
|
|
func(*args)
|
|
|
|
|
|
class _FakeTransport:
|
|
def __init__(self):
|
|
self.writes = []
|
|
|
|
def write(self, data, addr):
|
|
self.writes.append((data, addr))
|
|
|
|
|
|
def _dmrd_packet(peer_id):
|
|
return b"".join(
|
|
[
|
|
b"DMRD",
|
|
b"\x01",
|
|
b"\x00\x00\x01",
|
|
b"\x00\x00\x02",
|
|
peer_id,
|
|
b"\x80",
|
|
b"\x01\x02\x03\x04",
|
|
b"\x55" * 33,
|
|
]
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|