├── .travis.yml ├── README.md ├── batch_ping.go ├── batch_ping_test.go ├── demo └── main.go └── ping.go /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: go 3 | os: 4 | - osx 5 | - linux 6 | script: 7 | - go build -race -o test ./demo/main.go 8 | - sudo ./test 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # batch-ping # 2 | 3 | [![Build Status](https://travis-ci.org/caucy/batch_ping.svg?branch=master)](https://travis-ci.org/caucy/batch_ping) 4 | 5 | ICMP batch Ping library for Go, inspired by [go-ping](https://github.com/sparrc/go-ping) 6 | 7 | Here is a very simple example : 8 | 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "log" 15 | 16 | "github.com/caucy/batch_ping" 17 | ) 18 | 19 | func main() { 20 | ipSlice := []string{} 21 | // ip list should not more than 65535 22 | 23 | ipSlice = append(ipSlice, "2400:da00:2::29") //support ipv6 24 | ipSlice = append(ipSlice, "baidu.com") 25 | 26 | bp, err := ping.NewBatchPinger(ipSlice, false) // true will need to be root 27 | 28 | if err != nil { 29 | log.Fatalf("new batch ping err %v", err) 30 | } 31 | bp.SetDebug(false) // debug == true will fmt debug log 32 | 33 | bp.SetSource("") // if hava multi source ip, can use one isp 34 | bp.SetCount(10) 35 | 36 | bp.OnFinish = func(stMap map[string]*ping.Statistics) { 37 | for ip, st := range stMap { 38 | log.Printf("\n--- %s ping statistics ---\n", st.Addr) 39 | log.Printf("ip %s, %d packets transmitted, %d packets received, %v%% packet loss\n", ip, 40 | st.PacketsSent, st.PacketsRecv, st.PacketLoss) 41 | log.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 42 | st.MinRtt, st.AvgRtt, st.MaxRtt, st.StdDevRtt) 43 | log.Printf("rtts is %v \n", st.Rtts) 44 | } 45 | 46 | } 47 | 48 | err = bp.Run() 49 | if err != nil { 50 | log.Printf("run err %v \n", err) 51 | } 52 | bp.OnFinish(bp.Statistics()) 53 | } 54 | 55 | 56 | 57 | ``` 58 | 59 | It sends ICMP packet(s) and waits for a response. If it receives a response, 60 | it calls the "receive" callback. When it's finished, can call the "OnFinish" 61 | callback. 62 | 63 | ## Installation: 64 | 65 | ``` 66 | go get github.com/caucy/batch_ping 67 | ``` 68 | 69 | 70 | ## Note on linux support : 71 | 72 | On Mac, it can use unprivileged and privileged, but on Linux or docker, you should use privileged and have sudo permission. 73 | 74 | ``` 75 | sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" 76 | ``` 77 | 78 | ## feature: 79 | 80 | #### 1, bind source ip 81 | 82 | ``` 83 | bp.SetSource("") // if hava multi isp ip, can use one 84 | ``` 85 | 86 | 87 | #### 2, support ipv6 88 | 89 | can support use ipv4 and ipv6 at the same time 90 | 91 | #### 3, support ping multi ip 92 | 93 | NewBatchPinger can support multi ip ping 94 | 95 | #### 4, support two model 96 | 97 | can use unprivileged mode , need not to be root 98 | 99 | 100 | ## Attention: 101 | ping can support ping many ip, id is pid,but should notice modify udp.sendspace , udp.recvspace and raw.recvspace and so on. 102 | 103 | fix same bug of github.com/sparrc/go-ping, such as if the dst server use the iptable ban ping, go-ping will hang . 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /batch_ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "golang.org/x/net/icmp" 12 | "golang.org/x/net/ipv4" 13 | "golang.org/x/net/ipv6" 14 | ) 15 | 16 | // BatchPinger is Pinger manager 17 | type BatchPinger struct { 18 | // pingers []*Pinger 19 | done chan bool 20 | 21 | // mapIpAddr is ip addr map 22 | mapIpAddr map[string]string 23 | 24 | // mapIpPinger is ip pinger map 25 | mapIpPinger map[string]*Pinger 26 | 27 | // interval is the wait time between each packet send. Default is 1s. 28 | interval time.Duration 29 | 30 | // Timeout specifies a timeout before ping exits, regardless of how many 31 | // packets have been received. 32 | timeout time.Duration 33 | 34 | // Count tells pinger to stop after sending (and receiving) Count echo 35 | // packets. If this option is not specified, pinger will operate until 36 | // interrupted. 37 | 38 | //count is ping num for every addr 39 | count int 40 | 41 | //sendCount is the num has send 42 | sendCount int 43 | 44 | //source is source ip, can use this ip listen 45 | source string 46 | 47 | //network is network mode, may be ip or udp, and ip is privileged 48 | network string 49 | 50 | //id is the process id, should drop the pkg of other process 51 | id int 52 | 53 | //conn4 is ipv4 icmp PacketConn 54 | conn4 *icmp.PacketConn 55 | 56 | //conn6 is ipv6 icmp PacketConn 57 | conn6 *icmp.PacketConn 58 | 59 | //addrs is all addr 60 | addrs []string 61 | 62 | //debug model will print log 63 | debug bool 64 | 65 | // OnFinish can be called when Pinger exits 66 | OnFinish func(map[string]*Statistics) 67 | 68 | seqID int 69 | } 70 | 71 | //NewBatchPinger returns a new Pinger struct pointer, interval is default 1s, count default 5 72 | func NewBatchPinger(addrs []string, privileged bool) (batachPinger *BatchPinger, err error) { 73 | 74 | var network string 75 | if privileged { 76 | network = "ip" 77 | } else { 78 | network = "udp" 79 | } 80 | 81 | batachPinger = &BatchPinger{ 82 | interval: time.Second, 83 | timeout: time.Second * 100000, 84 | count: 5, 85 | network: network, 86 | id: getPId(), 87 | done: make(chan bool), 88 | addrs: addrs, 89 | mapIpPinger: make(map[string]*Pinger), 90 | mapIpAddr: make(map[string]string), 91 | } 92 | 93 | return batachPinger, nil 94 | } 95 | 96 | // SetDebug will fmt debug log 97 | func (bp *BatchPinger) SetDebug(debug bool) { 98 | bp.debug = debug 99 | } 100 | 101 | // SetSource set source ip 102 | func (bp *BatchPinger) SetSource(source string) { 103 | bp.source = source 104 | } 105 | 106 | // SetCount set ping count per addr 107 | func (bp *BatchPinger) SetCount(count int) { 108 | bp.count = count 109 | } 110 | 111 | // SetInterval set ping interval 112 | func (bp *BatchPinger) SetInterval(interval time.Duration) { 113 | bp.interval = interval 114 | } 115 | 116 | // SetTimeout Timeout specifies a timeout before ping exits, regardless of how many packets have been received. 117 | func (bp *BatchPinger) SetTimeout(timeout time.Duration) { 118 | bp.timeout = timeout 119 | } 120 | 121 | // getPId get process id 122 | func getPId() int { 123 | return os.Getpid() 124 | } 125 | 126 | // Run will multi-ping addrs 127 | func (bp *BatchPinger) Run() (err error) { 128 | if bp.conn4, err = icmp.ListenPacket(ipv4Proto[bp.network], bp.source); err != nil { 129 | return err 130 | } 131 | if bp.conn6, err = icmp.ListenPacket(ipv6Proto[bp.network], bp.source); err != nil { 132 | return err 133 | } 134 | bp.conn4.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true) 135 | bp.conn6.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true) 136 | 137 | for _, addr := range bp.addrs { 138 | pinger, err := NewPinger(addr, bp.id, bp.network) 139 | if err != nil { 140 | return err 141 | } 142 | bp.mapIpPinger[pinger.ipaddr.String()] = pinger 143 | bp.mapIpAddr[pinger.ipaddr.String()] = addr 144 | pinger.SetConns(bp.conn4, bp.conn6) 145 | } 146 | 147 | if bp.debug { 148 | log.Printf("[debug] pid %d \n", bp.id) 149 | } 150 | 151 | defer bp.conn4.Close() 152 | defer bp.conn6.Close() 153 | 154 | var wg sync.WaitGroup 155 | wg.Add(3) 156 | go bp.recvIpv4(&wg) 157 | go bp.recvIpv6(&wg) 158 | go bp.sendICMP(&wg) 159 | wg.Wait() 160 | return nil 161 | } 162 | 163 | func (bp *BatchPinger) recvIpv4(wg *sync.WaitGroup) { 164 | defer wg.Done() 165 | var ttl int 166 | 167 | for { 168 | select { 169 | case <-bp.done: 170 | return 171 | default: 172 | bytes := make([]byte, 512) 173 | bp.conn4.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) 174 | n, cm, addr, err := bp.conn4.IPv4PacketConn().ReadFrom(bytes) 175 | if cm != nil { 176 | ttl = cm.TTL 177 | } 178 | 179 | if err != nil { 180 | if neterr, ok := err.(*net.OpError); ok { 181 | if neterr.Timeout() { 182 | // Read timeout 183 | continue 184 | } else { 185 | if bp.debug { 186 | log.Printf("read err %s ", err) 187 | } 188 | return 189 | } 190 | } 191 | } 192 | 193 | recvPkg := &packet{bytes: bytes, nbytes: n, ttl: ttl, proto: protoIpv4, addr: addr} 194 | if bp.debug { 195 | log.Printf("recv addr %v \n", recvPkg.addr.String()) 196 | } 197 | err = bp.processPacket(recvPkg) 198 | if err != nil && bp.debug { 199 | log.Printf("processPacket err %v, recvpkg %v \n", err, recvPkg) 200 | } 201 | } 202 | } 203 | } 204 | 205 | func (bp *BatchPinger) recvIpv6(wg *sync.WaitGroup) { 206 | defer wg.Done() 207 | var ttl int 208 | for { 209 | select { 210 | case <-bp.done: 211 | return 212 | default: 213 | bytes := make([]byte, 512) 214 | bp.conn6.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) 215 | n, cm, addr, err := bp.conn6.IPv6PacketConn().ReadFrom(bytes) 216 | if cm != nil { 217 | ttl = cm.HopLimit 218 | } 219 | if err != nil { 220 | if neterr, ok := err.(*net.OpError); ok { 221 | if neterr.Timeout() { 222 | // Read timeout 223 | continue 224 | } 225 | } 226 | } 227 | 228 | recvPkg := &packet{bytes: bytes, nbytes: n, ttl: ttl, proto: protoIpv6, addr: addr} 229 | if bp.debug { 230 | log.Printf("recv addr %v \n", recvPkg.addr.String()) 231 | } 232 | err = bp.processPacket(recvPkg) 233 | if err != nil && bp.debug { 234 | log.Printf("processPacket err %v, recvpkg %v \n", err, recvPkg) 235 | } 236 | } 237 | 238 | } 239 | } 240 | 241 | func (bp *BatchPinger) sendICMP(wg *sync.WaitGroup) { 242 | defer wg.Done() 243 | timeout := time.NewTicker(bp.timeout) 244 | interval := time.NewTicker(bp.interval) 245 | 246 | for { 247 | select { 248 | case <-bp.done: 249 | return 250 | 251 | case <-timeout.C: 252 | close(bp.done) 253 | return 254 | 255 | case <-interval.C: 256 | bp.batchSendICMP() 257 | bp.sendCount++ 258 | if bp.sendCount >= bp.count { 259 | time.Sleep(bp.interval) 260 | close(bp.done) 261 | if bp.debug { 262 | log.Printf("send end sendcout %d, count %d \n", bp.sendCount, bp.count) 263 | } 264 | 265 | return 266 | } 267 | } 268 | } 269 | } 270 | 271 | // batchSendICMP let all addr send pkg once 272 | func (bp *BatchPinger) batchSendICMP() { 273 | for _, pinger := range bp.mapIpPinger { 274 | pinger.SendICMP(bp.seqID) 275 | pinger.PacketsSent++ 276 | } 277 | bp.seqID = (bp.seqID + 1) & 0xffff 278 | } 279 | 280 | func (bp *BatchPinger) processPacket(recv *packet) error { 281 | receivedAt := time.Now() 282 | var proto int 283 | if recv.proto == protoIpv4 { 284 | proto = protocolICMP 285 | } else { 286 | proto = protocolIPv6ICMP 287 | } 288 | 289 | var m *icmp.Message 290 | var err error 291 | 292 | if m, err = icmp.ParseMessage(proto, recv.bytes); err != nil { 293 | return fmt.Errorf("error parsing icmp message: %s", err.Error()) 294 | } 295 | 296 | if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply { 297 | // Not an echo reply, ignore it 298 | if bp.debug { 299 | log.Printf("pkg drop %v \n", m) 300 | } 301 | return nil 302 | } 303 | 304 | switch pkt := m.Body.(type) { 305 | case *icmp.Echo: 306 | // If we are privileged, we can match icmp.ID 307 | if pkt.ID != bp.id { 308 | if bp.debug { 309 | log.Printf("drop pkg %+v id %v addr %s \n", pkt, bp.id, recv.addr) 310 | } 311 | return nil 312 | } 313 | 314 | if len(pkt.Data) < timeSliceLength+trackerLength { 315 | return fmt.Errorf("insufficient data received; got: %d %v", 316 | len(pkt.Data), pkt.Data) 317 | } 318 | 319 | timestamp := bytesToTime(pkt.Data[:timeSliceLength]) 320 | 321 | var ip string 322 | if bp.network == "udp" { 323 | if ip, _, err = net.SplitHostPort(recv.addr.String()); err != nil { 324 | return fmt.Errorf("err ip : %v, err %v", recv.addr, err) 325 | } 326 | } else { 327 | ip = recv.addr.String() 328 | } 329 | 330 | if pinger, ok := bp.mapIpPinger[ip]; ok { 331 | pinger.PacketsRecv++ 332 | pinger.rtts = append(pinger.rtts, receivedAt.Sub(timestamp)) 333 | } 334 | 335 | default: 336 | // Very bad, not sure how this can happen 337 | return fmt.Errorf("invalid ICMP echo reply; type: '%T', '%v'", pkt, pkt) 338 | } 339 | 340 | return nil 341 | 342 | } 343 | 344 | // Statistics is all addr data Statistic 345 | func (bp *BatchPinger) Statistics() map[string]*Statistics { 346 | stMap := map[string]*Statistics{} 347 | for ip, pinger := range bp.mapIpPinger { 348 | addr := bp.mapIpAddr[ip] 349 | stMap[addr] = pinger.Statistics() 350 | } 351 | return stMap 352 | } 353 | 354 | // Finish will call OnFinish 355 | func (bp *BatchPinger) Finish() { 356 | handler := bp.OnFinish 357 | if bp.OnFinish != nil { 358 | handler(bp.Statistics()) 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /batch_ping_test.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewBatchPinger(t *testing.T) { 8 | type args struct { 9 | addrs []string 10 | privileged bool 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | wantErr bool 16 | }{ 17 | { 18 | name: "newBatchPinger", 19 | args: args{ 20 | addrs: []string{"baidu.com"}, 21 | privileged: true, 22 | }, 23 | wantErr: false, 24 | }, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | _, err := NewBatchPinger(tt.args.addrs, tt.args.privileged) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("NewBatchPinger() error = %v, wantErr %v", err, tt.wantErr) 31 | return 32 | } 33 | }) 34 | } 35 | } 36 | 37 | func TestNewBatchPinger_multiAddr(t *testing.T) { 38 | type args struct { 39 | addrs []string 40 | privileged bool 41 | } 42 | tests := []struct { 43 | name string 44 | args args 45 | wantErr bool 46 | }{ 47 | { 48 | name: "multi_addr", 49 | args: args{ 50 | addrs: []string{ 51 | "39.156.69.1", 52 | "39.156.69.2", 53 | "39.156.69.3", 54 | "39.156.69.4", 55 | "39.156.69.5", 56 | "39.156.69.6", 57 | "39.156.69.7", 58 | "39.156.69.8", 59 | "39.156.69.9", 60 | "39.156.69.10", 61 | "39.156.69.11", 62 | "39.156.69.12", 63 | "39.156.69.13", 64 | "39.156.69.14", 65 | "39.156.69.15", 66 | "39.156.69.16", 67 | "39.156.69.17", 68 | "39.156.69.18", 69 | "39.156.69.19", 70 | "39.156.69.20", 71 | "39.156.69.21", 72 | "39.156.69.22", 73 | "39.156.69.23", 74 | "39.156.69.24", 75 | "39.156.69.25", 76 | "39.156.69.26", 77 | "39.156.69.27", 78 | "39.156.69.28", 79 | "39.156.69.29", 80 | "39.156.69.30", 81 | "39.156.69.31", 82 | "39.156.69.32", 83 | "39.156.69.33", 84 | "39.156.69.34", 85 | "39.156.69.35", 86 | "39.156.69.36", 87 | "39.156.69.37", 88 | "39.156.69.38", 89 | "39.156.69.39", 90 | "39.156.69.40", 91 | "39.156.69.41", 92 | "39.156.69.42", 93 | "39.156.69.43", 94 | "39.156.69.44", 95 | "39.156.69.45", 96 | "39.156.69.46", 97 | "39.156.69.47", 98 | "39.156.69.48", 99 | "39.156.69.49", 100 | "39.156.69.50"}, 101 | privileged: true, 102 | }, 103 | wantErr: false, 104 | }, 105 | } 106 | for _, tt := range tests { 107 | t.Run(tt.name, func(t *testing.T) { 108 | batchPinger, err := NewBatchPinger(tt.args.addrs, tt.args.privileged) 109 | if (err != nil) != tt.wantErr { 110 | t.Errorf("NewBatchPinger() error = %v, wantErr %v", err, tt.wantErr) 111 | return 112 | } 113 | err = batchPinger.Run() 114 | if (err != nil) != tt.wantErr { 115 | t.Errorf("multi ping error = %v, wantErr %v", err, tt.wantErr) 116 | return 117 | } 118 | }) 119 | } 120 | } 121 | 122 | func TestNewBatchPinger_ipv6(t *testing.T) { 123 | type args struct { 124 | addrs []string 125 | privileged bool 126 | } 127 | tests := []struct { 128 | name string 129 | args args 130 | wantErr bool 131 | }{ 132 | { 133 | name: "newBatchPinger", 134 | args: args{ 135 | addrs: []string{"2400:da00:2::29"}, 136 | privileged: true, 137 | }, 138 | wantErr: false, 139 | }, 140 | } 141 | for _, tt := range tests { 142 | t.Run(tt.name, func(t *testing.T) { 143 | bp, err := NewBatchPinger(tt.args.addrs, tt.args.privileged) 144 | if (err != nil) != tt.wantErr { 145 | t.Errorf("NewBatchPinger() error = %v, wantErr %v", err, tt.wantErr) 146 | return 147 | } 148 | err = bp.Run() 149 | if (err != nil) != tt.wantErr { 150 | t.Errorf("ping ipv6 error = %v, wantErr %v", err, tt.wantErr) 151 | return 152 | } 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/caucy/batch_ping" 7 | ) 8 | 9 | func main() { 10 | ipSlice := []string{} 11 | // ip list should not more than 65535 12 | 13 | ipSlice = append(ipSlice, "2400:da00:2::29") //support ipv6 14 | ipSlice = append(ipSlice, "baidu.com") 15 | 16 | bp, err := ping.NewBatchPinger(ipSlice, true) // true will need to be root, false may be permission denied 17 | 18 | if err != nil { 19 | log.Fatalf("new batch ping err %v", err) 20 | } 21 | bp.SetDebug(false) // debug == true will fmt debug log 22 | 23 | bp.SetSource("") // if hava multi source ip, can use one isp 24 | bp.SetCount(10) 25 | 26 | bp.OnFinish = func(stMap map[string]*ping.Statistics) { 27 | for ip, st := range stMap { 28 | log.Printf("\n--- %s ping statistics ---\n", st.Addr) 29 | log.Printf("ip %s, %d packets transmitted, %d packets received, %v%% packet loss\n", ip, 30 | st.PacketsSent, st.PacketsRecv, st.PacketLoss) 31 | log.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 32 | st.MinRtt, st.AvgRtt, st.MaxRtt, st.StdDevRtt) 33 | log.Printf("rtts is %v \n", st.Rtts) 34 | } 35 | 36 | } 37 | 38 | err = bp.Run() 39 | if err != nil { 40 | log.Printf("run err %v \n", err) 41 | } 42 | bp.OnFinish(bp.Statistics()) 43 | } 44 | -------------------------------------------------------------------------------- /ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math" 7 | "math/rand" 8 | "net" 9 | "syscall" 10 | "time" 11 | 12 | "golang.org/x/net/icmp" 13 | "golang.org/x/net/ipv4" 14 | "golang.org/x/net/ipv6" 15 | ) 16 | 17 | const ( 18 | timeSliceLength = 8 19 | trackerLength = 8 20 | protocolICMP = 1 21 | protocolIPv6ICMP = 58 22 | protoIpv4 = "ipv4" 23 | protoIpv6 = "ipv6" 24 | ) 25 | 26 | var ( 27 | ipv4Proto = map[string]string{"ip": "ip4:icmp", "udp": "udp4"} 28 | ipv6Proto = map[string]string{"ip": "ip6:ipv6-icmp", "udp": "udp6"} 29 | ) 30 | 31 | // NewPinger returns a new Pinger struct pointer 32 | func NewPinger(addr string, pid int, network string) (*Pinger, error) { 33 | ipaddr, err := net.ResolveIPAddr("ip", addr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | var ipv4 bool 39 | if isIPv4(ipaddr.IP) { 40 | ipv4 = true 41 | } else if isIPv6(ipaddr.IP) { 42 | ipv4 = false 43 | } 44 | 45 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 46 | return &Pinger{ 47 | ipaddr: ipaddr, 48 | addr: addr, 49 | Interval: time.Second, 50 | Timeout: time.Second * 100000, 51 | Count: -1, 52 | id: pid, 53 | network: network, 54 | ipv4: ipv4, 55 | Size: timeSliceLength, 56 | Tracker: r.Int63n(math.MaxInt64), 57 | }, nil 58 | } 59 | 60 | // Pinger represents ICMP packet sender/receiver 61 | type Pinger struct { 62 | // Interval is the wait time between each packet send. Default is 1s. 63 | Interval time.Duration 64 | 65 | // Timeout specifies a timeout before ping exits, regardless of how many 66 | // packets have been received. 67 | Timeout time.Duration 68 | 69 | // Count tells pinger to stop after sending (and receiving) Count echo 70 | // packets. If this option is not specified, pinger will operate until 71 | // interrupted. 72 | Count int 73 | 74 | // Number of packets sent 75 | PacketsSent int 76 | 77 | // Number of packets received 78 | PacketsRecv int 79 | 80 | // rtts is all of the Rtts 81 | rtts []time.Duration 82 | 83 | // OnRecv is called when Pinger receives and processes a packet 84 | OnRecv func(*Packet) 85 | 86 | // OnFinish is called when Pinger exits 87 | OnFinish func(*Statistics) 88 | 89 | // Size of packet being sent 90 | Size int 91 | 92 | // Tracker: Used to uniquely identify packet when non-priviledged 93 | Tracker int64 94 | 95 | // Source is the source IP address 96 | Source string 97 | 98 | ipaddr *net.IPAddr 99 | addr string 100 | 101 | ipv4 bool 102 | size int 103 | id int 104 | network string 105 | 106 | // conn4 is ipv4 icmp PacketConn 107 | conn4 *icmp.PacketConn 108 | 109 | // conn6 is ipv6 icmp PacketConn 110 | conn6 *icmp.PacketConn 111 | } 112 | 113 | type packet struct { 114 | bytes []byte 115 | nbytes int 116 | ttl int 117 | proto string 118 | addr net.Addr 119 | } 120 | 121 | // Packet represents a received and processed ICMP echo packet. 122 | type Packet struct { 123 | // Rtt is the round-trip time it took to ping. 124 | Rtt time.Duration 125 | 126 | // IPAddr is the address of the host being pinged. 127 | IPAddr *net.IPAddr 128 | 129 | // Addr is the string address of the host being pinged. 130 | Addr string 131 | 132 | // NBytes is the number of bytes in the message. 133 | Nbytes int 134 | 135 | // Seq is the ICMP sequence number. 136 | Seq int 137 | 138 | // TTL is the Time To Live on the packet. 139 | Ttl int 140 | } 141 | 142 | // Statistics represent the stats of a currently running or finished 143 | // pinger operation. 144 | type Statistics struct { 145 | // PacketsRecv is the number of packets received. 146 | PacketsRecv int 147 | 148 | // PacketsSent is the number of packets sent. 149 | PacketsSent int 150 | 151 | // PacketLoss is the percentage of packets lost. 152 | PacketLoss float64 153 | 154 | // IPAddr is the address of the host being pinged. 155 | IPAddr *net.IPAddr 156 | 157 | // Addr is the string address of the host being pinged. 158 | Addr string 159 | 160 | // Rtts is all of the round-trip times sent via this pinger. 161 | Rtts []time.Duration 162 | 163 | // MinRtt is the minimum round-trip time sent via this pinger. 164 | MinRtt time.Duration 165 | 166 | // MaxRtt is the maximum round-trip time sent via this pinger. 167 | MaxRtt time.Duration 168 | 169 | // AvgRtt is the average round-trip time sent via this pinger. 170 | AvgRtt time.Duration 171 | 172 | // StdDevRtt is the standard deviation of the round-trip times sent via 173 | // this pinger. 174 | StdDevRtt time.Duration 175 | } 176 | 177 | // SetConns set ipv4 and ipv6 conn 178 | func (p *Pinger) SetConns(conn4 *icmp.PacketConn, conn6 *icmp.PacketConn) { 179 | p.conn4 = conn4 180 | p.conn6 = conn6 181 | } 182 | 183 | // SetIPAddr sets the ip address of the target host. 184 | func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) { 185 | var ipv4 bool 186 | if isIPv4(ipaddr.IP) { 187 | ipv4 = true 188 | } else if isIPv6(ipaddr.IP) { 189 | ipv4 = false 190 | } 191 | 192 | p.ipaddr = ipaddr 193 | p.addr = ipaddr.String() 194 | p.ipv4 = ipv4 195 | } 196 | 197 | // IPAddr returns the ip address of the target host. 198 | func (p *Pinger) IPAddr() *net.IPAddr { 199 | return p.ipaddr 200 | } 201 | 202 | // SetAddr resolves and sets the ip address of the target host, addr can be a 203 | // DNS name like "www.google.com" or IP like "127.0.0.1". 204 | func (p *Pinger) SetAddr(addr string) error { 205 | ipaddr, err := net.ResolveIPAddr("ip", addr) 206 | if err != nil { 207 | return err 208 | } 209 | 210 | p.SetIPAddr(ipaddr) 211 | p.addr = addr 212 | return nil 213 | } 214 | 215 | // Addr returns the string ip address of the target host. 216 | func (p *Pinger) Addr() string { 217 | return p.addr 218 | } 219 | 220 | // SetPrivileged sets the type of ping pinger will send. 221 | // false means pinger will send an "unprivileged" UDP ping. 222 | // true means pinger will send a "privileged" raw ICMP ping. 223 | // NOTE: setting to true requires that it be run with super-user privileges. 224 | func (p *Pinger) SetPrivileged(privileged bool) { 225 | if privileged { 226 | p.network = "ip" 227 | } else { 228 | p.network = "udp" 229 | } 230 | } 231 | 232 | // Privileged returns whether pinger is running in privileged mode. 233 | func (p *Pinger) Privileged() bool { 234 | return p.network == "ip" 235 | } 236 | 237 | func (p *Pinger) finish() { 238 | handler := p.OnFinish 239 | if handler != nil { 240 | s := p.Statistics() 241 | handler(s) 242 | } 243 | } 244 | 245 | // Statistics returns the statistics of the pinger. This can be run while the 246 | // pinger is running or after it is finished. OnFinish calls this function to 247 | // get it's finished statistics. 248 | func (p *Pinger) Statistics() *Statistics { 249 | loss := float64(p.PacketsSent-p.PacketsRecv) / float64(p.PacketsSent) * 100 250 | var min, max, total time.Duration 251 | if len(p.rtts) > 0 { 252 | min = p.rtts[0] 253 | max = p.rtts[0] 254 | } 255 | for _, rtt := range p.rtts { 256 | if rtt < min { 257 | min = rtt 258 | } 259 | if rtt > max { 260 | max = rtt 261 | } 262 | total += rtt 263 | } 264 | s := Statistics{ 265 | PacketsSent: p.PacketsSent, 266 | PacketsRecv: p.PacketsRecv, 267 | PacketLoss: loss, 268 | Rtts: p.rtts, 269 | Addr: p.addr, 270 | IPAddr: p.ipaddr, 271 | MaxRtt: max, 272 | MinRtt: min, 273 | } 274 | if len(p.rtts) > 0 { 275 | s.AvgRtt = total / time.Duration(len(p.rtts)) 276 | var sumsquares time.Duration 277 | for _, rtt := range p.rtts { 278 | sumsquares += (rtt - s.AvgRtt) * (rtt - s.AvgRtt) 279 | } 280 | s.StdDevRtt = time.Duration(math.Sqrt( 281 | float64(sumsquares / time.Duration(len(p.rtts))))) 282 | } 283 | return &s 284 | } 285 | 286 | func (p *Pinger) SendICMP(seqID int) { 287 | var typ icmp.Type 288 | if p.ipv4 { 289 | typ = ipv4.ICMPTypeEcho 290 | } else { 291 | typ = ipv6.ICMPTypeEchoRequest 292 | } 293 | 294 | var dst net.Addr = p.ipaddr 295 | if p.network == "udp" { 296 | dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone} 297 | } 298 | 299 | t := append(timeToBytes(time.Now()), intToBytes(p.Tracker)...) 300 | if remainSize := p.Size - timeSliceLength - trackerLength; remainSize > 0 { 301 | t = append(t, bytes.Repeat([]byte{1}, remainSize)...) 302 | } 303 | 304 | body := &icmp.Echo{ 305 | ID: p.id, 306 | Seq: seqID, 307 | Data: t, 308 | } 309 | 310 | msg := &icmp.Message{ 311 | Type: typ, 312 | Code: 0, 313 | Body: body, 314 | } 315 | 316 | msgBytes, err := msg.Marshal(nil) 317 | if err != nil { 318 | return 319 | } 320 | 321 | for { 322 | if p.ipv4 { 323 | if _, err := p.conn4.WriteTo(msgBytes, dst); err != nil { 324 | if neterr, ok := err.(*net.OpError); ok { 325 | if neterr.Err == syscall.ENOBUFS { 326 | continue 327 | } 328 | } 329 | } 330 | } else { 331 | if _, err := p.conn6.WriteTo(msgBytes, dst); err != nil { 332 | if neterr, ok := err.(*net.OpError); ok { 333 | if neterr.Err == syscall.ENOBUFS { 334 | continue 335 | } 336 | } 337 | } 338 | } 339 | break 340 | } 341 | 342 | return 343 | } 344 | 345 | func bytesToTime(b []byte) time.Time { 346 | var nsec int64 347 | for i := uint8(0); i < 8; i++ { 348 | nsec += int64(b[i]) << ((7 - i) * 8) 349 | } 350 | return time.Unix(nsec/1000000000, nsec%1000000000) 351 | } 352 | 353 | func isIPv4(ip net.IP) bool { 354 | return len(ip.To4()) == net.IPv4len 355 | } 356 | 357 | func isIPv6(ip net.IP) bool { 358 | return len(ip) == net.IPv6len 359 | } 360 | 361 | func timeToBytes(t time.Time) []byte { 362 | nsec := t.UnixNano() 363 | b := make([]byte, 8) 364 | for i := uint8(0); i < 8; i++ { 365 | b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff) 366 | } 367 | return b 368 | } 369 | 370 | func bytesToInt(b []byte) int64 { 371 | return int64(binary.BigEndian.Uint64(b)) 372 | } 373 | 374 | func intToBytes(tracker int64) []byte { 375 | b := make([]byte, 8) 376 | binary.BigEndian.PutUint64(b, uint64(tracker)) 377 | return b 378 | } 379 | --------------------------------------------------------------------------------