feat: trust peer

This commit is contained in:
2026-02-07 03:17:37 +08:00
parent d8ffc5eea5
commit f3adb56bd0
19 changed files with 438 additions and 155 deletions

View File

@@ -5,8 +5,10 @@ import (
"fmt"
"log/slog"
"mesh-drop/internal/config"
"mesh-drop/internal/security"
"net"
"runtime"
"sort"
"sync"
"time"
@@ -15,8 +17,8 @@ import (
const (
DiscoveryPort = 9988
HeartbeatRate = 3 * time.Second
PeerTimeout = 10 * time.Second
HeartbeatRate = 1 * time.Second
PeerTimeout = 2 * time.Second
)
type Service struct {
@@ -26,9 +28,11 @@ type Service struct {
config *config.Config
FileServerPort int
// key 使用 peer.id 和 peer.ip 组合而成的 hash
// Key: peer.ID
peers map[string]*Peer
peersMutex sync.RWMutex
self Peer
}
func NewService(config *config.Config, app *application.App, port int) *Service {
@@ -38,10 +42,17 @@ func NewService(config *config.Config, app *application.App, port int) *Service
config: config,
FileServerPort: port,
peers: make(map[string]*Peer),
self: Peer{
ID: config.GetID(),
Name: config.GetHostName(),
Port: port,
OS: OS(runtime.GOOS),
PublicKey: config.PublicKey,
},
}
}
func (s *Service) GetLocalIPs() ([]string, bool) {
func GetLocalIPs() ([]string, bool) {
interfaces, err := net.Interfaces()
if err != nil {
slog.Error("Failed to get network interfaces", "error", err, "component", "discovery")
@@ -114,11 +125,22 @@ func (s *Service) startBroadcasting() {
continue
}
packet := PresencePacket{
ID: s.ID,
Name: s.config.GetHostName(),
Port: s.FileServerPort,
OS: OS(runtime.GOOS),
ID: s.ID,
Name: s.config.GetHostName(),
Port: s.FileServerPort,
OS: OS(runtime.GOOS),
PublicKey: s.config.PublicKey,
}
// 签名
sigData := packet.SignPayload()
sig, err := security.Sign(s.config.PrivateKey, sigData)
if err != nil {
slog.Error("Failed to sign discovery packet", "error", err)
continue
}
packet.Signature = sig
data, _ := json.Marshal(packet)
for _, iface := range interfaces {
// 过滤掉 Down 的接口和 Loopback 接口
@@ -195,12 +217,33 @@ func (s *Service) startListening() {
continue
}
s.handleHeartbeat(packet, remoteAddr.IP.String())
// 验证签名
sig := packet.Signature
sigData := packet.SignPayload()
valid, err := security.Verify(packet.PublicKey, sigData, sig)
if err != nil || !valid {
slog.Warn("Received invalid discovery packet signature", "id", packet.ID, "ip", remoteAddr.IP.String())
continue
}
// 验证身份一致性 (防止 ID 欺骗)
trustMismatch := false
trustedKeys := s.config.GetTrustedPeer()
if knownKey, ok := trustedKeys[packet.ID]; ok {
if knownKey != packet.PublicKey {
slog.Warn("SECURITY ALERT: Peer ID mismatch with known public key (Spoofing attempt?)", "id", packet.ID, "known_key", knownKey, "received_key", packet.PublicKey)
trustMismatch = true
// 当发现 ID 欺骗时,不更新 peer而是标记为 trustMismatch
// 用户可以手动重新添加信任
}
}
s.handleHeartbeat(packet, remoteAddr.IP.String(), trustMismatch)
}
}
// handleHeartbeat 处理心跳包
func (s *Service) handleHeartbeat(pkt PresencePacket, ip string) {
func (s *Service) handleHeartbeat(pkt PresencePacket, ip string, trustMismatch bool) {
s.peersMutex.Lock()
peer, exists := s.peers[pkt.ID]
@@ -215,19 +258,27 @@ func (s *Service) handleHeartbeat(pkt PresencePacket, ip string) {
LastSeen: time.Now(),
},
},
Port: pkt.Port,
OS: pkt.OS,
Port: pkt.Port,
OS: pkt.OS,
PublicKey: pkt.PublicKey,
TrustMismatch: trustMismatch,
}
s.peers[peer.ID] = peer
slog.Info("New device found", "name", pkt.Name, "ip", ip, "component", "discovery")
} else {
// 更新节点
peer.Name = pkt.Name
peer.OS = pkt.OS
// 只有在没有身份不匹配的情况下才更新元数据,防止欺骗攻击导致 UI 闪烁/篡改
if !trustMismatch {
peer.Name = pkt.Name
peer.OS = pkt.OS
peer.PublicKey = pkt.PublicKey
}
peer.Routes[ip] = &RouteState{
IP: ip,
LastSeen: time.Now(),
}
// 如果之前存在不匹配,即使这次匹配了,也不要重置,防止欺骗攻击
peer.TrustMismatch = peer.TrustMismatch || trustMismatch
}
s.peersMutex.Unlock()
@@ -246,7 +297,6 @@ func (s *Service) startCleanup() {
for id, peer := range s.peers {
for ip, route := range peer.Routes {
// 超过10秒没心跳认为下线
if now.Sub(route.LastSeen) > PeerTimeout {
delete(peer.Routes, ip)
changed = true
@@ -274,16 +324,24 @@ func (s *Service) Start() {
go s.startCleanup()
}
func (s *Service) GetPeerByIP(ip string) *Peer {
func (s *Service) GetPeerByIP(ip string) (*Peer, bool) {
s.peersMutex.RLock()
defer s.peersMutex.RUnlock()
for _, p := range s.peers {
if p.Routes[ip] != nil {
return p
return p, true
}
}
return nil
return nil, false
}
func (s *Service) GetPeerByID(id string) (*Peer, bool) {
s.peersMutex.RLock()
defer s.peersMutex.RUnlock()
peer, ok := s.peers[id]
return peer, ok
}
func (s *Service) GetPeers() []Peer {
@@ -294,9 +352,16 @@ func (s *Service) GetPeers() []Peer {
for _, p := range s.peers {
list = append(list, *p)
}
sort.Slice(list, func(i, j int) bool {
return list[i].Name < list[j].Name
})
return list
}
func (s *Service) GetID() string {
return s.ID
}
func (s *Service) GetSelf() Peer {
return s.self
}