initial: ZeroTier-like P2P mesh VPN server with multi-tenant Web UI
This commit is contained in:
193
internal/vl1/peer.go
Normal file
193
internal/vl1/peer.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user