add ARP; add tap internal calls; more lneto2 additions

This commit is contained in:
soypat
2025-02-17 23:49:37 -03:00
parent 523f609761
commit 6e9aafc1e0
9 changed files with 880 additions and 41 deletions
+25
View File
@@ -0,0 +1,25 @@
package arp
import "errors"
//go:generate stringer -type=Operation -linecomment -output stringers.go .
const (
sizeHeader = 8
sizeHeaderv4 = sizeHeader + 6*2 + 4*2
sizeHeaderv6 = sizeHeader + 6*2 + 16*2
)
var (
errARPBufferFull = errors.New("ARP client need handling:too many ops pending")
errShortARP = errors.New("packet too short to be ARP")
errARPUnsupported = errors.New("ARP not supprortedf")
)
// Operation represents the type of ARP packet, either request or reply/response.
type Operation uint8
const (
OpRequest Operation = 1 // request
OpReply Operation = 2 // reply
)
+141
View File
@@ -0,0 +1,141 @@
package arp
import (
"encoding/binary"
"errors"
"github.com/soypat/lneto/lneto2"
)
// NewARPFrame returns a ARPFrame with data set to buf.
// An error is returned if the buffer size is smaller than 28 (IPv4 min size).
// Users should still call [ARPFrame.ValidateSize] before working
// with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeaderv4 {
return Frame{buf: nil}, errors.New("ARP packet too short")
}
return Frame{buf: buf}, nil
}
// Frame encapsulates the raw data of an ARP packet
// and provides methods for manipulating, validating and
// retrieving fields and payload data. See [RFC826].
//
// [RFC826]: https://tools.ietf.org/html/rfc826
type Frame struct {
buf []byte
}
// RawData returns the underlying slice with which the frame was created.
func (afrm Frame) RawData() []byte { return afrm.buf }
// HardwareType specifies the network link protocol type. Example: Ethernet is 1.
func (afrm Frame) Hardware() (Type uint16, length uint8) {
Type = binary.BigEndian.Uint16(afrm.buf[0:2])
return Type, afrm.hwlen()
}
func (afrm Frame) hwlen() uint8 {
return afrm.buf[4]
}
// SetHardware sets the networl link protocol type. See [Frame.SetHardware].
func (afrm Frame) SetHardware(Type uint16, length uint8) {
binary.BigEndian.PutUint16(afrm.buf[0:2], Type)
afrm.buf[4] = length
}
// Protocol returns the internet protocol type and length. See [lneto2.EtherType].
func (afrm Frame) Protocol() (Type lneto2.EtherType, length uint8) {
Type = lneto2.EtherType(binary.BigEndian.Uint16(afrm.buf[2:4]))
return Type, afrm.protolen()
}
func (afrm Frame) protolen() uint8 { return afrm.buf[5] }
// SetProtocol sets the protocol type and length fields of the ARP frame. See [Frame.Protocol] and [lneto2.EtherType].
func (afrm Frame) SetProtocol(Type lneto2.EtherType, length uint8) {
binary.BigEndian.PutUint16(afrm.buf[2:4], uint16(Type))
afrm.buf[5] = length
}
// Operation returns the ARP header operation field. See [Operation].
func (afrm Frame) Operation() Operation { return Operation(afrm.buf[6]) }
// SetOperation sets the ARP header operation field. See [Operation].
func (afrm Frame) SetOperation(b Operation) { afrm.buf[6] = uint8(b) }
// Sender returns the hardware (MAC) and protocol addresses of sender of ARP packet.
// In an ARP request MAC address is used to indicate
// the address of the host sending the request. In an ARP reply MAC address is
// used to indicate the address of the host that the request was looking for.
func (afrm Frame) Sender() (hardwareAddr []byte, proto []byte) {
_, hlen := afrm.Hardware()
_, ilen := afrm.Protocol()
return afrm.buf[8 : 8+hlen], afrm.buf[8+hlen : 8+hlen+ilen]
}
// Target returns the hardware (MAC) and protocol addresses of target of ARP packet.
// In an ARP request MAC target is ignored. In ARP reply MAC is used to indicate the address of host that originated request.
func (afrm Frame) Target() (hardwareAddr []byte, proto []byte) {
_, hlen := afrm.Hardware()
_, ilen := afrm.Protocol()
toff := 8 + hlen + ilen
return afrm.buf[toff : toff+hlen], afrm.buf[toff+hlen : toff+hlen+ilen]
}
// Sender4 returns the IPv4 sender addresses. See [Frame.Sender].
func (afrm Frame) Sender4() (hardwareAddr *[6]byte, proto *[4]byte) {
return (*[6]byte)(afrm.buf[8:14]), (*[4]byte)(afrm.buf[14:18])
}
// Target4 returns the IPv4 target addresses. See [Frame.Sender].
func (afrm Frame) Target4() (hardwareAddr *[6]byte, proto *[4]byte) {
return (*[6]byte)(afrm.buf[18:24]), (*[4]byte)(afrm.buf[24:28])
}
// Sender6 returns the IPv6 sender addresses. See [Frame.Sender].
func (afrm Frame) Sender16() (hardwareAddr *[6]byte, proto *[16]byte) {
return (*[6]byte)(afrm.buf[8:14]), (*[16]byte)(afrm.buf[14:30])
}
// Target6 returns the IPv6 target addresses. See [Frame.Sender].
func (afrm Frame) Target16() (hardwareAddr *[6]byte, proto *[16]byte) {
return (*[6]byte)(afrm.buf[30:36]), (*[16]byte)(afrm.buf[36:52])
}
// ClearHeader zeros out the fixed(non-variable) header contents.
func (afrm Frame) ClearHeader() {
for i := range afrm.buf[:8] {
afrm.buf[i] = 0
}
}
func (afrm Frame) Clip() Frame {
return Frame{buf: afrm.buf[:sizeHeader+2*int(afrm.hwlen())+2*int(afrm.protolen())]}
}
func (afrm Frame) SwapTargetSender() {
hwTarget, protoTarget := afrm.Target()
hwSender, protoSender := afrm.Sender()
for i := range hwTarget {
hwTarget[i], hwSender[i] = hwSender[i], hwTarget[i]
}
for i := range protoTarget {
protoTarget[i], protoSender[i] = protoSender[i], protoTarget[i]
}
}
// Validation API
//
// ValidateSize checks the frame's size fields and compares with the actual buffer
// the frame. It returns a non-nil error on finding an inconsistency.
func (afrm Frame) ValidateSize(v *lneto2.Validator) {
_, hlen := afrm.Hardware()
_, ilen := afrm.Protocol()
minLen := 8 + 2*(hlen+ilen)
if len(afrm.buf) < int(minLen) {
v.AddError(errShortARP)
}
}
+170
View File
@@ -0,0 +1,170 @@
package arp
import (
"bytes"
"errors"
"github.com/soypat/lneto/lneto2"
)
type Handler struct {
ourHWAddr []byte
ourProtoAddr []byte
htype uint16
protoType lneto2.EtherType
pending [][sizeHeaderv6]byte
queries []queryResult
}
type HandlerConfig struct {
HardwareAddr []byte
ProtocolAddr []byte
MaxQueries int
MaxPending int
HardwareType uint16
ProtocolType lneto2.EtherType
}
func NewHandler(cfg HandlerConfig) (*Handler, error) {
if len(cfg.HardwareAddr) == 0 || len(cfg.HardwareAddr) > 255 ||
len(cfg.ProtocolAddr) == 0 || len(cfg.ProtocolAddr) > 255 {
return nil, errors.New("invalid Handler address config")
} else if cfg.MaxQueries <= 0 || cfg.MaxPending <= 0 {
return nil, errors.New("invalid Handler query or pending config")
}
h := &Handler{
pending: make([][sizeHeaderv6]byte, 0, cfg.MaxPending),
htype: cfg.HardwareType,
protoType: cfg.ProtocolType,
ourHWAddr: cfg.HardwareAddr,
ourProtoAddr: cfg.ProtocolAddr,
queries: make([]queryResult, 0, cfg.MaxQueries),
}
return h, nil
}
type queryResult struct {
protoaddr []byte
hwaddr []byte
querysent bool
}
// ResetState drops pending queries and incoming requests.
func (c *Handler) ResetState() {
c.pending = c.pending[:0]
c.queries = c.queries[:0]
}
func (c *Handler) expectSize() int {
return sizeHeader + 2*len(c.ourHWAddr) + 2*len(c.ourProtoAddr)
}
func (c *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) {
for i := range c.queries {
if bytes.Equal(protoAddr, c.queries[i].protoaddr) {
if !c.queries[i].querysent {
return nil, errors.New("query not yet sent")
} else if len(c.queries[i].hwaddr) == 0 {
return nil, errors.New("no response yet")
}
return c.queries[i].hwaddr, nil
}
}
return nil, errors.New("query not exist or dropped")
}
func (c *Handler) StartQuery(proto []byte) error {
if len(proto) != len(c.ourProtoAddr) {
return errors.New("bad protocol address length")
} else if len(c.queries) == cap(c.queries) {
return errors.New("too many ongoing queries")
}
c.queries = c.queries[:len(c.queries)+1]
q := &c.queries[len(c.queries)-1]
q.hwaddr = q.hwaddr[:0]
q.querysent = false
q.protoaddr = append(q.protoaddr[:0], proto...)
return nil
}
func (c *Handler) Send(b []byte) (int, error) {
n := c.expectSize()
if len(b) < n {
return 0, errShortARP
}
if len(c.pending) > 0 {
// pop frame.
afrm, _ := NewFrame(c.pending[len(c.pending)-1][:])
c.pending = c.pending[:len(c.pending)-1]
afrm.SetOperation(OpReply)
afrm.SwapTargetSender()
hwsender, _ := afrm.Sender()
copy(hwsender, c.ourHWAddr)
n := copy(b, afrm.Clip().RawData())
return n, nil
}
for i := range c.queries {
if !c.queries[i].querysent {
c.queries[i].querysent = true
afrm, _ := NewFrame(b)
afrm.SetHardware(c.htype, uint8(len(c.ourHWAddr)))
afrm.SetProtocol(c.protoType, uint8(len(c.ourProtoAddr)))
afrm.SetOperation(OpRequest)
hwSender, protoSender := afrm.Sender()
copy(hwSender, c.ourHWAddr)
copy(protoSender, c.ourProtoAddr)
hwTarget, protoTarget := afrm.Target()
copy(protoTarget, c.queries[i].protoaddr)
for j := range hwTarget {
hwTarget[j] = 0
}
return n, nil
}
}
return 0, nil
}
func (c *Handler) Recv(b []byte) error {
if len(c.pending) == cap(c.pending) {
return errARPBufferFull
}
afrm, err := NewFrame(b)
if err != nil {
return err
}
var vld lneto2.Validator
afrm.ValidateSize(&vld)
if vld.HasError() {
return vld.Err()
}
htype, hlen := afrm.Hardware()
if htype != c.htype || int(hlen) != len(c.ourHWAddr) {
return errors.New("bad ARP hardware")
}
protoType, protoLen := afrm.Protocol()
if protoType != c.protoType || int(protoLen) != len(c.ourProtoAddr) {
return errors.New("bad ARP proto")
}
switch afrm.Operation() {
case OpRequest:
_, protoaddr := afrm.Target()
if !bytes.Equal(protoaddr, c.ourProtoAddr) {
return nil // Not for us.
}
c.pending = c.pending[:len(c.pending)+1] // Extend pending buffer.
copy(c.pending[len(c.pending)-1][:], afrm.buf) // Set pending buffer.
case OpReply:
hwaddr, protoaddr := afrm.Sender()
for i := range c.queries {
if len(c.queries[i].hwaddr) == 0 && bytes.Equal(c.queries[i].protoaddr, protoaddr) {
c.queries[i].hwaddr = append(c.queries[i].hwaddr[:0], hwaddr...)
return nil
}
}
default:
return errARPUnsupported
}
return nil
}
+101
View File
@@ -0,0 +1,101 @@
package arp
import (
"bytes"
"log"
"testing"
"github.com/soypat/lneto/lneto2"
)
func TestHandler(t *testing.T) {
c1, err := NewHandler(HandlerConfig{
HardwareAddr: []byte{0xde, 0xad, 0xbe, 0xef, 0x00, 0x00},
ProtocolAddr: []byte{192, 168, 1, 1},
MaxQueries: 1,
MaxPending: 1,
HardwareType: 1,
ProtocolType: lneto2.EtherTypeIPv4,
})
if err != nil {
t.Fatal(err)
}
c2, err := NewHandler(HandlerConfig{
HardwareAddr: []byte{0xc0, 0xff, 0xee, 0xc0, 0xff, 0xee},
ProtocolAddr: []byte{192, 168, 1, 2},
MaxQueries: 1,
MaxPending: 1,
HardwareType: 1,
ProtocolType: lneto2.EtherTypeIPv4,
})
if err != nil {
t.Fatal(err)
}
var buf, discard [64]byte
n, err := c1.Send(buf[:])
if err != nil {
t.Fatal("error on should be nop send:", err)
} else if n > 0 {
t.Fatal("should not send if no query")
}
n, err = c2.Send(buf[:])
if err != nil {
t.Fatal("error on should be nop send:", err)
} else if n > 0 {
t.Fatal("should not send if no query")
}
// Perform ARP exchange.
expectHWAddr := c2.ourHWAddr
queryAddr := c2.ourProtoAddr
err = c1.StartQuery(queryAddr)
if err != nil {
t.Fatal(err)
}
n, err = c1.Send(buf[:]) // Send Request.
if err != nil {
t.Fatal(err)
} else if n == 0 {
t.Fatal("expected send of data after first query")
}
err = c2.Recv(buf[:n]) // Receive request.
if err != nil {
t.Fatal(err)
}
n, err = c2.Send(buf[:]) // Send response.
if err != nil {
t.Fatal(err)
} else if n == 0 {
t.Fatal("got no response to request")
}
n, err = c2.Send(discard[:]) // Double tap check, should send nothing.
if err != nil {
t.Fatal("double tap send error:", err)
} else if n > 0 {
t.Fatal("wanted no data sent after response sent")
}
err = c1.Recv(buf[:]) // Receive response.
if err != nil {
t.Fatal(err)
}
hwaddr, err := c1.QueryResult(queryAddr)
if err != nil {
log.Fatal("expected query result:", err)
} else if !bytes.Equal(hwaddr, expectHWAddr) {
log.Fatalf("expected to get hwaddr %x!=%x", hwaddr, expectHWAddr)
}
n, err = c1.Send(buf[:])
if err != nil {
t.Fatal(err)
} else if n > 0 {
t.Fatal("expected no data")
}
n, err = c2.Send(buf[:])
if err != nil {
t.Fatal(err)
} else if n > 0 {
t.Fatal("expected no data")
}
}