mirror of
https://github.com/alireza0/s-ui.git
synced 2026-10-22 20:01:52 +00:00
fix(hysteria2): preserve user identity across hot updates (#1231)
* fix(hysteria2): use stable user identity on hot updates Replace Service[int] (position-based) with Service[string] (name-based) so that existing sessions retain a correct stable identity when the user list changes via UpdateUsers. Remove the now-redundant userNameList field. Fixes panic: runtime error: index out of range after user deletion, and wrong username attribution when list positions shift. * fix(hysteria2): gofmt struct field alignment after userNameList removal * fix: extend stable user identity fix to hysteria, tuic, trojan, vless, vmess The same Service[int] → Service[string] bug fixed for hysteria2 in the previous commit affects all five remaining inbound protocols. When users are deleted or reordered, integer position-based identity causes panics (index out of range) and wrong username attribution in logs and traffic stats. Apply the same fix across hysteria v1, tuic, trojan, vless, and vmess: - Service[int] → Service[string] in all five inbound structs - Pass user.Name as the stable identity instead of the list index - Remove the now-unnecessary cached `users []option.*User` fields from trojan, vless, and vmess structs (they existed solely to map indexes back to names) - UpdateUsers helpers rebuilt to pass names, keeping auth/password fields unchanged Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: add go vet + gofmt check for patched protocols * fix: gofmt struct field alignment in hysteria and tuic inbound * fix: address review feedback on stable-identity PR Three fixes from maintainer review: 1. Logging regression (trojan/vless/vmess): when a client has no name, the old code fell back to the index as a log label; the new code was emitting `[] inbound connection`. Align with the hysteria2 pattern: omit the `[name]` prefix entirely when name is empty. 2. Duplicate/empty client names: trojan's Service.UpdateUsers rejects a repeated identity, so two clients with the same name on the same inbound would make UpdateUsers fail. vmess silently overwrites the earlier entry. vless misattributes flow. Add server-side validation in ClientService.Save (new, edit, addbulk) that rejects empty names and names already in use on any shared inbound within the same tx. 3. Remove .github/workflows/check.yml: the workflow only triggered on the feature branch, so it would be permanently dead after merge. * fix: make client name validation global and trim whitespace * ci: temporarily re-add check workflow for CI verification * ci: remove temporary check workflow * validate client names on bulk save paths too --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
parent
88bde26fcf
commit
bb4baebb13
13 changed files with 147 additions and 145 deletions
|
|
@ -25,12 +25,11 @@ func RegisterInbound(registry *inbound.Registry) {
|
|||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
service *hysteria.Service[int]
|
||||
userNameList []string
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
service *hysteria.Service[string]
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
|
||||
|
|
@ -70,7 +69,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
} else {
|
||||
udpTimeout = C.UDPTimeout
|
||||
}
|
||||
service, err := hysteria.NewService[int](hysteria.ServiceOptions{
|
||||
service, err := hysteria.NewService[string](hysteria.ServiceOptions{
|
||||
Context: ctx,
|
||||
Logger: logger,
|
||||
SendBPS: sendBps,
|
||||
|
|
@ -90,12 +89,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userList := make([]int, 0, len(options.Users))
|
||||
userNameList := make([]string, 0, len(options.Users))
|
||||
userList := make([]string, 0, len(options.Users))
|
||||
userPasswordList := make([]string, 0, len(options.Users))
|
||||
for index, user := range options.Users {
|
||||
userList = append(userList, index)
|
||||
userNameList = append(userNameList, user.Name)
|
||||
for _, user := range options.Users {
|
||||
userList = append(userList, user.Name)
|
||||
var password string
|
||||
if user.AuthString != "" {
|
||||
password = user.AuthString
|
||||
|
|
@ -106,7 +103,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
}
|
||||
service.UpdateUsers(userList, userPasswordList)
|
||||
inbound.service = service
|
||||
inbound.userNameList = userNameList
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +118,7 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
|
|||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
||||
} else {
|
||||
|
|
@ -144,8 +139,7 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,10 @@ import (
|
|||
)
|
||||
|
||||
func (h *Inbound) UpdateUsers(users []option.HysteriaUser) error {
|
||||
userList := make([]int, 0, len(users))
|
||||
userNameList := make([]string, 0, len(users))
|
||||
userList := make([]string, 0, len(users))
|
||||
userPasswordList := make([]string, 0, len(users))
|
||||
for index, user := range users {
|
||||
userList = append(userList, index)
|
||||
userNameList = append(userNameList, user.Name)
|
||||
for _, user := range users {
|
||||
userList = append(userList, user.Name)
|
||||
var password string
|
||||
if user.AuthString != "" {
|
||||
password = user.AuthString
|
||||
|
|
@ -20,6 +18,5 @@ func (h *Inbound) UpdateUsers(users []option.HysteriaUser) error {
|
|||
userPasswordList = append(userPasswordList, password)
|
||||
}
|
||||
h.service.UpdateUsers(userList, userPasswordList)
|
||||
h.userNameList = userNameList
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,11 @@ func RegisterInbound(registry *inbound.Registry) {
|
|||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
service *hysteria2.Service[int]
|
||||
userNameList []string
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
service *hysteria2.Service[string]
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) {
|
||||
|
|
@ -113,7 +112,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
} else {
|
||||
udpTimeout = C.UDPTimeout
|
||||
}
|
||||
service, err := hysteria2.NewService[int](hysteria2.ServiceOptions{
|
||||
service, err := hysteria2.NewService[string](hysteria2.ServiceOptions{
|
||||
Context: ctx,
|
||||
Logger: logger,
|
||||
BrutalDebug: options.BrutalDebug,
|
||||
|
|
@ -129,17 +128,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userList := make([]int, 0, len(options.Users))
|
||||
userNameList := make([]string, 0, len(options.Users))
|
||||
userList := make([]string, 0, len(options.Users))
|
||||
userPasswordList := make([]string, 0, len(options.Users))
|
||||
for index, user := range options.Users {
|
||||
userList = append(userList, index)
|
||||
userNameList = append(userNameList, user.Name)
|
||||
for _, user := range options.Users {
|
||||
userList = append(userList, user.Name)
|
||||
userPasswordList = append(userPasswordList, user.Password)
|
||||
}
|
||||
service.UpdateUsers(userList, userPasswordList)
|
||||
inbound.service = service
|
||||
inbound.userNameList = userNameList
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
|
|
@ -155,8 +151,7 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
|
|||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
||||
} else {
|
||||
|
|
@ -177,8 +172,7 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@ import (
|
|||
)
|
||||
|
||||
func (h *Inbound) UpdateUsers(users []option.Hysteria2User) error {
|
||||
userList := make([]int, 0, len(users))
|
||||
userNameList := make([]string, 0, len(users))
|
||||
userList := make([]string, 0, len(users))
|
||||
userPasswordList := make([]string, 0, len(users))
|
||||
for index, user := range users {
|
||||
userList = append(userList, index)
|
||||
userNameList = append(userNameList, user.Name)
|
||||
for _, user := range users {
|
||||
userList = append(userList, user.Name)
|
||||
userPasswordList = append(userPasswordList, user.Password)
|
||||
}
|
||||
h.service.UpdateUsers(userList, userPasswordList)
|
||||
h.userNameList = userNameList
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
|
@ -34,8 +33,7 @@ type Inbound struct {
|
|||
router adapter.ConnectionRouterEx
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
service *trojan.Service[int]
|
||||
users []option.TrojanUser
|
||||
service *trojan.Service[string]
|
||||
tlsConfig tls.ServerConfig
|
||||
fallbackAddr M.Socksaddr
|
||||
fallbackAddrTLSNextProto map[string]M.Socksaddr
|
||||
|
|
@ -47,7 +45,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
Adapter: inbound.NewAdapter(C.TypeTrojan, tag),
|
||||
router: router,
|
||||
logger: logger,
|
||||
users: options.Users,
|
||||
}
|
||||
if options.TLS != nil {
|
||||
tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{
|
||||
|
|
@ -86,9 +83,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
}
|
||||
fallbackHandler = adapter.NewUpstreamContextHandlerEx(inbound.fallbackConnection, nil)
|
||||
}
|
||||
service := trojan.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnection, inbound.newPacketConnection), fallbackHandler, logger)
|
||||
err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int {
|
||||
return index
|
||||
service := trojan.NewService[string](adapter.NewUpstreamContextHandlerEx(inbound.newConnection, inbound.newPacketConnection), fallbackHandler, logger)
|
||||
err := service.UpdateUsers(common.Map(options.Users, func(it option.TrojanUser) string {
|
||||
return it.Name
|
||||
}), common.Map(options.Users, func(it option.TrojanUser) string {
|
||||
return it.Password
|
||||
}))
|
||||
|
|
@ -184,36 +181,34 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
|||
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
user, loaded := auth.UserFromContext[string](ctx)
|
||||
if !loaded {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
return
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
if user != "" {
|
||||
metadata.User = user
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
}
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
user, loaded := auth.UserFromContext[string](ctx)
|
||||
if !loaded {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
return
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
if user != "" {
|
||||
metadata.User = user
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
}
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,9 @@ import (
|
|||
)
|
||||
|
||||
func (h *Inbound) UpdateUsers(users []option.TrojanUser) error {
|
||||
err := h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.TrojanUser) int {
|
||||
return index
|
||||
return h.service.UpdateUsers(common.Map(users, func(it option.TrojanUser) string {
|
||||
return it.Name
|
||||
}), common.Map(users, func(it option.TrojanUser) string {
|
||||
return it.Password
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.users = users
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,11 @@ func RegisterInbound(registry *inbound.Registry) {
|
|||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
router adapter.ConnectionRouterEx
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
server *tuic.Service[int]
|
||||
userNameList []string
|
||||
router adapter.ConnectionRouterEx
|
||||
logger log.ContextLogger
|
||||
listener *listener.Listener
|
||||
tlsConfig tls.ServerConfig
|
||||
server *tuic.Service[string]
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) {
|
||||
|
|
@ -63,7 +62,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
} else {
|
||||
udpTimeout = C.UDPTimeout
|
||||
}
|
||||
service, err := tuic.NewService[int](tuic.ServiceOptions{
|
||||
service, err := tuic.NewService[string](tuic.ServiceOptions{
|
||||
Context: ctx,
|
||||
Logger: logger,
|
||||
TLSConfig: tlsConfig,
|
||||
|
|
@ -77,8 +76,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var userList []int
|
||||
var userNameList []string
|
||||
var userList []string
|
||||
var userUUIDList [][16]byte
|
||||
var userPasswordList []string
|
||||
for index, user := range options.Users {
|
||||
|
|
@ -89,14 +87,12 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if err != nil {
|
||||
return nil, E.Cause(err, "invalid uuid for user ", index)
|
||||
}
|
||||
userList = append(userList, index)
|
||||
userNameList = append(userNameList, user.Name)
|
||||
userList = append(userList, user.Name)
|
||||
userUUIDList = append(userUUIDList, userUUID)
|
||||
userPasswordList = append(userPasswordList, user.Password)
|
||||
}
|
||||
service.UpdateUsers(userList, userUUIDList, userPasswordList)
|
||||
inbound.server = service
|
||||
inbound.userNameList = userNameList
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
|
|
@ -112,8 +108,7 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
|
|||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
||||
} else {
|
||||
|
|
@ -134,8 +129,7 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||
metadata.User = userName
|
||||
h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func (h *Inbound) UpdateUsers(users []option.TUICUser) error {
|
||||
userList := make([]int, 0, len(users))
|
||||
userNameList := make([]string, 0, len(users))
|
||||
userList := make([]string, 0, len(users))
|
||||
userUUIDList := make([][16]byte, 0, len(users))
|
||||
userPasswordList := make([]string, 0, len(users))
|
||||
for index, user := range users {
|
||||
|
|
@ -20,12 +19,10 @@ func (h *Inbound) UpdateUsers(users []option.TUICUser) error {
|
|||
if err != nil {
|
||||
return E.Cause(err, "invalid uuid for user ", index)
|
||||
}
|
||||
userList = append(userList, index)
|
||||
userNameList = append(userNameList, user.Name)
|
||||
userList = append(userList, user.Name)
|
||||
userUUIDList = append(userUUIDList, userUUID)
|
||||
userPasswordList = append(userPasswordList, user.Password)
|
||||
}
|
||||
h.server.UpdateUsers(userList, userUUIDList, userPasswordList)
|
||||
h.userNameList = userNameList
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"github.com/sagernet/sing/common/auth"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
|
@ -39,8 +38,7 @@ type Inbound struct {
|
|||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
users []option.VLESSUser
|
||||
service *vless.Service[int]
|
||||
service *vless.Service[string]
|
||||
tlsConfig tls.ServerConfig
|
||||
transport adapter.V2RayServerTransport
|
||||
}
|
||||
|
|
@ -51,19 +49,18 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
ctx: ctx,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
users: options.Users,
|
||||
}
|
||||
var err error
|
||||
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service := vless.NewService[int](logger, adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx))
|
||||
service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
|
||||
return index
|
||||
}), common.Map(inbound.users, func(it option.VLESSUser) string {
|
||||
service := vless.NewService[string](logger, adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx))
|
||||
service.UpdateUsers(common.Map(options.Users, func(it option.VLESSUser) string {
|
||||
return it.Name
|
||||
}), common.Map(options.Users, func(it option.VLESSUser) string {
|
||||
return it.UUID
|
||||
}), common.Map(inbound.users, func(it option.VLESSUser) string {
|
||||
}), common.Map(options.Users, func(it option.VLESSUser) string {
|
||||
return it.Flow
|
||||
}))
|
||||
inbound.service = service
|
||||
|
|
@ -167,41 +164,45 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
|||
func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
user, loaded := auth.UserFromContext[string](ctx)
|
||||
if !loaded {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
return
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
if user != "" {
|
||||
metadata.User = user
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
}
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
user, loaded := auth.UserFromContext[string](ctx)
|
||||
if !loaded {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
return
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
if user != "" {
|
||||
metadata.User = user
|
||||
}
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination)
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
if user != "" {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet addr connection")
|
||||
}
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
if user != "" {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
}
|
||||
}
|
||||
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ import (
|
|||
)
|
||||
|
||||
func (h *Inbound) UpdateUsers(users []option.VLESSUser) error {
|
||||
h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.VLESSUser) int {
|
||||
return index
|
||||
h.service.UpdateUsers(common.Map(users, func(it option.VLESSUser) string {
|
||||
return it.Name
|
||||
}), common.Map(users, func(it option.VLESSUser) string {
|
||||
return it.UUID
|
||||
}), common.Map(users, func(it option.VLESSUser) string {
|
||||
return it.Flow
|
||||
}))
|
||||
h.users = users
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"github.com/sagernet/sing/common/auth"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
|
@ -40,8 +39,7 @@ type Inbound struct {
|
|||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
service *vmess.Service[int]
|
||||
users []option.VMessUser
|
||||
service *vmess.Service[string]
|
||||
tlsConfig tls.ServerConfig
|
||||
transport adapter.V2RayServerTransport
|
||||
}
|
||||
|
|
@ -52,7 +50,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
ctx: ctx,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
users: options.Users,
|
||||
}
|
||||
var err error
|
||||
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
|
||||
|
|
@ -66,10 +63,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if options.Transport != nil && options.Transport.Type != "" {
|
||||
serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection())
|
||||
}
|
||||
service := vmess.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx), serviceOptions...)
|
||||
service := vmess.NewService[string](adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx), serviceOptions...)
|
||||
inbound.service = service
|
||||
err = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int {
|
||||
return index
|
||||
err = service.UpdateUsers(common.Map(options.Users, func(it option.VMessUser) string {
|
||||
return it.Name
|
||||
}), common.Map(options.Users, func(it option.VMessUser) string {
|
||||
return it.UUID
|
||||
}), common.Map(options.Users, func(it option.VMessUser) int {
|
||||
|
|
@ -173,41 +170,45 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
|||
func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
user, loaded := auth.UserFromContext[string](ctx)
|
||||
if !loaded {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
return
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
if user != "" {
|
||||
metadata.User = user
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
}
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
user, loaded := auth.UserFromContext[string](ctx)
|
||||
if !loaded {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
return
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
if user != "" {
|
||||
metadata.User = user
|
||||
}
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination)
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
if user != "" {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet addr connection")
|
||||
}
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
if user != "" {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
}
|
||||
}
|
||||
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,11 @@ import (
|
|||
)
|
||||
|
||||
func (h *Inbound) UpdateUsers(users []option.VMessUser) error {
|
||||
err := h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.VMessUser) int {
|
||||
return index
|
||||
return h.service.UpdateUsers(common.Map(users, func(it option.VMessUser) string {
|
||||
return it.Name
|
||||
}), common.Map(users, func(it option.VMessUser) string {
|
||||
return it.UUID
|
||||
}), common.Map(users, func(it option.VMessUser) int {
|
||||
return it.AlterId
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.users = users
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,28 @@ func (s *ClientService) GetAll() (*[]model.Client, error) {
|
|||
return &clients, nil
|
||||
}
|
||||
|
||||
// validateClientName rejects empty names and globally duplicate names,
|
||||
// then stores the trimmed name back on the client.
|
||||
func (s *ClientService) validateClientName(tx *gorm.DB, client *model.Client) error {
|
||||
name := strings.TrimSpace(client.Name)
|
||||
if name == "" {
|
||||
return common.NewError("client name must not be empty")
|
||||
}
|
||||
query := tx.Model(model.Client{}).Where("name = ?", name)
|
||||
if client.Id != 0 {
|
||||
query = query.Where("id != ?", client.Id)
|
||||
}
|
||||
var count int64
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return common.NewErrorf("client name %q is already in use", name)
|
||||
}
|
||||
client.Name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) {
|
||||
var err error
|
||||
var inboundIds []uint
|
||||
|
|
@ -58,6 +80,9 @@ func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, host
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.validateClientName(tx, &client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = setConfigIdentity(&client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -91,7 +116,17 @@ func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, host
|
|||
return nil, err
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
// Every client is validated before any of them is written, so the
|
||||
// batch has to be checked against itself as well as against the table.
|
||||
seen := make(map[string]bool, len(clients))
|
||||
for _, client := range clients {
|
||||
if err = s.validateClientName(tx, client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if seen[client.Name] {
|
||||
return nil, common.NewErrorf("duplicate client name %q in request", client.Name)
|
||||
}
|
||||
seen[client.Name] = true
|
||||
if err = setConfigIdentity(client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -116,7 +151,15 @@ func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, host
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seen := make(map[string]bool, len(clients))
|
||||
for _, client := range clients {
|
||||
if err = s.validateClientName(tx, client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if seen[client.Name] {
|
||||
return nil, common.NewErrorf("duplicate client name %q in request", client.Name)
|
||||
}
|
||||
seen[client.Name] = true
|
||||
changedInboundIds, err := s.findInboundsChanges(tx, client, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue