Package dns

Domain normalization, syntax validation, DNS pre-flight resolution checks, multi-resolver consistency cross-checks, static hosts-file override detection, origin-AS attribution, and SPF/DMARC sender-authority probes. Sources: internal/dns/dns.go, internal/dns/resolvers.go, internal/dns/hosts.go, internal/dns/asn.go, internal/dns/spf.go.


NormalizeDomain

func NormalizeDomain(d string) string

Lowercases the input and strips scheme, path, port, and trailing dot. Mirrors the shell normalize_domain helper.

dns.NormalizeDomain("HTTPS://Example.COM:8443/path")
// => "example.com"

ValidSyntax

func ValidSyntax(d string) bool

Reports whether d looks like a dotted domain name: labels of alphanumerics and hyphens with at least one dot. Used to reject malformed input before any network call.

dns.ValidSyntax("jdsoft.com")  // => true
dns.ValidSyntax("no-dot")      // => false

DomainCheck

type DomainCheck struct {
    SystemLive  bool
    PublicLive  bool
    SystemAddrs []net.IP
    PublicAddrs []net.IP
    HostsHit    bool
    HostsIP     string
    Consistency Consistency
}

One domain's full probe: system liveness for pre-flight gating, public liveness for suppression attribution, the aggregate consistency verdict, the first live A record per side (retained for per-path interrogation — never scored by raw address), and the hosts-file override state. The pre-flight gate aborts on system failure and reuses these outcomes downstream, so each resolver is queried exactly once per domain.


CheckDomain

func CheckDomain(ctx context.Context, domain string, timeout time.Duration) DomainCheck

Probes the system resolver and pinned public resolvers once. Use SystemLive for NXDOMAIN gating and Consistency for scoring and messaging.


Consistency

type Consistency struct {
    Checked  bool
    Tampered bool
    Kind     SplitKind
    Detail   string
}

Verdict of a multi-resolver cross-check. Detail names the disagreeing resolvers and the split kind when tampered.


SplitKind

const (
    SplitNone SplitKind = iota
    SplitSuppression
    SplitHorizon
    SplitNS
)

Split direction. Suppression (system dark, public live) signals local tampering; horizon (system live, public dark) is the expected split-horizon shape of internal names and never scores; NS marks delegation splits.


CheckConsistency

func CheckConsistency(ctx context.Context, domain string, timeout time.Duration) Consistency

Queries the system resolver plus pinned 1.1.1.1 and 8.8.8.8 servers in parallel (stdlib net.Resolver with a pinned dialer; filtered resolvers excluded by design). Flags NXDOMAIN-vs-live splits and NS delegation splits; raw A records are never compared, so CDN/GeoDNS churn stays quiet. Inconclusive resolvers abstain, and fewer than two conclusive votes is never tampering. Never fails closed.

ParameterDescription
ctxParent context; the global 15 second budget.
domainAlready-normalized domain to cross-check.
timeoutPer-resolver deadline (the CLI --timeout value).

CheckHostsOverride

func CheckHostsOverride(domain string) (ip string, ok bool)

Reports a hardcoded static mapping for domain in the OS hosts database (/etc/hosts on unix, C:\Windows\System32\drivers\etc\hosts on Windows — see HostsPath). Such a mapping short-circuits every resolver, so no cross-check can see it; it is probed once in pre-flight alongside the resolver queries. Fail-open: unreadable or missing files simply report no hit, never an error.


ParseHostsFile

func ParseHostsFile(content, domain string) (string, bool)

Pure scanner behind CheckHostsOverride: ignores comment lines and inline comments, matches hostnames case-insensitively (trailing-dot tolerant), first mapping wins (OS resolver precedence). Safe to unit test.


LookupASN

func LookupASN(ctx context.Context, ip net.IP, timeout time.Duration) (ASNInfo, bool)

Attributes ip to its origin autonomous system via Team Cymru's DNS service (<reversed-octets>.origin.asn.cymru.com TXT), queried through the pinned 1.1.1.1 resolver — never the system resolver, so a poisoned local resolver cannot attest to its own redirection. IPv4 only (IPv6 reports not-ok). Fail-open: unanswered or unparsable queries yield not-ok. Result type:

type ASNInfo struct {
    ASN    string // origin AS number, e.g. "15169"
    Prefix string // announced prefix, e.g. "8.8.8.0/24"
    CC     string // registrant country code, e.g. "US"
}

ParseCymruTXT

func ParseCymruTXT(txt string) (ASNInfo, bool)

Pure parser for one Cymru origin answer ("15169 | 8.8.8.0/24 | US | arin | 1992-12-01"). Companion helpers: ASNQueryName builds the query name (false for IPv6), ASNMatch reports a shared origin AS (unknown sides never match).


CheckSPFAuthority

func CheckSPFAuthority(ctx context.Context, target string, suspectIP net.IP, timeout time.Duration) (SPFResult, bool)

Evaluates the target's v=spf1 record for the suspect's IP (RFC 7208 mechanism subset: ip4/ip6 CIDRs, a, mx, recursive include:, redirect=; only a +-qualifier match authorizes). Recursive includes share the RFC 10-lookup budget; macro mechanisms, multiple v=spf1 records, and lookup errors all fail open (not-ok). Result type:

type SPFResult struct {
    Authorized bool   // the IP is covered by a pass-qualifier mechanism
    Cover      string // the covering mechanism, e.g. "include:_spf.google.com"
}

CheckDMARC

func CheckDMARC(ctx context.Context, target string, timeout time.Duration) (string, bool)

Reads the target's DMARC enforcement policy (none, quarantine, reject) from _dmarc.<target> TXT through the pinned resolver. Display-only context (a p=reject target tolerates no unauthorized senders); fail-open on any problem.


Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)