mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
Add process_name/package_name/user/user_id rule item
This commit is contained in:
126
route/router.go
126
route/router.go
@@ -8,6 +8,7 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-box/common/geosite"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -39,41 +41,36 @@ import (
|
||||
var _ adapter.Router = (*Router)(nil)
|
||||
|
||||
type Router struct {
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
dnsLogger log.ContextLogger
|
||||
|
||||
outbounds []adapter.Outbound
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
rules []adapter.Rule
|
||||
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
dnsLogger log.ContextLogger
|
||||
outbounds []adapter.Outbound
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
rules []adapter.Rule
|
||||
defaultDetour string
|
||||
defaultOutboundForConnection adapter.Outbound
|
||||
defaultOutboundForPacketConnection adapter.Outbound
|
||||
|
||||
needGeoIPDatabase bool
|
||||
needGeositeDatabase bool
|
||||
geoIPOptions option.GeoIPOptions
|
||||
geositeOptions option.GeositeOptions
|
||||
geoIPReader *geoip.Reader
|
||||
geositeReader *geosite.Reader
|
||||
geositeCache map[string]adapter.Rule
|
||||
|
||||
dnsClient *dns.Client
|
||||
defaultDomainStrategy dns.DomainStrategy
|
||||
dnsRules []adapter.Rule
|
||||
defaultTransport dns.Transport
|
||||
transports []dns.Transport
|
||||
transportMap map[string]dns.Transport
|
||||
|
||||
interfaceBindManager control.BindManager
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
autoDetectInterface bool
|
||||
defaultInterface string
|
||||
interfaceMonitor DefaultInterfaceMonitor
|
||||
|
||||
trafficController adapter.TrafficController
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
needGeoIPDatabase bool
|
||||
needGeositeDatabase bool
|
||||
geoIPOptions option.GeoIPOptions
|
||||
geositeOptions option.GeositeOptions
|
||||
geoIPReader *geoip.Reader
|
||||
geositeReader *geosite.Reader
|
||||
geositeCache map[string]adapter.Rule
|
||||
dnsClient *dns.Client
|
||||
defaultDomainStrategy dns.DomainStrategy
|
||||
dnsRules []adapter.Rule
|
||||
defaultTransport dns.Transport
|
||||
transports []dns.Transport
|
||||
transportMap map[string]dns.Transport
|
||||
interfaceBindManager control.BindManager
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
autoDetectInterface bool
|
||||
defaultInterface string
|
||||
interfaceMonitor DefaultInterfaceMonitor
|
||||
trafficController adapter.TrafficController
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
processSearcher process.Searcher
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
|
||||
@@ -84,8 +81,8 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||
dnsRules: make([]adapter.Rule, 0, len(dnsOptions.Rules)),
|
||||
needGeoIPDatabase: hasGeoRule(options.Rules, isGeoIPRule) || hasGeoDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||
needGeositeDatabase: hasGeoRule(options.Rules, isGeositeRule) || hasGeoDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||
geoIPOptions: common.PtrValueOrDefault(options.GeoIP),
|
||||
geositeOptions: common.PtrValueOrDefault(options.Geosite),
|
||||
geositeCache: make(map[string]adapter.Rule),
|
||||
@@ -221,6 +218,13 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
|
||||
}
|
||||
router.interfaceMonitor = interfaceMonitor
|
||||
}
|
||||
if hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess {
|
||||
searcher, err := process.NewSearcher(logger)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create process searcher")
|
||||
}
|
||||
router.processSearcher = searcher
|
||||
}
|
||||
return router, nil
|
||||
}
|
||||
|
||||
@@ -376,6 +380,14 @@ func (r *Router) Start() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.processSearcher != nil {
|
||||
if starter, isStarter := r.processSearcher.(common.Starter); isStarter {
|
||||
err := starter.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize process searcher")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -396,6 +408,7 @@ func (r *Router) Close() error {
|
||||
common.PtrOrNil(r.geoIPReader),
|
||||
r.interfaceMonitor,
|
||||
r.networkMonitor,
|
||||
r.processSearcher,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -464,7 +477,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
metadata.DestinationAddresses = addresses
|
||||
r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
|
||||
}
|
||||
matchedRule, detour := r.match(ctx, metadata, r.defaultOutboundForConnection)
|
||||
matchedRule, detour := r.match(ctx, &metadata, r.defaultOutboundForConnection)
|
||||
if !common.Contains(detour.Network(), C.NetworkTCP) {
|
||||
conn.Close()
|
||||
return E.New("missing supported outbound, closing connection")
|
||||
@@ -509,7 +522,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
metadata.DestinationAddresses = addresses
|
||||
r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
|
||||
}
|
||||
matchedRule, detour := r.match(ctx, metadata, r.defaultOutboundForPacketConnection)
|
||||
matchedRule, detour := r.match(ctx, &metadata, r.defaultOutboundForPacketConnection)
|
||||
if !common.Contains(detour.Network(), C.NetworkUDP) {
|
||||
conn.Close()
|
||||
return E.New("missing supported outbound, closing packet connection")
|
||||
@@ -532,9 +545,34 @@ func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr
|
||||
return r.dnsClient.Lookup(ctx, r.matchDNS(ctx), domain, r.defaultDomainStrategy)
|
||||
}
|
||||
|
||||
func (r *Router) match(ctx context.Context, metadata adapter.InboundContext, defaultOutbound adapter.Outbound) (adapter.Rule, adapter.Outbound) {
|
||||
func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (adapter.Rule, adapter.Outbound) {
|
||||
if r.processSearcher != nil {
|
||||
processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.Addr, int(metadata.Source.Port))
|
||||
if err != nil {
|
||||
r.logger.DebugContext(ctx, "failed to search process: ", err)
|
||||
} else {
|
||||
if processInfo.ProcessPath != "" {
|
||||
r.logger.DebugContext(ctx, "found process path: ", processInfo.ProcessPath)
|
||||
} else if processInfo.PackageName != "" {
|
||||
r.logger.DebugContext(ctx, "found package name: ", processInfo.PackageName)
|
||||
} else if processInfo.UserId != -1 {
|
||||
if /*needUserName &&*/ true {
|
||||
osUser, _ := user.LookupId(F.ToString(processInfo.UserId))
|
||||
if osUser != nil {
|
||||
processInfo.User = osUser.Username
|
||||
}
|
||||
}
|
||||
if processInfo.User != "" {
|
||||
r.logger.DebugContext(ctx, "found user: ", processInfo.User)
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "found user id: ", processInfo.UserId)
|
||||
}
|
||||
}
|
||||
metadata.ProcessInfo = processInfo
|
||||
}
|
||||
}
|
||||
for i, rule := range r.rules {
|
||||
if rule.Match(&metadata) {
|
||||
if rule.Match(metadata) {
|
||||
detour := rule.Outbound()
|
||||
r.logger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||
if outbound, loaded := r.Outbound(detour); loaded {
|
||||
@@ -606,7 +644,7 @@ func (r *Router) URLTestHistoryStorage(create bool) *urltest.HistoryStorage {
|
||||
return r.urlTestHistoryStorage
|
||||
}
|
||||
|
||||
func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||
for _, rule := range rules {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
@@ -624,7 +662,7 @@ func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bo
|
||||
return false
|
||||
}
|
||||
|
||||
func hasGeoDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
||||
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
||||
for _, rule := range rules {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
@@ -658,6 +696,14 @@ func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
||||
return len(rule.Geosite) > 0
|
||||
}
|
||||
|
||||
func isProcessRule(rule option.DefaultRule) bool {
|
||||
return len(rule.ProcessName) > 0
|
||||
}
|
||||
|
||||
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
||||
return len(rule.ProcessName) > 0
|
||||
}
|
||||
|
||||
func notPrivateNode(code string) bool {
|
||||
return code != "private"
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||
return nil, E.New("invalid network: ", options.Network)
|
||||
}
|
||||
}
|
||||
if len(options.User) > 0 {
|
||||
item := NewUserItem(options.User)
|
||||
if len(options.AuthUser) > 0 {
|
||||
item := NewAuthUserItem(options.AuthUser)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
@@ -155,6 +155,26 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.ProcessName) > 0 {
|
||||
item := NewProcessItem(options.ProcessName)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.PackageName) > 0 {
|
||||
item := NewPackageNameItem(options.PackageName)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.User) > 0 {
|
||||
item := NewUserItem(options.User)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.UserID) > 0 {
|
||||
item := NewUserIDItem(options.UserID)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
|
||||
37
route/rule_auth_user.go
Normal file
37
route/rule_auth_user.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*AuthUserItem)(nil)
|
||||
|
||||
type AuthUserItem struct {
|
||||
users []string
|
||||
userMap map[string]bool
|
||||
}
|
||||
|
||||
func NewAuthUserItem(users []string) *AuthUserItem {
|
||||
userMap := make(map[string]bool)
|
||||
for _, protocol := range users {
|
||||
userMap[protocol] = true
|
||||
}
|
||||
return &AuthUserItem{
|
||||
users: users,
|
||||
userMap: userMap,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AuthUserItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return r.userMap[metadata.User]
|
||||
}
|
||||
|
||||
func (r *AuthUserItem) String() string {
|
||||
if len(r.users) == 1 {
|
||||
return F.ToString("auth_user=", r.users[0])
|
||||
}
|
||||
return F.ToString("auth_user=[", strings.Join(r.users, " "), "]")
|
||||
}
|
||||
@@ -70,8 +70,8 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||
return nil, E.New("invalid network: ", options.Network)
|
||||
}
|
||||
}
|
||||
if len(options.User) > 0 {
|
||||
item := NewUserItem(options.User)
|
||||
if len(options.AuthUser) > 0 {
|
||||
item := NewAuthUserItem(options.AuthUser)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
@@ -126,6 +126,26 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.ProcessName) > 0 {
|
||||
item := NewProcessItem(options.ProcessName)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.PackageName) > 0 {
|
||||
item := NewPackageNameItem(options.PackageName)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.User) > 0 {
|
||||
item := NewUserItem(options.User)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.UserID) > 0 {
|
||||
item := NewUserIDItem(options.UserID)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.Outbound) > 0 {
|
||||
item := NewOutboundRule(options.Outbound)
|
||||
rule.items = append(rule.items, item)
|
||||
|
||||
43
route/rule_package_name.go
Normal file
43
route/rule_package_name.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*PackageNameItem)(nil)
|
||||
|
||||
type PackageNameItem struct {
|
||||
packageNames []string
|
||||
packageMap map[string]bool
|
||||
}
|
||||
|
||||
func NewPackageNameItem(packageNameList []string) *PackageNameItem {
|
||||
rule := &PackageNameItem{
|
||||
packageNames: packageNameList,
|
||||
packageMap: make(map[string]bool),
|
||||
}
|
||||
for _, packageName := range packageNameList {
|
||||
rule.packageMap[packageName] = true
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.PackageName == "" {
|
||||
return false
|
||||
}
|
||||
return r.packageMap[metadata.ProcessInfo.PackageName]
|
||||
}
|
||||
|
||||
func (r *PackageNameItem) String() string {
|
||||
var description string
|
||||
pLen := len(r.packageNames)
|
||||
if pLen == 1 {
|
||||
description = "package_name=" + r.packageNames[0]
|
||||
} else {
|
||||
description = "package_name=[" + strings.Join(r.packageNames, " ") + "]"
|
||||
}
|
||||
return description
|
||||
}
|
||||
44
route/rule_process.go
Normal file
44
route/rule_process.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*ProcessItem)(nil)
|
||||
|
||||
type ProcessItem struct {
|
||||
processes []string
|
||||
processMap map[string]bool
|
||||
}
|
||||
|
||||
func NewProcessItem(processNameList []string) *ProcessItem {
|
||||
rule := &ProcessItem{
|
||||
processes: processNameList,
|
||||
processMap: make(map[string]bool),
|
||||
}
|
||||
for _, processName := range processNameList {
|
||||
rule.processMap[strings.ToLower(processName)] = true
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *ProcessItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" {
|
||||
return false
|
||||
}
|
||||
return r.processMap[strings.ToLower(filepath.Base(metadata.ProcessInfo.ProcessPath))]
|
||||
}
|
||||
|
||||
func (r *ProcessItem) String() string {
|
||||
var description string
|
||||
pLen := len(r.processes)
|
||||
if pLen == 1 {
|
||||
description = "process_name=" + r.processes[0]
|
||||
} else {
|
||||
description = "process_name=[" + strings.Join(r.processes, " ") + "]"
|
||||
}
|
||||
return description
|
||||
}
|
||||
@@ -26,7 +26,10 @@ func NewUserItem(users []string) *UserItem {
|
||||
}
|
||||
|
||||
func (r *UserItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return r.userMap[metadata.User]
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.User == "" {
|
||||
return false
|
||||
}
|
||||
return r.userMap[metadata.ProcessInfo.User]
|
||||
}
|
||||
|
||||
func (r *UserItem) String() string {
|
||||
|
||||
44
route/rule_user_id.go
Normal file
44
route/rule_user_id.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*UserIdItem)(nil)
|
||||
|
||||
type UserIdItem struct {
|
||||
userIds []int32
|
||||
userIdMap map[int32]bool
|
||||
}
|
||||
|
||||
func NewUserIDItem(userIdList []int32) *UserIdItem {
|
||||
rule := &UserIdItem{
|
||||
userIds: userIdList,
|
||||
userIdMap: make(map[int32]bool),
|
||||
}
|
||||
for _, userId := range userIdList {
|
||||
rule.userIdMap[userId] = true
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *UserIdItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.ProcessInfo == nil || metadata.ProcessInfo.UserId == -1 {
|
||||
return false
|
||||
}
|
||||
return r.userIdMap[metadata.ProcessInfo.UserId]
|
||||
}
|
||||
|
||||
func (r *UserIdItem) String() string {
|
||||
var description string
|
||||
pLen := len(r.userIds)
|
||||
if pLen == 1 {
|
||||
description = "user_id=" + F.ToString(r.userIds[0])
|
||||
} else {
|
||||
description = "user_id=[" + strings.Join(F.MapToString(r.userIds), " ") + "]"
|
||||
}
|
||||
return description
|
||||
}
|
||||
Reference in New Issue
Block a user