scan

package
v0.2.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ARPCache

func ARPCache(ctx context.Context, runner Runner) map[string]string

ARPCache reads the OS ARP table (via `arp -a -n`) and returns an IP->MAC map for all resolved L2 neighbors. This requires no privileges and complements nmap, which only reports MACs when run as root. Incomplete entries and the broadcast/multicast addresses are skipped. Runs through the supplied runner so it works locally or over SSH.

func DiscoverMACs

func DiscoverMACs(ctx context.Context, targets []string, runner Runner) (map[string]string, error)

DiscoverMACs runs a lightweight discovery in XML and extracts IP->MAC pairs (same L2 only).

func HostDiscovery

func HostDiscovery(ctx context.Context, targets []string, runner Runner) ([]string, error)

HostDiscovery uses 'nmap -sn -oG -' to find live hosts quickly, via the provided runner (local or ssh).

func HostDiscoveryWithMACs

func HostDiscoveryWithMACs(ctx context.Context, targets []string, runner Runner) ([]string, map[string]string, error)

HostDiscoveryWithMACs performs one XML discovery sweep and returns both live hosts and any L2 MAC addresses nmap observed. It avoids a duplicate sweep when privileged or remote scans request MAC data.

func IsRoot

func IsRoot() bool

IsRoot reports whether the process is already running as root, in which case nmap can do ARP discovery, SYN scans, and MAC reporting without sudo.

func MergeARPHosts

func MergeARPHosts(live []string, targets []string, arp map[string]string) []string

MergeARPHosts adds IPs from the ARP cache that fall inside the scanned targets but were missed by unprivileged host discovery. Returns the combined, de-duplicated host list. This recovers most of what `sudo nmap` would find on a local subnet without needing root.

func NativeDiscovery

func NativeDiscovery(ctx context.Context, targets []string, runner Runner, progress func(done, total int)) ([]string, map[string]string, error)

NativeDiscovery finds live hosts without invoking nmap, using the ARP cache plus a bounded TCP connect sweep (see package sweep). It mirrors HostDiscoveryWithMACs so callers can swap between the two.

On a LAN this is typically an order of magnitude faster than `nmap -sn`, because the timeout policy is ours rather than nmap's WAN-tuned defaults, and because the ARP cache answers for free. It needs no root.

The runner is used only for the ARP read, so this still works over SSH. Note the TCP probes originate from *this* machine even when a runner points elsewhere, so native discovery is meant for local scans; callers scanning via SSH should stay on the nmap path.

func NativeDiscoverySupported

func NativeDiscoverySupported(runner Runner) bool

NativeDiscoverySupported reports whether native discovery makes sense for this runner. It requires probing from the local machine, so an SSH runner (which would probe the wrong network) is excluded.

func NativeDiscoveryTimed added in v0.2.8

func NativeDiscoveryTimed(ctx context.Context, targets []string, runner Runner, progress func(done, total int)) ([]string, map[string]string, map[string]time.Duration, error)

NativeDiscoveryTimed is NativeDiscovery plus the round-trip time each host took to answer.

The sweep already measures it — every discovery is a timed TCP connect — and discarding it meant the RTT column stayed empty on exactly the fast path most scans use. Reporting it costs no extra packets.

func NativePortScanViable

func NativePortScanViable(cfg Config, runner Runner) bool

NativePortScanViable reports whether a native port scan can serve this config. It cannot when the caller needs what only nmap provides: service versions, OS detection, or NSE output (the -A presets), or UDP.

func PrimeSudo

func PrimeSudo() error

PrimeSudo runs an interactive `sudo -v` to cache credentials. It connects the real terminal so the password prompt is visible — callers MUST invoke this BEFORE a Bubble Tea alt-screen takes over, otherwise the prompt is hidden. Returns nil if already root or on successful authentication.

func SudoAvailable

func SudoAvailable() bool

SudoAvailable reports whether the sudo binary exists on PATH.

func SudoPrimed

func SudoPrimed() bool

SudoPrimed reports whether sudo can run non-interactively right now (credentials cached from a recent authentication).

func ValidatePorts added in v0.2.4

func ValidatePorts(spec string) error

ValidatePorts reports whether a port specification is usable, without running anything. Front ends call it before a scan starts so a typo is a message at the prompt rather than an empty result table after the work is done — the native and nmap paths disagree about what malformed syntax means, and neither disagreement should be visible to a user who simply mistyped.

An empty spec is valid: it means "use the preset's default ports".

Types

type Address

type Address struct {
	Addr     string `xml:"addr,attr"`
	AddrType string `xml:"addrtype,attr"`
	Vendor   string `xml:"vendor,attr,omitempty"`
}

type Config

type Config struct {
	Preset         string
	Ports          string
	UseSYN         bool
	Concurrency    int
	HostTimeout    time.Duration
	DisableVendors bool
	NeedMAC        bool
	// BatchSize groups targets into one nmap process. Values <=1 preserve the
	// original one-process-per-host behavior.
	BatchSize int
	// DiscardResults avoids retaining raw XML after OnResult has consumed it.
	// Streaming TUI scans use this to bound memory on large/deep scans.
	DiscardResults bool
	// Progress, if set, is called after each host finishes scanning.
	// It may be invoked from multiple goroutines.
	Progress func(done, total int)
	// OnResult, if set, is called with each host's result as soon as it
	// completes, enabling streaming consumers. May be invoked from
	// multiple goroutines.
	OnResult func(HostResult)
}

type Host

type Host struct {
	Status    Status    `xml:"status"`
	Addresses []Address `xml:"address"`
	Hostnames Hostnames `xml:"hostnames"`
	Ports     Ports     `xml:"ports"`
	OS        OS        `xml:"os"`
	Times     Times     `xml:"times"`
}

func (Host) BestOSDetail

func (h Host) BestOSDetail() *OSDetail

BestOSDetail returns the structured best OS guess, or nil if none.

func (Host) BestOSGuess

func (h Host) BestOSGuess() string

BestOSGuess returns the highest-accuracy OS match name, or "".

func (Host) RTT

func (h Host) RTT() time.Duration

RTT returns the host's smoothed round-trip time, or 0 if nmap reported none.

type HostResult

type HostResult struct {
	IP       string
	Targets  []string // all targets represented when this is a batched result
	XMLBytes []byte   // raw nmap xml for this host
	Err      error
	Fallback bool // requested SYN scan retried as TCP connect
}

func HostsUpResults

func HostsUpResults(ips []string) []HostResult

HostsUpResults synthesizes minimal "host up, no ports" results for a set of live IPs, for the discover-only fast path. The XML mirrors what nmap emits for an up host with no open ports, so the existing parsers/printers consume it unchanged.

func NativePortScan

func NativePortScan(ctx context.Context, hosts []string, cfg Config, progress func(done, total int)) []HostResult

NativePortScan probes open ports on the given hosts without invoking nmap, returning results in the same shape the nmap path produces so every existing parser, printer, and report renderer consumes them unchanged.

It is dramatically faster than nmap for the common "what's open here" case, but it reports port numbers and well-known service names only — no version detection, OS fingerprinting, or NSE output. Callers needing those must use the nmap path (the deep/default presets).

progress may be nil. When set it is called from multiple goroutines as hosts complete, so it must be safe for concurrent use.

cfg.OnResult, when set, receives each host the instant it finishes probing rather than after every host has. Callers should prefer it over the returned slice: the slice can only be complete once the slowest unreachable address has burned its full timeout, whereas a responsive LAN host streams in milliseconds. The returned slice is still complete, for callers that only want the final answer.

func NativePortScanWithMetadata added in v0.2.8

func NativePortScanWithMetadata(ctx context.Context, hosts []string, metadata map[string]NativeMetadata, cfg Config, progress func(done, total int)) []HostResult

NativePortScanWithMetadata preserves discovery timing and TLS identification through the native scan's existing XML/parser/rendering path.

The engine supplies the metadata: the discovery sweep already timed each host's answer, and passing that through here is what puts a value in the RTT column on the fast path. Setting IdentifyTLS on one metadata entry opts the run into bounded certificate identification after every open port is known.

func ScanHosts

func ScanHosts(ctx context.Context, live []string, cfg Config, runner Runner) ([]HostResult, error)

func SynthesizeNativeResult added in v0.2.8

func SynthesizeNativeResult(r sweep.PortResult, metadata NativeMetadata) HostResult

SynthesizeNativeResult exposes the no-I/O XML conversion for pipeline code that enriches a completed open-port result before handing it to renderers.

type Hostname

type Hostname struct {
	Name string `xml:"name,attr"`
}

type Hostnames

type Hostnames struct {
	Names []Hostname `xml:"hostname"`
}

type LocalRunner

type LocalRunner struct{}

func (LocalRunner) Run

func (r LocalRunner) Run(ctx context.Context, bin string, args ...string) ([]byte, error)

type NativeMetadata added in v0.2.8

type NativeMetadata struct {
	RTT time.Duration
	TLS map[int]enrich.TLSInfo
	// IdentifyTLS opts this scan into active TLS handshakes. It is deliberately
	// separate from TLS: callers may supply already-known certificate data
	// without authorizing network activity.
	IdentifyTLS bool
	// TLSConfig bounds the active identification work. Zero values use the
	// conservative defaults in enrich.LookupTLS.
	TLSConfig enrich.TLSConfig
	// TLSMaxEndpoints caps handshakes across the entire scan. Values <= 0 use
	// the package default. The first metadata entry with IdentifyTLS set owns
	// these scan-wide settings.
	TLSMaxEndpoints int
}

NativeMetadata is enrichment to embed in one host's synthetic nmap XML.

type NmapRun

type NmapRun struct {
	Hosts []Host `xml:"host"`
}

func ParseOne

func ParseOne(xmlBytes []byte) (NmapRun, error)

type OS

type OS struct {
	Matches []OSMatch `xml:"osmatch"`
}

OS holds nmap's OS-detection guesses (populated by -A / -O presets).

type OSClass

type OSClass struct {
	Type     string   `xml:"type,attr"`
	Vendor   string   `xml:"vendor,attr"`
	OSFamily string   `xml:"osfamily,attr"`
	OSGen    string   `xml:"osgen,attr"`
	Accuracy int      `xml:"accuracy,attr"`
	CPEs     []string `xml:"cpe"`
}

OSClass is nmap's structured OS classification (vendor/family/generation) with optional CPE identifiers.

type OSDetail

type OSDetail struct {
	Name     string // full match name, e.g. "Linux 5.4 - 5.10"
	Accuracy int    // 0-100 confidence
	Vendor   string // e.g. "Linux", "HP", "Apple"
	Family   string // e.g. "Linux", "embedded", "macOS"
	Gen      string // e.g. "5.X"
	CPE      string // first OS CPE, e.g. "cpe:/o:linux:linux_kernel:5"
}

OSDetail describes the best OS guess in structured form for the detail pane.

type OSMatch

type OSMatch struct {
	Name     string    `xml:"name,attr"`
	Accuracy int       `xml:"accuracy,attr"`
	Classes  []OSClass `xml:"osclass"`
}

type Port

type Port struct {
	Protocol string    `xml:"protocol,attr"`
	PortID   int       `xml:"portid,attr"`
	State    PortState `xml:"state"`
	Service  Service   `xml:"service"`
	Scripts  []Script  `xml:"script"`
}

func (Port) HTTPTitle

func (p Port) HTTPTitle() string

HTTPTitle returns the page title reported by the http-title script, or "".

func (Port) TLSCert

func (p Port) TLSCert() *TLSCert

TLSCert extracts the certificate summary from the ssl-cert script, or nil.

type PortRisk

type PortRisk struct {
	Severity Severity
	Reason   string
}

PortRisk describes why an open port is interesting.

func ClassifyPort

func ClassifyPort(port int, service string) PortRisk

ClassifyPort assesses an open port. It prefers a port-number match, then falls back to the service name, returning {SevNone} when nothing stands out.

type PortState

type PortState struct {
	State string `xml:"state,attr"`
}

type Ports

type Ports struct {
	List []Port `xml:"port"`
}

type Runner

type Runner interface {
	Run(ctx context.Context, bin string, args ...string) ([]byte, error)
}

Runner abstracts how we execute commands (locally or over SSH).

func NewLocalRunner

func NewLocalRunner() Runner

func NewRunner

func NewRunner(sshTarget string) Runner

NewRunner returns a local runner (optionally elevated) or an SSH runner.

type SSHRunner

type SSHRunner struct {
	Target string // user@host (or host if agent configured)
}

----- SSH runner ----- Executes: ssh [options] -- <target> <quoted remote command>. The local exec uses an argv and "--" protects the destination from local ssh option parsing. SSH still hands the remote command to the remote login shell, so every remote argument is single-quoted before the command is passed to ssh. The input source is whoever supplies ndscan's target string; this quoting is defense-in-depth for wrapper scripts and CI, not a remote-attacker boundary.

func (*SSHRunner) Run

func (r *SSHRunner) Run(ctx context.Context, bin string, args ...string) ([]byte, error)

type Script

type Script struct {
	ID     string        `xml:"id,attr"`
	Output string        `xml:"output,attr"`
	Tables []ScriptTable `xml:"table"`
	Elems  []ScriptElem  `xml:"elem"`
}

Script is one NSE script result attached to a port (from --script / -A).

type ScriptElem

type ScriptElem struct {
	Key   string `xml:"key,attr"`
	Value string `xml:",chardata"`
}

ScriptElem is a single key/value leaf in a script result.

type ScriptTable

type ScriptTable struct {
	Key    string        `xml:"key,attr"`
	Tables []ScriptTable `xml:"table"`
	Elems  []ScriptElem  `xml:"elem"`
}

ScriptTable is a (possibly nested) keyed table within a script result.

type Service

type Service struct {
	Name      string   `xml:"name,attr,omitempty"`
	Product   string   `xml:"product,attr,omitempty"`
	Version   string   `xml:"version,attr,omitempty"`
	ExtraInfo string   `xml:"extrainfo,attr,omitempty"`
	Tunnel    string   `xml:"tunnel,attr,omitempty"` // e.g. "ssl"
	CPEs      []string `xml:"cpe"`
}

func (Service) CPE

func (s Service) CPE() string

CPE returns the service's first CPE identifier, or "".

type Severity

type Severity int

Severity ranks how noteworthy an open port is on a local network.

const (
	// SevNone means the port is unremarkable / expected.
	SevNone Severity = iota
	// SevInfo flags a service worth noticing (admin panels, databases).
	SevInfo
	// SevWarn flags a service that is commonly a security concern when
	// exposed (remote access, file sharing, legacy protocols).
	SevWarn
	// SevHigh flags a service that is almost always a problem to find open
	// (cleartext credentials, unauthenticated remote control).
	SevHigh
)

func (Severity) String

func (s Severity) String() string

type Status

type Status struct {
	State string `xml:"state,attr"`
}

type TLSCert

type TLSCert struct {
	CommonName   string // subject CN
	Organization string // subject O
	Issuer       string // issuer CN
	NotAfter     string // expiry, e.g. "2027-06-10T14:59:44"
}

TLSCert is a condensed view of an ssl-cert script result.

func (TLSCert) Summary

func (c TLSCert) Summary() string

Summary renders the cert compactly, e.g. "Acme Inc — exp 2027-06-10".

type Times

type Times struct {
	SRTT int `xml:"srtt,attr"`
}

Times holds nmap's timing data; srtt is the smoothed round-trip time in microseconds (populated once nmap has probed the host).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL