1/*
2Package pingscanner scan alive IPs of the given CIDR range in parallel.
3
4Example usage:
5
6package main
7
8import (
9	"fmt"
10
11	ps "github.com/kotakanbe/go-pingscanner"
12)
13
14func main() {
15	scanner := ps.PingScanner{
16		CIDR: "192.168.11.0/24",
17		PingOptions: []string{
18			"-c1",
19			"-t1",
20		},
21		NumOfConcurrency: 100,
22	}
23	if aliveIPs, err := scanner.Scan(); err != nil {
24		fmt.Println(err)
25	} else {
26		if len(aliveIPs) < 1 {
27			fmt.Println("no alive hosts")
28		}
29		for _, ip := range aliveIPs {
30			fmt.Println(ip)
31		}
32	}
33}
34*/
35package pingscanner
36
37import (
38	"net"
39	"os/exec"
40	"sort"
41	"strings"
42)
43
44// PingScanner has information of Scanning.
45type PingScanner struct {
46	// CIDR (ex. 192.168.0.0/24)
47	CIDR string
48
49	// Number of concurrency ping process. (ex. 100)
50	NumOfConcurrency int
51
52	// ping command options. (ex. []string{"-c1", "-t1"})
53	PingOptions []string
54}
55
56type pong struct {
57	IP    string
58	Alive bool
59}
60
61// Scan ping to hosts in CIDR range.
62func (d PingScanner) Scan() (aliveIPs []string, err error) {
63	var hostsInCidr []string
64	if hostsInCidr, err = expandCidrIntoIPs(d.CIDR); err != nil {
65		return nil, err
66	}
67	pingChan := make(chan string, d.NumOfConcurrency)
68	pongChan := make(chan pong, len(hostsInCidr))
69	doneChan := make(chan []pong)
70
71	for i := 0; i < d.NumOfConcurrency; i++ {
72		go ping(pingChan, pongChan, d.PingOptions...)
73	}
74
75	go receivePong(len(hostsInCidr), pongChan, doneChan)
76
77	for _, ip := range hostsInCidr {
78		pingChan <- ip
79	}
80
81	alives := <-doneChan
82	for _, a := range alives {
83		aliveIPs = append(aliveIPs, a.IP)
84	}
85	sort.Strings(aliveIPs)
86	return
87}
88
89func expandCidrIntoIPs(cidr string) ([]string, error) {
90	splitted := strings.Split(cidr, "/")
91	if splitted[1] == "32" {
92		return []string{splitted[0]}, nil
93	}
94	ip, ipnet, err := net.ParseCIDR(cidr)
95	if err != nil {
96		return nil, err
97	}
98
99	var ips []string
100	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
101		ips = append(ips, ip.String())
102	}
103	// remove network address and broadcast address
104	return ips[1 : len(ips)-1], nil
105}
106
107//  http://play.golang.org/p/m8TNTtygK0
108func inc(ip net.IP) {
109	for j := len(ip) - 1; j >= 0; j-- {
110		ip[j]++
111		if ip[j] > 0 {
112			break
113		}
114	}
115}
116
117func ping(pingChan <-chan string, pongChan chan<- pong, pingOptions ...string) {
118	for ip := range pingChan {
119		pingOptions = append(pingOptions, ip)
120		_, err := exec.Command("ping", pingOptions...).Output()
121		var alive bool
122		if err != nil {
123			alive = false
124		} else {
125			alive = true
126		}
127		pongChan <- pong{IP: ip, Alive: alive}
128	}
129}
130
131func receivePong(pongNum int, pongChan <-chan pong, doneChan chan<- []pong) {
132	var alives []pong
133	for i := 0; i < pongNum; i++ {
134		pong := <-pongChan
135		if pong.Alive {
136			alives = append(alives, pong)
137		}
138	}
139	doneChan <- alives
140}
141