diff --git a/box.go b/box.go index a276324f..db57d095 100644 --- a/box.go +++ b/box.go @@ -30,6 +30,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing-box/service/usermanager" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" 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, "]") } } + 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 { var tag string if inboundOptions.Tag != "" { @@ -561,7 +575,7 @@ func (s *Box) start() error { if err != nil { 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 { return err } diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index b33f55c0..85c49a28 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -284,24 +284,29 @@ func (h *Inbound) Start(stage adapter.StartStage) error { 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() { + h.syncCredentials() ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { - creds, err := h.userManager.GetCredentials(C.TypeHysteria2) - 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 + h.syncCredentials() } } diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 95fa9ddb..5a970ccd 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -157,19 +157,24 @@ func (n *Inbound) Start(stage adapter.StartStage) error { 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() { + n.syncCredentials() ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { - creds, err := n.userManager.GetCredentials(C.TypeNaive) - 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) + n.syncCredentials() } } diff --git a/protocol/tuic/inbound.go b/protocol/tuic/inbound.go index 18dfc9ad..dff16e11 100644 --- a/protocol/tuic/inbound.go +++ b/protocol/tuic/inbound.go @@ -212,40 +212,45 @@ func (h *Inbound) Start(stage adapter.StartStage) error { 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() { + h.syncCredentials() ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { - creds, err := h.userManager.GetCredentials(C.TypeTUIC) - 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 - } + h.syncCredentials() } } diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index d6209404..08f98bef 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -76,7 +76,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo for i, c := range creds { userIndices[i] = i userUUIDs[i] = c.Credential - userFlows[i] = "" + userFlows[i] = "xtls-rprx-vision" userNameList[i] = c.Username } service.UpdateUsers(userIndices, userUUIDs, userFlows) @@ -171,26 +171,37 @@ func (h *Inbound) Start(stage adapter.StartStage) error { 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() { + h.syncCredentials() ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { - creds, err := h.userManager.GetCredentials(C.TypeVLESS) - 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 + h.syncCredentials() } } diff --git a/service/usermanager/manager.go b/service/usermanager/manager.go index bb4c8ea8..8cecb63d 100644 --- a/service/usermanager/manager.go +++ b/service/usermanager/manager.go @@ -127,8 +127,11 @@ func (m *Manager) Start(stage adapter.StartStage) error { } m.logger.Info("starting user manager") + m.logger.Debug("running initial credential fetch") if err := m.refreshCredentials(); err != nil { 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) @@ -187,6 +190,10 @@ func (m *Manager) Close() error { 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) { if credential == "" { return "", E.New("empty credential") @@ -303,6 +310,9 @@ func (m *Manager) refreshCredentials() error { if err != nil { return err } + if m.apiSecret != "" { + httpReq.Header.Set("Authorization", "Bearer "+m.apiSecret) + } resp, err := m.httpClient.Do(httpReq) if err != nil { return E.Cause(err, "fetch credentials") @@ -556,6 +566,9 @@ func (m *Manager) reportTrafficToServer() { return } httpReq.Header.Set("Content-Type", "application/json") + if m.apiSecret != "" { + httpReq.Header.Set("Authorization", "Bearer "+m.apiSecret) + } resp, err := m.httpClient.Do(httpReq) if err != nil { m.logger.Warn("traffic report failed: ", err)