pax_global_header00006660000000000000000000000064142372365260014524gustar00rootroot0000000000000052 comment=6706a2966875c189c24afd003ffe801ff69542a1 golang-github-mdlayher-arp-0.0~git20220512.6706a29/000077500000000000000000000000001423723652600211325ustar00rootroot00000000000000golang-github-mdlayher-arp-0.0~git20220512.6706a29/.travis.yml000066400000000000000000000004501423723652600232420ustar00rootroot00000000000000language: go go: - 1.x os: - linux before_install: - go get golang.org/x/lint/golint - go get honnef.co/go/tools/cmd/staticcheck - go get -d ./... script: - go build -tags=gofuzz ./... - go vet ./... - staticcheck ./... - golint -set_exit_status ./... - go test -v -race ./...golang-github-mdlayher-arp-0.0~git20220512.6706a29/LICENSE.md000066400000000000000000000020701423723652600225350ustar00rootroot00000000000000MIT License =========== Copyright (C) 2015 Matt Layher 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. golang-github-mdlayher-arp-0.0~git20220512.6706a29/README.md000066400000000000000000000012061423723652600224100ustar00rootroot00000000000000arp [![Build Status](https://travis-ci.org/mdlayher/arp.svg?branch=master)](https://travis-ci.org/mdlayher/arp) [![GoDoc](https://godoc.org/github.com/mdlayher/arp?status.svg)](https://godoc.org/github.com/mdlayher/arp) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/arp)](https://goreportcard.com/report/github.com/mdlayher/arp) === Package `arp` implements the ARP protocol, as described in RFC 826. MIT Licensed. Portions of this code are taken from the Go standard library. The Go standard library is Copyright (c) 2012 The Go Authors. All rights reserved. The Go license can be found at https://golang.org/LICENSE. golang-github-mdlayher-arp-0.0~git20220512.6706a29/client.go000066400000000000000000000155711423723652600227500ustar00rootroot00000000000000package arp import ( "errors" "net" "net/netip" "time" "github.com/mdlayher/ethernet" "github.com/mdlayher/packet" ) // errNoIPv4Addr is returned when an interface does not have an IPv4 // address. var errNoIPv4Addr = errors.New("no IPv4 address available for interface") // protocolARP is the uint16 EtherType representation of ARP (Address // Resolution Protocol, RFC 826). const protocolARP = 0x0806 // A Client is an ARP client, which can be used to send and receive // ARP packets. type Client struct { ifi *net.Interface ip netip.Addr p net.PacketConn } // Dial creates a new Client using the specified network interface. // Dial retrieves the IPv4 address of the interface and binds a raw socket // to send and receive ARP packets. func Dial(ifi *net.Interface) (*Client, error) { // Open raw socket to send and receive ARP packets using ethernet frames // we build ourselves. p, err := packet.Listen(ifi, packet.Raw, protocolARP, nil) if err != nil { return nil, err } return New(ifi, p) } // New creates a new Client using the specified network interface // and net.PacketConn. This allows the caller to define exactly how they bind to the // net.PacketConn. This is most useful to define what protocol to pass to socket(7). // // In most cases, callers would be better off calling Dial. func New(ifi *net.Interface, p net.PacketConn) (*Client, error) { // Check for usable IPv4 addresses for the Client addrs, err := ifi.Addrs() if err != nil { return nil, err } ipaddrs := make([]netip.Addr, len(addrs)) for i, a := range addrs { ipPrefix, err := netip.ParsePrefix(a.String()) if err != nil { return nil, err } ipaddrs[i] = ipPrefix.Addr() } return newClient(ifi, p, ipaddrs) } // newClient is the internal, generic implementation of newClient. It is used // to allow an arbitrary net.PacketConn to be used in a Client, so testing // is easier to accomplish. func newClient(ifi *net.Interface, p net.PacketConn, addrs []netip.Addr) (*Client, error) { ip, err := firstIPv4Addr(addrs) if err != nil { return nil, err } return &Client{ ifi: ifi, ip: ip, p: p, }, nil } // Close closes the Client's raw socket and stops sending and receiving // ARP packets. func (c *Client) Close() error { return c.p.Close() } // Request sends an ARP request, asking for the hardware address // associated with an IPv4 address. The response, if any, can be read // with the Read method. // // Unlike Resolve, which provides an easier interface for getting the // hardware address, Request allows sending many requests in a row, // retrieving the responses afterwards. func (c *Client) Request(ip netip.Addr) error { if !c.ip.IsValid() { return errNoIPv4Addr } // Create ARP packet for broadcast address to attempt to find the // hardware address of the input IP address arp, err := NewPacket(OperationRequest, c.ifi.HardwareAddr, c.ip, ethernet.Broadcast, ip) if err != nil { return err } return c.WriteTo(arp, ethernet.Broadcast) } // Resolve performs an ARP request, attempting to retrieve the // hardware address of a machine using its IPv4 address. Resolve must not // be used concurrently with Read. If you're using Read (usually in a // loop), you need to use Request instead. Resolve may read more than // one message if it receives messages unrelated to the request. func (c *Client) Resolve(ip netip.Addr) (net.HardwareAddr, error) { err := c.Request(ip) if err != nil { return nil, err } // Loop and wait for replies for { arp, _, err := c.Read() if err != nil { return nil, err } if arp.Operation != OperationReply || arp.SenderIP != ip { continue } return arp.SenderHardwareAddr, nil } } // Read reads a single ARP packet and returns it, together with its // ethernet frame. func (c *Client) Read() (*Packet, *ethernet.Frame, error) { buf := make([]byte, 128) for { n, _, err := c.p.ReadFrom(buf) if err != nil { return nil, nil, err } p, eth, err := parsePacket(buf[:n]) if err != nil { if err == errInvalidARPPacket { continue } return nil, nil, err } return p, eth, nil } } // WriteTo writes a single ARP packet to addr. Note that addr should, // but doesn't have to, match the target hardware address of the ARP // packet. func (c *Client) WriteTo(p *Packet, addr net.HardwareAddr) error { pb, err := p.MarshalBinary() if err != nil { return err } f := ðernet.Frame{ Destination: addr, Source: p.SenderHardwareAddr, EtherType: ethernet.EtherTypeARP, Payload: pb, } fb, err := f.MarshalBinary() if err != nil { return err } _, err = c.p.WriteTo(fb, &packet.Addr{HardwareAddr: addr}) return err } // Reply constructs and sends a reply to an ARP request. On the ARP // layer, it will be addressed to the sender address of the packet. On // the ethernet layer, it will be sent to the actual remote address // from which the request was received. // // For more fine-grained control, use WriteTo to write a custom // response. func (c *Client) Reply(req *Packet, hwAddr net.HardwareAddr, ip netip.Addr) error { p, err := NewPacket(OperationReply, hwAddr, ip, req.SenderHardwareAddr, req.SenderIP) if err != nil { return err } return c.WriteTo(p, req.SenderHardwareAddr) } // Copyright (c) 2012 The Go Authors. All rights reserved. // Source code in this file is based on src/net/interface_linux.go, // from the Go standard library. The Go license can be found here: // https://golang.org/LICENSE. // Documentation taken from net.PacketConn interface. Thanks: // http://golang.org/pkg/net/#PacketConn. // SetDeadline sets the read and write deadlines associated with the // connection. func (c *Client) SetDeadline(t time.Time) error { return c.p.SetDeadline(t) } // SetReadDeadline sets the deadline for future raw socket read calls. // If the deadline is reached, a raw socket read will fail with a timeout // (see type net.Error) instead of blocking. // A zero value for t means a raw socket read will not time out. func (c *Client) SetReadDeadline(t time.Time) error { return c.p.SetReadDeadline(t) } // SetWriteDeadline sets the deadline for future raw socket write calls. // If the deadline is reached, a raw socket write will fail with a timeout // (see type net.Error) instead of blocking. // A zero value for t means a raw socket write will not time out. // Even if a write times out, it may return n > 0, indicating that // some of the data was successfully written. func (c *Client) SetWriteDeadline(t time.Time) error { return c.p.SetWriteDeadline(t) } // HardwareAddr fetches the hardware address for the interface associated // with the connection. func (c Client) HardwareAddr() net.HardwareAddr { return c.ifi.HardwareAddr } // firstIPv4Addr attempts to retrieve the first detected IPv4 address from an // input slice of network addresses. func firstIPv4Addr(addrs []netip.Addr) (netip.Addr, error) { for _, a := range addrs { if a.Is4() { return a, nil } } return netip.Addr{}, errNoIPv4Addr } golang-github-mdlayher-arp-0.0~git20220512.6706a29/client_request_test.go000066400000000000000000000175771423723652600255670ustar00rootroot00000000000000package arp import ( "bytes" "errors" "io" "net" "net/netip" "testing" ) func ipv6loopback() netip.Addr { l := net.IPv6loopback a, ok := netip.AddrFromSlice(l) if !ok { panic("invalid loopback address") } return a } func TestClientRequestNoIPv4Address(t *testing.T) { c := &Client{} _, got := c.Resolve(netip.Addr{}) if want := errNoIPv4Addr; want != got { t.Fatalf("unexpected error for no IPv4 address:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestInvalidSourceHardwareAddr(t *testing.T) { c := &Client{ ifi: &net.Interface{}, ip: netip.IPv4Unspecified(), } _, got := c.Resolve(netip.IPv4Unspecified()) if want := ErrInvalidHardwareAddr; want != got { t.Fatalf("unexpected error for invalid source hardware address:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestIPv6Address(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), } _, got := c.Resolve(ipv6loopback()) if want := ErrInvalidIP; want != got { t.Fatalf("unexpected error for IPv6 address:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestErrWriteTo(t *testing.T) { errWriteTo := errors.New("test error") c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), p: &errWriteToPacketConn{ err: errWriteTo, }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := errWriteTo; want != got { t.Fatalf("unexpected error during WriteTo:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestErrReadFrom(t *testing.T) { errReadFrom := errors.New("test error") c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), p: &errReadFromPacketConn{ err: errReadFrom, }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := errReadFrom; want != got { t.Fatalf("unexpected error during ReadFrom:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestEthernetFrameUnexpectedEOF(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer([]byte{0}), }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := io.ErrUnexpectedEOF; want != got { t.Fatalf("unexpected error while reading ethernet frame:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestEthernetFrameWrongDestinationHardwareAddr(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad}, }, ip: netip.IPv4Unspecified(), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer(append([]byte{ // Ethernet frame with wrong destination hardware address 0, 0, 0, 0, 0, 0, // Wrong destination 0, 0, 0, 0, 0, 0, 0x00, 0x00, }, make([]byte, 46)...)), }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := io.EOF; want != got { t.Fatalf("unexpected error while reading ethernet frame with wrong destination hardware address:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestEthernetFrameWrongEtherType(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer(append([]byte{ // Ethernet frame with non-ARP EtherType 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, // Wrong EtherType }, make([]byte, 46)...)), }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := io.EOF; want != got { t.Fatalf("unexpected error while reading ethernet frame with wrong EtherType:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestARPPacketUnexpectedEOF(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer(append([]byte{ // Ethernet frame 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x06, // ARP packet with misleading hardware address length 0, 0, 0, 0, 255, 255, // Misleading hardware address length }, make([]byte, 40)...)), }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := io.ErrUnexpectedEOF; want != got { t.Fatalf("unexpected error while reading ARP packet:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestARPRequestInsteadOfResponse(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.IPv4Unspecified(), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer(append([]byte{ // Ethernet frame 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x06, // ARP request, not response 0, 1, 0x08, 0x06, 6, 4, 0, 1, // Request, not Response 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, make([]byte, 46)...)), }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := io.EOF; want != got { t.Fatalf("unexpected error while reading ARP response with wrong operation type:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestARPResponseWrongSenderIP(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, }, ip: netip.AddrFrom4([4]byte{192, 168, 1, 1}), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer(append([]byte{ // Ethernet frame 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x06, // ARP Packet not bound for this IP address 0, 1, 0x08, 0x06, 6, 4, 0, 2, 0, 0, 0, 0, 0, 0, 192, 168, 1, 10, // Wrong IP address 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, }, make([]byte, 46)...)), }, } _, got := c.Resolve(netip.IPv4Unspecified()) if want := io.EOF; want != got { t.Fatalf("unexpected error while reading ARP response with wrong sender IP:\n- want: %v\n- got: %v", want, got) } } func TestClientRequestOK(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad}, }, ip: netip.AddrFrom4([4]byte{192, 168, 1, 1}), p: &bufferReadFromPacketConn{ b: bytes.NewBuffer(append([]byte{ // Ethernet frame 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x08, 0x06, // ARP Packet 0, 1, 0x08, 0x06, 6, 4, 0, 2, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 192, 168, 1, 10, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, // mac needn't match ours 192, 168, 1, 2, // ip needn't match ours }, make([]byte, 40)...)), }, } wantHW := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} gotHW, err := c.Resolve(netip.AddrFrom4([4]byte{192, 168, 1, 10})) if err != nil { t.Fatal(err) } if want, got := wantHW, gotHW; !bytes.Equal(want, got) { t.Fatalf("unexpected hardware address for request:\n- want: %v\n- got: %v", want, got) } } // bufferReadFromPacketConn is a net.PacketConn which copies bytes from its // embedded buffer into b when when its ReadFrom method is called. type bufferReadFromPacketConn struct { b *bytes.Buffer noopPacketConn } func (p *bufferReadFromPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, err := p.b.Read(b) return n, nil, err } // errWriteToPacketConn is a net.PacketConn which always returns its embedded // error when its WriteTo method is called. type errWriteToPacketConn struct { err error noopPacketConn } func (p *errWriteToPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { return 0, p.err } // errReadFromPacketConn is a net.PacketConn which always returns its embedded // error when its ReadFrom method is called. type errReadFromPacketConn struct { err error noopPacketConn } func (p *errReadFromPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, p.err } golang-github-mdlayher-arp-0.0~git20220512.6706a29/client_test.go000066400000000000000000000076131423723652600240050ustar00rootroot00000000000000package arp import ( "net" "net/netip" "reflect" "testing" "time" ) func TestClientClose(t *testing.T) { p := &closeCapturePacketConn{} c := &Client{p: p} if err := c.Close(); err != nil { t.Fatal(err) } if !p.closed { t.Fatal("client was not closed") } } func TestClientSetDeadline(t *testing.T) { p := &deadlineCapturePacketConn{} c := &Client{p: p} d := time.Now() if err := c.SetDeadline(d); err != nil { t.Fatal(err) } if want, got := d, p.r; want != got { t.Fatalf("unexpected read deadline: %v != %v", want, got) } if want, got := d, p.w; want != got { t.Fatalf("unexpected write deadline: %v != %v", want, got) } } func TestClientSetReadDeadline(t *testing.T) { p := &deadlineCapturePacketConn{} c := &Client{p: p} d := time.Now() if err := c.SetReadDeadline(d); err != nil { t.Fatal(err) } if want, got := d, p.r; want != got { t.Fatalf("unexpected read deadline: %v != %v", want, got) } if want, got := (time.Time{}), p.w; want != got { t.Fatalf("non-zero write deadline: %v", got) } } func TestClientSetWriteDeadline(t *testing.T) { p := &deadlineCapturePacketConn{} c := &Client{p: p} d := time.Now() if err := c.SetWriteDeadline(d); err != nil { t.Fatal(err) } if want, got := (time.Time{}), p.r; want != got { t.Fatalf("non-zero read deadline: %v", got) } if want, got := d, p.w; want != got { t.Fatalf("unexpected write deadline: %v != %v", want, got) } } func TestClientHardwareAddr(t *testing.T) { c := &Client{ ifi: &net.Interface{ HardwareAddr: net.HardwareAddr{0, 1, 2, 3, 4, 5}, }, } if want, got := c.ifi.HardwareAddr.String(), c.HardwareAddr().String(); want != got { t.Fatalf("unexpected hardware address: %v != %v", want, got) } } func Test_newClient(t *testing.T) { tests := []struct { desc string addrs []netip.Addr c *Client err error }{ { desc: "no network addresses", c: &Client{}, err: errNoIPv4Addr, }, { desc: "OK", addrs: []netip.Addr{ netip.MustParseAddr("192.168.1.1"), }, c: &Client{ ip: netip.MustParseAddr("192.168.1.1"), }, }, } for i, tt := range tests { c, err := newClient(nil, nil, tt.addrs) if err != nil { if want, got := tt.err.Error(), err.Error(); want != got { t.Fatalf("[%02d] test %q, unexpected error: %v != %v", i, tt.desc, want, got) } continue } if want, got := tt.c, c; !reflect.DeepEqual(want, got) { t.Fatalf("[%02d] test %q, unexpected Client: %v != %v", i, tt.desc, want, got) } } } type closeCapturePacketConn struct { closed bool noopPacketConn } func (p *closeCapturePacketConn) Close() error { p.closed = true return nil } // deadlineCapturePacketConn is a net.PacketConn which captures read and // write deadlines. type deadlineCapturePacketConn struct { r time.Time w time.Time noopPacketConn } func (p *deadlineCapturePacketConn) SetDeadline(t time.Time) error { p.r = t p.w = t return nil } func (p *deadlineCapturePacketConn) SetReadDeadline(t time.Time) error { p.r = t return nil } func (p *deadlineCapturePacketConn) SetWriteDeadline(t time.Time) error { p.w = t return nil } // noopPacketConn is a net.PacketConn which simply no-ops any input. It is // embedded in other implementations so they do not have to implement every // single method. type noopPacketConn struct{} func (noopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, nil } func (noopPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { return 0, nil } func (noopPacketConn) Close() error { return nil } func (noopPacketConn) LocalAddr() net.Addr { return nil } func (noopPacketConn) SetDeadline(t time.Time) error { return nil } func (noopPacketConn) SetReadDeadline(t time.Time) error { return nil } func (noopPacketConn) SetWriteDeadline(t time.Time) error { return nil } func (noopPacketConn) HardwareAddr() net.HardwareAddr { return nil } golang-github-mdlayher-arp-0.0~git20220512.6706a29/cmd/000077500000000000000000000000001423723652600216755ustar00rootroot00000000000000golang-github-mdlayher-arp-0.0~git20220512.6706a29/cmd/arpc/000077500000000000000000000000001423723652600226225ustar00rootroot00000000000000golang-github-mdlayher-arp-0.0~git20220512.6706a29/cmd/arpc/README.md000066400000000000000000000007241423723652600241040ustar00rootroot00000000000000arpc ===== Command `arpc` provides a simple ARP client which can be used to retrieve hardware addresses of other machines in a LAN using their IPv4 addresses. Usage ----- ``` $ ./arpc -h Usage of ./arpc: -d=1s: timeout for ARP request -i="eth0": network interface to use for ARP request -ip="": IPv4 address destination for ARP request ``` Request hardware address for IPv4 address: ``` $ ./arpc -i eth0 -ip 192.168.1.1 192.168.1.1 -> 00:12:7f:eb:6b:40 ``` golang-github-mdlayher-arp-0.0~git20220512.6706a29/cmd/arpc/main.go000066400000000000000000000024121423723652600240740ustar00rootroot00000000000000// Command arpc provides a simple ARP client which can be used to retrieve // hardware addresses of other machines in a LAN using their IPv4 address. package main import ( "flag" "fmt" "log" "net" "net/netip" "time" "github.com/mdlayher/arp" ) var ( // durFlag is used to set a timeout for an ARP request durFlag = flag.Duration("d", 1*time.Second, "timeout for ARP request") // ifaceFlag is used to set a network interface for ARP requests ifaceFlag = flag.String("i", "eth0", "network interface to use for ARP request") // ipFlag is used to set an IPv4 address destination for an ARP request ipFlag = flag.String("ip", "", "IPv4 address destination for ARP request") ) func main() { flag.Parse() // Ensure valid network interface ifi, err := net.InterfaceByName(*ifaceFlag) if err != nil { log.Fatal(err) } // Set up ARP client with socket c, err := arp.Dial(ifi) if err != nil { log.Fatal(err) } defer c.Close() // Set request deadline from flag if err := c.SetDeadline(time.Now().Add(*durFlag)); err != nil { log.Fatal(err) } // Request hardware address for IP address ip, err := netip.ParseAddr(*ipFlag) if err != nil { log.Fatal(err) } mac, err := c.Resolve(ip) if err != nil { log.Fatal(err) } fmt.Printf("%s -> %s", ip, mac) } golang-github-mdlayher-arp-0.0~git20220512.6706a29/cmd/proxyarpd/000077500000000000000000000000001423723652600237255ustar00rootroot00000000000000golang-github-mdlayher-arp-0.0~git20220512.6706a29/cmd/proxyarpd/main.go000066400000000000000000000034021423723652600251770ustar00rootroot00000000000000package main import ( "bytes" "flag" "io" "log" "net" "net/netip" "github.com/mdlayher/arp" "github.com/mdlayher/ethernet" ) var ( // ifaceFlag is used to set a network interface for ARP traffic ifaceFlag = flag.String("i", "eth0", "network interface to use for ARP traffic") // ipFlag is used to set an IPv4 address to proxy ARP on behalf of ipFlag = flag.String("ip", "", "IP address for device to proxy ARP on behalf of") ) func main() { flag.Parse() // Ensure valid interface and IPv4 address ifi, err := net.InterfaceByName(*ifaceFlag) if err != nil { log.Fatal(err) } ip, err := netip.ParseAddr(*ipFlag) if err != nil || !ip.Is4() { log.Fatalf("invalid IPv4 address: %q", *ipFlag) } client, err := arp.Dial(ifi) if err != nil { log.Fatalf("couldn't create ARP client: %s", err) } // Handle ARP requests bound for designated IPv4 address, using proxy ARP // to indicate that the address belongs to this machine for { pkt, eth, err := client.Read() if err != nil { if err == io.EOF { log.Println("EOF") break } log.Fatalf("error processing ARP requests: %s", err) } // Ignore ARP replies if pkt.Operation != arp.OperationRequest { continue } // Ignore ARP requests which are not broadcast or bound directly for // this machine if !bytes.Equal(eth.Destination, ethernet.Broadcast) && !bytes.Equal(eth.Destination, ifi.HardwareAddr) { continue } log.Printf("request: who-has %s? tell %s (%s)", pkt.TargetIP, pkt.SenderIP, pkt.SenderHardwareAddr) // Ignore ARP requests which do not indicate the target IP if pkt.TargetIP != ip { continue } log.Printf(" reply: %s is-at %s", ip, ifi.HardwareAddr) if err := client.Reply(pkt, ifi.HardwareAddr, ip); err != nil { log.Fatal(err) } } } golang-github-mdlayher-arp-0.0~git20220512.6706a29/doc.go000066400000000000000000000001211423723652600222200ustar00rootroot00000000000000// Package arp implements the ARP protocol, as described in RFC 826. package arp golang-github-mdlayher-arp-0.0~git20220512.6706a29/fuzz.go000066400000000000000000000003531423723652600224600ustar00rootroot00000000000000//go:build gofuzz // +build gofuzz package arp func Fuzz(data []byte) int { p := new(Packet) if err := p.UnmarshalBinary(data); err != nil { return 0 } if _, err := p.MarshalBinary(); err != nil { panic(err) } return 1 } golang-github-mdlayher-arp-0.0~git20220512.6706a29/go.mod000066400000000000000000000002311423723652600222340ustar00rootroot00000000000000module github.com/mdlayher/arp go 1.12 require ( github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/mdlayher/packet v1.0.0 ) golang-github-mdlayher-arp-0.0~git20220512.6706a29/go.sum000066400000000000000000000044551423723652600222750ustar00rootroot00000000000000github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og= github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= github.com/mdlayher/socket v0.2.1 h1:F2aaOwb53VsBE+ebRS9bLd7yPOfYUMC8lOODdCBDY6w= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang-github-mdlayher-arp-0.0~git20220512.6706a29/packet.go000066400000000000000000000157131423723652600227370ustar00rootroot00000000000000package arp import ( "bytes" "encoding/binary" "errors" "io" "net" "net/netip" "github.com/mdlayher/ethernet" ) var ( // ErrInvalidHardwareAddr is returned when one or more invalid hardware // addresses are passed to NewPacket. ErrInvalidHardwareAddr = errors.New("invalid hardware address") // ErrInvalidIP is returned when one or more invalid IPv4 addresses are // passed to NewPacket. ErrInvalidIP = errors.New("invalid IPv4 address") // errInvalidARPPacket is returned when an ethernet frame does not // indicate that an ARP packet is contained in its payload. errInvalidARPPacket = errors.New("invalid ARP packet") ) //go:generate stringer -output=string.go -type=Operation // An Operation is an ARP operation, such as request or reply. type Operation uint16 // Operation constants which indicate an ARP request or reply. const ( OperationRequest Operation = 1 OperationReply Operation = 2 ) // A Packet is a raw ARP packet, as described in RFC 826. type Packet struct { // HardwareType specifies an IANA-assigned hardware type, as described // in RFC 826. HardwareType uint16 // ProtocolType specifies the internetwork protocol for which the ARP // request is intended. Typically, this is the IPv4 EtherType. ProtocolType uint16 // HardwareAddrLength specifies the length of the sender and target // hardware addresses included in a Packet. HardwareAddrLength uint8 // IPLength specifies the length of the sender and target IPv4 addresses // included in a Packet. IPLength uint8 // Operation specifies the ARP operation being performed, such as request // or reply. Operation Operation // SenderHardwareAddr specifies the hardware address of the sender of this // Packet. SenderHardwareAddr net.HardwareAddr // SenderIP specifies the IPv4 address of the sender of this Packet. SenderIP netip.Addr // TargetHardwareAddr specifies the hardware address of the target of this // Packet. TargetHardwareAddr net.HardwareAddr // TargetIP specifies the IPv4 address of the target of this Packet. TargetIP netip.Addr } // NewPacket creates a new Packet from an input Operation and hardware/IPv4 // address values for both a sender and target. // // If either hardware address is less than 6 bytes in length, or there is a // length mismatch between the two, ErrInvalidHardwareAddr is returned. // // If either IP address is not an IPv4 address, or there is a length mismatch // between the two, ErrInvalidIP is returned. func NewPacket(op Operation, srcHW net.HardwareAddr, srcIP netip.Addr, dstHW net.HardwareAddr, dstIP netip.Addr) (*Packet, error) { // Validate hardware addresses for minimum length, and matching length if len(srcHW) < 6 { return nil, ErrInvalidHardwareAddr } if len(dstHW) < 6 { return nil, ErrInvalidHardwareAddr } if !bytes.Equal(ethernet.Broadcast, dstHW) && len(srcHW) != len(dstHW) { return nil, ErrInvalidHardwareAddr } // Validate IP addresses to ensure they are IPv4 addresses, and // correct length var invalidIP netip.Addr if !srcIP.IsValid() || !srcIP.Is4() { return nil, ErrInvalidIP } if !dstIP.Is4() || dstIP == invalidIP { return nil, ErrInvalidIP } return &Packet{ // There is no Go-native way to detect hardware type of a network // interface, so default to 1 (ethernet 10Mb) for now HardwareType: 1, // Default to EtherType for IPv4 ProtocolType: uint16(ethernet.EtherTypeIPv4), // Populate other fields using input data HardwareAddrLength: uint8(len(srcHW)), IPLength: uint8(4), Operation: op, SenderHardwareAddr: srcHW, SenderIP: srcIP, TargetHardwareAddr: dstHW, TargetIP: dstIP, }, nil } // MarshalBinary allocates a byte slice containing the data from a Packet. // // MarshalBinary never returns an error. func (p *Packet) MarshalBinary() ([]byte, error) { // 2 bytes: hardware type // 2 bytes: protocol type // 1 byte : hardware address length // 1 byte : protocol length // 2 bytes: operation // N bytes: source hardware address // N bytes: source protocol address // N bytes: target hardware address // N bytes: target protocol address // Though an IPv4 address should always 4 bytes, go-fuzz // very quickly created several crasher scenarios which // indicated that these values can lie. b := make([]byte, 2+2+1+1+2+(p.IPLength*2)+(p.HardwareAddrLength*2)) // Marshal fixed length data binary.BigEndian.PutUint16(b[0:2], p.HardwareType) binary.BigEndian.PutUint16(b[2:4], p.ProtocolType) b[4] = p.HardwareAddrLength b[5] = p.IPLength binary.BigEndian.PutUint16(b[6:8], uint16(p.Operation)) // Marshal variable length data at correct offset using lengths // defined in p n := 8 hal := int(p.HardwareAddrLength) pl := int(p.IPLength) copy(b[n:n+hal], p.SenderHardwareAddr) n += hal sender4 := p.SenderIP.As4() copy(b[n:n+pl], sender4[:]) n += pl copy(b[n:n+hal], p.TargetHardwareAddr) n += hal target4 := p.TargetIP.As4() copy(b[n:n+pl], target4[:]) return b, nil } // UnmarshalBinary unmarshals a raw byte slice into a Packet. func (p *Packet) UnmarshalBinary(b []byte) error { // Must have enough room to retrieve hardware address and IP lengths if len(b) < 8 { return io.ErrUnexpectedEOF } // Retrieve fixed length data p.HardwareType = binary.BigEndian.Uint16(b[0:2]) p.ProtocolType = binary.BigEndian.Uint16(b[2:4]) p.HardwareAddrLength = b[4] p.IPLength = b[5] p.Operation = Operation(binary.BigEndian.Uint16(b[6:8])) // Unmarshal variable length data at correct offset using lengths // defined by ml and il // // These variables are meant to improve readability of offset calculations // for the code below n := 8 ml := int(p.HardwareAddrLength) ml2 := ml * 2 il := int(p.IPLength) il2 := il * 2 // Must have enough room to retrieve both hardware address and IP addresses addrl := n + ml2 + il2 if len(b) < addrl { return io.ErrUnexpectedEOF } // Allocate single byte slice to store address information, which // is resliced into fields bb := make([]byte, addrl-n) // Sender hardware address copy(bb[0:ml], b[n:n+ml]) p.SenderHardwareAddr = bb[0:ml] n += ml // Sender IP address copy(bb[ml:ml+il], b[n:n+il]) senderIP, ok := netip.AddrFromSlice(bb[ml : ml+il]) if !ok { return errors.New("Invalid Sender IP address") } p.SenderIP = senderIP n += il // Target hardware address copy(bb[ml+il:ml2+il], b[n:n+ml]) p.TargetHardwareAddr = bb[ml+il : ml2+il] n += ml // Target IP address copy(bb[ml2+il:ml2+il2], b[n:n+il]) targetIP, ok := netip.AddrFromSlice(bb[ml2+il : ml2+il2]) if !ok { return errors.New("Invalid Target IP address") } p.TargetIP = targetIP return nil } func parsePacket(buf []byte) (*Packet, *ethernet.Frame, error) { f := new(ethernet.Frame) if err := f.UnmarshalBinary(buf); err != nil { return nil, nil, err } // Ignore frames which do not have ARP EtherType if f.EtherType != ethernet.EtherTypeARP { return nil, nil, errInvalidARPPacket } p := new(Packet) if err := p.UnmarshalBinary(f.Payload); err != nil { return nil, nil, err } return p, f, nil } golang-github-mdlayher-arp-0.0~git20220512.6706a29/packet_test.go000066400000000000000000000240471423723652600237760ustar00rootroot00000000000000package arp import ( "bytes" "io" "net" "net/netip" "reflect" "testing" "github.com/mdlayher/ethernet" ) func TestNewPacket(t *testing.T) { zeroHW := net.HardwareAddr{0, 0, 0, 0, 0, 0} iboip1 := net.HardwareAddr(bytes.Repeat([]byte{0}, 20)) tests := []struct { desc string op Operation srcHW net.HardwareAddr srcIP netip.Addr dstHW net.HardwareAddr dstIP netip.Addr p *Packet err error }{ { desc: "short source hardware address", srcHW: net.HardwareAddr{0, 0, 0, 0, 0}, err: ErrInvalidHardwareAddr, }, { desc: "short destination hardware address", srcHW: zeroHW, dstHW: net.HardwareAddr{0, 0, 0, 0, 0}, err: ErrInvalidHardwareAddr, }, { desc: "hardware address length mismatch", srcHW: zeroHW, dstHW: net.HardwareAddr{0, 0, 0, 0, 0, 0, 0, 0}, err: ErrInvalidHardwareAddr, }, { desc: "IPv6 source IP address", srcHW: zeroHW, dstHW: zeroHW, srcIP: netip.IPv6Unspecified(), err: ErrInvalidIP, }, { desc: "IPv6 destination IP address", srcHW: zeroHW, dstHW: zeroHW, srcIP: netip.IPv4Unspecified(), dstIP: netip.IPv6Unspecified(), err: ErrInvalidIP, }, { desc: "Gratuitous ARP request, IPoIB hardware addresses", op: OperationRequest, srcHW: iboip1, dstHW: ethernet.Broadcast, srcIP: netip.IPv4Unspecified(), dstIP: netip.IPv4Unspecified(), p: &Packet{ HardwareType: 1, ProtocolType: uint16(ethernet.EtherTypeIPv4), HardwareAddrLength: 20, IPLength: 4, Operation: OperationRequest, SenderHardwareAddr: iboip1, SenderIP: netip.IPv4Unspecified(), TargetHardwareAddr: ethernet.Broadcast, TargetIP: netip.IPv4Unspecified(), }, }, { desc: "OK", op: OperationRequest, srcHW: zeroHW, dstHW: zeroHW, srcIP: netip.IPv4Unspecified(), dstIP: netip.IPv4Unspecified(), p: &Packet{ HardwareType: 1, ProtocolType: uint16(ethernet.EtherTypeIPv4), HardwareAddrLength: 6, IPLength: 4, Operation: OperationRequest, SenderHardwareAddr: zeroHW, SenderIP: netip.IPv4Unspecified(), TargetHardwareAddr: zeroHW, TargetIP: netip.IPv4Unspecified(), }, }, } for i, tt := range tests { p, err := NewPacket(tt.op, tt.srcHW, tt.srcIP, tt.dstHW, tt.dstIP) if err != nil { if want, got := tt.err, err; want != got { t.Fatalf("[%02d] test %q, unexpected error: %v != %v", i, tt.desc, want, got) } continue } if want, got := tt.p, p; !reflect.DeepEqual(want, got) { t.Fatalf("[%02d] test %q, unexpected Packet:\n- want: %v\n- got: %v", i, tt.desc, want, got) } } } func TestPacketMarshalBinary(t *testing.T) { zeroHW := net.HardwareAddr{0, 0, 0, 0, 0, 0} ip1 := netip.MustParseAddr("192.168.1.10") ip2 := netip.MustParseAddr("192.168.1.1") iboip1 := net.HardwareAddr(bytes.Repeat([]byte{0}, 20)) iboip2 := net.HardwareAddr(bytes.Repeat([]byte{1}, 20)) tests := []struct { desc string p *Packet b []byte }{ { desc: "ARP request to ethernet broadcast, 6 byte hardware addresses", p: &Packet{ HardwareType: 1, ProtocolType: uint16(ethernet.EtherTypeIPv4), HardwareAddrLength: 6, IPLength: 4, Operation: OperationRequest, SenderHardwareAddr: zeroHW, SenderIP: ip1, TargetHardwareAddr: ethernet.Broadcast, TargetIP: ip2, }, b: []byte{ 0, 1, 8, 0, 6, 4, 0, 1, 0, 0, 0, 0, 0, 0, 192, 168, 1, 10, 255, 255, 255, 255, 255, 255, 192, 168, 1, 1, }, }, { desc: "ARP reply over infiniband, 20 byte hardware addresses", p: &Packet{ HardwareType: 32, ProtocolType: uint16(ethernet.EtherTypeIPv4), HardwareAddrLength: 20, IPLength: 4, Operation: OperationReply, SenderHardwareAddr: iboip1, SenderIP: ip1, TargetHardwareAddr: iboip2, TargetIP: ip2, }, b: []byte{ 0, 32, 8, 0, 20, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 192, 168, 1, 1, }, }, } for i, tt := range tests { b, err := tt.p.MarshalBinary() if err != nil { t.Fatal(err) } if want, got := tt.b, b; !bytes.Equal(want, got) { t.Fatalf("[%02d] test %q, unexpected Packet bytes:\n- want: %v\n- got: %v", i, tt.desc, want, got) } } } func TestPacketUnmarshalBinary(t *testing.T) { zeroHW := net.HardwareAddr{0, 0, 0, 0, 0, 0} ip1 := netip.MustParseAddr("192.168.1.10") ip2 := netip.MustParseAddr("192.168.1.1") iboip1 := net.HardwareAddr(bytes.Repeat([]byte{0}, 20)) iboip2 := net.HardwareAddr(bytes.Repeat([]byte{1}, 20)) tests := []struct { desc string p *Packet b []byte err error }{ { desc: "short buffer", b: bytes.Repeat([]byte{0}, 7), err: io.ErrUnexpectedEOF, }, { desc: "short buffer, too short for hardware addresses", b: []byte{ 0, 1, 8, 0, 255, 4, 0, 1, }, err: io.ErrUnexpectedEOF, }, { desc: "short buffer, too short for IP addresses", b: []byte{ 0, 1, 8, 0, 6, 255, 0, 1, }, err: io.ErrUnexpectedEOF, }, { desc: "ARP request to ethernet broadcast, 6 byte hardware addresses", b: []byte{ 0, 1, 8, 0, 6, 4, 0, 1, 0, 0, 0, 0, 0, 0, 192, 168, 1, 10, 255, 255, 255, 255, 255, 255, 192, 168, 1, 1, }, p: &Packet{ HardwareType: 1, ProtocolType: uint16(ethernet.EtherTypeIPv4), HardwareAddrLength: 6, IPLength: 4, Operation: OperationRequest, SenderHardwareAddr: zeroHW, SenderIP: ip1, TargetHardwareAddr: ethernet.Broadcast, TargetIP: ip2, }, }, { desc: "ARP reply over infiniband, 20 byte hardware addresses", b: []byte{ 0, 32, 8, 0, 20, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 192, 168, 1, 1, }, p: &Packet{ HardwareType: 32, ProtocolType: uint16(ethernet.EtherTypeIPv4), HardwareAddrLength: 20, IPLength: 4, Operation: OperationReply, SenderHardwareAddr: iboip1, SenderIP: ip1, TargetHardwareAddr: iboip2, TargetIP: ip2, }, }, } for i, tt := range tests { p := new(Packet) if err := p.UnmarshalBinary(tt.b); err != nil { if want, got := tt.err, err; want != got { t.Fatalf("[%02d] test %q, unexpected error: %v != %v", i, tt.desc, want, got) } continue } if want, got := tt.p, p; !reflect.DeepEqual(want, got) { t.Fatalf("[%02d] test %q, unexpected Packet bytes:\n- want: %v\n- got: %v", i, tt.desc, want, got) } } } func Test_parsePacket(t *testing.T) { tests := []struct { desc string buf []byte p *Packet err error }{ { desc: "invalid ethernet frame", err: io.ErrUnexpectedEOF, }, { desc: "non-ARP EtherType", // Approximation of 14 byte ethernet frame header and // 42 byte blank payload (EtherType 0x0000) buf: make([]byte, 56), err: errInvalidARPPacket, }, { desc: "invalid ARP packet", buf: append([]byte{ // Ethernet frame 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x06, // ARP packet with misleading hardware address length 0, 0, 0, 0, 255, 255, // Misleading hardware address length }, make([]byte, 40)...), err: io.ErrUnexpectedEOF, }, { desc: "OK", buf: append([]byte{ // Ethernet frame 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x08, 0x06, // ARP Packet 0, 1, 0x08, 0x06, 6, 4, 0, 2, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 192, 168, 1, 10, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 192, 168, 1, 1, }, make([]byte, 40)...), p: &Packet{ HardwareType: 1, ProtocolType: 2054, HardwareAddrLength: 6, IPLength: 4, Operation: OperationReply, SenderHardwareAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, SenderIP: netip.MustParseAddr("192.168.1.10"), TargetHardwareAddr: net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad}, TargetIP: netip.MustParseAddr("192.168.1.1"), }, }, } for i, tt := range tests { p, _, err := parsePacket(tt.buf) if err != nil { if want, got := tt.err, err; want != got { t.Fatalf("[%02d] test %q, unexpected error: %v != %v", i, tt.desc, want, got) } continue } if want, got := tt.p, p; !reflect.DeepEqual(want, got) { t.Fatalf("[%02d] test %q, unexpected Packet:\n- want: %v\n- got: %v", i, tt.desc, want, got) } } } // Benchmarks for Packet.MarshalBinary func BenchmarkPacketMarshalBinary(b *testing.B) { p, err := NewPacket( OperationRequest, net.HardwareAddr{0xad, 0xbe, 0xef, 0xde, 0xad, 0xde}, netip.MustParseAddr("192.168.1.10"), net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad}, netip.MustParseAddr("192.168.1.1"), ) if err != nil { b.Fatal(err) } benchmarkPacketMarshalBinary(b, p) } func benchmarkPacketMarshalBinary(b *testing.B, p *Packet) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { if _, err := p.MarshalBinary(); err != nil { b.Fatal(err) } } } // Benchmarks for Packet.UnmarshalBinary func BenchmarkPacketUnmarshalBinary(b *testing.B) { p, err := NewPacket( OperationRequest, net.HardwareAddr{0xad, 0xbe, 0xef, 0xde, 0xad, 0xde}, netip.MustParseAddr("192.168.1.10"), net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad}, netip.MustParseAddr("192.168.1.1"), ) if err != nil { b.Fatal(err) } benchmarkPacketUnmarshalBinary(b, p) } func benchmarkPacketUnmarshalBinary(b *testing.B, p *Packet) { pb, err := p.MarshalBinary() if err != nil { b.Fatal(err) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { if err := p.UnmarshalBinary(pb); err != nil { b.Fatal(err) } } } golang-github-mdlayher-arp-0.0~git20220512.6706a29/string.go000066400000000000000000000006731423723652600227750ustar00rootroot00000000000000// Code generated by "stringer -output=string.go -type=Operation"; DO NOT EDIT. package arp import "strconv" const _Operation_name = "OperationRequestOperationReply" var _Operation_index = [...]uint8{0, 16, 30} func (i Operation) String() string { i -= 1 if i >= Operation(len(_Operation_index)-1) { return "Operation(" + strconv.FormatInt(int64(i+1), 10) + ")" } return _Operation_name[_Operation_index[i]:_Operation_index[i+1]] }