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
84 changes: 53 additions & 31 deletions arping.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ var (
// ErrTimeout error
ErrTimeout = errors.New("timeout")

// ErrNoUsableInterface no usable interface found
ErrNoUsableInterface = errors.New("no usable interface found")

verboseLog = log.New(ioutil.Discard, "", 0)
timeout = time.Duration(500 * time.Millisecond)
)
Expand All @@ -91,27 +94,40 @@ func Ping(dstIP net.IP) (net.HardwareAddr, time.Duration, error) {

// PingOverIfaceByName sends an arp ping over interface name 'ifaceName' to 'dstIP'
func PingOverIfaceByName(dstIP net.IP, ifaceName string) (net.HardwareAddr, time.Duration, error) {
if err := validateIP(dstIP); err != nil {
return nil, 0, err
}

iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return nil, 0, err
}
return PingOverIface(dstIP, *iface)
return PingWithOptions(dstIP, WithIfaceByName(ifaceName))
}

// PingOverIface sends an arp ping over interface 'iface' to 'dstIP'
func PingOverIface(dstIP net.IP, iface net.Interface) (net.HardwareAddr, time.Duration, error) {
return PingWithOptions(dstIP, WithIface(iface))
}

// PingWithOptions sends an arp ping to 'dstIP'
func PingWithOptions(dstIP net.IP, opts ...Option) (net.HardwareAddr, time.Duration, error) {
if err := validateIP(dstIP); err != nil {
return nil, 0, err
}

var ops = newOptions()
for _, opt := range opts {
if err := opt.apply(ops); err != nil {
return nil, 0, err
}
}

if ops.iface == nil {
return nil, 0, ErrNoUsableInterface
}
iface := *ops.iface
srcIP := ops.sourceIP
srcMac := iface.HardwareAddr
srcIP, err := findIPInNetworkFromIface(dstIP, iface)
if err != nil {
return nil, 0, err

if len(srcIP) == 0 {
ip, err := findIPInNetworkFromIface(dstIP, iface)
if err != nil {
return nil, 0, err
}
srcIP = ip
}

broadcastMac := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
Expand Down Expand Up @@ -162,44 +178,50 @@ func PingOverIface(dstIP net.IP, iface net.Interface) (net.HardwareAddr, time.Du
select {
case pingResult := <-pingResultChan:
return pingResult.mac, pingResult.duration, pingResult.err
case <-time.After(timeout):
case <-time.After(ops.timeout):
sock.deinitialize()
return nil, 0, ErrTimeout
}
}

// GratuitousArp sends an gratuitous arp from 'srcIP'
func GratuitousArp(srcIP net.IP) error {
if err := validateIP(srcIP); err != nil {
return err
}

iface, err := findUsableInterfaceForNetwork(srcIP)
if err != nil {
return err
}
return GratuitousArpOverIface(srcIP, *iface)
return GratuitousArpWithOptions(WithSourceIP(srcIP))
}

// GratuitousArpOverIfaceByName sends an gratuitous arp over interface name 'ifaceName' from 'srcIP'
func GratuitousArpOverIfaceByName(srcIP net.IP, ifaceName string) error {
if err := validateIP(srcIP); err != nil {
return err
}

iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return err
}
return GratuitousArpOverIface(srcIP, *iface)
return GratuitousArpWithOptions(WithSourceIP(srcIP), WithIfaceByName(ifaceName))
}

// GratuitousArpOverIface sends an gratuitous arp over interface 'iface' from 'srcIP'
func GratuitousArpOverIface(srcIP net.IP, iface net.Interface) error {
return GratuitousArpWithOptions(WithSourceIP(srcIP), WithIface(iface))
}

// GratuitousArpWithOptions sends an gratuitous arp
func GratuitousArpWithOptions(opts ...Option) error {
var ops = newOptions()
for _, opt := range opts {
if err := opt.apply(ops); err != nil {
return err
}
}

srcIP := ops.sourceIP
if err := validateIP(srcIP); err != nil {
return err
}

if ops.iface == nil {
iface, err := findUsableInterfaceForNetwork(srcIP)
if err != nil {
return err
}
ops.iface = iface
}
iface := *ops.iface

srcMac := iface.HardwareAddr
broadcastMac := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
request := newArpRequest(srcMac, srcIP, broadcastMac, srcIP)
Expand Down
3 changes: 1 addition & 2 deletions netutils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package arping

import (
"errors"
"fmt"
"net"
)
Expand Down Expand Up @@ -60,5 +59,5 @@ func findUsableInterfaceForNetwork(dstIP net.IP) (*net.Interface, error) {
logIfaceResult("USABLE", iface)
return &iface, nil
}
return nil, errors.New("no usable interface found")
return nil, ErrNoUsableInterface
}
77 changes: 77 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package arping

import (
"net"
"time"
)

type options struct {
iface *net.Interface
sourceIP net.IP
timeout time.Duration
}

func newOptions() *options {
return &options{timeout: timeout}
}

// Option configures source ip, timeout, Interface for PingWithOptions
type Option interface {
apply(*options) error
}

type ifaceName string

func (n ifaceName) apply(opts *options) error {
iface, err := net.InterfaceByName(string(n))
if err != nil {
return err
}
if err != nil {
return err
}
opts.iface = iface
return nil
}

// WithIfaceByName sends an arp ping over interface name 'name'
func WithIfaceByName(name string) Option {
return ifaceName(name)
}

type netInterface net.Interface

func (n netInterface) apply(opts *options) error {
opts.iface = (*net.Interface)(&n)
return nil
}

// WithIface sends an arp ping over interface 'iface'
func WithIface(iface net.Interface) Option {
return netInterface(iface)
}

type netIP net.IP

func (n netIP) apply(opts *options) error {
opts.sourceIP = net.IP(n)
return nil
}

// WithSourceIP sends an arp with source ip address
func WithSourceIP(srcIP net.IP) Option {
return netIP(srcIP)
}

type duration time.Duration

func (n duration) apply(opts *options) error {
opts.timeout = time.Duration(n)
return nil
}

// WithTimeout sets ping timeout
// not works with GratuitousArpWithOptions
func WithTimeout(timeout time.Duration) Option {
return duration(timeout)
}