Skip to content

Latest commit

 

History

History
247 lines (186 loc) · 5.82 KB

File metadata and controls

247 lines (186 loc) · 5.82 KB

SDK API Reference

Import path: github.com/boy-hack/ksubdomain/v2/sdk


Types

Config

type Config struct {
    Bandwidth      string             // bandwidth cap, e.g. "5m" (default "5m")
    Retry          int                // per-domain retry count, -1 = infinite (default 3)
    Resolvers      []string           // DNS resolver IPs; nil = built-in defaults
    Device         string             // single network interface; "" = auto-detect (backward-compat)
    Devices        []string           // multi-NIC: takes precedence over Device when non-empty
    Dictionary     string             // wordlist file for Enum; "" = built-in list
    Predict        bool               // enable AI subdomain prediction
    WildcardFilter string             // "none" | "basic" | "advanced" (default "none")
    Silent         bool               // suppress progress bar
    ExtraWriters   []outputter.Output // additional output sinks (see Custom sinks)
}

Note: Timeout is not configurable. The scanner uses a dynamic RTT-based timeout with a hardcoded upper bound of 10 s and lower bound of 1 s (TCP RFC 6298 EWMA, α=0.125, β=0.25).

DefaultConfig

var DefaultConfig = &Config{
    Bandwidth:      "5m",
    Retry:          3,
    WildcardFilter: "none",
}

Result

type Result struct {
    Domain  string   // resolved subdomain
    Type    string   // "A", "CNAME", "NS", "PTR", "TXT", "AAAA"
    Records []string // record values (IPs, target names, text, …)
}

Functions

NewScanner

func NewScanner(config *Config) *Scanner

Creates a new Scanner. If config is nil, DefaultConfig is used. Applies defaults for zero-value fields (Bandwidth, Retry).


Scanner methods

Enum

func (s *Scanner) Enum(domain string) ([]Result, error)

Enumerates subdomains of domain using the configured wordlist. Blocks until the scan completes and returns all results.

EnumWithContext

func (s *Scanner) EnumWithContext(ctx context.Context, domain string) ([]Result, error)

Like Enum, but honours ctx for cancellation.

EnumStream

func (s *Scanner) EnumStream(ctx context.Context, domain string, callback func(Result)) error

Enumerates subdomains and calls callback for each result as it arrives, without waiting for the scan to complete. Blocks until done or ctx is cancelled.

callback may be called from multiple goroutines concurrently. Implementations must be goroutine-safe.

var mu sync.Mutex
var results []sdk.Result

err := scanner.EnumStream(ctx, "example.com", func(r sdk.Result) {
    mu.Lock()
    results = append(results, r)
    mu.Unlock()
    fmt.Println(r.Domain)
})

Verify

func (s *Scanner) Verify(domains []string) ([]Result, error)

Verifies each domain in domains, returning those that resolve. Blocks until complete.

VerifyWithContext

func (s *Scanner) VerifyWithContext(ctx context.Context, domains []string) ([]Result, error)

Like Verify, but honours ctx.

VerifyStream

func (s *Scanner) VerifyStream(ctx context.Context, domains []string, callback func(Result)) error

Verifies domains and calls callback for each resolved result in real-time. Blocks until done or ctx is cancelled.


Error handling

Sentinel errors are exported from the sdk package. Use errors.Is:

_, err := scanner.Enum("example.com")
switch {
case errors.Is(err, sdk.ErrPermissionDenied):
    log.Fatal("run with sudo or grant CAP_NET_RAW")
case errors.Is(err, sdk.ErrDeviceNotFound):
    log.Fatal("wrong interface name — check --eth flag or Config.Device")
case errors.Is(err, sdk.ErrDeviceNotActive):
    log.Fatal("interface is down — run: sudo ip link set <iface> up")
case err != nil:
    log.Fatal(err)
}
Sentinel Meaning
ErrPermissionDenied Process lacks CAP_NET_RAW / not running as root
ErrDeviceNotFound Network interface name does not exist
ErrDeviceNotActive Interface exists but is not up
ErrPcapInit libpcap/npcap initialisation failed (other reason)
ErrDomainChanNil Internal: domain channel was nil (should not occur via SDK)

Custom output sinks

Implement outputter.Output from github.com/boy-hack/ksubdomain/v2/pkg/runner/outputter:

type Output interface {
    WriteDomainResult(result.Result) error
    Close() error
}

result.Result:

// github.com/boy-hack/ksubdomain/v2/pkg/runner/result
type Result struct {
    Subdomain string   // e.g. "www.example.com"
    Answers   []string // raw answer strings, e.g. "CNAME foo.example.com", "1.2.3.4"
}

Example — write resolved domains to a channel for external processing:

type chanWriter struct {
    ch chan<- string
}

func (w *chanWriter) WriteDomainResult(r result.Result) error {
    w.ch <- r.Subdomain
    return nil
}
func (w *chanWriter) Close() error { return nil }

// Usage:
ch := make(chan string, 1000)
scanner := sdk.NewScanner(&sdk.Config{
    ExtraWriters: []outputter.Output{&chanWriter{ch: ch}},
})
go scanner.Enum("example.com")
for domain := range ch {
    fmt.Println(domain)
}

Complete example

package main

import (
    "context"
    "errors"
    "fmt"
    "log"

    "github.com/boy-hack/ksubdomain/v2/sdk"
)

func main() {
    scanner := sdk.NewScanner(&sdk.Config{
        Bandwidth:      "10m",
        Retry:          3,
        WildcardFilter: "basic",
        Silent:         true,
    })

    ctx, cancel := context.WithTimeout(context.Background(), 5*60*1e9) // 5 min
    defer cancel()

    err := scanner.EnumStream(ctx, "example.com", func(r sdk.Result) {
        fmt.Printf("[%s] %s => %v\n", r.Type, r.Domain, r.Records)
    })
    if err != nil {
        if errors.Is(err, sdk.ErrPermissionDenied) {
            log.Fatal("need sudo")
        }
        log.Fatal(err)
    }
}