mirror of
https://github.com/Horizontal42/netsleuth.git
synced 2026-11-10 20:22:03 +03:00
139 lines
5.4 KiB
Python
139 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from netsleuth.models import ResolverProbe
|
|
from netsleuth.probes.dns_advanced import (
|
|
compare_answers,
|
|
detect_poisoning,
|
|
detect_transparent_proxy,
|
|
is_suspicious_answer,
|
|
parse_doh_json,
|
|
resolve_doh,
|
|
)
|
|
|
|
|
|
def test_parse_doh_json_extracts_addresses_from_a_successful_answer(api_fixture):
|
|
payload = api_fixture("doh_cloudflare.json")
|
|
assert parse_doh_json(payload) == ["104.16.132.229", "104.16.133.229"]
|
|
|
|
|
|
def test_parse_doh_json_returns_empty_list_on_nxdomain_status(api_fixture):
|
|
payload = api_fixture("doh_nxdomain.json")
|
|
assert parse_doh_json(payload) == []
|
|
|
|
|
|
def test_parse_doh_json_ignores_cname_and_keeps_only_a_records(api_fixture):
|
|
payload = api_fixture("doh_cname_then_a.json")
|
|
assert parse_doh_json(payload) == ["93.184.216.34"]
|
|
|
|
|
|
def test_parse_doh_json_returns_empty_list_on_empty_payload():
|
|
assert parse_doh_json({}) == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"addr,expected",
|
|
[
|
|
("0.0.0.0", True),
|
|
("127.0.0.1", True),
|
|
("10.1.1.1", True),
|
|
("192.0.2.1", True),
|
|
("1.1.1.1", False),
|
|
("104.16.132.229", False),
|
|
],
|
|
)
|
|
def test_is_suspicious_answer_table(addr, expected):
|
|
assert is_suspicious_answer(addr) is expected
|
|
|
|
|
|
def test_cdn_answers_that_merely_differ_are_not_flagged_as_poisoning():
|
|
system_probe = ResolverProbe(name="system", kind="system", answers=["1.1.1.1"])
|
|
doh_probes = [ResolverProbe(name="cloudflare", kind="doh", answers=["104.16.132.229"])]
|
|
assert detect_poisoning(system_probe, doh_probes) is False
|
|
|
|
|
|
def test_a_bogon_system_answer_against_a_clean_doh_answer_is_flagged_as_poisoning():
|
|
system_probe = ResolverProbe(name="system", kind="system", answers=["192.0.2.1"])
|
|
doh_probes = [ResolverProbe(name="cloudflare", kind="doh", answers=["1.1.1.1"])]
|
|
assert detect_poisoning(system_probe, doh_probes) is True
|
|
|
|
|
|
def test_detect_poisoning_is_false_when_both_sides_are_suspicious():
|
|
system_probe = ResolverProbe(name="system", kind="system", answers=["0.0.0.0"])
|
|
doh_probes = [ResolverProbe(name="cloudflare", kind="doh", answers=["127.0.0.1"])]
|
|
assert detect_poisoning(system_probe, doh_probes) is False
|
|
|
|
|
|
def test_detect_poisoning_is_false_when_both_sides_are_empty():
|
|
system_probe = ResolverProbe(name="system", kind="system", answers=[])
|
|
doh_probes = [ResolverProbe(name="cloudflare", kind="doh", answers=[])]
|
|
assert detect_poisoning(system_probe, doh_probes) is False
|
|
|
|
|
|
def test_compare_answers_reports_a_divergence_when_addresses_differ():
|
|
system_probe = ResolverProbe(name="system", kind="system", query_name="cloudflare.com", answers=["1.1.1.1"])
|
|
doh_probes = [
|
|
ResolverProbe(name="cloudflare", kind="doh", query_name="cloudflare.com", answers=["104.16.132.229"])
|
|
]
|
|
divergences = compare_answers(system_probe, doh_probes)
|
|
assert len(divergences) == 1
|
|
assert "cloudflare.com" in divergences[0]
|
|
|
|
|
|
def test_compare_answers_returns_empty_list_when_answers_match():
|
|
system_probe = ResolverProbe(name="system", kind="system", query_name="cloudflare.com", answers=["1.1.1.1"])
|
|
doh_probes = [ResolverProbe(name="cloudflare", kind="doh", query_name="cloudflare.com", answers=["1.1.1.1"])]
|
|
assert compare_answers(system_probe, doh_probes) == []
|
|
|
|
|
|
def test_detect_transparent_proxy_true_when_bogus_ip_answers():
|
|
bogus_probe = ResolverProbe(name="bogus", kind="system", answers=["93.184.216.34"], error=None)
|
|
detected, detail = detect_transparent_proxy(bogus_probe)
|
|
assert detected is True
|
|
assert "detect" in detail.lower()
|
|
|
|
|
|
def test_detect_transparent_proxy_false_and_says_not_detected_not_absent_on_timeout():
|
|
bogus_probe = ResolverProbe(name="bogus", kind="system", answers=[], error="timeout")
|
|
detected, detail = detect_transparent_proxy(bogus_probe)
|
|
assert detected is False
|
|
lowered = detail.lower()
|
|
assert "not detected" in lowered or "не обнаружен" in lowered
|
|
assert "absent" not in lowered
|
|
assert "none" not in lowered
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_doh_populates_answers_from_a_mocked_response(httpx_mock, api_fixture):
|
|
httpx_mock.add_response(
|
|
url="https://cloudflare-dns.com/dns-query?name=cloudflare.com&type=A",
|
|
json=api_fixture("doh_cloudflare.json"),
|
|
)
|
|
async with httpx.AsyncClient() as client:
|
|
probe = await resolve_doh(client, "cloudflare", "https://cloudflare-dns.com/dns-query", "cloudflare.com", 4.0)
|
|
assert probe.kind == "doh"
|
|
assert probe.answers == ["104.16.132.229", "104.16.133.229"]
|
|
assert probe.error is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_doh_sets_error_on_http_error_status_without_raising(httpx_mock):
|
|
httpx_mock.add_response(
|
|
url="https://cloudflare-dns.com/dns-query?name=cloudflare.com&type=A",
|
|
status_code=500,
|
|
)
|
|
async with httpx.AsyncClient() as client:
|
|
probe = await resolve_doh(client, "cloudflare", "https://cloudflare-dns.com/dns-query", "cloudflare.com", 4.0)
|
|
assert probe.error is not None
|
|
assert probe.answers == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_doh_sets_error_on_timeout_without_raising(httpx_mock):
|
|
httpx_mock.add_exception(httpx.TimeoutException("timed out"))
|
|
async with httpx.AsyncClient() as client:
|
|
probe = await resolve_doh(client, "cloudflare", "https://cloudflare-dns.com/dns-query", "cloudflare.com", 4.0)
|
|
assert probe.error is not None
|
|
assert probe.answers == []
|