// 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 } } }