"""TTL-driven re-resolution in the standalone scripts/hbc_mini.py.""" import asyncio import importlib.util import pathlib import socket import struct import sys _PATH = pathlib.Path(__file__).resolve().parents[1] / "scripts" / "hbc_mini.py" _SPEC = importlib.util.spec_from_file_location("hbc_mini", _PATH) mini = importlib.util.module_from_spec(_SPEC) sys.modules["hbc_mini"] = mini _SPEC.loader.exec_module(mini) V4, V6 = socket.AF_INET, socket.AF_INET6 def _rr(rtype, ttl, rdata): return b"\xc0\x0c" + struct.pack("!HHIH", rtype, 1, ttl, len(rdata)) + rdata def _response(qid, qtype, answers, rcode=0): q = mini._dns_build_query("hbd.example.com", qtype, qid)[12:] return struct.pack("!HHHHHH", qid, 0x8180 | rcode, 1, len(answers), 0, 0) + q + b"".join(answers) def test_min_ttl_covers_cname_and_address(): resp = _response(7, 1, [_rr(5, 60, b"\x01x\x00"), _rr(1, 300, b"\x01\x02\x03\x04")]) assert mini._dns_min_answer_ttl(resp, 7, 1) == 60 def test_unknown_ttl_cases(): assert mini._dns_min_answer_ttl(_response(7, 28, []), 7, 28) is None assert mini._dns_min_answer_ttl(_response(7, 1, [_rr(1, 9, b"1234")], rcode=3), 7, 1) is None assert mini._dns_min_answer_ttl(b"\x00\x07\x81", 7, 1) is None assert mini._dns_lookup_ttl("192.0.2.1") is None def test_refresh_delay(): assert mini._dns_refresh_delay([300, 120, None]) == 120 assert mini._dns_refresh_delay([0]) == mini._DNS_MIN_TTL assert mini._dns_refresh_delay([None]) == mini._DNS_DEFAULT_TTL def test_refresher_follows_a_change_and_adds_a_family(monkeypatch): answers = [[(V4, "9.9.9.9"), (V6, "2001:db8::9")], None, [(V4, "9.9.9.9")]] monkeypatch.setattr(mini, "_dns_resolve", lambda *a: answers.pop(0) if len(answers) > 1 else answers[0]) monkeypatch.setattr(mini, "_dns_lookup_ttl", lambda *a: None) monkeypatch.setattr(mini, "_DNS_DEFAULT_TTL", 0.01) monkeypatch.setattr(mini, "_DNS_FAILED_RETRY", 0.01) async def idle(*a): await asyncio.sleep(10) monkeypatch.setattr(mini, "_heartbeat_sender", idle) async def go(): mini._running = True mini._shutdown_event = asyncio.Event() mini._active_tasks = [] conns = [mini.AsyncConnection(1, "1.1.1.1", 5, V4, "me")] task = asyncio.create_task(mini._dns_refresher(conns, ["h"], 5, 0, "me", 10, None, 2)) await asyncio.sleep(0.2) mini._running = False mini._shutdown_event.set() await task for t in mini._active_tasks: t.cancel() return conns conns = asyncio.run(go()) assert [(c.conn_id, c.addr) for c in conns] == [(1, "9.9.9.9")] assert conns[0].af == V4