"ImportPath": "github.com/d2g/dhcp4client",
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
},
+ {
+ "ImportPath": "github.com/j-keck/arping",
+ "Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
+ },
{
"ImportPath": "github.com/mattn/go-shellwords",
"Comment": "v1.0.3",
--- /dev/null
+The MIT License (MIT)
+
+ Copyright (c) 2014-2016 j-keck [jhyphenkeck@gmail.com]
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
--- /dev/null
+# arping
+
+arping is a native go library to ping a host per arp datagram, or query a host mac address
+
+The currently supported platforms are: Linux and BSD.
+
+
+## Usage
+### arping library
+
+* import this library per `import "github.com/j-keck/arping"`
+* export GOPATH if not already (`export GOPATH=$PWD`)
+* download the library `go get`
+* run it `sudo -E go run <YOUR PROGRAMM>`
+* or build it `go build`
+
+
+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>`.
+
+For api doc and examples see: [godoc](http://godoc.org/github.com/j-keck/arping) or check the standalone under 'cmd/arping/main.go'.
+
+
+
+### arping executable
+
+To get a runnable pinger use `go get -u github.com/j-keck/arping/cmd/arping`. This will build the binary in $GOPATH/bin.
+
+arping requires raw socket access. So it must run as root, or with appropriate capabilities under Linux: `sudo setcap cap_net_raw+ep <ARPING_PATH>`.
+
--- /dev/null
+package arping
+
+import (
+ "bytes"
+ "encoding/binary"
+ "net"
+)
+
+const (
+ requestOper = 1
+ responseOper = 2
+)
+
+type arpDatagram struct {
+ htype uint16 // Hardware Type
+ ptype uint16 // Protocol Type
+ hlen uint8 // Hardware address Length
+ plen uint8 // Protocol address length
+ oper uint16 // Operation 1->request, 2->response
+ sha []byte // Sender hardware address, length from Hlen
+ spa []byte // Sender protocol address, length from Plen
+ tha []byte // Target hardware address, length from Hlen
+ tpa []byte // Target protocol address, length from Plen
+}
+
+func newArpRequest(
+ srcMac net.HardwareAddr,
+ srcIP net.IP,
+ dstMac net.HardwareAddr,
+ dstIP net.IP) arpDatagram {
+ return arpDatagram{
+ htype: uint16(1),
+ ptype: uint16(0x0800),
+ hlen: uint8(6),
+ plen: uint8(4),
+ oper: uint16(requestOper),
+ sha: srcMac,
+ spa: srcIP.To4(),
+ tha: dstMac,
+ tpa: dstIP.To4()}
+}
+
+func (datagram arpDatagram) Marshal() []byte {
+ buf := new(bytes.Buffer)
+ binary.Write(buf, binary.BigEndian, datagram.htype)
+ binary.Write(buf, binary.BigEndian, datagram.ptype)
+ binary.Write(buf, binary.BigEndian, datagram.hlen)
+ binary.Write(buf, binary.BigEndian, datagram.plen)
+ binary.Write(buf, binary.BigEndian, datagram.oper)
+ buf.Write(datagram.sha)
+ buf.Write(datagram.spa)
+ buf.Write(datagram.tha)
+ buf.Write(datagram.tpa)
+
+ return buf.Bytes()
+}
+
+func (datagram arpDatagram) MarshalWithEthernetHeader() []byte {
+ // ethernet frame header
+ var ethernetHeader []byte
+ ethernetHeader = append(ethernetHeader, datagram.tha...)
+ ethernetHeader = append(ethernetHeader, datagram.sha...)
+ ethernetHeader = append(ethernetHeader, []byte{0x08, 0x06}...) // arp
+
+ return append(ethernetHeader, datagram.Marshal()...)
+}
+
+func (datagram arpDatagram) SenderIP() net.IP {
+ return net.IP(datagram.spa)
+}
+func (datagram arpDatagram) SenderMac() net.HardwareAddr {
+ return net.HardwareAddr(datagram.sha)
+}
+
+func (datagram arpDatagram) IsResponseOf(request arpDatagram) bool {
+ return datagram.oper == responseOper && bytes.Compare(request.spa, datagram.tpa) == 0
+}
+
+func parseArpDatagram(buffer []byte) arpDatagram {
+ var datagram arpDatagram
+
+ b := bytes.NewBuffer(buffer)
+ binary.Read(b, binary.BigEndian, &datagram.htype)
+ binary.Read(b, binary.BigEndian, &datagram.ptype)
+ binary.Read(b, binary.BigEndian, &datagram.hlen)
+ binary.Read(b, binary.BigEndian, &datagram.plen)
+ binary.Read(b, binary.BigEndian, &datagram.oper)
+
+ haLen := int(datagram.hlen)
+ paLen := int(datagram.plen)
+ datagram.sha = b.Next(haLen)
+ datagram.spa = b.Next(paLen)
+ datagram.tha = b.Next(haLen)
+ datagram.tpa = b.Next(paLen)
+
+ return datagram
+}
--- /dev/null
+// Package arping is a native go library to ping a host per arp datagram, or query a host mac address
+//
+// 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)
+// }
+// }
+//
+//
+// 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")
+// }
+// }
+//
+package arping
+
+import (
+ "errors"
+ "io/ioutil"
+ "log"
+ "net"
+ "os"
+ "time"
+)
+
+var (
+ // ErrTimeout error
+ ErrTimeout = errors.New("timeout")
+
+ verboseLog = log.New(ioutil.Discard, "", 0)
+ timeout = time.Duration(500 * time.Millisecond)
+)
+
+// Ping sends an arp ping to 'dstIP'
+func Ping(dstIP net.IP) (net.HardwareAddr, time.Duration, error) {
+ iface, err := findUsableInterfaceForNetwork(dstIP)
+ if err != nil {
+ return nil, 0, err
+ }
+ return PingOverIface(dstIP, *iface)
+}
+
+// PingOverIfaceByName sends an arp ping over interface name 'ifaceName' to 'dstIP'
+func PingOverIfaceByName(dstIP net.IP, ifaceName string) (net.HardwareAddr, time.Duration, error) {
+ iface, err := net.InterfaceByName(ifaceName)
+ if err != nil {
+ return nil, 0, err
+ }
+ return PingOverIface(dstIP, *iface)
+}
+
+// PingOverIface sends an arp ping over interface 'iface' to 'dstIP'
+func PingOverIface(dstIP net.IP, iface net.Interface) (net.HardwareAddr, time.Duration, error) {
+ srcMac := iface.HardwareAddr
+ srcIP, err := findIPInNetworkFromIface(dstIP, iface)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ broadcastMac := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+ request := newArpRequest(srcMac, srcIP, broadcastMac, dstIP)
+
+ if err := initialize(iface); err != nil {
+ return nil, 0, err
+ }
+ defer deinitialize()
+
+ type PingResult struct {
+ mac net.HardwareAddr
+ duration time.Duration
+ err error
+ }
+ pingResultChan := make(chan PingResult)
+
+ go func() {
+ // send arp request
+ verboseLog.Printf("arping '%s' over interface: '%s' with address: '%s'\n", dstIP, iface.Name, srcIP)
+ if sendTime, err := send(request); err != nil {
+ pingResultChan <- PingResult{nil, 0, err}
+ } else {
+ for {
+ // receive arp response
+ response, receiveTime, err := receive()
+
+ if err != nil {
+ pingResultChan <- PingResult{nil, 0, err}
+ return
+ }
+
+ if response.IsResponseOf(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
+ }
+
+ verboseLog.Printf("ignore received arp: srcIP: '%s', srcMac: '%s'\n",
+ response.SenderIP(), response.SenderMac())
+ }
+ }
+ }()
+
+ select {
+ case pingResult := <-pingResultChan:
+ return pingResult.mac, pingResult.duration, pingResult.err
+ case <-time.After(timeout):
+ return nil, 0, ErrTimeout
+ }
+}
+
+// GratuitousArp sends an gratuitous arp from 'srcIP'
+func GratuitousArp(srcIP net.IP) error {
+ iface, err := findUsableInterfaceForNetwork(srcIP)
+ if err != nil {
+ return err
+ }
+ return GratuitousArpOverIface(srcIP, *iface)
+}
+
+// GratuitousArpOverIfaceByName sends an gratuitous arp over interface name 'ifaceName' from 'srcIP'
+func GratuitousArpOverIfaceByName(srcIP net.IP, ifaceName string) error {
+ iface, err := net.InterfaceByName(ifaceName)
+ if err != nil {
+ return err
+ }
+ return GratuitousArpOverIface(srcIP, *iface)
+}
+
+// GratuitousArpOverIface sends an gratuitous arp over interface 'iface' from 'srcIP'
+func GratuitousArpOverIface(srcIP net.IP, iface net.Interface) error {
+ srcMac := iface.HardwareAddr
+ broadcastMac := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+ request := newArpRequest(srcMac, srcIP, broadcastMac, srcIP)
+
+ if err := initialize(iface); err != nil {
+ return err
+ }
+ defer deinitialize()
+ verboseLog.Printf("gratuitous arp over interface: '%s' with address: '%s'\n", iface.Name, srcIP)
+ _, err := send(request)
+ return err
+}
+
+// EnableVerboseLog enables verbose logging on stdout
+func EnableVerboseLog() {
+ verboseLog = log.New(os.Stdout, "", 0)
+}
+
+// SetTimeout sets ping timeout
+func SetTimeout(t time.Duration) {
+ timeout = t
+}
--- /dev/null
+// +build darwin freebsd openbsd
+
+package arping
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "os"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+var bpf *os.File
+var bpfFd int
+var buflen int
+
+var bpfArpFilter = []syscall.BpfInsn{
+ // make sure this is an arp packet
+ *syscall.BpfStmt(syscall.BPF_LD+syscall.BPF_H+syscall.BPF_ABS, 12),
+ *syscall.BpfJump(syscall.BPF_JMP+syscall.BPF_JEQ+syscall.BPF_K, 0x0806, 0, 1),
+ // if we passed all the tests, ask for the whole packet.
+ *syscall.BpfStmt(syscall.BPF_RET+syscall.BPF_K, -1),
+ // otherwise, drop it.
+ *syscall.BpfStmt(syscall.BPF_RET+syscall.BPF_K, 0),
+}
+
+func initialize(iface net.Interface) (err error) {
+ verboseLog.Println("search available /dev/bpfX")
+ for i := 0; i <= 10; i++ {
+ bpfPath := fmt.Sprintf("/dev/bpf%d", i)
+ bpf, err = os.OpenFile(bpfPath, os.O_RDWR, 0666)
+ if err != nil {
+ verboseLog.Printf(" open failed: %s - %s\n", bpfPath, err.Error())
+ } else {
+ verboseLog.Printf(" open success: %s\n", bpfPath)
+ break
+ }
+ }
+ bpfFd = int(bpf.Fd())
+ if bpfFd == -1 {
+ return errors.New("unable to open /dev/bpfX")
+ }
+
+ if err := syscall.SetBpfInterface(bpfFd, iface.Name); err != nil {
+ return err
+ }
+
+ if err := syscall.SetBpfImmediate(bpfFd, 1); err != nil {
+ return err
+ }
+
+ buflen, err = syscall.BpfBuflen(bpfFd)
+ if err != nil {
+ return err
+ }
+
+ if err := syscall.SetBpf(bpfFd, bpfArpFilter); err != nil {
+ return err
+ }
+
+ if err := syscall.FlushBpf(bpfFd); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func send(request arpDatagram) (time.Time, error) {
+ _, err := syscall.Write(bpfFd, request.MarshalWithEthernetHeader())
+ return time.Now(), err
+}
+
+func receive() (arpDatagram, time.Time, error) {
+ buffer := make([]byte, buflen)
+ n, err := syscall.Read(bpfFd, buffer)
+ if err != nil {
+ return arpDatagram{}, time.Now(), err
+ }
+
+ //
+ // FreeBSD uses a different bpf header (bh_tstamp differ in it's size)
+ // https://www.freebsd.org/cgi/man.cgi?bpf(4)#BPF_HEADER
+ //
+ var bpfHdrLength int
+ if runtime.GOOS == "freebsd" {
+ bpfHdrLength = 26
+ } else {
+ bpfHdrLength = 18
+ }
+
+ // skip bpf header + 14 bytes ethernet header
+ var hdrLength = bpfHdrLength + 14
+
+ return parseArpDatagram(buffer[hdrLength:n]), time.Now(), nil
+}
+
+func deinitialize() error {
+ return bpf.Close()
+}
--- /dev/null
+package arping
+
+import (
+ "net"
+ "syscall"
+ "time"
+)
+
+var sock int
+var toSockaddr syscall.SockaddrLinklayer
+
+func initialize(iface net.Interface) error {
+ toSockaddr = syscall.SockaddrLinklayer{Ifindex: iface.Index}
+
+ // 1544 = htons(ETH_P_ARP)
+ const proto = 1544
+ var err error
+ sock, err = syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, proto)
+ return err
+}
+
+func send(request arpDatagram) (time.Time, error) {
+ return time.Now(), syscall.Sendto(sock, request.MarshalWithEthernetHeader(), 0, &toSockaddr)
+}
+
+func receive() (arpDatagram, time.Time, error) {
+ buffer := make([]byte, 128)
+ n, _, err := syscall.Recvfrom(sock, buffer, 0)
+ if err != nil {
+ return arpDatagram{}, time.Now(), err
+ }
+ // skip 14 bytes ethernet header
+ return parseArpDatagram(buffer[14:n]), time.Now(), nil
+}
+
+func deinitialize() error {
+ return syscall.Close(sock)
+}
--- /dev/null
+// windows currently not supported.
+// dummy implementation to prevent compilation errors under windows
+
+package arping
+
+import (
+ "errors"
+ "net"
+ "time"
+)
+
+var errWindowsNotSupported = errors.New("arping under windows not supported")
+
+func initialize(iface net.Interface) error {
+ return errWindowsNotSupported
+}
+
+func send(request arpDatagram) (time.Time, error) {
+ return time.Now(), errWindowsNotSupported
+}
+
+func receive() (arpDatagram, time.Time, error) {
+ return arpDatagram{}, time.Now(), errWindowsNotSupported
+}
+
+func deinitialize() error {
+ return errWindowsNotSupported
+}
--- /dev/null
+package arping
+
+import (
+ "errors"
+ "fmt"
+ "net"
+)
+
+func findIPInNetworkFromIface(dstIP net.IP, iface net.Interface) (net.IP, error) {
+ addrs, err := iface.Addrs()
+
+ if err != nil {
+ return nil, err
+ }
+
+ for _, a := range addrs {
+ if ipnet, ok := a.(*net.IPNet); ok {
+ if ipnet.Contains(dstIP) {
+ return ipnet.IP, nil
+ }
+ }
+ }
+ return nil, fmt.Errorf("iface: '%s' can't reach ip: '%s'", iface.Name, dstIP)
+}
+
+func findUsableInterfaceForNetwork(dstIP net.IP) (*net.Interface, error) {
+ ifaces, err := net.Interfaces()
+
+ if err != nil {
+ return nil, err
+ }
+
+ isDown := func(iface net.Interface) bool {
+ return iface.Flags&1 == 0
+ }
+
+ hasAddressInNetwork := func(iface net.Interface) bool {
+ if _, err := findIPInNetworkFromIface(dstIP, iface); err != nil {
+ return false
+ }
+ return true
+ }
+
+ verboseLog.Println("search usable interface")
+ logIfaceResult := func(msg string, iface net.Interface) {
+ verboseLog.Printf("%10s: %6s %18s %s", msg, iface.Name, iface.HardwareAddr, iface.Flags)
+ }
+
+ for _, iface := range ifaces {
+ if isDown(iface) {
+ logIfaceResult("DOWN", iface)
+ continue
+ }
+
+ if !hasAddressInNetwork(iface) {
+ logIfaceResult("OTHER NET", iface)
+ continue
+ }
+
+ logIfaceResult("USABLE", iface)
+ return &iface, nil
+ }
+ return nil, errors.New("no usable interface found")
+}