fix: multi issues - TUN read loop, SDWAN routing for TenantID=0, WS keepalive 10s
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user