initial: ZeroTier-like P2P mesh VPN server with multi-tenant Web UI
This commit is contained in:
120
internal/vl2/arp.go
Normal file
120
internal/vl2/arp.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user