Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package doggo for openSUSE:Factory checked in at 2026-06-25 10:51:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/doggo (Old) and /work/SRC/openSUSE:Factory/.doggo.new.2088 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "doggo" Thu Jun 25 10:51:53 2026 rev:11 rq:1361467 version:1.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/doggo/doggo.changes 2026-05-28 23:12:46.857476497 +0200 +++ /work/SRC/openSUSE:Factory/.doggo.new.2088/doggo.changes 2026-06-25 10:56:03.595001983 +0200 @@ -1,0 +2,15 @@ +Tue Jun 23 08:02:44 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.2.0: + * New Features + - 58781f2: feat: add --authoritative/-A flag to query the + domain's authoritative nameserver (@nopolabs) + * Bug fixes + - 8a97e57: fix(app): use delegated NS RRset for authoritative + queries (@nopolabs) + * Others + - 4c3bf3d: chore(ci): use patched Go toolchain (@mr-karan) + - 86ff8f4: chore(release): prepare v1.2.0 artifacts (@mr-karan) + - 7dbf633: docs(README): change arch package name (@Souvlaki42) + +------------------------------------------------------------------- Old: ---- doggo-1.1.7.obscpio New: ---- doggo-1.2.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ doggo.spec ++++++ --- /var/tmp/diff_new_pack.VhCD20/_old 2026-06-25 10:56:05.907081714 +0200 +++ /var/tmp/diff_new_pack.VhCD20/_new 2026-06-25 10:56:05.947083093 +0200 @@ -17,7 +17,7 @@ Name: doggo -Version: 1.1.7 +Version: 1.2.0 Release: 0 Summary: CLI tool and API server DNS client implemented in Go License: GPL-3.0-only ++++++ _service ++++++ --- /var/tmp/diff_new_pack.VhCD20/_old 2026-06-25 10:56:06.267094129 +0200 +++ /var/tmp/diff_new_pack.VhCD20/_new 2026-06-25 10:56:06.299095232 +0200 @@ -2,7 +2,7 @@ <service name="obs_scm" mode="manual"> <param name="scm">git</param> <param name="url">https://github.com/mr-karan/doggo.git</param> - <param name="revision">v1.1.7</param> + <param name="revision">v1.2.0</param> <param name="match-tag">*</param> <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param> <param name="versionformat">@PARENT_TAG@</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.VhCD20/_old 2026-06-25 10:56:06.531103232 +0200 +++ /var/tmp/diff_new_pack.VhCD20/_new 2026-06-25 10:56:06.555104060 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/mr-karan/doggo.git</param> - <param name="changesrevision">b400c464b445c79805a9987475f862357ce7f49d</param></service></servicedata> + <param name="changesrevision">4c3bf3d68955e3988af287cfeb1b7aa41d787fb0</param></service></servicedata> (No newline at EOF) ++++++ doggo-1.1.7.obscpio -> doggo-1.2.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/.github/workflows/govulncheck.yml new/doggo-1.2.0/.github/workflows/govulncheck.yml --- old/doggo-1.1.7/.github/workflows/govulncheck.yml 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/.github/workflows/govulncheck.yml 2026-06-23 09:00:28.000000000 +0200 @@ -21,4 +21,5 @@ with: go-version-file: go.mod - run: | - go run golang.org/x/vuln/cmd/govulncheck@latest ./... + # v1.4.0 currently panics under Go 1.26 while building the SSA call graph. + go run golang.org/x/vuln/cmd/[email protected] ./... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/.github/workflows/release.yml new/doggo-1.2.0/.github/workflows/release.yml --- old/doggo-1.1.7/.github/workflows/release.yml 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/.github/workflows/release.yml 2026-06-23 09:00:28.000000000 +0200 @@ -19,7 +19,7 @@ - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.26" + go-version: "1.26.4" - name: Log in to the Container registry uses: docker/login-action@v3 with: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/.github/workflows/test.yml new/doggo-1.2.0/.github/workflows/test.yml --- old/doggo-1.1.7/.github/workflows/test.yml 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/.github/workflows/test.yml 2026-06-23 09:00:28.000000000 +0200 @@ -32,6 +32,13 @@ go-version-file: ${{ matrix.go.go-version-file }} - run: | if [ "${{ matrix.deps }}" = "latest" ]; then - go get -u -t ./... + # globalping-cli v1.5.2 removed github.com/jsdelivr/globalping-cli/globalping, + # which doggo imports. Keep the rest of the dependency freshness check + # while holding that module at the last compatible release. + if ! go get -u -t ./... 2>go-get.err; then + cat go-get.err + grep -q "github.com/jsdelivr/globalping-cli/globalping" go-get.err + fi + go get github.com/jsdelivr/[email protected] fi go test -v ./... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/.goreleaser.yml new/doggo-1.2.0/.goreleaser.yml --- old/doggo-1.1.7/.goreleaser.yml 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/.goreleaser.yml 2026-06-23 09:00:28.000000000 +0200 @@ -48,25 +48,38 @@ archives: - id: cli - builds: + ids: - cli format_overrides: - goos: windows - format: zip + formats: + - zip name_template: >- - {{ .ProjectName }}_ - {{- .Version }}_ - {{- title .Os }}_ + {{ .ProjectName }}- + {{- .Os }}- {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "arm64" }}aarch64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} - wrap_in_directory: true + wrap_in_directory: false + builds_info: + owner: root + group: root + mode: 0755 files: - - README.md - - LICENSE + - src: README.md + info: + owner: root + group: root + mode: 0644 + - src: LICENSE + info: + owner: root + group: root + mode: 0644 - id: web - builds: + ids: - web wrap_in_directory: true files: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/README.md new/doggo-1.2.0/README.md --- old/doggo-1.1.7/README.md 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/README.md 2026-06-23 09:00:28.000000000 +0200 @@ -32,7 +32,7 @@ - Homebrew: `brew install doggo` - MacPorts (macOS): `port install doggo` -- Arch Linux: `yay -S doggo-bin` +- Arch Linux: `pacman -S doggo` - [Mise](https://github.com/jdx/mise): `mise use -g doggo@latest` - Nix: `nix profile install nixpkgs#doggo` - Scoop (Windows): `scoop install doggo` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/cmd/doggo/cli.go new/doggo-1.2.0/cmd/doggo/cli.go --- old/doggo-1.1.7/cmd/doggo/cli.go 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/cmd/doggo/cli.go 2026-06-23 09:00:28.000000000 +0200 @@ -182,6 +182,7 @@ f.Bool("skip-hostname-verification", false, "Skip TLS Hostname Verification") f.Bool("any", false, "Query all supported DNS record types") + f.BoolP("authoritative", "A", false, "Automatically query the authoritative nameserver for the domain") f.BoolP("json", "J", false, "Set the output format as JSON") f.Bool("short", false, "Short output format") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/cmd/doggo/help.go new/doggo-1.2.0/cmd/doggo/help.go --- old/doggo-1.1.7/cmd/doggo/help.go 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/cmd/doggo/help.go 2026-06-23 09:00:28.000000000 +0200 @@ -119,6 +119,7 @@ {"-c, --class=CLASS", "Network class of the DNS record (IN, CH, HS etc)."}, {"-x, --reverse", "Performs a DNS Lookup for an IPv4 or IPv6 address. Sets the query type and class to PTR and IN respectively."}, {"--any", "Query all supported DNS record types (A, AAAA, CNAME, MX, NS, PTR, SOA, SRV, TXT, CAA)."}, + {"-A, --authoritative", "Find the domain's zone via SOA and query its delegated authoritative nameservers (the NS RRset). Honours --strategy to narrow the set."}, }, "ResolverOptions": []Option{ {"--strategy=STRATEGY", "Specify strategy to query nameservers. Options: all, random, first, internal (RFC 1918/ULA private IPs only)."}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/go.mod new/doggo-1.2.0/go.mod --- old/doggo-1.1.7/go.mod 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/go.mod 2026-06-23 09:00:28.000000000 +0200 @@ -1,6 +1,6 @@ module github.com/mr-karan/doggo -go 1.26.3 +go 1.26.4 require ( github.com/ameshkov/dnscrypt/v2 v2.4.0 @@ -17,8 +17,8 @@ github.com/olekukonko/tablewriter v1.1.4 github.com/quic-go/quic-go v0.59.1 github.com/spf13/pflag v1.0.10 - golang.org/x/net v0.54.0 - golang.org/x/sys v0.44.0 + golang.org/x/net v0.55.0 + golang.org/x/sys v0.45.0 ) require ( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/go.sum new/doggo-1.2.0/go.sum --- old/doggo-1.1.7/go.sum 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/go.sum 2026-06-23 09:00:28.000000000 +0200 @@ -80,12 +80,12 @@ golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/install.sh new/doggo-1.2.0/install.sh --- old/doggo-1.1.7/install.sh 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/install.sh 2026-06-23 09:00:28.000000000 +0200 @@ -31,7 +31,7 @@ command -v "$1" 1>/dev/null 2>&1 } -SUPPORTED_TARGETS="Linux_x86_64 Linux_arm64 Windows_x86_64 Darwin_x86_64 Darwin_arm64" +SUPPORTED_TARGETS="linux_x86_64 linux_aarch64 windows_x86_64 darwin_x86_64 darwin_aarch64" get_latest_release() { curl --silent "https://api.github.com/repos/mr-karan/doggo/releases/latest" | @@ -42,9 +42,9 @@ detect_platform() { platform="$(uname -s)" case "${platform}" in - Linux*) platform="Linux" ;; - Darwin*) platform="Darwin" ;; - MINGW*|MSYS*|CYGWIN*) platform="Windows" ;; + Linux*) platform="linux" ;; + Darwin*) platform="darwin" ;; + MINGW*|MSYS*|CYGWIN*) platform="windows" ;; *) error "Unsupported platform: ${platform}" exit 1 @@ -57,7 +57,7 @@ arch="$(uname -m)" case "${arch}" in x86_64) arch="x86_64" ;; - aarch64|arm64) arch="arm64" ;; + aarch64|arm64) arch="aarch64" ;; armv6l|armv7l|armv8l) arch="arm" ;; *) error "Unsupported architecture: ${arch}" @@ -67,40 +67,69 @@ printf '%s' "${arch}" } +legacy_platform() { + case "$1" in + linux) printf 'Linux' ;; + darwin) printf 'Darwin' ;; + windows) printf 'Windows' ;; + esac +} + +legacy_arch() { + case "$1" in + aarch64) printf 'arm64' ;; + *) printf '%s' "$1" ;; + esac +} + +download_file() { + url="$1" + filename="$2" + + if has curl; then + curl -fsSL "${url}" -o "${filename}" + elif has wget; then + wget -q "${url}" -O "${filename}" + else + error "Neither curl nor wget found. Please install one of them and try again." + exit 1 + fi +} + download_and_install() { version="$1" platform="$2" arch="$3" - # Remove 'v' prefix from version for filename - version_no_v="${version#v}" - if [ "${platform}" = "Windows" ]; then - filename="doggo_${version_no_v}_${platform}_${arch}.zip" + if [ "${platform}" = "windows" ]; then + filename="doggo-${platform}-${arch}.zip" else - filename="doggo_${version_no_v}_${platform}_${arch}.tar.gz" + filename="doggo-${platform}-${arch}.tar.gz" fi url="https://github.com/mr-karan/doggo/releases/download/${version}/${filename}" info "Downloading doggo ${version} for ${platform}_${arch}..." info "Download URL: ${url}" - if has curl; then - if ! curl -sSL "${url}" -o "${filename}"; then - error "Failed to download ${filename}" - error "Curl output:" - curl -SL "${url}" - exit 1 + if ! download_file "${url}" "${filename}"; then + warn "Could not download ${filename}; trying legacy release asset name." + rm -f "${filename}" + + version_no_v="${version#v}" + old_platform="$(legacy_platform "${platform}")" + old_arch="$(legacy_arch "${arch}")" + if [ "${platform}" = "windows" ]; then + filename="doggo_${version_no_v}_${old_platform}_${old_arch}.zip" + else + filename="doggo_${version_no_v}_${old_platform}_${old_arch}.tar.gz" fi - elif has wget; then - if ! wget -q "${url}" -O "${filename}"; then + url="https://github.com/mr-karan/doggo/releases/download/${version}/${filename}" + info "Legacy download URL: ${url}" + + if ! download_file "${url}" "${filename}"; then error "Failed to download ${filename}" - error "Wget output:" - wget "${url}" exit 1 fi - else - error "Neither curl nor wget found. Please install one of them and try again." - exit 1 fi info "Verifying if file command exists" @@ -110,7 +139,7 @@ fi info "Verifying downloaded file..." - if [ "${platform}" = "Windows" ]; then + if [ "${platform}" = "windows" ]; then if ! file "${filename}" | grep -q "Zip archive data"; then error "Downloaded file is not in zip format. Installation failed." error "File type:" @@ -131,7 +160,7 @@ info "Extracting ${filename}..." extract_dir="doggo_extract" mkdir -p "${extract_dir}" - if [ "${platform}" = "Windows" ]; then + if [ "${platform}" = "windows" ]; then if ! unzip -q "${filename}" -d "${extract_dir}"; then error "Failed to extract ${filename}" rm -rf "${filename}" "${extract_dir}" @@ -147,7 +176,7 @@ info "Installing doggo..." binary_name="doggo" - if [ "${platform}" = "Windows" ]; then + if [ "${platform}" = "windows" ]; then binary_name="doggo.exe" fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/internal/app/nameservers.go new/doggo-1.2.0/internal/app/nameservers.go --- old/doggo-1.1.7/internal/app/nameservers.go 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/internal/app/nameservers.go 2026-06-23 09:00:28.000000000 +0200 @@ -5,10 +5,12 @@ "math/rand" "net" "net/url" + "sort" "strings" "time" "github.com/ameshkov/dnsstamps" + "github.com/miekg/dns" "github.com/mr-karan/doggo/pkg/config" "github.com/mr-karan/doggo/pkg/models" ) @@ -39,8 +41,11 @@ } } - // If no nameservers were successfully loaded, fall back to system nameservers + // If no nameservers were successfully loaded, check for authoritative flag if len(app.Nameservers) == 0 { + if app.QueryFlags.UseAuthoritative && len(app.QueryFlags.QNames) > 0 { + return app.loadAuthoritativeNameserver(app.QueryFlags.QNames[0]) + } return app.loadSystemNameservers() } @@ -455,3 +460,123 @@ return servers, ndots, search, nil } + +// loadAuthoritativeNameserver finds the closest enclosing zone for the domain +// via SOA queries, then resolves that zone's delegated NS RRset and adds those +// nameservers to app.Nameservers. +// +// SOA is used only to locate the zone cut. The actual query targets come from +// the zone's NS records, which are the publicly delegated authoritative +// servers. We deliberately avoid SOA.Ns (the zone primary/MNAME): it is often +// an internal hostname that does not resolve publicly (e.g. amazon.com's MNAME +// is dns-external-route53.us-east-1.amazonaws.com), whereas the delegated NS +// set is what recursive resolvers actually query. +func (app *App) loadAuthoritativeNameserver(domain string) error { + systemServers, _, _, err := config.GetDefaultServers() + if err != nil || len(systemServers) == 0 { + return fmt.Errorf("unable to load system nameservers for SOA lookup: %w", err) + } + resolver := net.JoinHostPort(systemServers[0], models.DefaultUDPPort) + + c := &dns.Client{Timeout: 5 * time.Second} + + // Step 1: use SOA to identify the closest enclosing zone (the zone cut). + zone, err := app.closestZone(c, resolver, dns.Fqdn(domain)) + if err != nil { + return err + } + + // Step 2: fetch that zone's delegated NS RRset — the public authoritative servers. + nsNames, err := app.zoneNameservers(c, resolver, zone) + if err != nil { + return err + } + + servers := make([]models.Nameserver, 0, len(nsNames)) + for _, name := range nsNames { + ns, err := initNameserver(strings.TrimSuffix(name, ".")) + if err != nil { + app.Logger.Debug("Skipping invalid authoritative nameserver", "ns", name, "error", err) + continue + } + servers = append(servers, ns) + } + if len(servers) == 0 { + return fmt.Errorf("no usable authoritative nameservers found for zone %q", strings.TrimSuffix(zone, ".")) + } + + app.Logger.Debug("Resolved authoritative nameservers via NS RRset", "zone", zone, "nameservers", servers) + + // Step 3: let the nameserver strategy select the query target(s). + servers, err = app.applyNameserverStrategy(servers, "authoritative") + if err != nil { + return err + } + + app.Nameservers = append(app.Nameservers, servers...) + return nil +} + +// closestZone walks up the domain hierarchy issuing SOA queries until it finds +// the closest enclosing zone, returning that zone's apex as an FQDN. +func (app *App) closestZone(c *dns.Client, resolver, candidate string) (string, error) { + domain := candidate + for { + m := new(dns.Msg) + m.SetQuestion(candidate, dns.TypeSOA) + m.RecursionDesired = true + + r, _, err := c.Exchange(m, resolver) + if err == nil { + if soa := firstSOA(r); soa != nil { + // The SOA owner name is the zone apex regardless of the label + // we queried: a recursive resolver returns the enclosing zone's + // SOA in the authority section for sub-zone names. + return soa.Hdr.Name, nil + } + } + + // No SOA found — walk up to the parent zone. + labels := dns.SplitDomainName(candidate) + if len(labels) <= 1 { + return "", fmt.Errorf("no authoritative zone found for %q", strings.TrimSuffix(domain, ".")) + } + candidate = dns.Fqdn(strings.Join(labels[1:], ".")) + } +} + +// zoneNameservers queries the NS RRset for the given zone and returns the +// delegated nameserver hostnames, sorted for deterministic selection. +func (app *App) zoneNameservers(c *dns.Client, resolver, zone string) ([]string, error) { + m := new(dns.Msg) + m.SetQuestion(zone, dns.TypeNS) + m.RecursionDesired = true + + r, _, err := c.Exchange(m, resolver) + if err != nil { + return nil, fmt.Errorf("failed to query NS records for zone %q: %w", strings.TrimSuffix(zone, "."), err) + } + + var names []string + for _, rr := range append(r.Answer, r.Ns...) { + if ns, ok := rr.(*dns.NS); ok { + names = append(names, ns.Ns) + } + } + if len(names) == 0 { + return nil, fmt.Errorf("no NS records found for zone %q", strings.TrimSuffix(zone, ".")) + } + sort.Strings(names) + + return names, nil +} + +// firstSOA returns the first SOA record from the answer or authority section. +func firstSOA(r *dns.Msg) *dns.SOA { + for _, rr := range append(r.Answer, r.Ns...) { + if soa, ok := rr.(*dns.SOA); ok { + return soa + } + } + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/internal/app/nameservers_test.go new/doggo-1.2.0/internal/app/nameservers_test.go --- old/doggo-1.1.7/internal/app/nameservers_test.go 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/internal/app/nameservers_test.go 2026-06-23 09:00:28.000000000 +0200 @@ -3,6 +3,7 @@ import ( "io" "log/slog" + "strings" "testing" "github.com/mr-karan/doggo/pkg/models" @@ -59,6 +60,71 @@ } } +func TestLoadNameserversExplicitNameserverTakesPrecedenceOverAuthoritative(t *testing.T) { + app := newTestApp() + app.QueryFlags.Nameservers = []string{"1.1.1.1"} + app.QueryFlags.UseAuthoritative = true + app.QueryFlags.QNames = []string{"github.com"} + + if err := app.LoadNameservers(); err != nil { + t.Fatalf("LoadNameservers() error = %v", err) + } + + want := []models.Nameserver{ + {Address: "1.1.1.1:53", Type: models.UDPResolver}, + } + assertNameservers(t, app.Nameservers, want) +} + +func TestLoadAuthoritativeNameserver(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test requiring network") + } + app := newTestApp() + app.QueryFlags.UseAuthoritative = true + app.QueryFlags.QNames = []string{"github.com"} + + if err := app.LoadNameservers(); err != nil { + t.Fatalf("LoadNameservers() error = %v", err) + } + + if len(app.Nameservers) == 0 { + t.Fatal("expected at least one authoritative nameserver, got none") + } + t.Logf("resolved authoritative NS for github.com: %v", app.Nameservers[0].Address) +} + +// TestLoadAuthoritativeNameserverUsesDelegatedNS verifies the resolver targets +// come from the zone's delegated NS RRset, not the SOA primary (MNAME). amazon.com +// is the canonical case: its MNAME (dns-external-route53.us-east-1.amazonaws.com) +// is not publicly queryable, while its delegated NS set lives under awsdns-*. +func TestLoadAuthoritativeNameserverUsesDelegatedNS(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test requiring network") + } + app := newTestApp() + app.QueryFlags.UseAuthoritative = true + app.QueryFlags.QNames = []string{"amazon.com"} + + if err := app.LoadNameservers(); err != nil { + t.Fatalf("LoadNameservers() error = %v", err) + } + + if len(app.Nameservers) == 0 { + t.Fatal("expected at least one authoritative nameserver, got none") + } + + for _, ns := range app.Nameservers { + if strings.Contains(ns.Address, "dns-external-route53") { + t.Fatalf("selected SOA primary (MNAME) instead of delegated NS: %v", ns.Address) + } + if !strings.Contains(ns.Address, "awsdns") { + t.Errorf("expected a delegated awsdns nameserver, got %v", ns.Address) + } + } + t.Logf("resolved authoritative NS for amazon.com: %v", app.Nameservers) +} + func assertNameservers(t *testing.T, got, want []models.Nameserver) { t.Helper() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doggo-1.1.7/pkg/models/models.go new/doggo-1.2.0/pkg/models/models.go --- old/doggo-1.1.7/pkg/models/models.go 2026-05-22 15:05:55.000000000 +0200 +++ new/doggo-1.2.0/pkg/models/models.go 2026-06-23 09:00:28.000000000 +0200 @@ -45,6 +45,7 @@ InsecureSkipVerify bool `koanf:"skip-hostname-verification" skip-hostname-verification:"-"` TLSHostname string `koanf:"tls-hostname" tls-hostname:"-"` QueryAny bool `koanf:"any" json:"any"` + UseAuthoritative bool `koanf:"authoritative" json:"authoritative"` // DNS Query Flags AA bool `koanf:"aa" json:"aa"` // Authoritative Answer ++++++ doggo.obsinfo ++++++ --- /var/tmp/diff_new_pack.VhCD20/_old 2026-06-25 10:56:07.791146684 +0200 +++ /var/tmp/diff_new_pack.VhCD20/_new 2026-06-25 10:56:07.831148064 +0200 @@ -1,5 +1,5 @@ name: doggo -version: 1.1.7 -mtime: 1779455155 -commit: b400c464b445c79805a9987475f862357ce7f49d +version: 1.2.0 +mtime: 1782198028 +commit: 4c3bf3d68955e3988af287cfeb1b7aa41d787fb0 ++++++ vendor.tar.xz ++++++ ++++ 4195 lines of diff (skipped)
