Skip to content

Commit

Permalink
add ARP resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Nov 20, 2023
1 parent 5026301 commit b5e2c76
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 23 deletions.
86 changes: 68 additions & 18 deletions stack/portstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import (
)

const (
_MTU = 1500
_MTU = 1500
arpOpWait = 0xffff
)

type PortStackConfig struct {
Expand Down Expand Up @@ -56,8 +57,10 @@ type PortStack struct {
pendingTCPv4 uint32
droppedPackets uint32
processedPackets uint32
pendingARP eth.ARPv4Header
logger *slog.Logger
// pending ARP reply that must be sent out.
pendingARPresponse eth.ARPv4Header
ARPresult eth.ARPv4Header
logger *slog.Logger
}

// Common errors.
Expand Down Expand Up @@ -116,16 +119,28 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) {

if etype == eth.EtherTypeARP {
ahdr := eth.DecodeARPv4Header(payload[eth.SizeEthernetHeader:])
if ps.hasPendingARP() || ahdr.HardwareLength != 6 || ahdr.ProtoLength != 4 || ahdr.HardwareType != 1 || ahdr.AssertEtherType() != eth.EtherTypeIPv4 {
return nil // Ignore ARP replies and unsupported requests.
if ahdr.HardwareLength != 6 || ahdr.ProtoLength != 4 || ahdr.HardwareType != 1 || ahdr.AssertEtherType() != eth.EtherTypeIPv4 {
return errors.New("unsupported ARP") // Ignore ARP unsupported requests.
}
if ahdr.ProtoTarget != ps.IP.As4() {
return nil // Not for us.
switch ahdr.Operation {
case 1: // ARP request.
if ps.hasPendingARP() || ahdr.ProtoTarget != ps.IP.As4() {
return nil // ARP reply pending or not for us.
}
// We need to respond to this ARP request.
ahdr.HardwareTarget = ps.MACAs6()
ahdr.Operation = 2 // Set as reply. This also flags the packet as pending.
ps.pendingARPresponse = ahdr

case 2: // ARP reply.
if ps.ARPresult.Operation != arpOpWait || ahdr.ProtoSender != ps.IP.As4() ||
ahdr.ProtoTarget != ps.ARPresult.ProtoTarget {
return nil // Result already received | not for us | does not correspond to last request.
}
ps.ARPresult = ahdr
default:
return errors.New("unsupported ARP operation")
}
// We need to respond to this ARP request.
ahdr.HardwareTarget = ps.MACAs6()
ahdr.Operation = 2 // Set as reply. This also flags the packet as pending.
ps.pendingARP = ahdr
return nil
}

Expand Down Expand Up @@ -252,19 +267,33 @@ func (ps *PortStack) HandleEth(dst []byte) (int, error) {
switch {
case len(dst) < _MTU:
return 0, io.ErrShortBuffer
case ps.pendingUDPv4 == 0 && ps.pendingTCPv4 == 0:
return 0, nil // No packets to handle

case ps.ARPresult.Operation == 1:
// We have a pending request from user to perform ARP.
ehdr := eth.EthernetHeader{
Destination: broadcastMAC,
Source: ps.MACAs6(),
SizeOrEtherType: uint16(eth.EtherTypeARP),
}
ehdr.Put(dst)
ps.ARPresult.Put(dst[eth.SizeEthernetHeader:])
ps.ARPresult.Operation = arpOpWait // Clear pending ARP to not loop.
return eth.SizeEthernetHeader + eth.SizeARPv4Header, nil

case ps.hasPendingARP():
// We need to respond to an ARP request.
// We need to respond to an ARP request that queries our address.
ehdr := eth.EthernetHeader{
Destination: ps.pendingARP.HardwareSender,
Destination: ps.pendingARPresponse.HardwareSender,
Source: ps.MACAs6(),
SizeOrEtherType: uint16(eth.EtherTypeARP),
}
ehdr.Put(dst)
ps.pendingARP.Put(dst[eth.SizeEthernetHeader:])
ps.pendingARP.Operation = 0 // Clear pending ARP.
ps.pendingARPresponse.Put(dst[eth.SizeEthernetHeader:])
ps.pendingARPresponse.Operation = 0 // Clear pending ARP.
return eth.SizeEthernetHeader + eth.SizeARPv4Header, nil

case ps.pendingUDPv4 == 0 && ps.pendingTCPv4 == 0:
return 0, nil // No ARP or packets to handle.
}

ps.info("HandleEth", slog.Int("dstlen", len(dst)))
Expand Down Expand Up @@ -321,8 +350,29 @@ func (ps *PortStack) HandleEth(dst []byte) (int, error) {
return 0, nil // Nothing handled.
}

var broadcastMAC = [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}

func (ps *PortStack) BeginResolveARPv4(target [4]byte) {
ps.ARPresult = eth.ARPv4Header{
Operation: 1, // Request.
HardwareType: 1, // Ethernet.
ProtoType: uint16(eth.EtherTypeIPv4),
HardwareLength: 6,
ProtoLength: 4,
HardwareSender: ps.MACAs6(),
ProtoSender: ps.IP.As4(),
HardwareTarget: [6]byte{}, // Zeroes, is filled by target.
ProtoTarget: target,
}
}

// ARPv4Result returns the result of the last ARPv4 request.
func (ps *PortStack) ARPv4Result() (eth.ARPv4Header, bool) {
return ps.ARPresult, ps.ARPresult.Operation == 2
}

func (ps *PortStack) hasPendingARP() bool {
return ps.pendingARP.Operation == 2 // 2 means reply.
return ps.pendingARPresponse.Operation == 2 // 2 means reply.
}

// OpenUDP opens a UDP port and sets the handler. If the port is already open
Expand Down
44 changes: 39 additions & 5 deletions stack/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,43 @@ import (

const exchangesToEstablish = 4

func TestARP(t *testing.T) {
const networkSize = 20 // How many distinct IP/MAC addresses on network.
stacks := createPortStacks(t, networkSize)

sender := stacks[0]
target := stacks[1]
const expectedARP = eth.SizeEthernetHeader + eth.SizeARPv4Header
// Send ARP request from sender to target.
sender.BeginResolveARPv4(target.IP.As4())
ex, n := exchangeStacks(t, 1, stacks...)
if n != expectedARP {
t.Errorf("ex[%d] sent=%d want=%d", ex, n, expectedARP)
}
// Target responds to sender.
ex, n = exchangeStacks(t, 1, stacks...)
if n != expectedARP {
t.Errorf("ex[%d] sent=%d want=%d", ex, n, expectedARP)
}

result, ok := sender.ARPv4Result()
if !ok {
t.Fatal("no ARP result")
}
if result.HardwareTarget != target.MACAs6() {
t.Errorf("result.HardwareTarget=%s want=%s", result.HardwareTarget, target.MACAs6())
}
if result.ProtoTarget != target.IP.As4() {
t.Errorf("result.ProtoTarget=%s want=%s", result.ProtoTarget, target.IP.As4())
}

// No more data to exchange.
ex, n = exchangeStacks(t, 1, stacks...)
if n != 0 {
t.Fatalf("ex[%d] sent=%d want=0", ex, n)
}
}

func TestTCPEstablish(t *testing.T) {
client, server := createTCPClientServerPair(t)

Expand Down Expand Up @@ -183,12 +220,9 @@ func exchangeStacks(t *testing.T, maxExchanges int, stacks ...*stack.PortStack)
}
if pipeN[isend] > 0 {
pkt, err := stack.ParseTCPPacket(getPayload(isend))
if err != nil {
t.Errorf("ex[%d] send[%d]: malformed packet: %v", ex, isend, sprintErr(err))
return ex, bytesSent
if err == nil {
t.Logf("ex[%d] send[%d]: %+v", ex, isend, pkt.TCP.Segment(len(pkt.Payload())))
}

t.Logf("ex[%d] send[%d]: %+v", ex, isend, pkt.TCP.Segment(len(pkt.Payload())))
}
bytesSent += pipeN[isend]
sentInTx += pipeN[isend]
Expand Down

0 comments on commit b5e2c76

Please sign in to comment.