ullaakut/nmap Go Library

repository·master·Indexed 21 days ago

https://github.com/ullaakut/nmap

A Go library providing idiomatic bindings for the nmap network scanner. It wraps the nmap binary and parses XML output to integrate network discovery, service detection, and security auditing into Go applications. Supports synchronous and asynchronous scans, progress reporting, and advanced configuration options for firewall evasion, IP/MAC spoofing, and interface/route retrieval. Compatible with nmap version 7.98.

Tokens
12.9K
Snippets
75
Records
86
Agent score
76%

What's inside ullaakut/nmap

  1. How nmap library works

    master

    The nmap library provides idiomatic Go bindings for the nmap binary. It works by shelling out to the nmap executable using Go's exec package and parsing the resulting XML output.

    Prerequisites:

    • The nmap binary must be installed and available on your system's PATH.
    • Compatibility is confirmed with nmap version 7.98.
  2. Handle elevated privileges for scans

    master

    Certain scan types (such as SYN scans, OS detection, or raw socket usage) require elevated system privileges. If you enable these options in your scanner configuration, you must run your compiled Go program with sudo or provide the appropriate platform capabilities.

    Tip: For unprivileged runs, use connect scans (e.g., -sT) to avoid requiring root access.

  3. Parse Nmap XML results into a Run struct

    master
    The Run struct is the primary container for all data extracted from an Nmap XML scan output. It includes scan metadata, host information, port details, and script outputs. While the parse function is internal, the Run struct is the public representation of the scan results.
  4. How ScanRunner and AsyncScanRunner interfaces work

    master

    The library defines two primary interfaces for executing scans, allowing you to treat different scanner configurations polymorphically:

    1. ScanRunner: Defines a synchronous execution model via Run(ctx context.Context) (*Run, error).
    2. AsyncScanRunner: Defines an asynchronous execution model via RunAsync(ctx context.Context) (<-chan []byte, <-chan []byte, <-chan RunResult, error).

    The Scanner struct implements both of these interfaces.

  5. Perform a synchronous network scan

    master

    Use nmap.NewScanner with functional options to configure your scan, then call scanner.Run(ctx) to execute it synchronously. This method blocks until the scan is complete or the context is canceled.

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"time"
    
    	"github.com/Ullaakut/nmap/v4"
    )
    
    func main() {
    	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    	defer cancel()
    
    	// Equivalent to `/usr/local/bin/nmap -p 80,443,843 google.com facebook.com youtube.com`,
    	// with a 5-minute timeout.
    	scanner, err := nmap.NewScanner(
    		nmap.WithTargets("scanme.nmap.org"),
    		nmap.WithPorts("80,443,843"),
    	)
    	if err != nil {
    		log.Fatalf("creating nmap scanner: %v", err)
    	}
    
    	result, err := scanner.Run(ctx)
    	if err != nil {
    		log.Fatalf("running network scan: %v", err)
    	}
    
    	warnings := result.Warnings()
    	if len(warnings) > 0 {
    		log.Printf("warning: %v\n", warnings) // Warnings are non-critical errors from nmap.
    	}
    
    	// Use the results to print an example output
    	for _, host := range result.Hosts {
    		if len(host.Ports) == 0 || len(host.Addresses) == 0 {
    			continue
    		}
    
    		fmt.Printf("Host %q:\n", host.Addresses[0])
    
    		for _, port := range host.Ports {
    			fmt.Printf("\tPort %d/%s %s %s\n", port.ID, port.Protocol, port.State, port.Service.Name)
    		}
    	}
    
    	fmt.Printf("Nmap done: %d hosts up scanned in %.2f seconds\n", len(result.Hosts), result.Stats.Finished.Elapsed)
    }
  6. Perform a synchronous scan with progress reporting

    master

    You can monitor scan progress using nmap.WithProgress(interval, callback).

    Important Notes:

    • This feature relies on terminal escape sequences and only works when the process is attached to a TTY.
    • Progress is not guaranteed to increase monotonically; nmap may revise its time estimates, causing the reported percentage to decrease.
    package main
    
    import (
    	"context"
    	"log"
    	"time"
    
    	"github.com/Ullaakut/nmap/v4"
    )
    
    func main() {
    	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    	defer cancel()
    
    	scanner, err := nmap.NewScanner(
    		nmap.WithTargets("scanme.nmap.org"),
    		nmap.WithPorts("1-1024"),
    		nmap.WithTimingTemplate(nmap.TimingAggressive),
    		nmap.WithProgress(time.Second, handleProgress),
    	)
    	if err != nil {
    		log.Fatalf("creating nmap scanner: %v", err)
    	}
    
    	_, err = scanner.Run(ctx)
    	if err != nil {
    		log.Fatalf("running network scan: %v", err)
    	}
    }
    
    func handleProgress(p nmap.TaskProgress) {
    	log.Println("Current progress: ", p.Percent)
    }
  7. Perform an asynchronous scan

    master

    For non-blocking scans, use scanner.RunAsync(ctx). This returns four channels: stdout, stderr, resultCh, and an err value. You can use a select statement to process real-time output from nmap while waiting for the final result.

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"time"
    
    	"github.com/Ullaakut/nmap/v4"
    )
    
    func main() {
    	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    	defer cancel()
    
    	scanner, err := nmap.NewScanner(
    		nmap.WithTargets("scanme.nmap.org"),
    		nmap.WithPorts("1-1024"),
    	)
    	if err != nil {
    		log.Fatalf("creating nmap scanner: %v", err)
    	}
    
    	stdout, stderr, resultCh, err := scanner.RunAsync(ctx)
    	if err != nil {
    		log.Fatalf("running network scan: %v", err)
    	}
    
    	for {
    		select {
    		case <-ctx.Done():
    			log.Fatalf("scan timed out: %v", ctx.Err())
    		case out := <-stdout:
    			fmt.Printf("nmap output: %s\n", out)
    		case errOut := <-stderr:
    			fmt.Printf("nmap error output: %s\n", errOut)
    		case result := <-resultCh:
    			if result.Err != nil {
    				log.Fatalf("running network scan: %v", result.Err)
    			}
    
    			fmt.Printf("Nmap done: %d hosts up\n", len(result.Result.Hosts))
    			return
    		}
    	}
    }
  8. Configure Idle Scan with a zombie host

    master

    Use WithIdleScan(zombieHost string, probePort int) to perform a blind TCP port scan using a third-party 'zombie' host. This is highly stealthy and can map IP-based trust relationships.

    • zombieHost: The IP address or hostname of the zombie.
    • probePort: (Optional) An integer representing the port on the zombie host to probe. If set to 0, the scan uses the zombie host without a specific probe port.
    // Scan using a specific port on the zombie host
    scanner := nmap.NewScanner(nmap.WithIdleScan("192.168.1.50", 80))
    
    // Scan using the zombie host generally
    scanner := nmap.NewScanner(nmap.WithIdleScan("192.168.1.50", 0))
  9. Configure IP options and TTL

    master

    Adjust IP-level header fields:

    • WithIPOptions(options string): Uses specified IP options (e.g., record route).
    • WithIPTimeToLive(ttl int16): Sets the IP time-to-live field. The value must be between 0 and 255.
    // Example: setting TTL
    scanner := nmap.NewScanner("target", 
        nmap.WithIPTimeToLive(128),
    )
  10. Set nmap verbosity with WithVerbosity

    master

    Use WithVerbosity(level int) to set the verbosity level of the nmap scan. The level must be an integer between 0 and 10 inclusive. This adds the -v<level> flag to the nmap arguments.

    // Example usage within a scanner configuration
    scanner.New(target, nmap.WithVerbosity(3))
  11. Run a synchronous scan with Run

    master

    The Run method executes the configured nmap scan synchronously. It blocks until the scan is complete or the provided context.Context is canceled. It returns a *Run object containing the parsed scan results or an error.

    Note: If you have configured a progress handler, Run will use a specialized execution path to handle real-time updates.

    ctx := context.Background()
    run, err := scanner.Run(ctx)
    if err != nil {
        log.Fatal(err)
    }
    // Use run.Hosts, run.Ports, etc.
  12. Set target exclusions from an input file with WithTargetExclusionInput

    master

    Use WithTargetExclusionInput to specify a file containing the list of targets to exclude from the scan. This uses the --excludefile flag.

    scanner := nmap.NewScanner(
        nmap.WithTargetExclusionInput("exclude_list.txt"),
    )