diff --git a/adapter/inbound/addition.go b/adapter/inbound/addition.go index 5966e784..47307ed7 100644 --- a/adapter/inbound/addition.go +++ b/adapter/inbound/addition.go @@ -16,6 +16,12 @@ func WithInName(name string) Addition { } } +func WithInUser(user string) Addition { + return func(metadata *C.Metadata) { + metadata.InUser = user + } +} + func WithSpecialRules(specialRules string) Addition { return func(metadata *C.Metadata) { metadata.SpecialRules = specialRules diff --git a/constant/metadata.go b/constant/metadata.go index 198c447d..edc58aec 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -133,6 +133,7 @@ type Metadata struct { InIP netip.Addr `json:"inboundIP"` InPort string `json:"inboundPort"` InName string `json:"inboundName"` + InUser string `json:"inboundUser"` Host string `json:"host"` DNSMode DNSMode `json:"dnsMode"` Uid uint32 `json:"uid"` diff --git a/constant/rule.go b/constant/rule.go index 28c629a0..906f3cef 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -14,12 +14,14 @@ const ( SrcPort DstPort InPort + InUser + InName + InType Process ProcessPath RuleSet Network Uid - INTYPE SubRules MATCH AND @@ -55,6 +57,12 @@ func (rt RuleType) String() string { return "DstPort" case InPort: return "InPort" + case InUser: + return "InUser" + case InName: + return "InName" + case InType: + return "InType" case Process: return "Process" case ProcessPath: @@ -67,8 +75,6 @@ func (rt RuleType) String() string { return "Network" case Uid: return "Uid" - case INTYPE: - return "InType" case SubRules: return "SubRules" case AND: diff --git a/listener/sing/context.go b/listener/sing/context.go index f7aed851..a500e4a4 100644 --- a/listener/sing/context.go +++ b/listener/sing/context.go @@ -2,8 +2,11 @@ package sing import ( "context" + "golang.org/x/exp/slices" "github.com/Dreamacro/clash/adapter/inbound" + + "github.com/sagernet/sing/common/auth" ) type contextKey string @@ -22,3 +25,20 @@ func getAdditions(ctx context.Context) []inbound.Addition { } return nil } + +func combineAdditions(ctx context.Context, additions []inbound.Addition) []inbound.Addition { + additionsCloned := false + if ctxAdditions := getAdditions(ctx); len(ctxAdditions) > 0 { + additions = slices.Clone(additions) + additionsCloned = true + additions = append(additions, ctxAdditions...) + } + if user, ok := auth.UserFromContext[string](ctx); ok { + if !additionsCloned { + additions = slices.Clone(additions) + additionsCloned = true + } + additions = append(additions, inbound.WithInUser(user)) + } + return additions +} diff --git a/listener/sing/sing.go b/listener/sing/sing.go index 9bf52e9c..c60bbe67 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -3,7 +3,6 @@ package sing import ( "context" "errors" - "golang.org/x/exp/slices" "net" "net/netip" "sync" @@ -74,11 +73,6 @@ func UpstreamMetadata(metadata M.Metadata) M.Metadata { } func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - additions := h.Additions - if ctxAdditions := getAdditions(ctx); len(ctxAdditions) > 0 { - additions = slices.Clone(additions) - additions = append(additions, ctxAdditions...) - } switch metadata.Destination.Fqdn { case mux.Destination.Fqdn: return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata)) @@ -103,16 +97,11 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta if deadline.NeedAdditionalReadDeadline(conn) { conn = N.NewDeadlineConn(conn) // conn from sing should check NeedAdditionalReadDeadline } - h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{ExtendedConn: N.NewExtendedConn(conn), wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type, additions...) + h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{ExtendedConn: N.NewExtendedConn(conn), wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type, combineAdditions(ctx, h.Additions)...) return nil } func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { - additions := h.Additions - if ctxAdditions := getAdditions(ctx); len(ctxAdditions) > 0 { - additions = slices.Clone(additions) - additions = append(additions, ctxAdditions...) - } if deadline.NeedAdditionalReadDeadline(conn) { conn = deadline.NewFallbackPacketConn(bufio.NewNetPacketConn(conn)) // conn from sing should check NeedAdditionalReadDeadline } @@ -162,7 +151,7 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network. buff: buff, } select { - case h.UdpIn <- inbound.NewPacket(target, packet, h.Type, additions...): + case h.UdpIn <- inbound.NewPacket(target, packet, h.Type, combineAdditions(ctx, h.Additions)...): default: } } diff --git a/rules/common/in_name.go b/rules/common/in_name.go new file mode 100644 index 00000000..1e2abe15 --- /dev/null +++ b/rules/common/in_name.go @@ -0,0 +1,49 @@ +package common + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" + "strings" +) + +type InName struct { + *Base + names []string + adapter string + payload string +} + +func (u *InName) Match(metadata *C.Metadata) (bool, string) { + for _, name := range u.names { + if metadata.InName == name { + return true, u.adapter + } + } + return false, "" +} + +func (u *InName) RuleType() C.RuleType { + return C.InName +} + +func (u *InName) Adapter() string { + return u.adapter +} + +func (u *InName) Payload() string { + return u.payload +} + +func NewInName(iNames, adapter string) (*InName, error) { + names := strings.Split(iNames, "/") + if len(names) == 0 { + return nil, fmt.Errorf("in name couldn't be empty") + } + + return &InName{ + Base: &Base{}, + names: names, + adapter: adapter, + payload: iNames, + }, nil +} diff --git a/rules/common/in_type.go b/rules/common/in_type.go index 520c9594..453045d8 100644 --- a/rules/common/in_type.go +++ b/rules/common/in_type.go @@ -23,7 +23,7 @@ func (u *InType) Match(metadata *C.Metadata) (bool, string) { } func (u *InType) RuleType() C.RuleType { - return C.INTYPE + return C.InType } func (u *InType) Adapter() string { @@ -37,7 +37,7 @@ func (u *InType) Payload() string { func NewInType(iTypes, adapter string) (*InType, error) { types := strings.Split(iTypes, "/") if len(types) == 0 { - return nil, fmt.Errorf("in type could be empty") + return nil, fmt.Errorf("in type couldn't be empty") } tps, err := parseInTypes(types) diff --git a/rules/common/in_user.go b/rules/common/in_user.go new file mode 100644 index 00000000..24f4b2e5 --- /dev/null +++ b/rules/common/in_user.go @@ -0,0 +1,49 @@ +package common + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" + "strings" +) + +type InUser struct { + *Base + users []string + adapter string + payload string +} + +func (u *InUser) Match(metadata *C.Metadata) (bool, string) { + for _, user := range u.users { + if metadata.InUser == user { + return true, u.adapter + } + } + return false, "" +} + +func (u *InUser) RuleType() C.RuleType { + return C.InUser +} + +func (u *InUser) Adapter() string { + return u.adapter +} + +func (u *InUser) Payload() string { + return u.payload +} + +func NewInUser(iUsers, adapter string) (*InUser, error) { + users := strings.Split(iUsers, "/") + if len(users) == 0 { + return nil, fmt.Errorf("in user couldn't be empty") + } + + return &InUser{ + Base: &Base{}, + users: users, + adapter: adapter, + payload: iUsers, + }, nil +} diff --git a/rules/parser.go b/rules/parser.go index 1a336225..df790bc3 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -47,6 +47,10 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string] parsed, parseErr = RC.NewUid(payload, target) case "IN-TYPE": parsed, parseErr = RC.NewInType(payload, target) + case "IN-USER": + parsed, parseErr = RC.NewInUser(payload, target) + case "IN-NAME": + parsed, parseErr = RC.NewInName(payload, target) case "SUB-RULE": parsed, parseErr = logic.NewSubRule(payload, target, subRules, ParseRule) case "AND":