fix: multi issues - TUN read loop, SDWAN routing for TenantID=0, WS keepalive 10s

This commit is contained in:
2026-03-03 11:24:00 +08:00
parent 9f6e065f3a
commit 10473020d2
22 changed files with 1122 additions and 76 deletions

View File

@@ -2,6 +2,7 @@
package server
import (
"fmt"
"log"
"net"
"net/http"
@@ -10,6 +11,7 @@ import (
"github.com/gorilla/websocket"
"github.com/openp2p-cn/inp2p/pkg/auth"
"github.com/openp2p-cn/inp2p/internal/store"
"github.com/openp2p-cn/inp2p/pkg/config"
"github.com/openp2p-cn/inp2p/pkg/protocol"
"github.com/openp2p-cn/inp2p/pkg/signal"
@@ -17,26 +19,27 @@ import (
// NodeInfo represents a connected client node.
type NodeInfo struct {
Name string `json:"name"`
Token uint64 `json:"-"`
User string `json:"user"`
Version string `json:"version"`
NATType protocol.NATType `json:"natType"`
PublicIP string `json:"publicIP"`
PublicPort int `json:"publicPort"`
LanIP string `json:"lanIP"`
OS string `json:"os"`
Mac string `json:"mac"`
ShareBandwidth int `json:"shareBandwidth"`
RelayEnabled bool `json:"relayEnabled"`
SuperRelay bool `json:"superRelay"`
HasIPv4 int `json:"hasIPv4"`
IPv6 string `json:"ipv6"`
LoginTime time.Time `json:"loginTime"`
LastHeartbeat time.Time `json:"lastHeartbeat"`
Conn *signal.Conn `json:"-"`
Name string `json:"name"`
Token uint64 `json:"-"`
TenantID int64 `json:"tenantId"`
User string `json:"user"`
Version string `json:"version"`
NATType protocol.NATType `json:"natType"`
PublicIP string `json:"publicIP"`
PublicPort int `json:"publicPort"`
LanIP string `json:"lanIP"`
OS string `json:"os"`
Mac string `json:"mac"`
ShareBandwidth int `json:"shareBandwidth"`
RelayEnabled bool `json:"relayEnabled"`
SuperRelay bool `json:"superRelay"`
HasIPv4 int `json:"hasIPv4"`
IPv6 string `json:"ipv6"`
LoginTime time.Time `json:"loginTime"`
LastHeartbeat time.Time `json:"lastHeartbeat"`
Conn *signal.Conn `json:"-"`
Apps []protocol.AppConfig `json:"apps"`
mu sync.RWMutex `json:"-"`
mu sync.RWMutex `json:"-"`
}
// IsOnline checks if node has sent heartbeat recently.
@@ -49,25 +52,43 @@ func (n *NodeInfo) IsOnline() bool {
// Server is the INP2P signaling server.
type Server struct {
cfg config.ServerConfig
nodes map[string]*NodeInfo // node name → info
nodes map[string]*NodeInfo
mu sync.RWMutex
upgrader websocket.Upgrader
quit chan struct{}
sdwanPath string
sdwan *sdwanStore
store *store.Store
tokens map[uint64]bool
}
func (s *Server) Store() *store.Store { return s.store }
// New creates a new server.
func New(cfg config.ServerConfig) *Server {
// Use absolute path for sdwan config to avoid working directory issues
sdwanPath := "/root/.openclaw/workspace/inp2p/sdwan.json"
tokens := make(map[uint64]bool)
if cfg.Token != 0 {
tokens[cfg.Token] = true
}
for _, t := range cfg.Tokens {
tokens[t] = true
}
st, err := store.Open(cfg.DBPath)
if err != nil {
log.Printf("[server] open store failed: %v", err)
}
return &Server{
cfg: cfg,
nodes: make(map[string]*NodeInfo),
sdwanPath: sdwanPath,
sdwan: newSDWANStore(sdwanPath),
store: st,
tokens: tokens,
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: 4096,
WriteBufferSize: 4096,
},
quit: make(chan struct{}),
}
@@ -93,6 +114,42 @@ func (s *Server) GetOnlineNodes() []*NodeInfo {
return out
}
// GetNodeForUser returns node if token matches (legacy) or tenant matches.
func (s *Server) GetNodeForUser(name string, token uint64) *NodeInfo {
s.mu.RLock()
defer s.mu.RUnlock()
n := s.nodes[name]
if n == nil {
return nil
}
if n.Token != token && n.TenantID == 0 {
return nil
}
return n
}
func (s *Server) GetNodeForTenant(name string, tenantID int64) *NodeInfo {
s.mu.RLock()
defer s.mu.RUnlock()
n := s.nodes[name]
if n == nil || n.TenantID != tenantID {
return nil
}
return n
}
func (s *Server) GetOnlineNodesByTenant(tenantID int64) []*NodeInfo {
s.mu.RLock()
defer s.mu.RUnlock()
var out []*NodeInfo
for _, n := range s.nodes {
if n.IsOnline() && n.TenantID == tenantID {
out = append(out, n)
}
}
return out
}
// GetRelayNodes returns nodes that can serve as relay.
// Priority: same-user private relay → super relay
func (s *Server) GetRelayNodes(forUser string, excludeNodes ...string) []*NodeInfo {
@@ -119,6 +176,28 @@ func (s *Server) GetRelayNodes(forUser string, excludeNodes ...string) []*NodeIn
return append(privateRelays, superRelays...)
}
// GetRelayNodesByTenant returns relay nodes within tenant.
func (s *Server) GetRelayNodesByTenant(tenantID int64, excludeNodes ...string) []*NodeInfo {
excludeSet := make(map[string]bool)
for _, n := range excludeNodes {
excludeSet[n] = true
}
s.mu.RLock()
defer s.mu.RUnlock()
var relays []*NodeInfo
for _, n := range s.nodes {
if !n.IsOnline() || excludeSet[n.Name] {
continue
}
if n.TenantID == tenantID && (n.RelayEnabled || n.SuperRelay) {
relays = append(relays, n)
}
}
return relays
}
// HandleWS is the WebSocket handler for client connections.
func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
ws, err := s.upgrader.Upgrade(w, r, nil)
@@ -151,8 +230,26 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
return
}
// Verify token
if loginReq.Token != s.cfg.Token {
// Verify token: master token OR tenant API key (DB) OR node_secret (DB)
valid := s.tokens[loginReq.Token]
log.Printf("[server] login check: token=%d, cfg.Token=%d, valid=%v", loginReq.Token, s.cfg.Token, valid)
var tenantID int64
if !valid && s.store != nil {
// try api key (string) or node secret
if loginReq.NodeSecret != "" {
if ten, err := s.store.VerifyNodeSecret(loginReq.Node, loginReq.NodeSecret); err == nil && ten != nil {
valid = true
tenantID = ten.ID
}
}
if !valid {
if ten, err := s.store.VerifyAPIKey(fmt.Sprintf("%d", loginReq.Token)); err == nil && ten != nil {
valid = true
tenantID = ten.ID
}
}
}
if !valid {
log.Printf("[server] login denied: %s (token mismatch)", loginReq.Node)
conn.Write(protocol.MsgLogin, protocol.SubLoginRsp, protocol.LoginRsp{
Error: 1,
@@ -174,6 +271,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
node := &NodeInfo{
Name: loginReq.Node,
Token: loginReq.Token,
TenantID: tenantID,
User: loginReq.User,
Version: loginReq.Version,
NATType: loginReq.NATType,
@@ -211,11 +309,21 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
s.broadcastNodeOnline(loginReq.Node)
// Push current SDWAN config right after login (if exists and enabled)
if cfg := s.sdwan.get(); cfg.Enabled && cfg.GatewayCIDR != "" {
if err := conn.Write(protocol.MsgPush, protocol.SubPushSDWANConfig, cfg); err != nil {
log.Printf("[server] sdwan config push failed: %v", err)
} else {
log.Printf("[server] sdwan config pushed to %s", loginReq.Node)
if node.TenantID > 0 {
if cfg := s.sdwan.getTenant(node.TenantID); cfg.Enabled && cfg.GatewayCIDR != "" {
if err := conn.Write(protocol.MsgPush, protocol.SubPushSDWANConfig, cfg); err != nil {
log.Printf("[server] sdwan config push failed: %v", err)
} else {
log.Printf("[server] sdwan config pushed to %s", loginReq.Node)
}
}
} else {
if cfg := s.sdwan.get(); cfg.Enabled && cfg.GatewayCIDR != "" {
if err := conn.Write(protocol.MsgPush, protocol.SubPushSDWANConfig, cfg); err != nil {
log.Printf("[server] sdwan config push failed: %v", err)
} else {
log.Printf("[server] sdwan config pushed to %s", loginReq.Node)
}
}
}
// Event-driven SDWAN peer notification
@@ -378,10 +486,13 @@ func (s *Server) handleRelayNodeReq(conn *signal.Conn, requester *NodeInfo, req
// PushConnect sends a punch coordination message to a peer node.
func (s *Server) PushConnect(fromNode *NodeInfo, toNodeName string, app protocol.AppConfig) error {
toNode := s.GetNode(toNodeName)
toNode := s.GetNodeForUser(toNodeName, fromNode.Token)
if toNode == nil || !toNode.IsOnline() {
return &NodeOfflineError{Node: toNodeName}
}
if fromNode.TenantID != 0 && toNode.TenantID != fromNode.TenantID {
return &NodeOfflineError{Node: toNodeName}
}
// Push connect request to the destination
req := protocol.ConnectReq{
@@ -392,6 +503,7 @@ func (s *Server) PushConnect(fromNode *NodeInfo, toNodeName string, app protocol
IP: fromNode.PublicIP,
NATType: fromNode.NATType,
HasIPv4: fromNode.HasIPv4,
Token: auth.GenTOTP(fromNode.Token, time.Now().Unix()),
},
AppName: app.AppName,
Protocol: app.Protocol,
@@ -406,12 +518,19 @@ func (s *Server) PushConnect(fromNode *NodeInfo, toNodeName string, app protocol
// broadcastNodeOnline notifies interested nodes that a peer came online.
func (s *Server) broadcastNodeOnline(nodeName string) {
s.mu.RLock()
newNode := s.nodes[nodeName]
defer s.mu.RUnlock()
if newNode == nil {
return
}
for _, n := range s.nodes {
if n.Name == nodeName {
continue
}
if n.Token != newNode.Token && (newNode.TenantID == 0 || n.TenantID != newNode.TenantID) {
continue
}
// Check if this node has any app targeting the new node
n.mu.RLock()
interested := false