Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions arp_datagram.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
126 changes: 74 additions & 52 deletions arping.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <BIN>`.
//
//
// 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 (
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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}
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions arping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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")
}
Expand Down Expand Up @@ -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)
}
Expand Down
28 changes: 14 additions & 14 deletions cmd/arping/main.go
Original file line number Diff line number Diff line change
@@ -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 <ARPING_PATH>
//
// under FreeBSD: as root
// under Linux: as root or with 'cap_net_raw' permission: sudo setcap cap_net_raw+ep <ARPING_PATH>
//
// 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 (
Expand Down Expand Up @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/j-keck/arping
module github.com/fzu-huang/arping

go 1.12
go 1.17
19 changes: 16 additions & 3 deletions netutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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
Expand Down