Files
zeromesh/internal/vl2/arp.go

121 lines
2.4 KiB
Go

package vl2
import (
"encoding/binary"
"log/slog"
"net"
"sync"
"time"
)
type ARPEntry struct {
MAC net.HardwareAddr
LastSeen time.Time
}
type ARPProxy struct {
cache map[string]*ARPEntry
mu sync.RWMutex
log *slog.Logger
}
func NewARPProxy(log *slog.Logger) *ARPProxy {
return &ARPProxy{
cache: make(map[string]*ARPEntry),
log: log.With("component", "arp"),
}
}
func (a *ARPProxy) Learn(ip net.IP, mac net.HardwareAddr) {
key := ip.String()
a.mu.Lock()
defer a.mu.Unlock()
a.cache[key] = &ARPEntry{
MAC: mac,
LastSeen: time.Now(),
}
}
func (a *ARPProxy) Lookup(ip net.IP) net.HardwareAddr {
key := ip.String()
a.mu.RLock()
defer a.mu.RUnlock()
entry, ok := a.cache[key]
if !ok {
return nil
}
return entry.MAC
}
func (a *ARPProxy) HandleARP(frame *EthernetFrame) []byte {
if len(frame.Payload) < 28 {
return nil
}
pl := frame.Payload
hrd := binary.BigEndian.Uint16(pl[0:2])
pro := binary.BigEndian.Uint16(pl[2:4])
hln := pl[4]
pln := pl[5]
op := binary.BigEndian.Uint16(pl[6:8])
if hrd != 1 || pro != EtherTypeIPv4 || hln != 6 || pln != 4 {
return nil
}
senderMAC := net.HardwareAddr(pl[8:14])
senderIP := net.IP(pl[14:18])
targetIP := net.IP(pl[24:28])
a.Learn(senderIP, senderMAC)
if op == 1 { // ARP request
if localMAC := a.Lookup(targetIP); localMAC != nil {
reply := make([]byte, 42)
copy(reply[0:6], senderMAC)
copy(reply[6:12], localMAC)
binary.BigEndian.PutUint16(reply[12:14], EtherTypeARP)
reply[14] = 0x00
reply[15] = 0x01
binary.BigEndian.PutUint16(reply[16:18], EtherTypeIPv4)
reply[18] = 6
reply[19] = 4
binary.BigEndian.PutUint16(reply[20:22], 2)
copy(reply[22:28], localMAC)
copy(reply[28:32], targetIP)
copy(reply[32:38], senderMAC)
copy(reply[38:42], senderIP)
return reply
}
}
return nil
}
func (a *ARPProxy) PeerFromARP(frame *EthernetFrame) (net.IP, net.HardwareAddr) {
if len(frame.Payload) < 28 {
return nil, nil
}
pl := frame.Payload
hrd := binary.BigEndian.Uint16(pl[0:2])
pro := binary.BigEndian.Uint16(pl[2:4])
if hrd != 1 || pro != EtherTypeIPv4 {
return nil, nil
}
return net.IP(pl[14:18]), net.HardwareAddr(pl[8:14])
}
func (a *ARPProxy) CleanExpired() {
a.mu.Lock()
defer a.mu.Unlock()
cutoff := time.Now().Add(-30 * time.Minute)
for k, v := range a.cache {
if v.LastSeen.Before(cutoff) {
delete(a.cache, k)
}
}
}