nadzoring.dns_lookup.poisoning module

DNS poisoning detection and analysis functionality.

This module detects DNS cache poisoning, censorship, and manipulation by comparing responses from multiple resolvers across different geographic locations and providers.

nadzoring.dns_lookup.poisoning.CDN_NETWORKS: dict[str, list[str]] = {'Akamai': ['23.32.0.0/11', '23.64.0.0/14', '23.72.0.0/13', '23.192.0.0/11', '23.224.0.0/13', '23.248.0.0/14', '2.16.0.0/13', '2.20.0.0/14', '2.22.0.0/15', '2.23.0.0/16', '69.192.0.0/16', '95.100.0.0/15', '96.6.0.0/15', '104.64.0.0/10'], 'Amazon AWS': ['13.32.0.0/15', '13.224.0.0/14', '52.84.0.0/15', '54.182.0.0/16', '54.192.0.0/16', '54.230.0.0/16', '54.239.128.0/18', '99.84.0.0/15', '143.204.0.0/16', '144.220.0.0/16', '13.248.0.0/14', '15.248.0.0/16', '18.64.0.0/14', '52.124.0.0/14', '52.222.0.0/15'], 'Azure CDN': ['13.73.0.0/16', '13.80.0.0/15', '13.88.0.0/16', '13.104.0.0/14', '13.107.128.0/22', '40.90.0.0/15', '40.126.0.0/18', '52.168.0.0/14', '52.224.0.0/14', '52.239.0.0/15'], 'CloudFront': ['13.32.0.0/15', '13.224.0.0/14', '13.249.0.0/16', '18.64.0.0/14', '18.154.0.0/15', '52.84.0.0/15', '54.182.0.0/16', '54.192.0.0/16', '54.230.0.0/16', '54.239.128.0/18', '99.84.0.0/15', '143.204.0.0/16', '144.220.0.0/16', '146.254.0.0/16'], 'Cloudflare': ['1.1.1.0/24', '1.0.0.0/24', '104.16.0.0/12', '172.64.0.0/13', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22'], 'Facebook': ['31.13.24.0/21', '31.13.64.0/18', '45.64.40.0/22', '66.220.144.0/20', '69.63.176.0/20', '69.171.224.0/19', '74.119.76.0/22', '102.132.96.0/20', '103.4.96.0/22', '129.134.0.0/17', '157.240.0.0/17', '173.252.64.0/18', '179.60.192.0/22', '185.60.216.0/22', '204.15.20.0/22'], 'Fastly': ['23.235.32.0/20', '104.156.80.0/20', '151.101.0.0/16', '157.52.64.0/18', '172.111.64.0/18', '185.31.16.0/22', '199.27.72.0/21', '199.232.0.0/16'], 'Google': ['8.8.8.0/24', '8.8.4.0/24', '64.233.160.0/19', '66.102.0.0/20', '66.249.64.0/19', '72.14.192.0/18', '74.125.0.0/16', '108.177.8.0/21', '142.250.0.0/15', '172.217.0.0/16', '173.194.0.0/16', '207.126.144.0/20', '209.85.128.0/17', '216.58.192.0/19', '216.239.32.0/19'], 'Mail.ru': ['94.100.176.0/20', '95.163.0.0/16', '185.5.128.0/22', '185.30.176.0/22', '185.86.176.0/22', '217.69.128.0/20'], 'Microsoft': ['13.64.0.0/11', '13.96.0.0/13', '13.104.0.0/14', '20.0.0.0/8', '40.64.0.0/10', '52.96.0.0/14', '52.112.0.0/14', '52.120.0.0/14', '104.40.0.0/13', '104.208.0.0/13'], 'Netflix': ['3.160.0.0/12', '23.192.0.0/11', '34.192.0.0/10', '52.48.0.0/12', '54.144.0.0/12', '108.128.0.0/12', '184.72.0.0/14', '185.2.220.0/22', '185.48.244.0/22'], 'Twitter': ['104.244.40.0/21', '199.16.156.0/22', '199.59.148.0/22', '192.133.76.0/22', '209.237.192.0/19', '69.195.160.0/19'], 'Yandex': ['5.45.192.0/18', '37.9.64.0/18', '77.88.0.0/18', '84.252.128.0/17', '87.250.224.0/19', '93.158.128.0/18', '95.108.128.0/17', '141.8.128.0/18', '199.21.96.0/22', '213.180.192.0/19']}

Known CDN and cloud provider network ranges for IP ownership detection.

class nadzoring.dns_lookup.poisoning.IPAnalysisResult[source]

Bases: TypedDict

Result of IP address pattern analysis.

count

Total number of IP addresses analysed.

Type:

int

unique

Number of unique IP addresses.

Type:

int

ipv4

Count of IPv4 addresses.

Type:

int

ipv6

Count of IPv6 addresses.

Type:

int

private

Count of private (RFC 1918) addresses.

Type:

int

reserved

Count of reserved addresses.

Type:

int

owners

Inferred provider name for each IP.

Type:

list[str]

countries

Inferred country for each IP (simplified heuristic).

Type:

list[str]

count: int
countries: list[str]
ipv4: int
ipv6: int
owners: list[str]
private: int
reserved: int
unique: int
class nadzoring.dns_lookup.poisoning.InconsistencyDetail[source]

Bases: TypedDict

Detailed information about a DNS response inconsistency.

server

IP of the DNS server that returned inconsistent results.

Type:

str

server_name

Provider name from SERVER_NAMES.

Type:

str

server_country

Country code from SERVER_COUNTRIES.

Type:

str

type

Inconsistency class — "error_mismatch", "record_mismatch", "cdn_variation", or "ttl_mismatch".

Type:

Literal[‘error_mismatch’, ‘record_mismatch’, ‘cdn_variation’, ‘ttl_mismatch’]

severity

Impact level — "high", "medium", "low", or "info".

Type:

Literal[‘high’, ‘medium’, ‘low’, ‘info’]

control_error

Error from the control server, if any.

Type:

str | None

test_error

Error from the test server, if any.

Type:

str | None

control_records

Records returned by the control server.

Type:

list[str]

test_records

Records returned by the test server.

Type:

list[str]

control_ttl

TTL from the control server.

Type:

int | None

test_ttl

TTL from the test server.

Type:

int | None

diff

Description or numeric magnitude of the difference.

Type:

str | int | None

common_records

Records present in both responses.

Type:

list[str] | None

control_analysis

IP analysis for the control records.

Type:

nadzoring.dns_lookup.poisoning.IPAnalysisResult

test_analysis

IP analysis for the test records.

Type:

nadzoring.dns_lookup.poisoning.IPAnalysisResult

owner

Shared owner when both sides belong to the same network.

Type:

str | None

control_owner

Inferred owner of the control records.

Type:

str | None

test_owner

Inferred owner of the test records.

Type:

str | None

common_records: list[str] | None
control_analysis: IPAnalysisResult
control_error: str | None
control_owner: str | None
control_records: list[str]
control_ttl: int | None
diff: str | int | None
owner: str | None
server: str
server_country: str
server_name: str
severity: Literal['high', 'medium', 'low', 'info']
test_analysis: IPAnalysisResult
test_error: str | None
test_owner: str | None
test_records: list[str]
test_ttl: int | None
type: Literal['error_mismatch', 'record_mismatch', 'cdn_variation', 'ttl_mismatch']
class nadzoring.dns_lookup.poisoning.MetricsResult[source]

Bases: TypedDict

Aggregated metrics from a DNS poisoning test run.

total_tested

Number of test servers queried.

Type:

int

poisoned

Whether poisoning indicators exceed the threshold.

Type:

bool

confidence

Confidence score (0-100).

Type:

float

mismatches

Count of record mismatches.

Type:

int

cdn_variations

Count of CDN-related IP variations.

Type:

int

cdn_detected

Whether CDN usage was identified.

Type:

bool

cdn_owner

Name of the detected CDN provider.

Type:

str

cdn_percentage

Percentage of IPs belonging to the CDN.

Type:

float

unique_ips_seen

Unique IPs across all test results.

Type:

int

ip_diversity

IPs not present in the control result.

Type:

int

control_ip_count

IPs returned by the control server.

Type:

int

consensus_top

Top-3 most common IPs with counts.

Type:

list[tuple[str, int]]

consensus_rate

Percentage of servers returning the most common IP.

Type:

float

geo_diversity

Unique countries among test servers.

Type:

int

anycast_likely

Whether anycast routing is probable.

Type:

bool

cdn_likely

Whether CDN usage is probable.

Type:

bool

poisoning_likely

Whether deliberate poisoning is probable.

Type:

bool

anycast_likely: bool
cdn_detected: bool
cdn_likely: bool
cdn_owner: str
cdn_percentage: float
cdn_variations: int
confidence: float
consensus_rate: float
consensus_top: list[tuple[str, int]]
control_ip_count: int
geo_diversity: int
ip_diversity: int
mismatches: int
poisoned: bool
poisoning_likely: bool
total_tested: int
unique_ips_seen: int
nadzoring.dns_lookup.poisoning.SERVER_COUNTRIES: dict[str, str] = {'1.0.0.1': 'AU', '1.1.1.1': 'AU', '149.112.112.112': 'CH', '185.228.168.9': 'CA', '185.228.169.9': 'CA', '208.67.220.220': 'US', '208.67.222.222': 'US', '64.6.64.6': 'US', '64.6.65.6': 'US', '76.76.19.19': 'CA', '8.8.4.4': 'US', '8.8.8.8': 'US', '9.9.9.9': 'CH', '94.140.14.14': 'CY', '94.140.15.15': 'CY'}

Mapping of public DNS server IPs to their country codes.

nadzoring.dns_lookup.poisoning.SERVER_NAMES: dict[str, str] = {'1.0.0.1': 'Cloudflare', '1.1.1.1': 'Cloudflare', '149.112.112.112': 'Quad9', '185.228.168.9': 'CleanBrowsing', '185.228.169.9': 'CleanBrowsing', '208.67.220.220': 'OpenDNS', '208.67.222.222': 'OpenDNS', '64.6.64.6': 'Verisign', '64.6.65.6': 'Verisign', '76.76.19.19': 'ControlD', '8.8.4.4': 'Google', '8.8.8.8': 'Google', '9.9.9.9': 'Quad9', '94.140.14.14': 'AdGuard', '94.140.15.15': 'AdGuard'}

Mapping of public DNS server IPs to their provider names.

nadzoring.dns_lookup.poisoning._analyze_ip_patterns(records: list[str]) IPAnalysisResult[source]

Analyse IP address patterns for characteristics and ownership.

Parameters:

records – IP address strings to analyse.

Returns:

IPAnalysisResult dict, or an empty dict for empty input.

nadzoring.dns_lookup.poisoning._build_result(domain: str, record_type: str, control_server: str, control_result: DNSResult, test_results: dict[str, DNSResult], additional_results: dict[str, DNSResult] | None, inconsistencies: list[InconsistencyDetail], mismatches: int, cdn_variations: int, metrics: MetricsResult, poisoning_level: str) PoisoningCheckResult[source]

Assemble the final PoisoningCheckResult from collected data.

Parameters:
  • domain – Tested domain name.

  • record_type – DNS record type queried.

  • control_server – Control server IP.

  • control_result – DNS result from the control server.

  • test_results – Dict of test server results.

  • additional_results – Optional supplementary record results.

  • inconsistencies – Detected inconsistencies.

  • mismatches – Count of record mismatches.

  • cdn_variations – Count of CDN variations.

  • metrics – Aggregated metrics from _calculate_metrics().

  • poisoning_level – Poisoning severity label.

Returns:

Complete PoisoningCheckResult.

nadzoring.dns_lookup.poisoning._calculate_metrics(test_results: dict[str, DNSResult], control_result: DNSResult, mismatches: int, cdn_variations: int) MetricsResult[source]

Compute poisoning-detection metrics from test results.

Parameters:
  • test_results – Dict mapping server IPs to DNS results.

  • control_result – Control server DNS result.

  • mismatches – Count of record mismatches.

  • cdn_variations – Count of CDN-related variations.

Returns:

MetricsResult with confidence, diversity, and detection flags.

nadzoring.dns_lookup.poisoning._compare_results(control: DNSResult, test: DNSResult, server: str) InconsistencyDetail | None[source]

Compare control and test DNS results and return an inconsistency if found.

Detects error_mismatch, record_mismatch, cdn_variation, and ttl_mismatch (> 1 hour) types.

Parameters:
  • control – DNS result from the trusted control resolver.

  • test – DNS result from the test resolver.

  • server – IP address of the test server.

Returns:

InconsistencyDetail when a discrepancy is found, None when results are consistent.

nadzoring.dns_lookup.poisoning._count_severities(inconsistencies: list[InconsistencyDetail]) dict[str, int][source]

Aggregate inconsistency counts by severity level.

Parameters:

inconsistencies – List of inconsistency detail dicts.

Returns:

Dict with "high", "medium", "low", and "info" keys.

nadzoring.dns_lookup.poisoning._determine_poisoning_level(confidence: float, *, poisoned: bool, cdn_detected: bool) str[source]

Map confidence and detection flags to a poisoning severity label.

Parameters:
  • confidence – Confidence score (0-100).

  • poisoned – Whether poisoning indicators exceed the threshold.

  • cdn_detected – Whether CDN usage was identified.

Returns:

One of "NONE", "LOW", "MEDIUM", "HIGH", "CRITICAL", or "SUSPICIOUS".

nadzoring.dns_lookup.poisoning._get_additional_records(domain: str, additional_types: list[str] | None, control_server: str) dict[str, DNSResult] | None[source]

Retrieve supplementary DNS record types from the control server.

Parameters:
  • domain – Domain name to query.

  • additional_types – Record types to query, or None to skip.

  • control_server – Control server IP address.

Returns:

Dict mapping record types to results, or None when additional_types is empty/None.

nadzoring.dns_lookup.poisoning._test_dns_servers(domain: str, record_type: RecordType, test_servers: list[str], control_result: DNSResult, control_server: str) tuple[dict[str, DNSResult], list[InconsistencyDetail], int, int][source]

Query all test servers and collect comparison results.

The control server itself is skipped if it appears in test_servers.

Parameters:
  • domain – Domain to query.

  • record_type – DNS record type to query.

  • test_servers – Test server IP addresses.

  • control_result – Result from the control server for comparison.

  • control_server – Control server IP (skipped when encountered in list).

Returns:

Four-tuple of (test_results, inconsistencies, mismatches, cdn_variations).

nadzoring.dns_lookup.poisoning.check_dns_poisoning(domain: str, control_server: str = '8.8.8.8', test_servers: list[str] | None = None, record_type: str = 'A', additional_types: list[str] | None = None) PoisoningCheckResult[source]

Check for DNS poisoning, censorship, or manipulation.

Queries the trusted control_server and compares its response against multiple test servers. CDN and anycast patterns are classified separately from true poisoning.

Parameters:
  • domain – Domain to test (e.g. "example.com").

  • control_server – Trusted DNS server IP used as the baseline. Defaults to "8.8.8.8" (Google DNS).

  • test_servers – Test server IPs. None uses get_public_dns_servers().

  • record_type – Record type to query. Defaults to "A".

  • additional_types – Extra record types to query on the control server for additional context.

Returns:

PoisoningCheckResult with comprehensive analysis fields.

Examples

>>> result = check_dns_poisoning("example.com")
>>> if result["poisoned"]:
...     print(f"Confidence: {result['confidence']}%")
>>> result = check_dns_poisoning(
...     "example.com", test_servers=["1.1.1.1", "9.9.9.9"]
... )
nadzoring.dns_lookup.poisoning.get_ip_owner(ip: str) str[source]

Determine the CDN/cloud provider for an IP address.

Only IPv4 addresses are matched against CDN_NETWORKS; IPv6 always returns "Unknown".

Parameters:

ip – IP address string to check.

Returns:

Provider name if a match is found, otherwise "Unknown".

Examples

>>> get_ip_owner("8.8.8.8")
'Google'
>>> get_ip_owner("192.168.1.1")
'Unknown'
nadzoring.dns_lookup.poisoning.is_likely_cdn(ips: list[str]) tuple[bool, str, float][source]

Determine whether a set of IP addresses likely belongs to a CDN.

More than 50 % of IPs belonging to the same known provider is treated as CDN usage.

Parameters:

ips – IP address strings to analyse.

Returns:

Three-tuple of (is_cdn, owner_name, percentage). Returns (False, "Unknown", 0.0) for empty input.

Examples

>>> is_likely_cdn(["1.1.1.1", "1.0.0.1", "8.8.8.8"])
(True, 'Cloudflare', 66.7)