feat: implement CLI flags and configuration handling (HAN-382)
CI / test (pull_request) Waiting to run
CI / test (pull_request) Waiting to run
- Created internal/config/config.go with Config struct containing all 15 CLI flags - Added ParseQueryType() for validating record types (A, AAAA, NS, CNAME, MX, TXT, SOA, PTR, ANY) - Added ParseUDPSize() with validation (512-4096) - Added ParseMaxDepth() with validation (1-100) - Added ParseRetries() with validation (0-10) - Added Validate() for cross-field validation (e.g., --always-tcp requires --allow-tcp) - Added GetDomain() for validating the required positional argument - Added PrintUsage() displaying all flags with --no- variants for display options - Updated cmd/exploredns/main.go with flag definitions using the standard flag package - Proper flag parsing and config population - Manual parsing of debug level (supports -dd) - Support for --no-* display flags (e.g., --no-show-progress) - Config validation before execution - Integration with traversal engine using dns.QueryConfig and traverse.TraverserConfig - Clean error handling with descriptive messages All flags implemented: - --type TYPE (default A) - --root-server HOST - --all-root-servers - --root-aaaa - --follow-aaaa - --udp-size SIZE (default 2048) - --allow-tcp (default true) - --always-tcp - --max-depth DEPTH (default 20) - --retries TIMES (default 2) - --fast (default true) - --verbose, -v - --debug, -d (stackable: -dd) - --quiet, -q - --show-progress, --no-show-progress - --show-resolves, --no-show-resolves - --show-servers, --no-show-servers - --show-versions, --no-show-versions - --show-all-stats, --no-show-all-stats - --show-results, --no-show-results - --show-summary-results, --no-show-summary-results Validation: Invalid inputs produce clear error messages, exploredns --help displays usage with all options, all existing tests pass Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -1 +1,221 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidQueryType = errors.New("invalid query type")
|
||||
ErrInvalidUDPSize = errors.New("UDP size must be between 512 and 4096")
|
||||
ErrInvalidMaxDepth = errors.New("max depth must be between 1 and 100")
|
||||
ErrInvalidRetries = errors.New("retries must be between 0 and 10")
|
||||
ErrMissingDomain = errors.New("domain is required")
|
||||
ErrAlwaysTCPRequiresTCP = errors.New("--always-tcp requires --allow-tcp")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
QueryType string
|
||||
RootServer string
|
||||
AllRootServers bool
|
||||
RootAAAA bool
|
||||
FollowAAAA bool
|
||||
UDPSize int
|
||||
AllowTCP bool
|
||||
AlwaysTCP bool
|
||||
MaxDepth int
|
||||
Retries int
|
||||
Fast bool
|
||||
Verbose bool
|
||||
Debug int
|
||||
Quiet bool
|
||||
|
||||
ShowProgress bool
|
||||
ShowResolves bool
|
||||
ShowServers bool
|
||||
ShowVersions bool
|
||||
ShowAllStats bool
|
||||
ShowResults bool
|
||||
ShowSummaryResults bool
|
||||
}
|
||||
|
||||
func ParseQueryType(s string) (uint16, error) {
|
||||
s = strings.ToUpper(s)
|
||||
switch s {
|
||||
case "A":
|
||||
return dns.TypeA, nil
|
||||
case "AAAA":
|
||||
return dns.TypeAAAA, nil
|
||||
case "NS":
|
||||
return dns.TypeNS, nil
|
||||
case "CNAME":
|
||||
return dns.TypeCNAME, nil
|
||||
case "MX":
|
||||
return dns.TypeMX, nil
|
||||
case "TXT":
|
||||
return dns.TypeTXT, nil
|
||||
case "SOA":
|
||||
return dns.TypeSOA, nil
|
||||
case "PTR":
|
||||
return dns.TypePTR, nil
|
||||
case "ANY":
|
||||
return dns.TypeANY, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidQueryType, s)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseUDPSize(s string) (int, error) {
|
||||
var size int
|
||||
if _, err := fmt.Sscanf(s, "%d", &size); err != nil {
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidUDPSize, s)
|
||||
}
|
||||
if size < 512 || size > 4096 {
|
||||
return 0, fmt.Errorf("%w: %d (must be 512-4096)", ErrInvalidUDPSize, size)
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func ParseMaxDepth(s string) (int, error) {
|
||||
var depth int
|
||||
if _, err := fmt.Sscanf(s, "%d", &depth); err != nil {
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidMaxDepth, s)
|
||||
}
|
||||
if depth < 1 || depth > 100 {
|
||||
return 0, fmt.Errorf("%w: %d (must be 1-100)", ErrInvalidMaxDepth, depth)
|
||||
}
|
||||
return depth, nil
|
||||
}
|
||||
|
||||
func ParseRetries(s string) (int, error) {
|
||||
var retries int
|
||||
if _, err := fmt.Sscanf(s, "%d", &retries); err != nil {
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidRetries, s)
|
||||
}
|
||||
if retries < 0 || retries > 10 {
|
||||
return 0, fmt.Errorf("%w: %d (must be 0-10)", ErrInvalidRetries, retries)
|
||||
}
|
||||
return retries, nil
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
if _, err := ParseQueryType(c.QueryType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.UDPSize < 512 || c.UDPSize > 4096 {
|
||||
return fmt.Errorf("%w: %d", ErrInvalidUDPSize, c.UDPSize)
|
||||
}
|
||||
|
||||
if c.MaxDepth < 1 || c.MaxDepth > 100 {
|
||||
return fmt.Errorf("%w: %d", ErrInvalidMaxDepth, c.MaxDepth)
|
||||
}
|
||||
|
||||
if c.Retries < 0 || c.Retries > 10 {
|
||||
return fmt.Errorf("%w: %d", ErrInvalidRetries, c.Retries)
|
||||
}
|
||||
|
||||
if c.AlwaysTCP && !c.AllowTCP {
|
||||
return ErrAlwaysTCPRequiresTCP
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) GetDomain(args []string) (string, error) {
|
||||
if len(args) == 0 {
|
||||
return "", ErrMissingDomain
|
||||
}
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
func (c *Config) ParseRootServer() (net.IP, error) {
|
||||
if c.RootServer == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return net.ParseIP(c.RootServer), nil
|
||||
}
|
||||
|
||||
func PrintUsage() {
|
||||
fmt.Fprintf(os.Stderr, "ExploreDNS - DNS reconnaissance and exploration tool\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Usage:\n")
|
||||
fmt.Fprintf(os.Stderr, " exploredns [flags] <domain>\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||
|
||||
flagGroups := map[string][][2]string{
|
||||
"Query Options": {
|
||||
{"--type", "Record type (default A)"},
|
||||
{"--root-server", "Override root server"},
|
||||
{"--all-root-servers", "Use all 13 root servers"},
|
||||
{"--root-aaaa", "Include IPv6 root addresses"},
|
||||
{"--follow-aaaa", "Only follow AAAA for referrals"},
|
||||
},
|
||||
"Transport Options": {
|
||||
{"--udp-size", "EDNS0 buffer size (default 2048)"},
|
||||
{"--allow-tcp", "TCP fallback on truncation (default true)"},
|
||||
{"--always-tcp", "Always use TCP"},
|
||||
{"--retries", "Retry count (default 2)"},
|
||||
},
|
||||
"Traversal Options": {
|
||||
{"--max-depth", "Max traversal depth (default 20)"},
|
||||
{"--fast", "Fast mode: reuse earlier branch cache (default true)"},
|
||||
},
|
||||
"Output Options": {
|
||||
{"--verbose, -v", "Verbose output"},
|
||||
{"--debug, -d", "Debug mode (stackable: -dd for library debug)"},
|
||||
{"--quiet, -q", "Suppress supplementary info"},
|
||||
{"--show-progress", "Show traversal progress"},
|
||||
{"--no-show-progress", "Hide traversal progress"},
|
||||
{"--show-resolves", "Show glue resolution details"},
|
||||
{"--no-show-resolves", "Hide glue resolution details"},
|
||||
{"--show-servers", "Show servers queried"},
|
||||
{"--no-show-servers", "Hide servers queried"},
|
||||
{"--show-versions", "Show server versions"},
|
||||
{"--no-show-versions", "Hide server versions"},
|
||||
{"--show-all-stats", "Show all statistics"},
|
||||
{"--no-show-all-stats", "Hide all statistics"},
|
||||
{"--show-results", "Show query results"},
|
||||
{"--no-show-results", "Hide query results"},
|
||||
{"--show-summary-results", "Show summary of results"},
|
||||
{"--no-show-summary-results", "Hide summary of results"},
|
||||
},
|
||||
}
|
||||
|
||||
for group, flags := range flagGroups {
|
||||
fmt.Fprintf(os.Stderr, "\n%s:\n", group)
|
||||
for _, f := range flags {
|
||||
fmt.Fprintf(os.Stderr, " %-25s %s\n", f[0], f[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
QueryType: "A",
|
||||
RootServer: "",
|
||||
AllRootServers: false,
|
||||
RootAAAA: false,
|
||||
FollowAAAA: false,
|
||||
UDPSize: 2048,
|
||||
AllowTCP: true,
|
||||
AlwaysTCP: false,
|
||||
MaxDepth: 20,
|
||||
Retries: 2,
|
||||
Fast: true,
|
||||
Verbose: false,
|
||||
Debug: 0,
|
||||
Quiet: false,
|
||||
ShowProgress: true,
|
||||
ShowResolves: true,
|
||||
ShowServers: true,
|
||||
ShowVersions: true,
|
||||
ShowAllStats: true,
|
||||
ShowResults: true,
|
||||
ShowSummaryResults: true,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user