fix: register user_manager in context, immediate credential sync for all protocols, fix vless flow, add Authorization header
Some checks failed
Build / Calculate version (push) Has been cancelled
Build / Build binary (push) Has been cancelled
Build / Build Darwin binaries (push) Has been cancelled
Build / Build Windows binaries (push) Has been cancelled
Build / Build Android (push) Has been cancelled
Build / Publish Android (push) Has been cancelled
Build / Build Apple clients (push) Has been cancelled
Build / Upload builds (push) Has been cancelled

This commit is contained in:
Niko Marmeladkov 2026-06-18 22:16:18 +03:00
parent 3123c2f29a
commit 691cce1361
Signed by untrusted user who does not match committer: Niko
GPG key ID: E3B955F9442D44E3
6 changed files with 124 additions and 71 deletions

16
box.go
View file

@ -30,6 +30,7 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/service/usermanager"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
@ -290,6 +291,19 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize endpoint[", i, "]") return nil, E.Cause(err, "initialize endpoint[", i, "]")
} }
} }
if experimentalOptions.UserManager != nil {
userManager, err := usermanager.New(
ctx,
logFactory.NewLogger("service/user_manager"),
"user_manager",
*experimentalOptions.UserManager,
)
if err != nil {
return nil, E.Cause(err, "create user_manager")
}
service.MustRegister[adapter.UserManager](ctx, userManager)
internalServices = append(internalServices, userManager)
}
for i, inboundOptions := range options.Inbounds { for i, inboundOptions := range options.Inbounds {
var tag string var tag string
if inboundOptions.Tag != "" { if inboundOptions.Tag != "" {
@ -561,7 +575,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service) err = adapter.Start(s.logger, adapter.StartStateStart, s.service, s.inbound)
if err != nil { if err != nil {
return err return err
} }

View file

@ -284,24 +284,29 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
return h.service.Start(packetConn) return h.service.Start(packetConn)
} }
func (h *Inbound) syncCredentials() {
creds, err := h.userManager.GetCredentials(C.TypeHysteria2)
if err != nil || len(creds) == 0 {
return
}
userList := make([]int, len(creds))
userNameList := make([]string, len(creds))
userPasswordList := make([]string, len(creds))
for i, c := range creds {
userList[i] = i
userNameList[i] = c.Username
userPasswordList[i] = c.Credential
}
h.service.UpdateUsers(userList, userPasswordList)
h.userNameList = userNameList
}
func (h *Inbound) credentialLoop() { func (h *Inbound) credentialLoop() {
h.syncCredentials()
ticker := time.NewTicker(30 * time.Second) ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
creds, err := h.userManager.GetCredentials(C.TypeHysteria2) h.syncCredentials()
if err != nil || len(creds) == 0 {
continue
}
userList := make([]int, len(creds))
userNameList := make([]string, len(creds))
userPasswordList := make([]string, len(creds))
for i, c := range creds {
userList[i] = i
userNameList[i] = c.Username
userPasswordList[i] = c.Credential
}
h.service.UpdateUsers(userList, userPasswordList)
h.userNameList = userNameList
} }
} }

View file

@ -157,19 +157,24 @@ func (n *Inbound) Start(stage adapter.StartStage) error {
return nil return nil
} }
func (n *Inbound) syncCredentials() {
creds, err := n.userManager.GetCredentials(C.TypeNaive)
if err != nil || len(creds) == 0 {
return
}
users := make([]auth.User, len(creds))
for i, c := range creds {
users[i] = auth.User{Username: c.Username, Password: c.Credential}
}
n.authenticator = auth.NewAuthenticator(users)
}
func (n *Inbound) credentialLoop() { func (n *Inbound) credentialLoop() {
n.syncCredentials()
ticker := time.NewTicker(30 * time.Second) ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
creds, err := n.userManager.GetCredentials(C.TypeNaive) n.syncCredentials()
if err != nil || len(creds) == 0 {
continue
}
users := make([]auth.User, len(creds))
for i, c := range creds {
users[i] = auth.User{Username: c.Username, Password: c.Credential}
}
n.authenticator = auth.NewAuthenticator(users)
} }
} }

View file

@ -212,40 +212,45 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
return h.server.Start(packetConn) return h.server.Start(packetConn)
} }
func (h *Inbound) syncCredentials() {
creds, err := h.userManager.GetCredentials(C.TypeTUIC)
if err != nil || len(creds) == 0 {
return
}
userList := make([]int, 0, len(creds))
userNameList := make([]string, 0, len(creds))
userUUIDList := make([][16]byte, 0, len(creds))
userPasswordList := make([]string, 0, len(creds))
for i, c := range creds {
parts := strings.SplitN(c.Credential, ":", 2)
if len(parts) < 1 || parts[0] == "" {
continue
}
userUUID, pErr := uuid.FromString(parts[0])
if pErr != nil {
continue
}
password := ""
if len(parts) == 2 {
password = parts[1]
}
userList = append(userList, i)
userNameList = append(userNameList, c.Username)
userUUIDList = append(userUUIDList, userUUID)
userPasswordList = append(userPasswordList, password)
}
if len(userList) > 0 {
h.server.UpdateUsers(userList, userUUIDList, userPasswordList)
h.userNameList = userNameList
}
}
func (h *Inbound) credentialLoop() { func (h *Inbound) credentialLoop() {
h.syncCredentials()
ticker := time.NewTicker(30 * time.Second) ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
creds, err := h.userManager.GetCredentials(C.TypeTUIC) h.syncCredentials()
if err != nil || len(creds) == 0 {
continue
}
userList := make([]int, 0, len(creds))
userNameList := make([]string, 0, len(creds))
userUUIDList := make([][16]byte, 0, len(creds))
userPasswordList := make([]string, 0, len(creds))
for i, c := range creds {
parts := strings.SplitN(c.Credential, ":", 2)
if len(parts) < 1 || parts[0] == "" {
continue
}
userUUID, pErr := uuid.FromString(parts[0])
if pErr != nil {
continue
}
password := ""
if len(parts) == 2 {
password = parts[1]
}
userList = append(userList, i)
userNameList = append(userNameList, c.Username)
userUUIDList = append(userUUIDList, userUUID)
userPasswordList = append(userPasswordList, password)
}
if len(userList) > 0 {
h.server.UpdateUsers(userList, userUUIDList, userPasswordList)
h.userNameList = userNameList
}
} }
} }

View file

@ -76,7 +76,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
for i, c := range creds { for i, c := range creds {
userIndices[i] = i userIndices[i] = i
userUUIDs[i] = c.Credential userUUIDs[i] = c.Credential
userFlows[i] = "" userFlows[i] = "xtls-rprx-vision"
userNameList[i] = c.Username userNameList[i] = c.Username
} }
service.UpdateUsers(userIndices, userUUIDs, userFlows) service.UpdateUsers(userIndices, userUUIDs, userFlows)
@ -171,26 +171,37 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
return nil return nil
} }
func (h *Inbound) syncCredentials() {
creds, err := h.userManager.GetCredentials(C.TypeVLESS)
if err != nil {
h.logger.Warn("syncCredentials: GetCredentials error: ", err)
return
}
if len(creds) == 0 {
h.logger.Warn("syncCredentials: no vless credentials yet")
return
}
h.logger.Info("syncCredentials: loaded ", len(creds), " vless users")
userIndices := make([]int, len(creds))
userUUIDs := make([]string, len(creds))
userFlows := make([]string, len(creds))
userNameList := make([]string, len(creds))
for i, c := range creds {
userIndices[i] = i
userUUIDs[i] = c.Credential
userFlows[i] = "xtls-rprx-vision"
userNameList[i] = c.Username
}
h.service.UpdateUsers(userIndices, userUUIDs, userFlows)
h.userNameList = userNameList
}
func (h *Inbound) credentialLoop() { func (h *Inbound) credentialLoop() {
h.syncCredentials()
ticker := time.NewTicker(30 * time.Second) ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
creds, err := h.userManager.GetCredentials(C.TypeVLESS) h.syncCredentials()
if err != nil || len(creds) == 0 {
continue
}
userIndices := make([]int, len(creds))
userUUIDs := make([]string, len(creds))
userFlows := make([]string, len(creds))
userNameList := make([]string, len(creds))
for i, c := range creds {
userIndices[i] = i
userUUIDs[i] = c.Credential
userFlows[i] = ""
userNameList[i] = c.Username
}
h.service.UpdateUsers(userIndices, userUUIDs, userFlows)
h.userNameList = userNameList
} }
} }

View file

@ -127,8 +127,11 @@ func (m *Manager) Start(stage adapter.StartStage) error {
} }
m.logger.Info("starting user manager") m.logger.Info("starting user manager")
m.logger.Debug("running initial credential fetch")
if err := m.refreshCredentials(); err != nil { if err := m.refreshCredentials(); err != nil {
m.logger.Warn("initial credential fetch failed: ", err) m.logger.Warn("initial credential fetch failed: ", err)
} else {
m.logger.Debug("initial credential fetch succeeded, protocols=", len(m.protocolUsers))
} }
router := service.FromContext[adapter.Router](m.ctx) router := service.FromContext[adapter.Router](m.ctx)
@ -187,6 +190,10 @@ func (m *Manager) Close() error {
return nil return nil
} }
func (m *Manager) Name() string {
return "user_manager[" + m.Tag() + "]"
}
func (m *Manager) Authenticate(ctx context.Context, protocol string, credential string, addr string) (string, error) { func (m *Manager) Authenticate(ctx context.Context, protocol string, credential string, addr string) (string, error) {
if credential == "" { if credential == "" {
return "", E.New("empty credential") return "", E.New("empty credential")
@ -303,6 +310,9 @@ func (m *Manager) refreshCredentials() error {
if err != nil { if err != nil {
return err return err
} }
if m.apiSecret != "" {
httpReq.Header.Set("Authorization", "Bearer "+m.apiSecret)
}
resp, err := m.httpClient.Do(httpReq) resp, err := m.httpClient.Do(httpReq)
if err != nil { if err != nil {
return E.Cause(err, "fetch credentials") return E.Cause(err, "fetch credentials")
@ -556,6 +566,9 @@ func (m *Manager) reportTrafficToServer() {
return return
} }
httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Content-Type", "application/json")
if m.apiSecret != "" {
httpReq.Header.Set("Authorization", "Bearer "+m.apiSecret)
}
resp, err := m.httpClient.Do(httpReq) resp, err := m.httpClient.Do(httpReq)
if err != nil { if err != nil {
m.logger.Warn("traffic report failed: ", err) m.logger.Warn("traffic report failed: ", err)