Skip to content

feat: add -mx-resolve flag to resolve MX hosts to IPs in one pass (#972)#978

Open
ChrisJr404 wants to merge 3 commits into
projectdiscovery:devfrom
ChrisJr404:mx-resolve-flag
Open

feat: add -mx-resolve flag to resolve MX hosts to IPs in one pass (#972)#978
ChrisJr404 wants to merge 3 commits into
projectdiscovery:devfrom
ChrisJr404:mx-resolve-flag

Conversation

@ChrisJr404
Copy link
Copy Markdown

@ChrisJr404 ChrisJr404 commented May 3, 2026

Closes #972.

Background

When doing recon you usually want the IPs that back each MX hostname, not just the hostnames themselves. The current workflow is to pipe dnsx -mx's output back into another dnsx (or dig) call:

echo "google.com" | dnsx -mx -resp -silent | awk '{print $NF}' | dnsx -resp -silent

That's a brittle two-step pipeline that breaks when MX outputs ever change format and forces an extra DNS round-trip per pass.

Change

Add a new -mx-resolve / -mxr flag that runs the existing MX query then follows up with an A lookup for each returned MX hostname, deduping the resolved addresses across hosts. The IPs are emitted as a new MX_IP record type so existing -mx output is unchanged for users who don't pass the new flag, and -mx-resolve implies -mx so a single flag gets you everything.

Hosts that fail to resolve (NXDOMAIN, transient error, empty MX list) are silently dropped — the MX line still lists the hostname so the gap is visible to the operator.

Verification

$ echo "google.com" | dnsx -mx-resolve -resp -silent
google.com [MX] [smtp.google.com]
google.com [MX_IP] [108.177.122.27]
google.com [MX_IP] [172.217.215.26]
google.com [MX_IP] [172.217.215.27]
google.com [MX_IP] [142.250.9.27]
google.com [MX_IP] [108.177.122.26]

$ echo "github.com" | dnsx -mx-resolve -resp -silent
github.com [MX] [github-com.mail.protection.outlook.com]
github.com [MX_IP] [52.101.194.17]
github.com [MX_IP] [52.101.10.12]
github.com [MX_IP] [40.93.192.1]
github.com [MX_IP] [52.101.41.183]

$ echo "kernel.org" | dnsx -mx-resolve -resp -silent
kernel.org [MX] [smtp1.kernel.org]
kernel.org [MX] [smtp2.kernel.org]
kernel.org [MX] [smtp3.kernel.org]
kernel.org [MX_IP] [35.83.38.47]
kernel.org [MX_IP] [35.85.114.177]
kernel.org [MX_IP] [44.230.10.245]

$ echo "example.com" | dnsx -mx-resolve -resp -silent
example.com [MX] []                # no MX → no MX_IP, no error

go build ./... and go vet ./internal/runner/ both clean.

Notes

  • Diff is +37 / 0 lines across two files. No public API change. Setting -mx-resolve without -mx works (we auto-enable -mx); setting both is a no-op of the implication.
  • Scope is text/-resp output. JSON output already serialises the underlying dnsData so MX hostnames remain there; the resolved IPs aren't yet wired into the JSON shape — happy to follow up with a mx_ip JSON field if you'd like that as a separate PR (it's an additive schema change so worth its own discussion).
  • The new flag uses short form -mxr because -mx is already taken; mirrors the -resp / -re shortening pattern.

Summary by CodeRabbit

  • New Features
    • Added a CLI flag to enable MX hostname resolution into IP addresses.
    • When enabled, MX lookups emit resolved IP entries alongside MX hostnames.
    • Resolved IPs are deduplicated; lookups are rate-limited and failed resolutions are skipped silently.

…ojectdiscovery#972)

When doing recon you usually want the IPs that back the MX hostnames,
not just the hostnames themselves — and you don't want to have to pipe
the output back into dnsx to get them.

Add a new -mx-resolve / -mxr flag that runs the existing MX query then
follows up with an A lookup for each returned MX hostname, deduping
the resolved addresses across hosts. The IPs are emitted as a new
"MX_IP" record type so existing -mx output is unchanged for users who
don't pass the new flag, and -mx-resolve implies -mx so a single flag
gets you everything.

Hosts that fail to resolve (NXDOMAIN, transient error) are silently
dropped — the MX line still lists the hostname so the gap is visible.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ad5cc5c-9ba7-4e29-b31d-08c107266ad9

📥 Commits

Reviewing files that changed from the base of the PR and between 0fcc884 and 38fdb31.

📒 Files selected for processing (1)
  • internal/runner/runner.go

Walkthrough

Adds a new CLI option --mx-resolve / -mxr and Options.MXResolve. When enabled, MX querying is implied and each MX hostname is resolved; deduplicated IPs are emitted as additional MX_IP outputs while original MX hostname outputs are retained.

Changes

MX Hostname Resolution Feature

Layer / File(s) Summary
Option Definition & CLI Flag
internal/runner/options.go
Adds exported MXResolve bool to Options and registers --mx-resolve / -mxr flag in ParseOptions().
Query Configuration
internal/runner/options.go
configureQueryOptions() sets options.MX = true when MXResolve is enabled (makes MX querying implied).
Resolution Integration (responseTypeFilterMap path)
internal/runner/runner.go
When r.options.MXResolve is true, resolves dnsData.MX via resolveMXHosts and emits MX_IP outputs alongside existing typed outputs.
Resolution Integration (normal output path)
internal/runner/runner.go
When r.options.MXResolve and r.options.MX are true, resolves MX hostnames and emits MX_IP outputs without removing existing MX hostname output.
Resolution Logic
internal/runner/runner.go
New unexported resolveMXHosts(mxHosts []string) []string trims trailing dots, skips empties, rate-limits lookups via r.limiter, optionally increments requests stat, ignores lookup errors, and returns deduplicated resolved IPs.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Worker
  participant DNSx as dnsx.Lookup
  participant Output

  Worker->>Worker: receive dnsData with MX hostnames
  alt MXResolve enabled
    Worker->>DNSx: rate-limited A lookup per MX hostname
    DNSx-->>Worker: return IPs or error
    Worker->>Worker: aggregate & deduplicate IPs
    Worker->>Output: emit MX_IP records for resolved IPs
  end
  Worker->>Output: emit MX hostname records (unchanged)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
A flag named -mxr hops into view,
MX names chase IPs across the dew,
Dots are trimmed, failed hops pass along,
Unique IPs sing in the output song,
A little rabbit cheers: "DNS, hop on through!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a -mx-resolve flag to resolve MX hosts to IPs, which is the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/runner/options.go`:
- Line 134: The help text for the mx-resolve flag is inaccurate:
options.MXResolve description claims it resolves "A/AAAA" but the implementation
still uses dnsx.Lookup which only returns A records; update either the behavior
or the description. Either change the flag text passed to flagSet.BoolVarP to
say "resolve MX hostnames to A records (implies -mx)" to match dnsx.Lookup, or
modify the MX resolution code path to call a resolver that returns both A and
AAAA (e.g., replace dnsx.Lookup with a DNS API that returns IPv6 as well or add
dnsx.LookupAAAA/dnsx.LookupIP calls) so options.MXResolve truly returns A and
AAAA; make this change where options.MXResolve is consumed and where dnsx.Lookup
is invoked.

In `@internal/runner/runner.go`:
- Around line 812-815: resolveMXHosts currently performs per-hostname DNS
queries outside the request limiter, so -mx-resolve can exceed RPS and misreport
stats; update resolveMXHosts (or its caller) to execute all DNS lookups via the
existing r.limiter (e.g., acquire/release or submit lookups through the same
throttled lookup helper used elsewhere) so each MX hostname lookup is counted
and rate-limited, and ensure the same change is applied to the other
MX-resolution block referenced (the calls around lines 851-873) that use
r.options.MXResolve and r.outputRecordType.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5bd2fdb7-3b66-44cb-89be-548ffb1bdebd

📥 Commits

Reviewing files that changed from the base of the PR and between 168928b and f959285.

📒 Files selected for processing (2)
  • internal/runner/options.go
  • internal/runner/runner.go

Comment thread internal/runner/options.go Outdated
Comment thread internal/runner/runner.go
- resolveMXHosts now calls r.limiter.Take() before each follow-up A
  lookup so -mx-resolve respects -rl the same way the main worker does.
- The flag's help text said 'A/AAAA records' but dnsx.Lookup only
  returns A records today; tightened to 'A records' so the help matches
  the implementation. AAAA support is a worthwhile follow-up but needs
  a new helper in libs/dnsx — out of scope for this PR.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/runner/runner.go (1)

779-816: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

MX_IP output is silently suppressed when -filter-type is active.

The responseTypeFilterMap block (lines 779–790) exits early with continue before the MX-resolve block at lines 812–816 is reached. As a result, dnsx -mx-resolve -filter-type mx will emit MX hostnames but never the derived MX_IP records.

🛠️ Proposed fix
 		if len(r.options.responseTypeFilterMap) > 0 {
 			r.outputRecordType(domain, dnsData.A, "A", dnsData.CDNName, dnsData.ASN)
 			r.outputRecordType(domain, dnsData.AAAA, "AAAA", dnsData.CDNName, dnsData.ASN)
 			r.outputRecordType(domain, dnsData.CNAME, "CNAME", dnsData.CDNName, dnsData.ASN)
 			r.outputRecordType(domain, dnsData.MX, "MX", dnsData.CDNName, dnsData.ASN)
+			if r.options.MXResolve {
+				if mxIPs := r.resolveMXHosts(dnsData.MX); len(mxIPs) > 0 {
+					r.outputRecordType(domain, mxIPs, "MX_IP", dnsData.CDNName, dnsData.ASN)
+				}
+			}
 			r.outputRecordType(domain, dnsData.NS, "NS", dnsData.CDNName, dnsData.ASN)
 			...
 			continue
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/runner.go` around lines 779 - 816, The responseTypeFilterMap
branch exits early and therefore never performs MX host resolution for MX_IP;
update the block that calls r.outputRecordType(..., "MX", ...) to also, when
r.options.MXResolve is true, call r.resolveMXHosts(dnsData.MX) and emit the
derived IPs via r.outputRecordType(domain, mxIPs, "MX_IP", dnsData.CDNName,
dnsData.ASN) if any are returned (apply the same dedupe/length checks used in
the MXResolve logic below); this keeps MX and MX_IP output consistent whether
-filter-type is active or not.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/runner/runner.go`:
- Around line 857-875: The MX resolver is performing auxiliary A lookups via
r.dnsx.Lookup(host) in Runner.resolveMXHosts but never increments the stats
counters, so -mx-resolve -stats undercounts requests; update the code to call
the stats increment (the same method used in wildcard.go) immediately before
each r.dnsx.Lookup(host) so the live requests counter reflects these follow-up A
queries; also consider adding the resulting lookup count to the overall total
(the variable managed in prepareInput) or at minimum document that total is a
lower bound if you choose only to increment the live requests counter.

---

Outside diff comments:
In `@internal/runner/runner.go`:
- Around line 779-816: The responseTypeFilterMap branch exits early and
therefore never performs MX host resolution for MX_IP; update the block that
calls r.outputRecordType(..., "MX", ...) to also, when r.options.MXResolve is
true, call r.resolveMXHosts(dnsData.MX) and emit the derived IPs via
r.outputRecordType(domain, mxIPs, "MX_IP", dnsData.CDNName, dnsData.ASN) if any
are returned (apply the same dedupe/length checks used in the MXResolve logic
below); this keeps MX and MX_IP output consistent whether -filter-type is active
or not.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c754f9a-c119-47fd-9640-ca6267addd5e

📥 Commits

Reviewing files that changed from the base of the PR and between f959285 and 0fcc884.

📒 Files selected for processing (2)
  • internal/runner/options.go
  • internal/runner/runner.go
✅ Files skipped from review due to trivial changes (1)
  • internal/runner/options.go

Comment thread internal/runner/runner.go
… counter

CodeRabbit flagged two gaps in the -mx-resolve path:

  1. The response-type-filter branch in worker() returned before reaching
     the MX-resolve block, so 'dnsx -mx-resolve -rtf <type>' silently
     dropped the derived MX_IP records. Mirror the unfiltered branch by
     calling resolveMXHosts inside the filter branch as well.

  2. resolveMXHosts performs auxiliary A-record lookups via r.dnsx.Lookup
     but never incremented the live 'requests' counter, so '-mx-resolve
     -stats' undercounted total queries. Bump the counter alongside each
     additional lookup, matching the pattern in InputWorker.
@neo-by-projectdiscovery-dev
Copy link
Copy Markdown

neo-by-projectdiscovery-dev Bot commented May 3, 2026

Neo - PR Security Review

Caution

Neo couldn't finish analyzing this pull request during this run. Please run the review again.

Comment @pdneo help for available commands. · Open in Neo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve MX lookup: resolve MX hosts to IPs

1 participant