feat: nlog, config hot-reload, and concurrency fixes

This commit is contained in:
xboard
2026-03-24 23:12:55 +08:00
parent 191e58b9ee
commit 7e3ad7cb23
22 changed files with 572 additions and 281 deletions
+3 -3
View File
@@ -3,7 +3,6 @@ package singbox
import (
"encoding/base64"
"fmt"
"log/slog"
"net"
"path/filepath"
"strconv"
@@ -11,6 +10,7 @@ import (
"github.com/cedar2025/xboard-node/internal/config"
"github.com/cedar2025/xboard-node/internal/kernel"
"github.com/cedar2025/xboard-node/internal/nlog"
"github.com/cedar2025/xboard-node/internal/panel"
"github.com/go-viper/mapstructure/v2"
)
@@ -251,7 +251,7 @@ func buildRoutes(panelRoutes []panel.RouteRule, custom []map[string]any) M {
func mergeCustomSingbox(cfg M, kcfg config.KernelConfig) {
custom, err := kernel.LoadCustomConfig(kcfg.CustomConfig)
if err != nil {
slog.Error("failed to load custom sing-box config", "error", err)
nlog.Core().Error("failed to load custom sing-box config", "error", err)
return
}
if custom == nil {
@@ -427,7 +427,7 @@ func buildShadowsocks(base M, nc *panel.NodeConfig, users []panel.User) M {
base["users"] = userList
if nc.Plugin != "" {
slog.Warn("sing-box shadowsocks inbound does not support plugin, ignoring", "plugin", nc.Plugin)
nlog.Core().Warn("sing-box shadowsocks inbound does not support plugin, ignoring", "plugin", nc.Plugin)
}
return base
+22 -10
View File
@@ -3,7 +3,6 @@ package singbox
import (
"context"
"io"
"log/slog"
"net"
"sort"
"sync"
@@ -15,6 +14,8 @@ import (
singM "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.org/x/time/rate"
"github.com/cedar2025/xboard-node/internal/nlog"
)
const rateLimitThreshold = 64 * 1024
@@ -171,7 +172,7 @@ func (t *ConnTracker) RoutedConnection(
if dlf := t.deviceLimitFunc.Load(); dlf != nil {
if limit, hasLimit := (*dlf)(uuid); hasLimit {
if t.checkDeviceGate(us, sourceIP, limit) {
slog.Info("singbox: device limit gate-keep, rejecting connection",
nlog.Core().Info("singbox: device limit gate-keep, rejecting connection",
"user", uuid, "ip", sourceIP, "limit", limit)
conn.Close()
return conn
@@ -197,17 +198,17 @@ func (t *ConnTracker) RoutedConnection(
}
return &trackedConn{
Conn: conn,
tracker: t,
us: us,
connID: connID,
Conn: conn,
tracker: t,
us: us,
connID: connID,
sourceIP: sourceIP,
limiter: lim,
ctx: ctx,
limiter: lim,
ctx: ctx,
}
}
// RoutedPacketConnection wraps a UDP PacketConn with per-user byte counting.
// RoutedPacketConnection wraps UDP with per-user counting (UDP not in connMap).
func (t *ConnTracker) RoutedPacketConnection(
ctx context.Context, conn N.PacketConn,
metadata adapter.InboundContext,
@@ -221,6 +222,17 @@ func (t *ConnTracker) RoutedPacketConnection(
us := t.users[uid]
t.usersMu.RUnlock()
if dlf := t.deviceLimitFunc.Load(); dlf != nil {
if limit, hasLimit := (*dlf)(uuid); hasLimit {
if t.checkDeviceGate(us, sourceIP, limit) {
nlog.Core().Info("singbox: device limit gate-keep, rejecting UDP connection",
"user", uuid, "ip", sourceIP, "limit", limit)
conn.Close()
return conn
}
}
}
if us != nil {
us.addConn(sourceIP)
}
@@ -372,7 +384,7 @@ func (t *ConnTracker) removeConnRef(connID string) {
type trackedConn struct {
net.Conn
tracker *ConnTracker
us *userStats // per-user stats (upload/download atomics + IP tracking)
us *userStats // per-user stats (upload/download atomics + IP tracking)
connID string
sourceIP string
limiter *rate.Limiter
+11 -11
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"sync"
"time"
@@ -20,6 +19,7 @@ import (
"github.com/cedar2025/xboard-node/internal/config"
"github.com/cedar2025/xboard-node/internal/kernel"
"github.com/cedar2025/xboard-node/internal/nlog"
"github.com/cedar2025/xboard-node/internal/panel"
)
@@ -88,7 +88,7 @@ func (s *SingBox) Start(nodeConfig *panel.NodeConfig, users []panel.User, certFi
return fmt.Errorf("marshal config: %w", err)
}
slog.Debug("sing-box config generated", "len", len(data))
nlog.Core().Debug("sing-box config generated", "len", len(data))
ctx, cancel := context.WithCancel(context.Background())
ctx = include.Context(ctx)
@@ -147,7 +147,7 @@ func (s *SingBox) Start(nodeConfig *panel.NodeConfig, users []panel.User, certFi
go recycleOldBox(oldBox, oldCancel, oldCtx, oldTracker)
}
slog.Debug("sing-box started", "users", len(users))
nlog.Core().Debug("sing-box started", "users", len(users))
return nil
}
@@ -176,7 +176,7 @@ func recycleOldBox(oldBox *box.Box, oldCancel context.CancelFunc, oldCtx context
if oldCancel != nil {
oldCancel()
}
slog.Debug("sing-box: old instance recycled")
nlog.Core().Debug("sing-box: old instance recycled")
}
// Reload hot-swaps the inbound users and routing rules without restarting the box.
@@ -213,9 +213,9 @@ func (s *SingBox) Reload(nodeConfig *panel.NodeConfig, users []panel.User, certF
// Update routing rules
if err := router.UpdateRules(opts.Route.Rules, opts.Route.RuleSet); err != nil {
slog.Debug("routing reload failed", "error", err)
nlog.Core().Debug("routing reload failed", "error", err)
} else {
slog.Debug("sing-box routing reloaded")
nlog.Core().Debug("sing-box routing reloaded")
}
nopFactory := singLog.NewNOPFactory()
@@ -270,7 +270,7 @@ func (s *SingBox) Reload(nodeConfig *panel.NodeConfig, users []panel.User, certF
if err == nil {
continue
}
slog.Warn("incremental update failed, falling back to recreate", "tag", tag, "error", err)
nlog.Core().Warn("incremental update failed, falling back to recreate", "tag", tag, "error", err)
}
}
@@ -292,7 +292,7 @@ func (s *SingBox) Reload(nodeConfig *panel.NodeConfig, users []panel.User, certF
s.connTracker.SetUserMap(buildUserMap(users))
}
slog.Debug("sing-box reloaded", "users", len(users))
nlog.Core().Debug("sing-box reloaded", "users", len(users))
s.users = users
s.nodeConfig = nodeConfig
s.certFile = certFile
@@ -559,7 +559,7 @@ func (s *SingBox) reloadInboundsLocked(users []panel.User) error {
if err == nil {
continue
}
slog.Warn("incremental update failed, recreating inbound", "tag", tag, "error", err)
nlog.Core().Warn("incremental update failed, recreating inbound", "tag", tag, "error", err)
}
_ = im.Remove(tag)
@@ -573,7 +573,7 @@ func (s *SingBox) reloadInboundsLocked(users []panel.User) error {
s.connTracker.SetUserMap(buildUserMap(users))
}
slog.Debug("sing-box users hot-swapped", "users", len(users))
nlog.Core().Debug("sing-box users hot-swapped", "users", len(users))
return nil
}
@@ -594,7 +594,7 @@ func (s *SingBox) CloseConnection(_ context.Context, connID string) error {
return fmt.Errorf("not running")
}
if !ct.CloseByID(connID) {
slog.Debug("CloseConnection: connection not found (already closed?)", "id", connID)
nlog.Core().Debug("CloseConnection: connection not found (already closed?)", "id", connID)
}
return nil
}