From fce60b8ad3e84b170079fd8cd3263aa78e374d30 Mon Sep 17 00:00:00 2001 From: will Date: Thu, 18 Jun 2026 15:14:09 +0900 Subject: [PATCH] fix: force-close user connections on removal, fix UDP upload/download counters, use self-hosted releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - service.go: call CloseUserConnections() before RemoveUsers() so banned/reset users are disconnected immediately instead of waiting for natural timeout (upstream issue #32, PR pending 4+ months) - conntracker.go: swap UnwrapPacketReader/Writer upload/download counters for UDP PacketConn path — Reader=upload, Writer=download to match TCP path (upstream issue #55) - xbctl/main.go: change downloadBase to JP01 Gitea so xbctl upgrade pulls self-hosted patched releases instead of upstream --- cmd/xbctl/main.go | 8 +++----- internal/kernel/singbox/conntracker.go | 4 ++-- internal/service/service.go | 12 ++++++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cmd/xbctl/main.go b/cmd/xbctl/main.go index 4a05d66..53e6731 100644 --- a/cmd/xbctl/main.go +++ b/cmd/xbctl/main.go @@ -30,7 +30,7 @@ const ( serviceName = "xboard-node.service" serviceFilePath = "/etc/systemd/system/xboard-node.service" defaultInstallRoot = "/etc/xboard-node" - downloadBase = "https://github.com/cedar2025/xboard-node/releases" + downloadBase = "http://103.112.1.141:3000/will/xboard-node" ) var ( @@ -593,10 +593,8 @@ func ensureRoot(cmd string) error { } func resolveDownloadURL(artifact, version string) string { - if version == "latest" { - return downloadBase + "/latest/download/" + artifact - } - return downloadBase + "/download/" + version + "/" + artifact + // Gitea release download URL: {base}/releases/download/{tag}/{file} + return downloadBase + "/releases/download/" + version + "/" + artifact } func downloadFile(url, dest string) error { diff --git a/internal/kernel/singbox/conntracker.go b/internal/kernel/singbox/conntracker.go index 77d6852..c7f9e9d 100644 --- a/internal/kernel/singbox/conntracker.go +++ b/internal/kernel/singbox/conntracker.go @@ -791,14 +791,14 @@ func (c *trackedPacketConn) UnwrapPacketReader() (N.PacketReader, []N.CountFunc) if c.us == nil { return c.PacketConn, nil } - return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.download)} + return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.upload)} } func (c *trackedPacketConn) UnwrapPacketWriter() (N.PacketWriter, []N.CountFunc) { if c.us == nil { return c.PacketConn, nil } - return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.upload)} + return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.download)} } func (c *trackedPacketConn) Upstream() any { return c.PacketConn } diff --git a/internal/service/service.go b/internal/service/service.go index 890940d..266e73e 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -838,6 +838,18 @@ func (s *Service) applyUserDelta(ctx context.Context, action string, deltaUsers return } + // Force-close active connections for removed users before revoking + // credentials. Without this, existing long-lived connections + // (QUIC/TCP) persist until natural timeout — subscription resets + // and bans would not take immediate effect. + if s.kernel.Capabilities().ForceCloseUser { + for _, u := range deltaUsers { + if err := s.kernel.CloseUserConnections(ctx, u.UUID); err != nil { + nlog.Core().Debug(fmt.Sprintf("CloseUserConnections(%s): %v", u.UUID, err)) + } + } + } + prevUsers, prevHash := s.prepareUserState(filtered) removed, err := s.kernel.RemoveUsers(deltaUsers) if err != nil {