Initial commit: 首次建仓,建立目录结构

This commit is contained in:
FXY
2026-06-11 23:49:54 +08:00
commit 4038a476b5
9396 changed files with 2372905 additions and 0 deletions

View File

@ -0,0 +1,288 @@
"""
Compatibility layer for pycares 5.x API.
This module provides result types compatible with pycares 4.x API
to maintain backward compatibility with existing code.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Union, cast
import pycares
from . import error
_SINGLE_RESULT_QTYPES = frozenset(
{
pycares.QUERY_TYPE_CNAME,
pycares.QUERY_TYPE_SOA,
pycares.QUERY_TYPE_PTR,
}
)
def _maybe_str(data: bytes) -> str | bytes:
"""Decode bytes as ASCII, return bytes if decode fails (pycares 4.x)."""
try:
return data.decode('ascii')
except UnicodeDecodeError:
return data
@dataclass(frozen=True, slots=True)
class AresQueryAResult:
"""A record result (compatible with pycares 4.x ares_query_a_result)."""
host: str
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryAAAAResult:
"""AAAA record result (pycares 4.x compat)."""
host: str
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryCNAMEResult:
"""CNAME record result (pycares 4.x compat)."""
cname: str
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryMXResult:
"""MX record result (pycares 4.x compat)."""
host: str
priority: int
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryNSResult:
"""NS record result (pycares 4.x compat)."""
host: str
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryTXTResult:
"""TXT record result (pycares 4.x compat)."""
text: str | bytes # str if ASCII, bytes otherwise (pycares 4.x behavior)
ttl: int
@dataclass(frozen=True, slots=True)
class AresQuerySOAResult:
"""SOA record result (pycares 4.x compat)."""
nsname: str
hostmaster: str
serial: int
refresh: int
retry: int
expires: int
minttl: int
ttl: int
@dataclass(frozen=True, slots=True)
class AresQuerySRVResult:
"""SRV record result (pycares 4.x compat)."""
host: str
port: int
priority: int
weight: int
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryNAPTRResult:
"""NAPTR record result (pycares 4.x compat)."""
order: int
preference: int
flags: str
service: str
regex: str
replacement: str
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryCAAResult:
"""CAA record result (pycares 4.x compat)."""
critical: int
property: str
value: str
ttl: int
@dataclass(frozen=True, slots=True)
class AresQueryPTRResult:
"""PTR record result (pycares 4.x compat)."""
name: str
ttl: int
aliases: list[str]
@dataclass(frozen=True, slots=True)
class AresHostResult:
"""Host result (compatible with pycares 4.x ares_host_result)."""
name: str
aliases: list[str]
addresses: list[str]
# Type alias for a single converted record
ConvertedRecord = Union[
AresQueryAResult,
AresQueryAAAAResult,
AresQueryCNAMEResult,
AresQueryMXResult,
AresQueryNSResult,
AresQueryTXTResult,
AresQuerySOAResult,
AresQuerySRVResult,
AresQueryNAPTRResult,
AresQueryCAAResult,
AresQueryPTRResult,
pycares.DNSRecord, # Unknown types returned as-is
]
# Type alias for query results
QueryResult = Union[
list[AresQueryAResult],
list[AresQueryAAAAResult],
AresQueryCNAMEResult,
list[AresQueryMXResult],
list[AresQueryNSResult],
list[AresQueryTXTResult],
AresQuerySOAResult,
list[AresQuerySRVResult],
list[AresQueryNAPTRResult],
list[AresQueryCAAResult],
AresQueryPTRResult,
list[ConvertedRecord], # For ANY query type
]
def _convert_record(record: pycares.DNSRecord) -> ConvertedRecord:
"""Convert a single DNS record to pycares 4.x compatible format."""
ttl = record.ttl
record_type = record.type
if record_type == pycares.QUERY_TYPE_A:
a_data = cast(pycares.ARecordData, record.data)
return AresQueryAResult(host=a_data.addr, ttl=ttl)
if record_type == pycares.QUERY_TYPE_AAAA:
aaaa_data = cast(pycares.AAAARecordData, record.data)
return AresQueryAAAAResult(host=aaaa_data.addr, ttl=ttl)
if record_type == pycares.QUERY_TYPE_CNAME:
cname_data = cast(pycares.CNAMERecordData, record.data)
return AresQueryCNAMEResult(cname=cname_data.cname, ttl=ttl)
if record_type == pycares.QUERY_TYPE_MX:
mx_data = cast(pycares.MXRecordData, record.data)
return AresQueryMXResult(
host=mx_data.exchange, priority=mx_data.priority, ttl=ttl
)
if record_type == pycares.QUERY_TYPE_NS:
ns_data = cast(pycares.NSRecordData, record.data)
return AresQueryNSResult(host=ns_data.nsdname, ttl=ttl)
if record_type == pycares.QUERY_TYPE_TXT:
txt_data = cast(pycares.TXTRecordData, record.data)
return AresQueryTXTResult(text=_maybe_str(txt_data.data), ttl=ttl)
if record_type == pycares.QUERY_TYPE_SOA:
soa_data = cast(pycares.SOARecordData, record.data)
return AresQuerySOAResult(
nsname=soa_data.mname,
hostmaster=soa_data.rname,
serial=soa_data.serial,
refresh=soa_data.refresh,
retry=soa_data.retry,
expires=soa_data.expire,
minttl=soa_data.minimum,
ttl=ttl,
)
if record_type == pycares.QUERY_TYPE_SRV:
srv_data = cast(pycares.SRVRecordData, record.data)
return AresQuerySRVResult(
host=srv_data.target,
port=srv_data.port,
priority=srv_data.priority,
weight=srv_data.weight,
ttl=ttl,
)
if record_type == pycares.QUERY_TYPE_NAPTR:
naptr_data = cast(pycares.NAPTRRecordData, record.data)
return AresQueryNAPTRResult(
order=naptr_data.order,
preference=naptr_data.preference,
flags=naptr_data.flags,
service=naptr_data.service,
regex=naptr_data.regexp,
replacement=naptr_data.replacement,
ttl=ttl,
)
if record_type == pycares.QUERY_TYPE_CAA:
caa_data = cast(pycares.CAARecordData, record.data)
return AresQueryCAAResult(
critical=caa_data.critical,
property=caa_data.tag,
value=caa_data.value,
ttl=ttl,
)
if record_type == pycares.QUERY_TYPE_PTR:
ptr_data = cast(pycares.PTRRecordData, record.data)
return AresQueryPTRResult(name=ptr_data.dname, ttl=ttl, aliases=[])
# Return raw record for unknown types
return record
def convert_result(dns_result: pycares.DNSResult, qtype: int) -> QueryResult:
"""Convert pycares 5.x DNSResult to pycares 4.x compatible format."""
# For ANY - convert all records and return mixed list
if qtype == pycares.QUERY_TYPE_ANY:
return [_convert_record(record) for record in dns_result.answer]
results: list[ConvertedRecord] = []
for record in dns_result.answer:
record_type = record.type
# Filter by query type since answer can contain other types
# (e.g., CNAME records when querying for A/AAAA)
if record_type != qtype:
continue
converted = _convert_record(record)
# CNAME, SOA, and PTR return single result, not list
if record_type in _SINGLE_RESULT_QTYPES:
return cast(QueryResult, converted)
results.append(converted)
# NOERROR/NODATA: c-ares delivered ARES_SUCCESS but the answer has no
# records of the queried type. pycares 4.x raised ARES_ENODATA here;
# without this branch single-result qtypes (CNAME/SOA/PTR) would
# resolve to [] and crash callers reading .name/.cname/.nsname.
if not results:
raise error.DNSError(
pycares.errno.ARES_ENODATA,
pycares.errno.strerror(pycares.errno.ARES_ENODATA),
)
return results