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

@@ -13,6 +13,7 @@ import (
"os"
"os/signal"
"syscall"
"time"
"github.com/openp2p-cn/inp2p/internal/server"
"github.com/openp2p-cn/inp2p/pkg/auth"
@@ -90,17 +91,16 @@ func main() {
srv := server.New(cfg)
srv.StartCleanup()
// Auth Middleware
authMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
// Admin-only Middleware
adminMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v1/auth/login" {
next(w, r)
return
}
// Check Authorization header
authHeader := r.Header.Get("Authorization")
expected := fmt.Sprintf("Bearer %d", cfg.Token)
if authHeader != expected {
valid := authHeader == fmt.Sprintf("Bearer %d", cfg.Token)
if !valid {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
@@ -110,6 +110,32 @@ func main() {
}
}
// Tenant or Admin Middleware
tenantMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v1/auth/login" {
next(w, r)
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == fmt.Sprintf("Bearer %d", cfg.Token) {
next(w, r)
return
}
// check API key
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
next(w, r)
return
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
return
}
}
mux := http.NewServeMux()
mux.HandleFunc("/ws", srv.HandleWS)
@@ -117,6 +143,12 @@ func main() {
webDir := "/root/.openclaw/workspace/inp2p/web"
mux.Handle("/", http.FileServer(http.Dir(webDir)))
// Tenant APIs (API key auth inside handlers)
mux.HandleFunc("/api/v1/admin/tenants", adminMiddleware(srv.HandleAdminCreateTenant))
mux.HandleFunc("/api/v1/admin/tenants/", adminMiddleware(srv.HandleAdminCreateAPIKey))
mux.HandleFunc("/api/v1/tenants/enroll", srv.HandleTenantEnroll)
mux.HandleFunc("/api/v1/enroll/consume", srv.HandleEnrollConsume)
mux.HandleFunc("/api/v1/auth/login", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@@ -138,7 +170,16 @@ func main() {
req.Token = req2.Token
}
if req.Token != cfg.Token {
valid := req.Token == cfg.Token
if !valid {
for _, t := range cfg.Tokens {
if req.Token == t {
valid = true
break
}
}
}
if !valid {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, `{"error":1,"message":"invalid token"}`)
@@ -148,31 +189,54 @@ func main() {
fmt.Fprintf(w, `{"error":0,"token":"%d"}`, cfg.Token)
})
mux.HandleFunc("/api/v1/health", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/health", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status":"ok","version":"%s","nodes":%d}`, config.Version, len(srv.GetOnlineNodes()))
}))
mux.HandleFunc("/api/v1/nodes", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/nodes", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
// tenant filter by API key
tenantID := int64(0)
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
tenantID = ten.ID
}
}
if tenantID > 0 {
nodes := srv.GetOnlineNodesByTenant(tenantID)
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": nodes})
return
}
nodes := srv.GetOnlineNodes()
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": nodes})
}))
mux.HandleFunc("/api/v1/sdwans", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/sdwans", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
// tenant filter by API key
tenantID := int64(0)
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
tenantID = ten.ID
}
}
if tenantID > 0 {
_ = json.NewEncoder(w).Encode(srv.GetSDWANTenant(tenantID))
return
}
_ = json.NewEncoder(w).Encode(srv.GetSDWAN())
}))
mux.HandleFunc("/api/v1/sdwan/edit", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/sdwan/edit", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -182,6 +246,22 @@ func main() {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// tenant filter by API key
tenantID := int64(0)
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
tenantID = ten.ID
}
}
if tenantID > 0 {
if err := srv.SetSDWANTenant(tenantID, req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "ok"})
return
}
if err := srv.SetSDWAN(req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -191,7 +271,7 @@ func main() {
}))
// Remote Config Push API
mux.HandleFunc("/api/v1/nodes/apps", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/nodes/apps", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -209,6 +289,17 @@ func main() {
http.Error(w, "node not found", http.StatusNotFound)
return
}
// tenant filter by API key
tenantID := int64(0)
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
tenantID = ten.ID
}
}
if tenantID > 0 && node.TenantID != tenantID {
http.Error(w, "node not found", http.StatusNotFound)
return
}
// Push to client
_ = node.Conn.Write(protocol.MsgPush, protocol.SubPushConfig, req.Apps)
w.Header().Set("Content-Type", "application/json")
@@ -216,7 +307,7 @@ func main() {
}))
// Kick (disconnect) a node
mux.HandleFunc("/api/v1/nodes/kick", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/nodes/kick", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -233,13 +324,24 @@ func main() {
http.Error(w, "node not found or offline", http.StatusNotFound)
return
}
// tenant filter by API key
tenantID := int64(0)
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
tenantID = ten.ID
}
}
if tenantID > 0 && node.TenantID != tenantID {
http.Error(w, "node not found", http.StatusNotFound)
return
}
node.Conn.Close()
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "node kicked"})
}))
// Trigger P2P connect between two nodes
mux.HandleFunc("/api/v1/connect", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/connect", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -260,6 +362,17 @@ func main() {
http.Error(w, "source node offline", http.StatusNotFound)
return
}
// tenant filter by API key
tenantID := int64(0)
if srv.Store() != nil {
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
tenantID = ten.ID
}
}
if tenantID > 0 && fromNode.TenantID != tenantID {
http.Error(w, "node not found", http.StatusNotFound)
return
}
app := protocol.AppConfig{
AppName: req.AppName,
Protocol: "tcp",
@@ -269,6 +382,14 @@ func main() {
DstPort: req.DstPort,
Enabled: 1,
}
// enforce same-tenant target
if tenantID > 0 {
toNode := srv.GetNode(req.To)
if toNode == nil || toNode.TenantID != tenantID {
http.Error(w, "node not found", http.StatusNotFound)
return
}
}
if err := srv.PushConnect(fromNode, req.To, app); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
@@ -280,7 +401,7 @@ func main() {
}))
// Server uptime + detailed stats
mux.HandleFunc("/api/v1/stats", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/api/v1/stats", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
nodes := srv.GetOnlineNodes()
coneCount, symmCount, unknCount := 0, 0, 0
relayCount := 0
@@ -317,7 +438,13 @@ func main() {
}
log.Printf("[main] signaling server on :%d (no TLS — use reverse proxy for production)", cfg.WSPort)
httpSrv := &http.Server{Handler: mux}
// Enable TCP keepalive at server level
httpSrv := &http.Server{
Handler: mux,
ReadHeaderTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
if err := httpSrv.Serve(ln); err != http.ErrServerClosed {
log.Fatalf("[main] serve: %v", err)