Architecture

This document describes the architecture of the subenum tool, a Go-based command-line utility for subdomain enumeration.

1. Overview

The subenum tool operates through a sequence of steps to discover valid subdomains for a given target domain:

  1. Initialization: Parses command-line arguments, including the target domain, path to the wordlist file, concurrency level, and DNS timeout.
  2. Wildcard Detection: Resolves two random subdomains to detect wildcard DNS. If detected, exits unless -force is set.
  3. Wordlist Ingestion: Reads the wordlist file into memory, deduplicating entries in a single pass.
  4. Concurrent Resolution: A pool of worker goroutines is established. Each worker takes a prefix from the wordlist, constructs a full subdomain string (e.g., prefix.targetdomain.com), and attempts to resolve it using DNS.
  5. Output: Resolved subdomains are printed to stdout (pipe-friendly); all progress, verbose, and diagnostic output goes to stderr.
  6. Completion: The tool waits for all DNS lookups to complete before exiting.

This architecture is designed to be efficient by performing multiple DNS lookups concurrently, while also providing control over the level of concurrency and timeout settings.

Package Structure

main.go                        - CLI entry point (flag parsing, wiring, -tui dispatch)
internal/scan/runner.go        - Scan engine: Config, Event types, Run(ctx, cfg, events)
internal/dns/resolver.go       - ResolveTypes, ResolveDomainWithRetry, CheckWildcard, ParseTypes
internal/dns/simulate.go       - SimulateResolve
internal/output/writer.go      - Thread-safe Writer (results→stdout, diagnostics→stderr)
internal/wordlist/reader.go    - LoadWordlist (dedup + sanitize)
internal/tui/model.go          - Root Bubble Tea model (form → scan state machine)
internal/tui/form.go           - Config form screen (textinput fields + toggles)
internal/tui/scan_view.go      - Live results screen (viewport + progress bar)
internal/tui/config.go         - Session persistence (load/save ~/.config/subenum/last.json)

2. Key Components / Modules

2.1. Argument Parsing

2.2. Wordlist Processing (internal/wordlist)

2.3. DNS Resolution Engine (internal/dns)

2.4. Concurrency Management (internal/scan)

2.5. Output Formatting (internal/output)

2.6. Progress Monitoring

2.7. Session Persistence (internal/tui/config.go)

3. Data Flow

The flow of data through the subenum application can be summarized as follows:

  1. Input: The user provides command-line arguments: the target domain, the path to a wordlist file (-w), a concurrency level (-t), a DNS timeout (-timeout), a DNS server (-dns-server), attempts (-attempts), output options (-o, -format), and scan tuning (-rate, -type, -recursive, -depth), plus flags for verbose mode (-v), progress reporting (-progress), and force mode (-force). The TUI (-tui) gathers the equivalent values from its form instead.
  2. Configuration: These arguments are parsed and validated by the Argument Parsing component and assembled into a scan.Config, which is the single input to the scan engine.
  3. Wildcard Detection: Inside scan.Run, two random subdomains are resolved against the target domain (skipped in simulation mode). If either resolves, wildcard DNS is detected and an EventWildcard is emitted; the scan aborts with an EventError unless -force is set.
  4. Wordlist Loading: wordlist.LoadWordlist reads the file in a single pass, sanitizes lines, and deduplicates entries into a slice, which is passed to scan.Run via Config.Entries.
  5. Dispatch: A dispatcher goroutine seeds its queue from the entry slice (constructing prefix.domain jobs at depth 1, deduplicated through a visited set) and feeds the internal jobs channel. It owns the queue, the visited set, and a pending-work counter; it does not close jobs until the counter drains to zero or the context is cancelled.
  6. Resolution: cfg.Concurrency worker goroutines read jobs from jobs (each job already holds the full domain) and call dns.ResolveDomainWithRetry (or dns.SimulateResolve in simulation mode) for the requested record types, honoring the optional rate-limiter gate.
  7. Result Emission: On a successful resolution the worker increments the found counter and emits an EventResult carrying the domain and its typed records. The caller (CLI Writer or TUI scan view) renders it; the Writer routes results to stdout and any -o file in the selected -format.
  8. Recursive Expansion (optional): when -recursive is set and a resolved job is below the -depth cap, the worker submits one child per wordlist entry back to the dispatcher over the enqueue channel. The dispatcher’s visited set deduplicates them and grows the progress total as new work is admitted.
  9. Progress Tracking: Workers increment atomic processed/found counters; a separate ticker goroutine emits EventProgress once per second so the caller can update its display.
  10. Termination: Each worker signals completion of a job over the completed channel. When the pending counter reaches zero the dispatcher closes jobs, the workers exit, and scan.Run waits on the sync.WaitGroup, stops the progress ticker, emits EventDone, and closes the events channel. On SIGINT/SIGTERM the context is cancelled, the dispatcher closes jobs early, and the same drain-and-finish path runs with partial counts.

Visually, this can be seen as:

User Input -> Argument Parser -> scan.Config -> scan.Run() [Dispatcher -> jobs -> Worker Pool -> DNS Resolver] -> Event Channel -> Output (if resolved)

4. Error Handling Strategy

subenum handles different types of errors at various stages of its operation:

4.1. User Input Errors

4.2. File Operation Errors

4.3. DNS Resolution Errors

4.5. Graceful Shutdown

The tool listens for SIGINT and SIGTERM signals. Upon receiving an interrupt, it cancels the work context, drains in-flight workers, and exits cleanly with a summary of results processed so far.

4.6. Output File Support

When the -o flag is provided, resolved subdomains are written to the specified file (one per line) in addition to stdout. A mutex protects concurrent writes to both stdout and the output file.

4.7. Retry Mechanism

The -attempts flag (default: 1) controls the total number of DNS resolution attempts per subdomain. A value of 1 means no retries. A short linear backoff delay is applied between attempts to handle transient DNS failures. The deprecated -retries flag is still accepted as an alias but prints a warning to stderr.

Home Architecture Developer Guide Docker Contributing