121 lines
2.4 KiB
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)
|
|
}
|
|
}
|
|
}
|