initial: ZeroTier-like P2P mesh VPN server with multi-tenant Web UI

This commit is contained in:
xieyao
2026-06-17 03:50:23 +08:00
commit ee5f036325
74 changed files with 9517 additions and 0 deletions

120
internal/vl2/arp.go Normal file
View 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)
}
}
}

92
internal/vl2/frame.go Normal file
View File

@@ -0,0 +1,92 @@
package vl2
import (
"encoding/binary"
"fmt"
"net"
"sync"
)
const (
MinFrameSize = 14
MaxFrameSize = 65535
EtherTypeIPv4 = 0x0800
EtherTypeARP = 0x0806
EtherTypeIPv6 = 0x86DD
)
type EthernetFrame struct {
DstMAC net.HardwareAddr
SrcMAC net.HardwareAddr
EtherType uint16
Payload []byte
}
func ParseEthernetFrame(data []byte) (*EthernetFrame, error) {
if len(data) < MinFrameSize {
return nil, fmt.Errorf("frame too short: %d < %d", len(data), MinFrameSize)
}
return &EthernetFrame{
DstMAC: net.HardwareAddr(data[0:6]),
SrcMAC: net.HardwareAddr(data[6:12]),
EtherType: binary.BigEndian.Uint16(data[12:14]),
Payload: data[14:],
}, nil
}
func (f *EthernetFrame) Encode() []byte {
buf := make([]byte, MinFrameSize+len(f.Payload))
copy(buf[0:6], f.DstMAC)
copy(buf[6:12], f.SrcMAC)
binary.BigEndian.PutUint16(buf[12:14], f.EtherType)
copy(buf[14:], f.Payload)
return buf
}
func (f *EthernetFrame) IsBroadcast() bool {
for _, b := range f.DstMAC {
if b != 0xff {
return false
}
}
return true
}
func (f *EthernetFrame) IsMulticast() bool {
return len(f.DstMAC) > 0 && f.DstMAC[0]&0x01 == 0x01 && !f.IsBroadcast()
}
func (f *EthernetFrame) IsARP() bool {
return f.EtherType == EtherTypeARP
}
func (f *EthernetFrame) IsIPv4() bool {
return f.EtherType == EtherTypeIPv4
}
func (f *EthernetFrame) IsIPv6() bool {
return f.EtherType == EtherTypeIPv6
}
type MACKey [6]byte
func MACToKey(mac net.HardwareAddr) MACKey {
var key MACKey
copy(key[:], mac)
return key
}
var frameBufPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, MaxFrameSize)
return &buf
},
}
func GetFrameBuf() *[]byte {
return frameBufPool.Get().(*[]byte)
}
func PutFrameBuf(buf *[]byte) {
frameBufPool.Put(buf)
}

19
internal/vl2/mac.go Normal file
View File

@@ -0,0 +1,19 @@
package vl2
import (
"net"
"zeromesh/internal/identity"
)
// GenerateMAC creates a deterministic MAC address from network ID and node address.
func GenerateMAC(networkID uint32, nodeAddr identity.Address) net.HardwareAddr {
mac := make(net.HardwareAddr, 6)
mac[0] = 0x02 // locally administered, unicast
mac[1] = byte(networkID >> 16)
mac[2] = byte(networkID >> 8)
mac[3] = byte(networkID)
mac[4] = nodeAddr[3]
mac[5] = nodeAddr[4]
return mac
}

38
internal/vl2/network.go Normal file
View File

@@ -0,0 +1,38 @@
package vl2
import (
"log/slog"
"zeromesh/internal/identity"
)
type NetworkConfig struct {
ID uint32
Name string
IPRange string
IP6Range string
MTU int
Multicast bool
}
type Network struct {
Config NetworkConfig
Switch *Switch
ARP *ARPProxy
LocalMAC [6]byte
log *slog.Logger
}
func NewNetwork(config NetworkConfig, nodeAddr identity.Address, sender PeerSender, log *slog.Logger) *Network {
netLog := log.With("network", config.ID, "name", config.Name)
mac := GenerateMAC(config.ID, nodeAddr)
var macArr [6]byte
copy(macArr[:], mac)
return &Network{
Config: config,
Switch: NewSwitch(config.ID, sender, netLog),
ARP: NewARPProxy(netLog),
LocalMAC: macArr,
log: netLog,
}
}

153
internal/vl2/switch.go Normal file
View File

@@ -0,0 +1,153 @@
package vl2
import (
"log/slog"
"net"
"sync"
"time"
"zeromesh/internal/identity"
)
const (
MACTableExpiry = 5 * time.Minute
MACTableMaxSize = 4096
)
type MACEntry struct {
PeerAddr identity.Address
LastSeen time.Time
IsLocal bool
}
type PeerSender interface {
SendToPeer(peerAddr identity.Address, networkID uint32, frame []byte) error
BroadcastToPeers(networkID uint32, frame []byte, excludePeer identity.Address) error
}
type Switch struct {
networkID uint32
macTable map[MACKey]*MACEntry
mu sync.RWMutex
sender PeerSender
log *slog.Logger
}
func NewSwitch(networkID uint32, sender PeerSender, log *slog.Logger) *Switch {
return &Switch{
networkID: networkID,
macTable: make(map[MACKey]*MACEntry),
sender: sender,
log: log.With("component", "switch", "network", networkID),
}
}
func (sw *Switch) HandleLocalFrame(frame []byte) error {
parsed, err := ParseEthernetFrame(frame)
if err != nil {
return err
}
sw.learn(parsed.SrcMAC, identity.Address{}, true)
if parsed.IsBroadcast() || parsed.IsMulticast() {
return sw.sender.BroadcastToPeers(sw.networkID, frame, identity.Address{})
}
sw.mu.RLock()
entry, found := sw.macTable[MACToKey(parsed.DstMAC)]
sw.mu.RUnlock()
if found && !entry.IsLocal {
return sw.sender.SendToPeer(entry.PeerAddr, sw.networkID, frame)
}
if !found {
sw.log.Debug("unknown dst MAC, flooding", "dst", parsed.DstMAC)
return sw.sender.BroadcastToPeers(sw.networkID, frame, identity.Address{})
}
return nil
}
func (sw *Switch) HandleRemoteFrame(peerAddr identity.Address, frame []byte) ([]byte, error) {
parsed, err := ParseEthernetFrame(frame)
if err != nil {
return nil, err
}
sw.learn(parsed.SrcMAC, peerAddr, false)
if parsed.IsBroadcast() || parsed.IsMulticast() {
_ = sw.sender.BroadcastToPeers(sw.networkID, frame, peerAddr)
return frame, nil
}
sw.mu.RLock()
entry, found := sw.macTable[MACToKey(parsed.DstMAC)]
sw.mu.RUnlock()
if found && entry.IsLocal {
return frame, nil
}
if found && !entry.IsLocal {
_ = sw.sender.SendToPeer(entry.PeerAddr, sw.networkID, frame)
return nil, nil
}
_ = sw.sender.BroadcastToPeers(sw.networkID, frame, peerAddr)
return frame, nil
}
func (sw *Switch) learn(mac net.HardwareAddr, peerAddr identity.Address, isLocal bool) {
key := MACToKey(mac)
sw.mu.Lock()
defer sw.mu.Unlock()
if len(sw.macTable) >= MACTableMaxSize {
sw.evictOldest()
}
sw.macTable[key] = &MACEntry{
PeerAddr: peerAddr,
LastSeen: time.Now(),
IsLocal: isLocal,
}
}
func (sw *Switch) evictOldest() {
var oldestKey MACKey
var oldestTime time.Time
first := true
for k, v := range sw.macTable {
if first || v.LastSeen.Before(oldestTime) {
oldestKey = k
oldestTime = v.LastSeen
first = false
}
}
if !first {
delete(sw.macTable, oldestKey)
}
}
func (sw *Switch) CleanExpired() int {
sw.mu.Lock()
defer sw.mu.Unlock()
cutoff := time.Now().Add(-MACTableExpiry)
removed := 0
for k, v := range sw.macTable {
if v.LastSeen.Before(cutoff) && !v.IsLocal {
delete(sw.macTable, k)
removed++
}
}
return removed
}
func (sw *Switch) MACTableSize() int {
sw.mu.RLock()
defer sw.mu.RUnlock()
return len(sw.macTable)
}