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

Add non-privileged datagram-oriented ICMP endpoints mode using UDP

This commit is contained in:
Tatsushi Demachi 2015-01-11 00:20:08 +09:00
parent 805934dbbf
commit 41b7495ae3
4 changed files with 242 additions and 178 deletions

View File

@ -44,8 +44,8 @@ callback. For more detail, refer [godoc][godoc] and if you need more example,
please see "cmd/ping/ping.go".
## Caution
This package now only implements ICMP ping using raw socket so the program
using this package needs to be run as root user.
This package implements ICMP ping using both raw socket and UDP. If your program
uses this package in raw socket mode, it needs to be run as a root user.
## License
go-fastping is under MIT License. See the [LICENSE][license] file for details.

View File

@ -1,14 +1,16 @@
package main
import (
"flag"
"fmt"
"github.com/tatsushid/go-fastping"
"net"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/tatsushid/go-fastping"
)
type response struct {
@ -17,17 +19,31 @@ type response struct {
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s {hostname}\n", os.Args[0])
var useUDP bool
flag.BoolVar(&useUDP, "udp", false, "use non-privileged datagram-oriented UDP as ICMP endpoints")
flag.BoolVar(&useUDP, "u", false, "use non-privileged datagram-oriented UDP as ICMP endpoints (shorthand)")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage:\n %s [options] hostname\n\nOptions:\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
hostname := flag.Arg(0)
if len(hostname) == 0 {
flag.Usage()
os.Exit(1)
}
p := fastping.NewPinger()
if useUDP {
p.Network("udp")
}
netProto := "ip4:icmp"
if strings.Index(os.Args[1], ":") != -1 {
if strings.Index(hostname, ":") != -1 {
netProto = "ip6:ipv6-icmp"
}
ra, err := net.ResolveIPAddr(netProto, os.Args[1])
ra, err := net.ResolveIPAddr(netProto, hostname)
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -29,8 +29,9 @@
// it calls "receive" callback. After that, MaxRTT time passed, it calls
// "idle" callback. If you need more example, please see "cmd/ping/ping.go".
//
// This library needs to run as a superuser for sending ICMP packets so when
// you run go test, please run as a following
// This library needs to run as a superuser for sending ICMP packets when
// privileged raw ICMP endpoints is used so in such a case, to run go test
// for the package, please run like a following
//
// sudo go test
//
@ -54,6 +55,11 @@ import (
const TimeSliceLength = 8
var (
ipv4Proto = map[string]string{"ip": "ip4:icmp", "udp": "udp4"}
ipv6Proto = map[string]string{"ip": "ip6:ipv6-icmp", "udp": "udp6"}
)
func byteSliceOfSize(n int) []byte {
b := make([]byte, n)
for i := 0; i < len(b); i++ {
@ -98,7 +104,7 @@ func ipv4Payload(b []byte) []byte {
type packet struct {
bytes []byte
addr *net.IPAddr
addr net.Addr
}
type context struct {
@ -120,6 +126,7 @@ type Pinger struct {
seq int
// key string is IPAddr.String()
addrs map[string]*net.IPAddr
network string
hasIPv4 bool
hasIPv6 bool
ctx *context
@ -147,6 +154,7 @@ func NewPinger() *Pinger {
id: rand.Intn(0xffff),
seq: rand.Intn(0xffff),
addrs: make(map[string]*net.IPAddr),
network: "ip",
hasIPv4: false,
hasIPv6: false,
Size: TimeSliceLength,
@ -157,6 +165,23 @@ func NewPinger() *Pinger {
}
}
// Network sets a network endpoints for ICMP ping and returns the previous
// setting. network arg should be "ip" or "udp" string or if others are
// specified, it returns an error. If this function isn't called, Pinger
// uses "ip" as default.
func (p *Pinger) Network(network string) (string, error) {
origNet := p.network
switch network {
case "ip":
fallthrough
case "udp":
p.network = network
default:
return origNet, errors.New(network + " can't be used as ICMP endpoint")
}
return origNet, nil
}
// AddIP adds an IP address to Pinger. ipaddr arg should be a string like
// "192.0.2.1".
func (p *Pinger) AddIP(ipaddr string) error {
@ -315,14 +340,14 @@ func (p *Pinger) run(once bool) {
p.debugln("Run(): Start")
var conn, conn6 *icmp.PacketConn
if p.hasIPv4 {
if conn = p.listen("ip4:icmp"); conn == nil {
if conn = p.listen(ipv4Proto[p.network]); conn == nil {
return
}
defer conn.Close()
}
if p.hasIPv6 {
if conn6 = p.listen("ip6:ipv6-icmp"); conn6 == nil {
if conn6 = p.listen(ipv6Proto[p.network]); conn6 == nil {
return
}
defer conn6.Close()
@ -438,10 +463,14 @@ func (p *Pinger) sendICMP(conn, conn6 *icmp.PacketConn) (map[string]*net.IPAddr,
}
queue[key] = addr
var dst net.Addr = addr
if p.network == "udp" {
dst = &net.UDPAddr{IP: addr.IP, Zone: addr.Zone}
}
p.debugln("sendICMP(): Invoke goroutine")
wg.Add(1)
go func(conn *icmp.PacketConn, ra *net.IPAddr, b []byte) {
go func(conn *icmp.PacketConn, ra net.Addr, b []byte) {
for {
if _, err := conn.WriteTo(bytes, ra); err != nil {
if neterr, ok := err.(*net.OpError); ok {
@ -454,7 +483,7 @@ func (p *Pinger) sendICMP(conn, conn6 *icmp.PacketConn) (map[string]*net.IPAddr,
}
p.debugln("sendICMP(): WriteTo End")
wg.Done()
}(cn, addr, bytes)
}(cn, dst, bytes)
}
wg.Wait()
p.debugln("sendICMP(): End")
@ -498,13 +527,8 @@ func (p *Pinger) recvICMP(conn *icmp.PacketConn, recv chan<- *packet, ctx *conte
}
p.debugln("recvICMP(): p.recv <- packet")
addr, ok := ra.(*net.IPAddr)
if !ok {
continue
}
select {
case recv <- &packet{bytes: bytes, addr: addr}:
case recv <- &packet{bytes: bytes, addr: ra}:
case <-ctx.stop:
p.debugln("recvICMP(): <-ctx.stop")
wg.Done()
@ -515,7 +539,17 @@ func (p *Pinger) recvICMP(conn *icmp.PacketConn, recv chan<- *packet, ctx *conte
}
func (p *Pinger) procRecv(recv *packet, queue map[string]*net.IPAddr) {
addr := recv.addr.String()
var ipaddr *net.IPAddr
switch adr := recv.addr.(type) {
case *net.IPAddr:
ipaddr = adr
case *net.UDPAddr:
ipaddr = &net.IPAddr{IP: adr.IP, Zone: adr.Zone}
default:
return
}
addr := ipaddr.String()
p.mu.Lock()
if _, ok := p.addrs[addr]; !ok {
p.mu.Unlock()
@ -525,10 +559,14 @@ func (p *Pinger) procRecv(recv *packet, queue map[string]*net.IPAddr) {
var bytes []byte
var proto int
if isIPv4(recv.addr.IP) {
if isIPv4(ipaddr.IP) {
if p.network == "ip" {
bytes = ipv4Payload(recv.bytes)
} else {
bytes = recv.bytes
}
proto = iana.ProtocolICMP
} else if isIPv6(recv.addr.IP) {
} else if isIPv6(ipaddr.IP) {
bytes = recv.bytes
proto = iana.ProtocolIPv6ICMP
} else {
@ -563,7 +601,7 @@ func (p *Pinger) procRecv(recv *packet, queue map[string]*net.IPAddr) {
handler := p.OnRecv
p.mu.Unlock()
if handler != nil {
handler(recv.addr, rtt)
handler(ipaddr, rtt)
}
}
}

View File

@ -38,7 +38,9 @@ func TestAddIP(t *testing.T) {
}
func TestRun(t *testing.T) {
for _, network := range []string{"ip", "udp"} {
p := NewPinger()
p.Network(network)
if err := p.AddIP("127.0.0.1"); err != nil {
t.Fatalf("AddIP failed: %v", err)
@ -89,10 +91,14 @@ func TestRun(t *testing.T) {
t.Fatalf("Pinger `::1` didn't responded")
}
}
}
func TestMultiRun(t *testing.T) {
for _, network := range []string{"ip", "udp"} {
p1 := NewPinger()
p1.Network(network)
p2 := NewPinger()
p2.Network(network)
if err := p1.AddIP("127.0.0.1"); err != nil {
t.Fatalf("AddIP 1 failed: %v", err)
@ -172,9 +178,12 @@ func TestMultiRun(t *testing.T) {
t.Fatalf("Pinger 2 didn't get correct response")
}
}
}
func TestRunLoop(t *testing.T) {
for _, network := range []string{"ip", "udp"} {
p := NewPinger()
p.Network(network)
if err := p.AddIP("127.0.0.1"); err != nil {
t.Fatalf("AddIP failed: %v", err)
@ -211,6 +220,7 @@ func TestRunLoop(t *testing.T) {
t.Fatalf("Pinger idle count less than 2")
}
}
}
func TestTimeToBytes(t *testing.T) {
// 2009-11-10 23:00:00 +0000 UTC = 1257894000000000000