diff options
Diffstat (limited to 'adapters/netscan/netscan.go')
| -rw-r--r-- | adapters/netscan/netscan.go | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/adapters/netscan/netscan.go b/adapters/netscan/netscan.go new file mode 100644 index 0000000..5aaf415 --- /dev/null +++ b/adapters/netscan/netscan.go @@ -0,0 +1,74 @@ +// Package netscan nudges the kernel into ARP-resolving every host on a subnet, +// so devices that never talk to us still show up in the neighbour table. It does +// this by attempting a TCP connection to each host: the result is irrelevant, +// the ARP exchange the kernel performs to send the SYN is the point. +package netscan + +import ( + "fmt" + "net" + "sync" + "time" +) + +const ( + probePort = "9" // discard; closed almost everywhere, so a fast RST + maxHostBits = 12 // refuse to sweep anything larger than a /20 (~4094 hosts) + sweepWorkers = 32 +) + +func Sweep(cidr string, timeout time.Duration) error { + hosts, err := hostIPs(cidr) + if err != nil { + return err + } + + sem := make(chan struct{}, sweepWorkers) + var wg sync.WaitGroup + for _, ip := range hosts { + wg.Add(1) + sem <- struct{}{} + go func(ip string) { + defer wg.Done() + defer func() { <-sem }() + if conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, probePort), timeout); err == nil { + conn.Close() + } + }(ip) + } + wg.Wait() + return nil +} + +func hostIPs(cidr string) ([]string, error) { + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + if ones, bits := ipnet.Mask.Size(); bits-ones > maxHostBits { + return nil, fmt.Errorf("netscan: subnet /%d too large to sweep", ones) + } + + ip := make(net.IP, len(ipnet.IP)) + copy(ip, ipnet.IP) + + ips := []string{} + for ; ipnet.Contains(ip); inc(ip) { + ips = append(ips, ip.String()) + } + + // drop the network and broadcast addresses + if len(ips) > 2 { + ips = ips[1 : len(ips)-1] + } + return ips, nil +} + +func inc(ip net.IP) { + for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] != 0 { + break + } + } +} |
