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

103
internal/vl1/noise.go Normal file
View File

@@ -0,0 +1,103 @@
package vl1
import (
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/sha3"
)
type NoiseCipher struct {
sendCipher cipher.AEAD
recvCipher cipher.AEAD
sendNonce uint64
recvNonce uint64
}
func NewNoiseCipher(sendKey, recvKey [32]byte) *NoiseCipher {
send, err := chacha20poly1305.New(sendKey[:])
if err != nil {
panic(err)
}
recv, err := chacha20poly1305.New(recvKey[:])
if err != nil {
panic(err)
}
return &NoiseCipher{
sendCipher: send,
recvCipher: recv,
}
}
func DeriveKeysFromPSK(psk string, localPub, remotePub []byte) ([32]byte, [32]byte) {
h := sha3.New256()
h.Write([]byte(psk))
h.Write(localPub)
h.Write(remotePub)
sum := h.Sum(nil)
var sendKey, recvKey [32]byte
copy(sendKey[:], sum[:32])
h.Reset()
h.Write(sum)
h.Write([]byte("reverse"))
rev := h.Sum(nil)
copy(recvKey[:], rev[:32])
return sendKey, recvKey
}
func (nc *NoiseCipher) Encrypt(plaintext []byte) ([]byte, error) {
nonce := make([]byte, 12)
binary.BigEndian.PutUint64(nonce[4:], nc.sendNonce)
nc.sendNonce++
ciphertext := nc.sendCipher.Seal(nil, nonce, plaintext, nil)
return ciphertext, nil
}
func (nc *NoiseCipher) Decrypt(ciphertext []byte) ([]byte, error) {
nonce := make([]byte, 12)
binary.BigEndian.PutUint64(nonce[4:], nc.recvNonce)
nc.recvNonce++
plaintext, err := nc.recvCipher.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("decrypt: %w", err)
}
return plaintext, nil
}
func (nc *NoiseCipher) EncryptTo(buf []byte, plaintext []byte) (int, error) {
nonce := make([]byte, 12)
binary.BigEndian.PutUint64(nonce[4:], nc.sendNonce)
nc.sendNonce++
ciphertext := nc.sendCipher.Seal(buf[:0], nonce, plaintext, nil)
return len(ciphertext), nil
}
func (nc *NoiseCipher) DecryptTo(buf []byte, ciphertext []byte) ([]byte, error) {
nonce := make([]byte, 12)
binary.BigEndian.PutUint64(nonce[4:], nc.recvNonce)
nc.recvNonce++
plaintext, err := nc.recvCipher.Open(buf[:0], nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("decrypt: %w", err)
}
return plaintext, nil
}
func generateSessionKey() [32]byte {
var key [32]byte
if _, err := io.ReadFull(rand.Reader, key[:]); err != nil {
panic(err)
}
return key
}

117
internal/vl1/packet.go Normal file
View File

@@ -0,0 +1,117 @@
package vl1
import (
"encoding/binary"
"fmt"
)
const (
Version = 1
MaxPacketSize = 65535
HeaderSize = 8
MaxFrameSize = 65535
MinFrameSize = 14
PacketTypeHandshake = byte(1)
PacketTypeData = byte(2)
PacketTypeKeepalive = byte(3)
)
type Header struct {
Version byte
Type byte
NetworkID uint32
Length uint16
}
func (h *Header) Encode(buf []byte) {
buf[0] = h.Version
buf[1] = h.Type
binary.BigEndian.PutUint32(buf[2:6], h.NetworkID)
binary.BigEndian.PutUint16(buf[6:8], h.Length)
}
func (h *Header) Decode(buf []byte) error {
if len(buf) < HeaderSize {
return fmt.Errorf("header too short: %d < %d", len(buf), HeaderSize)
}
h.Version = buf[0]
h.Type = buf[1]
h.NetworkID = binary.BigEndian.Uint32(buf[2:6])
h.Length = binary.BigEndian.Uint16(buf[6:8])
return nil
}
type Packet struct {
Header Header
Payload []byte
}
func NewHandshakePacket(payload []byte) Packet {
return Packet{
Header: Header{
Version: Version,
Type: PacketTypeHandshake,
Length: uint16(len(payload)),
},
Payload: payload,
}
}
func NewDataPacket(networkID uint32, payload []byte) Packet {
return Packet{
Header: Header{
Version: Version,
Type: PacketTypeData,
NetworkID: networkID,
Length: uint16(len(payload)),
},
Payload: payload,
}
}
func NewKeepalivePacket() Packet {
return Packet{
Header: Header{
Version: Version,
Type: PacketTypeKeepalive,
Length: 0,
},
}
}
func (p *Packet) Encode() []byte {
total := HeaderSize + len(p.Payload)
buf := make([]byte, total)
p.Header.Length = uint16(len(p.Payload))
p.Header.Encode(buf[:HeaderSize])
copy(buf[HeaderSize:], p.Payload)
return buf
}
func DecodePacket(data []byte) (*Packet, error) {
var p Packet
if err := p.Header.Decode(data); err != nil {
return nil, err
}
payloadLen := int(p.Header.Length)
if HeaderSize+payloadLen > len(data) {
return nil, fmt.Errorf("packet truncated: header claims %d + %d > %d", HeaderSize, payloadLen, len(data))
}
p.Payload = make([]byte, payloadLen)
copy(p.Payload, data[HeaderSize:HeaderSize+payloadLen])
return &p, nil
}
func DecodePacketInto(p *Packet, data []byte) error {
if err := p.Header.Decode(data); err != nil {
return err
}
payloadLen := int(p.Header.Length)
if HeaderSize+payloadLen > len(data) {
return fmt.Errorf("packet truncated")
}
p.Payload = make([]byte, payloadLen)
copy(p.Payload, data[HeaderSize:HeaderSize+payloadLen])
return nil
}

193
internal/vl1/peer.go Normal file
View File

@@ -0,0 +1,193 @@
package vl1
import (
"log/slog"
"net"
"sync"
"time"
"zeromesh/internal/identity"
)
type Peer struct {
Address identity.Address
PublicKey [32]byte
Endpoint *net.UDPAddr
Cipher *NoiseCipher
LastSeen time.Time
LastSend time.Time
Connected bool
mu sync.RWMutex
log *slog.Logger
}
func (p *Peer) Touch() {
p.mu.Lock()
defer p.mu.Unlock()
p.LastSeen = time.Now()
}
func (p *Peer) SetCipher(c *NoiseCipher) {
p.mu.Lock()
defer p.mu.Unlock()
p.Cipher = c
p.Connected = true
}
func (p *Peer) IsConnected() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.Connected
}
func (p *Peer) IsAlive() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return time.Since(p.LastSeen) < 90*time.Second
}
func (p *Peer) Encrypt(plaintext []byte) ([]byte, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return nil, ErrNoCipher
}
return p.Cipher.Encrypt(plaintext)
}
func (p *Peer) Decrypt(ciphertext []byte) ([]byte, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return nil, ErrNoCipher
}
return p.Cipher.Decrypt(ciphertext)
}
func (p *Peer) EncryptTo(buf []byte, plaintext []byte) (int, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return 0, ErrNoCipher
}
return p.Cipher.EncryptTo(buf, plaintext)
}
func (p *Peer) DecryptTo(buf []byte, ciphertext []byte) ([]byte, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return nil, ErrNoCipher
}
return p.Cipher.DecryptTo(buf, ciphertext)
}
func (p *Peer) NeedsKeepalive() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.Connected && time.Since(p.LastSend) > 25*time.Second
}
var ErrNoCipher = errNoCipher()
func errNoCipher() error {
return &noCipherError{}
}
type noCipherError struct{}
func (e *noCipherError) Error() string {
return "no cipher established"
}
type PeerManager struct {
peers map[identity.Address]*Peer
mu sync.RWMutex
log *slog.Logger
}
func NewPeerManager(log *slog.Logger) *PeerManager {
return &PeerManager{
peers: make(map[identity.Address]*Peer),
log: log,
}
}
func (pm *PeerManager) AddPeer(addr identity.Address, pubKey [32]byte, endpoint *net.UDPAddr) *Peer {
pm.mu.Lock()
defer pm.mu.Unlock()
peer := &Peer{
Address: addr,
PublicKey: pubKey,
Endpoint: endpoint,
LastSeen: time.Now(),
LastSend: time.Now(),
log: pm.log.With("peer", addr.String()),
}
pm.peers[addr] = peer
return peer
}
func (pm *PeerManager) GetPeer(addr identity.Address) *Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.peers[addr]
}
func (pm *PeerManager) GetPeerByEndpoint(endpoint *net.UDPAddr) *Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, p := range pm.peers {
if p.Endpoint != nil && p.Endpoint.IP.Equal(endpoint.IP) && p.Endpoint.Port == endpoint.Port {
return p
}
}
return nil
}
func (pm *PeerManager) RemovePeer(addr identity.Address) {
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.peers, addr)
}
func (pm *PeerManager) UpdatePeerEndpoint(addr identity.Address, endpoint *net.UDPAddr) {
pm.mu.Lock()
defer pm.mu.Unlock()
if p, ok := pm.peers[addr]; ok {
p.Endpoint = endpoint
}
}
func (pm *PeerManager) ConnectedPeers() []*Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
var result []*Peer
for _, p := range pm.peers {
if p.IsConnected() {
result = append(result, p)
}
}
return result
}
func (pm *PeerManager) AllPeers() []*Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make([]*Peer, 0, len(pm.peers))
for _, p := range pm.peers {
result = append(result, p)
}
return result
}
func (pm *PeerManager) CleanDead() {
pm.mu.Lock()
defer pm.mu.Unlock()
for addr, p := range pm.peers {
if p.Connected && !p.IsAlive() {
p.log.Warn("peer timed out, removing")
delete(pm.peers, addr)
}
}
}

112
internal/vl1/transport.go Normal file
View File

@@ -0,0 +1,112 @@
package vl1
import (
"fmt"
"log/slog"
"net"
"sync"
"syscall"
"time"
)
type Transport struct {
conn *net.UDPConn
port int
mu sync.RWMutex
closed bool
log *slog.Logger
}
func NewTransport(port int, log *slog.Logger) (*Transport, error) {
addr := &net.UDPAddr{Port: port}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return nil, fmt.Errorf("bind UDP port %d: %w", port, err)
}
actualPort := conn.LocalAddr().(*net.UDPAddr).Port
log.Info("VL1 transport listening", "port", actualPort)
return &Transport{
conn: conn,
port: actualPort,
log: log,
}, nil
}
func (t *Transport) Port() int {
return t.port
}
func (t *Transport) ReadFrom(buf []byte) (int, *net.UDPAddr, error) {
n, addr, err := t.conn.ReadFromUDP(buf)
return n, addr, err
}
func (t *Transport) SendTo(data []byte, addr *net.UDPAddr) error {
t.mu.RLock()
defer t.mu.RUnlock()
if t.closed {
return fmt.Errorf("transport closed")
}
_, err := t.conn.WriteToUDP(data, addr)
return err
}
func (t *Transport) SendPacket(pkt *Packet, addr *net.UDPAddr) error {
return t.SendTo(pkt.Encode(), addr)
}
func (t *Transport) Close() error {
t.mu.Lock()
defer t.mu.Unlock()
t.closed = true
return t.conn.Close()
}
func (t *Transport) SetSocketBuffers(rcvBuf, sndBuf int) error {
rawConn, err := t.conn.SyscallConn()
if err != nil {
return fmt.Errorf("get raw conn: %w", err)
}
var setErr error
err = rawConn.Control(func(fd uintptr) {
if rcvBuf > 0 {
if e := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUF, rcvBuf); e != nil {
setErr = fmt.Errorf("set SO_RCVBUF=%d: %w", rcvBuf, e)
return
}
}
if sndBuf > 0 {
if e := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUF, sndBuf); e != nil {
setErr = fmt.Errorf("set SO_SNDBUF=%d: %w", sndBuf, e)
return
}
}
})
if err != nil {
return err
}
return setErr
}
func (t *Transport) LocalAddr() net.Addr {
return t.conn.LocalAddr()
}
func (t *Transport) SetReadDeadline(deadline time.Time) error {
return t.conn.SetReadDeadline(deadline)
}
var packetBufPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, MaxPacketSize)
return &buf
},
}
func GetPacketBuf() *[]byte {
return packetBufPool.Get().(*[]byte)
}
func PutPacketBuf(buf *[]byte) {
packetBufPool.Put(buf)
}