1
0
mirror of https://github.com/taigrr/go-fastping synced 2025-01-18 05:03:15 -08:00

Add IPv6 support

Closes #3.
This commit is contained in:
Tatsushi Demachi 2014-10-11 14:48:23 +09:00
parent 8242021828
commit d22f240852
3 changed files with 131 additions and 44 deletions

View File

@ -6,6 +6,7 @@ import (
"net" "net"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
) )
@ -21,7 +22,12 @@ func main() {
os.Exit(1) os.Exit(1)
} }
p := fastping.NewPinger() p := fastping.NewPinger()
ra, err := net.ResolveIPAddr("ip4:icmp", os.Args[1])
netProto := "ip4:icmp"
if strings.Index(os.Args[1], ":") != -1 {
netProto = "ip6:ipv6-icmp"
}
ra, err := net.ResolveIPAddr(netProto, os.Args[1])
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@ -73,6 +73,14 @@ func bytesToTime(b []byte) time.Time {
return time.Unix(nsec/1000000000, nsec%1000000000) return time.Unix(nsec/1000000000, nsec%1000000000)
} }
func isIPv4(ip net.IP) bool {
return len(ip.To4()) == net.IPv4len
}
func isIPv6(ip net.IP) bool {
return len(ip) == net.IPv6len
}
type packet struct { type packet struct {
bytes []byte bytes []byte
addr *net.IPAddr addr *net.IPAddr
@ -96,9 +104,11 @@ type Pinger struct {
id int id int
seq int seq int
// key string is IPAddr.String() // key string is IPAddr.String()
addrs map[string]*net.IPAddr addrs map[string]*net.IPAddr
ctx *context hasIPv4 bool
mu sync.Mutex hasIPv6 bool
ctx *context
mu sync.Mutex
// Number of (nano,milli)seconds of an idle timeout. Once it passed, // Number of (nano,milli)seconds of an idle timeout. Once it passed,
// the library calls an idle callback function. It is also used for an // the library calls an idle callback function. It is also used for an
// interval time of RunLoop() method // interval time of RunLoop() method
@ -108,18 +118,19 @@ type Pinger struct {
Debug bool Debug bool
} }
// NewPinger returns a new Pinger // NewPinger returns a new Pinger struct pointer
func NewPinger() *Pinger { func NewPinger() *Pinger {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
p := &Pinger{ return &Pinger{
id: rand.Intn(0xffff), id: rand.Intn(0xffff),
seq: rand.Intn(0xffff), seq: rand.Intn(0xffff),
addrs: make(map[string]*net.IPAddr), addrs: make(map[string]*net.IPAddr),
hasIPv4: false,
hasIPv6: false,
MaxRTT: time.Second, MaxRTT: time.Second,
handlers: make(map[string]interface{}), handlers: make(map[string]interface{}),
Debug: false, Debug: false,
} }
return p
} }
// AddIP adds an IP address to Pinger. ipaddr arg should be a string like // AddIP adds an IP address to Pinger. ipaddr arg should be a string like
@ -131,6 +142,11 @@ func (p *Pinger) AddIP(ipaddr string) error {
} }
p.mu.Lock() p.mu.Lock()
p.addrs[addr.String()] = &net.IPAddr{IP: addr} p.addrs[addr.String()] = &net.IPAddr{IP: addr}
if isIPv4(addr) {
p.hasIPv4 = true
} else if isIPv6(addr) {
p.hasIPv6 = true
}
p.mu.Unlock() p.mu.Unlock()
return nil return nil
} }
@ -140,6 +156,11 @@ func (p *Pinger) AddIP(ipaddr string) error {
func (p *Pinger) AddIPAddr(ip *net.IPAddr) { func (p *Pinger) AddIPAddr(ip *net.IPAddr) {
p.mu.Lock() p.mu.Lock()
p.addrs[ip.String()] = ip p.addrs[ip.String()] = ip
if isIPv4(ip.IP) {
p.hasIPv4 = true
} else if isIPv6(ip.IP) {
p.hasIPv6 = true
}
p.mu.Unlock() p.mu.Unlock()
} }
@ -250,27 +271,52 @@ func (p *Pinger) Err() error {
return p.ctx.err return p.ctx.err
} }
func (p *Pinger) run(once bool) { func (p *Pinger) listen(netProto string) *net.IPConn {
p.debugln("Run(): Start") conn, err := net.ListenIP(netProto, nil)
conn, err := net.ListenIP("ip4:icmp", &net.IPAddr{IP: net.IPv4zero})
if err != nil { if err != nil {
p.mu.Lock() p.mu.Lock()
p.ctx.err = err p.ctx.err = err
p.mu.Unlock() p.mu.Unlock()
p.debugln("Run(): close(p.ctx.done)") p.debugln("Run(): close(p.ctx.done)")
close(p.ctx.done) close(p.ctx.done)
return return nil
}
return conn
}
func (p *Pinger) run(once bool) {
p.debugln("Run(): Start")
var conn, conn6 *net.IPConn
if p.hasIPv4 {
if conn = p.listen("ip4:icmp"); conn == nil {
return
}
defer conn.Close()
}
if p.hasIPv6 {
if conn6 = p.listen("ip6:ipv6-icmp"); conn6 == nil {
return
}
defer conn6.Close()
} }
defer conn.Close()
recv := make(chan *packet) recv := make(chan *packet)
recvCtx := newContext() recvCtx := newContext()
wg := new(sync.WaitGroup)
p.debugln("Run(): call recvICMP4()") p.debugln("Run(): call recvICMP()")
go p.recvICMP4(conn, recv, recvCtx) if conn != nil {
wg.Add(1)
go p.recvICMP(conn, recv, recvCtx, wg)
}
if conn6 != nil {
wg.Add(1)
go p.recvICMP(conn6, recv, recvCtx, wg)
}
p.debugln("Run(): call sendICMP4()") p.debugln("Run(): call sendICMP()")
queue, err := p.sendICMP4(conn) queue, err := p.sendICMP(conn, conn6)
ticker := time.NewTicker(p.MaxRTT) ticker := time.NewTicker(p.MaxRTT)
@ -298,8 +344,8 @@ mainloop:
if once || err != nil { if once || err != nil {
break mainloop break mainloop
} }
p.debugln("Run(): call sendICMP4()") p.debugln("Run(): call sendICMP()")
queue, err = p.sendICMP4(conn) queue, err = p.sendICMP(conn, conn6)
case r := <-recv: case r := <-recv:
p.debugln("Run(): <-recv") p.debugln("Run(): <-recv")
p.procRecv(r, queue) p.procRecv(r, queue)
@ -310,8 +356,8 @@ mainloop:
p.debugln("Run(): close(recvCtx.stop)") p.debugln("Run(): close(recvCtx.stop)")
close(recvCtx.stop) close(recvCtx.stop)
p.debugln("Run(): <-recvCtx.done") p.debugln("Run(): wait recvICMP()")
<-recvCtx.done wg.Wait()
p.mu.Lock() p.mu.Lock()
p.ctx.err = err p.ctx.err = err
@ -322,18 +368,33 @@ mainloop:
p.debugln("Run(): End") p.debugln("Run(): End")
} }
func (p *Pinger) sendICMP4(conn *net.IPConn) (map[string]*net.IPAddr, error) { func (p *Pinger) sendICMP(conn, conn6 *net.IPConn) (map[string]*net.IPAddr, error) {
p.debugln("sendICMP4(): Start") p.debugln("sendICMP(): Start")
p.mu.Lock() p.mu.Lock()
p.id = rand.Intn(0xffff) p.id = rand.Intn(0xffff)
p.seq = rand.Intn(0xffff) p.seq = rand.Intn(0xffff)
p.mu.Unlock() p.mu.Unlock()
queue := make(map[string]*net.IPAddr) queue := make(map[string]*net.IPAddr)
var wg sync.WaitGroup wg := new(sync.WaitGroup)
for k, v := range p.addrs { for key, addr := range p.addrs {
var typ int
var cn *net.IPConn
if isIPv4(addr.IP) {
typ = icmpv4EchoRequest
cn = conn
} else if isIPv6(addr.IP) {
typ = icmpv6EchoRequest
cn = conn6
} else {
continue
}
if cn == nil {
continue
}
p.mu.Lock() p.mu.Lock()
bytes, err := (&icmpMessage{ bytes, err := (&icmpMessage{
Type: icmpv4EchoRequest, Code: 0, Type: typ, Code: 0,
Body: &icmpEcho{ Body: &icmpEcho{
ID: p.id, Seq: p.seq, ID: p.id, Seq: p.seq,
Data: timeToBytes(time.Now()), Data: timeToBytes(time.Now()),
@ -345,11 +406,11 @@ func (p *Pinger) sendICMP4(conn *net.IPConn) (map[string]*net.IPAddr, error) {
return queue, err return queue, err
} }
queue[k] = v queue[key] = addr
p.debugln("sendICMP4(): Invoke goroutine") p.debugln("sendICMP(): Invoke goroutine")
wg.Add(1) wg.Add(1)
go func(ra *net.IPAddr, b []byte) { go func(conn *net.IPConn, ra *net.IPAddr, b []byte) {
for { for {
if _, _, err := conn.WriteMsgIP(bytes, nil, ra); err != nil { if _, _, err := conn.WriteMsgIP(bytes, nil, ra); err != nil {
if neterr, ok := err.(*net.OpError); ok { if neterr, ok := err.(*net.OpError); ok {
@ -360,48 +421,51 @@ func (p *Pinger) sendICMP4(conn *net.IPConn) (map[string]*net.IPAddr, error) {
} }
break break
} }
p.debugln("sendICMP4(): WriteMsgIP End") p.debugln("sendICMP(): WriteMsgIP End")
wg.Done() wg.Done()
}(v, bytes) }(cn, addr, bytes)
} }
wg.Wait() wg.Wait()
p.debugln("sendICMP4(): End") p.debugln("sendICMP(): End")
return queue, nil return queue, nil
} }
func (p *Pinger) recvICMP4(conn *net.IPConn, recv chan<- *packet, ctx *context) { func (p *Pinger) recvICMP(conn *net.IPConn, recv chan<- *packet, ctx *context, wg *sync.WaitGroup) {
p.debugln("recvICMP4(): Start") p.debugln("recvICMP(): Start")
for { for {
select { select {
case <-ctx.stop: case <-ctx.stop:
p.debugln("recvICMP4(): <-ctx.stop") p.debugln("recvICMP(): <-ctx.stop")
close(ctx.done) wg.Done()
p.debugln("recvICMP4(): close(ctx.done)") p.debugln("recvICMP(): wg.Done()")
return return
default: default:
} }
bytes := make([]byte, 512) bytes := make([]byte, 512)
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100))
p.debugln("recvICMP4(): ReadMsgIP Start") p.debugln("recvICMP(): ReadMsgIP Start")
_, _, _, ra, err := conn.ReadMsgIP(bytes, nil) _, _, _, ra, err := conn.ReadMsgIP(bytes, nil)
p.debugln("recvICMP4(): ReadMsgIP End") p.debugln("recvICMP(): ReadMsgIP End")
if err != nil { if err != nil {
if neterr, ok := err.(*net.OpError); ok { if neterr, ok := err.(*net.OpError); ok {
if neterr.Timeout() { if neterr.Timeout() {
p.debugln("recvICMP4(): Read Timeout") p.debugln("recvICMP(): Read Timeout")
continue continue
} else { } else {
p.debugln("recvICMP4(): OpError happen", err) p.debugln("recvICMP(): OpError happen", err)
p.mu.Lock() p.mu.Lock()
ctx.err = err ctx.err = err
p.mu.Unlock() p.mu.Unlock()
p.debugln("recvICMP(): close(ctx.done)")
close(ctx.done) close(ctx.done)
p.debugln("recvICMP(): wg.Done()")
wg.Done()
return return
} }
} }
} }
p.debugln("recvICMP4(): p.recv <- packet") p.debugln("recvICMP(): p.recv <- packet")
recv <- &packet{bytes: bytes, addr: ra} recv <- &packet{bytes: bytes, addr: ra}
} }
} }
@ -415,14 +479,22 @@ func (p *Pinger) procRecv(recv *packet, queue map[string]*net.IPAddr) {
} }
p.mu.Unlock() p.mu.Unlock()
bytes := ipv4Payload(recv.bytes) var bytes []byte
if isIPv4(recv.addr.IP) {
bytes = ipv4Payload(recv.bytes)
} else if isIPv6(recv.addr.IP) {
bytes = recv.bytes
} else {
return
}
var m *icmpMessage var m *icmpMessage
var err error var err error
if m, err = parseICMPMessage(bytes); err != nil { if m, err = parseICMPMessage(bytes); err != nil {
return return
} }
if m.Type != icmpv4EchoReply { if m.Type != icmpv4EchoReply && m.Type != icmpv6EchoReply {
return return
} }

View File

@ -48,7 +48,11 @@ func TestRun(t *testing.T) {
t.Fatalf("AddIP failed: %v", err) t.Fatalf("AddIP failed: %v", err)
} }
found1, found100 := false, false if err := p.AddIP("::1"); err != nil {
t.Fatalf("AddIP failed: %v", err)
}
found1, found100, foundv6 := false, false, false
called, idle := false, false called, idle := false, false
err := p.AddHandler("receive", func(ip *net.IPAddr, d time.Duration) { err := p.AddHandler("receive", func(ip *net.IPAddr, d time.Duration) {
called = true called = true
@ -56,6 +60,8 @@ func TestRun(t *testing.T) {
found1 = true found1 = true
} else if ip.String() == "127.0.0.100" { } else if ip.String() == "127.0.0.100" {
found100 = true found100 = true
} else if ip.String() == "::1" {
foundv6 = true
} }
}) })
if err != nil { if err != nil {
@ -85,6 +91,9 @@ func TestRun(t *testing.T) {
if found100 { if found100 {
t.Fatalf("Pinger `127.0.0.100` responded") t.Fatalf("Pinger `127.0.0.100` responded")
} }
if !foundv6 {
t.Fatalf("Pinger `::1` didn't responded")
}
} }
func TestMultiRun(t *testing.T) { func TestMultiRun(t *testing.T) {