diff --git a/arp_datagram.go b/arp_datagram.go index 1443bc2..162a76c 100644 --- a/arp_datagram.go +++ b/arp_datagram.go @@ -73,8 +73,24 @@ func (datagram arpDatagram) SenderMac() net.HardwareAddr { } func (datagram arpDatagram) IsResponseOf(request arpDatagram) bool { - return datagram.oper == responseOper && bytes.Equal(request.spa, datagram.tpa) && - bytes.Equal(request.tpa, datagram.spa) + return datagram.oper == responseOper && bytes.Equal(request.tpa, datagram.spa) && + bytes.Equal(request.spa, datagram.tpa) +} + +func (datagram arpDatagram) IsResponseOfTarget(request arpDatagram) bool { + return datagram.oper == responseOper && bytes.Equal(request.tpa, datagram.spa) +} + +// IsDuplicateRequestOf case: Cisco switch will re-send an ARP request when it received a DAD-ARP request +func (datagram arpDatagram) IsDuplicateRequestOf(request arpDatagram, ignoreCheck bool) bool { + return datagram.oper == requestOper && bytes.Equal(request.tpa, datagram.spa) && + (ignoreCheck || bytes.Equal(request.tpa, datagram.tpa)) +} + +// IsResponseOfDADRequest case: H3C switch will reply DAD ARP request when it received a DAD-ARP request +func (datagram arpDatagram) IsResponseOfDADRequest(request arpDatagram, ignoreCheck bool) bool { + return datagram.oper == responseOper && bytes.Equal(request.tpa, datagram.spa) && + (ignoreCheck || bytes.Equal(request.tpa, datagram.tpa)) } func parseArpDatagram(buffer []byte) arpDatagram { diff --git a/arping.go b/arping.go index b305731..4b79cf7 100644 --- a/arping.go +++ b/arping.go @@ -2,60 +2,57 @@ // // The currently supported platforms are: Linux and BSD. // -// // The library requires raw socket access. So it must run as root, or with appropriate capabilities under linux: // `sudo setcap cap_net_raw+ep `. // -// // Examples: // -// ping a host: -// ------------ -// package main -// import ("fmt"; "github.com/j-keck/arping"; "net") -// -// func main(){ -// dstIP := net.ParseIP("192.168.1.1") -// if hwAddr, duration, err := arping.Ping(dstIP); err != nil { -// fmt.Println(err) -// } else { -// fmt.Printf("%s (%s) %d usec\n", dstIP, hwAddr, duration/1000) -// } -// } +// ping a host: +// ------------ +// package main +// import ("fmt"; "github.com/j-keck/arping"; "net") // +// func main(){ +// dstIP := net.ParseIP("192.168.1.1") +// if hwAddr, duration, err := arping.Ping(dstIP); err != nil { +// fmt.Println(err) +// } else { +// fmt.Printf("%s (%s) %d usec\n", dstIP, hwAddr, duration/1000) +// } +// } // -// resolve mac address: -// -------------------- -// package main -// import ("fmt"; "github.com/j-keck/arping"; "net") // -// func main(){ -// dstIP := net.ParseIP("192.168.1.1") -// if hwAddr, _, err := arping.Ping(dstIP); err != nil { -// fmt.Println(err) -// } else { -// fmt.Printf("%s is at %s\n", dstIP, hwAddr) -// } -// } +// resolve mac address: +// -------------------- +// package main +// import ("fmt"; "github.com/j-keck/arping"; "net") // +// func main(){ +// dstIP := net.ParseIP("192.168.1.1") +// if hwAddr, _, err := arping.Ping(dstIP); err != nil { +// fmt.Println(err) +// } else { +// fmt.Printf("%s is at %s\n", dstIP, hwAddr) +// } +// } // -// check if host is online: -// ------------------------ -// package main -// import ("fmt"; "github.com/j-keck/arping"; "net") // -// func main(){ -// dstIP := net.ParseIP("192.168.1.1") -// _, _, err := arping.Ping(dstIP) -// if err == arping.ErrTimeout { -// fmt.Println("offline") -// }else if err != nil { -// fmt.Println(err.Error()) -// }else{ -// fmt.Println("online") -// } -// } +// check if host is online: +// ------------------------ +// package main +// import ("fmt"; "github.com/j-keck/arping"; "net") // +// func main(){ +// dstIP := net.ParseIP("192.168.1.1") +// _, _, err := arping.Ping(dstIP) +// if err == arping.ErrTimeout { +// fmt.Println("offline") +// }else if err != nil { +// fmt.Println(err.Error()) +// }else{ +// fmt.Println("online") +// } +// } package arping import ( @@ -77,7 +74,7 @@ var ( ) // Ping sends an arp ping to 'dstIP' -func Ping(dstIP net.IP) (net.HardwareAddr, time.Duration, error) { +func Ping(dstIP, srcIP net.IP, ignoreAddrMatch bool) (net.HardwareAddr, time.Duration, error) { if err := validateIP(dstIP); err != nil { return nil, 0, err } @@ -86,11 +83,11 @@ func Ping(dstIP net.IP) (net.HardwareAddr, time.Duration, error) { if err != nil { return nil, 0, err } - return PingOverIface(dstIP, *iface) + return PingOverIface(dstIP, srcIP, *iface, ignoreAddrMatch) } // PingOverIfaceByName sends an arp ping over interface name 'ifaceName' to 'dstIP' -func PingOverIfaceByName(dstIP net.IP, ifaceName string) (net.HardwareAddr, time.Duration, error) { +func PingOverIfaceByName(dstIP, srcIP net.IP, ifaceName string, ignoreAddrMatch bool) (net.HardwareAddr, time.Duration, error) { if err := validateIP(dstIP); err != nil { return nil, 0, err } @@ -99,19 +96,24 @@ func PingOverIfaceByName(dstIP net.IP, ifaceName string) (net.HardwareAddr, time if err != nil { return nil, 0, err } - return PingOverIface(dstIP, *iface) + return PingOverIface(dstIP, srcIP, *iface, ignoreAddrMatch) } -// PingOverIface sends an arp ping over interface 'iface' to 'dstIP' -func PingOverIface(dstIP net.IP, iface net.Interface) (net.HardwareAddr, time.Duration, error) { - if err := validateIP(dstIP); err != nil { +// PingOverIface forcedly sends an arp ping over interface 'iface' to 'dstIP' +func PingOverIface(dstIP, srcIP net.IP, iface net.Interface, ignoreAddrMatch bool) ( + net.HardwareAddr, time.Duration, error) { + err := validateIP(dstIP) + if err != nil { return nil, 0, err } srcMac := iface.HardwareAddr - srcIP, err := findIPInNetworkFromIface(dstIP, iface) - if err != nil { - return nil, 0, err + + if srcIP == nil { + srcIP, err = findIPInNetworkFromIface(dstIP, iface, true) + if err != nil { + return nil, 0, err + } } broadcastMac := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} @@ -145,6 +147,26 @@ func PingOverIface(dstIP net.IP, iface net.Interface) (net.HardwareAddr, time.Du return } + if ignoreAddrMatch && response.IsResponseOfTarget(request) { + duration := receiveTime.Sub(sendTime) + verboseLog.Printf("process received arp: srcIP: '%s', srcMac: '%s'\n", + response.SenderIP(), response.SenderMac()) + pingResultChan <- PingResult{response.SenderMac(), duration, err} + return + } + + if srcIP.Equal(net.IPv4zero) || srcIP.Equal(net.IPv6zero) { + // if arping with src IP as IPv4zero, we expect no response but request from dst IP + if response.IsDuplicateRequestOf(request, ignoreAddrMatch) || + response.IsResponseOfDADRequest(request, ignoreAddrMatch) { + duration := receiveTime.Sub(sendTime) + verboseLog.Printf("process received arp: srcIP: '%s', srcMac: '%s'\n", + response.SenderIP(), response.SenderMac()) + pingResultChan <- PingResult{response.SenderMac(), duration, err} + return + } + } + if response.IsResponseOf(request) { duration := receiveTime.Sub(sendTime) verboseLog.Printf("process received arp: srcIP: '%s', srcMac: '%s'\n", diff --git a/arping_test.go b/arping_test.go index cabf4f5..30210f9 100644 --- a/arping_test.go +++ b/arping_test.go @@ -11,7 +11,7 @@ import ( func TestPingWithInvalidIP(t *testing.T) { ip := net.ParseIP("invalid") - _, _, err := Ping(ip) + _, _, err := Ping(ip, nil, false) if err == nil { t.Error("error expected") } @@ -22,7 +22,7 @@ func TestPingWithInvalidIP(t *testing.T) { func TestPingWithV6IP(t *testing.T) { ip := net.ParseIP("fe80::e2cb:4eff:fed5:ca4e") - _, _, err := Ping(ip) + _, _, err := Ping(ip, nil, false) if err == nil { t.Error("error expected") } @@ -58,7 +58,7 @@ func TestGoroutinesDoesNotLeak(t *testing.T) { spawnNumGoroutines := 5 for i := 0; i < spawnNumGoroutines; i++ { - _, _, err := Ping(ip) + _, _, err := Ping(ip, nil, false) if err != ErrTimeout { t.Fatalf("timeout error expected, but not received - received err: %v", err) } diff --git a/cmd/arping/main.go b/cmd/arping/main.go index 8c1fb77..898d867 100644 --- a/cmd/arping/main.go +++ b/cmd/arping/main.go @@ -1,33 +1,33 @@ -// // command line arping utility which use the 'arping' library // // this utility need raw socket access, please run it -// under FreeBSD: as root -// under Linux: as root or with 'cap_net_raw' permission: sudo setcap cap_net_raw+ep // +// under FreeBSD: as root +// under Linux: as root or with 'cap_net_raw' permission: sudo setcap cap_net_raw+ep // // options: -// -h: print help and exit -// -v: verbose output -// -U: unsolicited/gratuitous ARP mode -// -i: interface name to use -// -t: timeout - duration with unit - such as 100ms, 500ms, 1s ... // +// -h: print help and exit +// -v: verbose output +// -U: unsolicited/gratuitous ARP mode +// -i: interface name to use +// -t: timeout - duration with unit - such as 100ms, 500ms, 1s ... // // exit code: -// 0: target online -// 1: target offline -// 2: error occurred - see command output // +// 0: target online +// 1: target offline +// 2: error occurred - see command output package main import ( "flag" "fmt" - "github.com/j-keck/arping" "net" "os" "time" + + "github.com/fzu-huang/arping" ) var ( @@ -66,9 +66,9 @@ func main() { } } else { if len(*ifaceNameFlag) > 0 { - hwAddr, durationNanos, err = arping.PingOverIfaceByName(dstIP, *ifaceNameFlag) + hwAddr, durationNanos, err = arping.PingOverIfaceByName(dstIP, nil, *ifaceNameFlag, false) } else { - hwAddr, durationNanos, err = arping.Ping(dstIP) + hwAddr, durationNanos, err = arping.Ping(dstIP, nil, false) } } diff --git a/go.mod b/go.mod index 04c772b..17ec164 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/j-keck/arping +module github.com/fzu-huang/arping -go 1.12 +go 1.17 diff --git a/netutils.go b/netutils.go index 2b707d3..6885f71 100644 --- a/netutils.go +++ b/netutils.go @@ -6,20 +6,33 @@ import ( "net" ) -func findIPInNetworkFromIface(dstIP net.IP, iface net.Interface) (net.IP, error) { +// findIPInNetworkFromIface find an ip from iface as src +func findIPInNetworkFromIface(dstIP net.IP, iface net.Interface, ignoreNet bool) (net.IP, error) { addrs, err := iface.Addrs() if err != nil { return nil, err } - for _, a := range addrs { + if len(addrs) == 0 { + return nil, fmt.Errorf("iface: '%s' do not contains any ip", iface.Name) + } + + var firstIP net.IP + for i, a := range addrs { if ipnet, ok := a.(*net.IPNet); ok { if ipnet.Contains(dstIP) { return ipnet.IP, nil } + if i == 0 { + firstIP = ipnet.IP + } } } + + if ignoreNet { + return firstIP, nil + } return nil, fmt.Errorf("iface: '%s' can't reach ip: '%s'", iface.Name, dstIP) } @@ -35,7 +48,7 @@ func findUsableInterfaceForNetwork(dstIP net.IP) (*net.Interface, error) { } hasAddressInNetwork := func(iface net.Interface) bool { - if _, err := findIPInNetworkFromIface(dstIP, iface); err != nil { + if _, err := findIPInNetworkFromIface(dstIP, iface, false); err != nil { return false } return true