From 7d5d0918fb3099a92a6f2614c70a4f8c0ae2063b Mon Sep 17 00:00:00 2001 From: srk24 <25836646+srk24@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:40:58 +0800 Subject: [PATCH] feat:add fule type process_path_prefix, process_path_regex --- docs/clients/android/features.md | 2 + docs/clients/apple/features.md | 2 + docs/configuration/dns/rule.md | 26 ++++++++++ docs/configuration/dns/rule.zh.md | 26 ++++++++++ docs/configuration/route/rule.md | 28 ++++++++++ docs/configuration/route/rule.zh.md | 29 ++++++++++- docs/configuration/rule-set/headless-rule.md | 26 ++++++++++ option/rule.go | 2 + option/rule_dns.go | 2 + option/rule_set.go | 38 +++++++------- route/rule_default.go | 13 +++++ route/rule_dns.go | 13 +++++ route/rule_headless.go | 13 +++++ route/rule_item_process_path_prefix.go | 42 +++++++++++++++ route/rule_item_process_path_regex.go | 54 ++++++++++++++++++++ 15 files changed, 297 insertions(+), 19 deletions(-) create mode 100644 route/rule_item_process_path_prefix.go create mode 100644 route/rule_item_process_path_regex.go diff --git a/docs/clients/android/features.md b/docs/clients/android/features.md index 8fe84add..aac07419 100644 --- a/docs/clients/android/features.md +++ b/docs/clients/android/features.md @@ -40,6 +40,8 @@ SFA provides an unprivileged TUN implementation through Android VpnService. |-----------------------|------------------|-----------------------------------| | `process_name` | :material-close: | No permission | | `process_path` | :material-close: | No permission | +| `process_path_prefix` | :material-close: | No permission | +| `process_path_regex` | :material-close: | No permission | | `package_name` | :material-check: | / | | `user` | :material-close: | Use `package_name` instead | | `user_id` | :material-close: | Use `package_name` instead | diff --git a/docs/clients/apple/features.md b/docs/clients/apple/features.md index 7d419103..c2ac3df4 100644 --- a/docs/clients/apple/features.md +++ b/docs/clients/apple/features.md @@ -42,6 +42,8 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension |-----------------------|------------------|-----------------------| | `process_name` | :material-close: | No permission | | `process_path` | :material-close: | No permission | +| `process_path_prefix` | :material-close: | No permission | +| `process_path_regex` | :material-close: | No permission | | `package_name` | :material-close: | / | | `user` | :material-close: | No permission | | `user_id` | :material-close: | No permission | diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 6b3d6519..d38d74a4 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -103,6 +103,12 @@ icon: material/new-box "process_path": [ "/usr/bin/curl" ], + "process_path_prefix": [ + "/usr/bin/" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -268,6 +274,26 @@ Match process name. Match process path. +#### process_path_prefix + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using prefix. + +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index eecddb38..49f293e2 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -103,6 +103,12 @@ icon: material/new-box "process_path": [ "/usr/bin/curl" ], + "process_path_prefix": [ + "/usr/bin/" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -266,6 +272,26 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配进程路径。 +#### process_path_prefix + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配进程路径前缀。 + +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + #### package_name 匹配 Android 应用包名。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 8e61e51d..8f29ad0b 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -7,6 +7,8 @@ icon: material/alert-decagram :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [process_path_prefix](#process_path_prefix) + :material-plus: [process_path_regex](#process_path_regex) !!! quote "Changes in sing-box 1.8.0" @@ -101,6 +103,12 @@ icon: material/alert-decagram "process_path": [ "/usr/bin/curl" ], + "process_path_prefix": [ + "/usr/bin/" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -277,6 +285,26 @@ Match process name. Match process path. +#### process_path_prefix + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using prefix. + +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 68e66cf5..de165ba4 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -6,7 +6,8 @@ icon: material/alert-decagram :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) - :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [process_path_prefix](#process_path_prefix) + :material-plus: [process_path_regex](#process_path_regex) !!! quote "sing-box 1.8.0 中的更改" @@ -99,6 +100,12 @@ icon: material/alert-decagram "process_path": [ "/usr/bin/curl" ], + "process_path_prefix": [ + "/usr/bin/" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -275,6 +282,26 @@ icon: material/alert-decagram 匹配进程路径。 +#### process_path_prefix + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配进程路径前缀。 + +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + #### package_name 匹配 Android 应用包名。 diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index e766904b..9dde8797 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -57,6 +57,12 @@ "process_path": [ "/usr/bin/curl" ], + "process_path_prefix": [ + "/usr/bin/" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -160,6 +166,26 @@ Match process name. Match process path. +#### process_path_prefix + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using prefix. + +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. diff --git a/option/rule.go b/option/rule.go index 5c69ef00..ebf77908 100644 --- a/option/rule.go +++ b/option/rule.go @@ -88,6 +88,8 @@ type _DefaultRule struct { PortRange Listable[string] `json:"port_range,omitempty"` ProcessName Listable[string] `json:"process_name,omitempty"` ProcessPath Listable[string] `json:"process_path,omitempty"` + ProcessPathPrefix Listable[string] `json:"process_path_prefix,omitempty"` + ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` PackageName Listable[string] `json:"package_name,omitempty"` User Listable[string] `json:"user,omitempty"` UserID Listable[int32] `json:"user_id,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 2afe245e..ead783b5 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -88,6 +88,8 @@ type _DefaultDNSRule struct { PortRange Listable[string] `json:"port_range,omitempty"` ProcessName Listable[string] `json:"process_name,omitempty"` ProcessPath Listable[string] `json:"process_path,omitempty"` + ProcessPathPrefix Listable[string] `json:"process_path_prefix,omitempty"` + ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` PackageName Listable[string] `json:"package_name,omitempty"` User Listable[string] `json:"user,omitempty"` UserID Listable[int32] `json:"user_id,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index a83b2c47..fdc979eb 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -144,24 +144,26 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` + Network Listable[string] `json:"network,omitempty"` + Domain Listable[string] `json:"domain,omitempty"` + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + SourcePort Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange Listable[string] `json:"source_port_range,omitempty"` + Port Listable[uint16] `json:"port,omitempty"` + PortRange Listable[string] `json:"port_range,omitempty"` + ProcessName Listable[string] `json:"process_name,omitempty"` + ProcessPath Listable[string] `json:"process_path,omitempty"` + ProcessPathPrefix Listable[string] `json:"process_path_prefix,omitempty"` + ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` + PackageName Listable[string] `json:"package_name,omitempty"` + WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` diff --git a/route/rule_default.go b/route/rule_default.go index 4bbc2b6b..de6cb711 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -179,6 +179,19 @@ 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.ProcessPathPrefix) > 0 { + item := NewProcessPathPrefixItem(options.ProcessPathPrefix) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + 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) diff --git a/route/rule_dns.go b/route/rule_dns.go index 1b79d30b..708de58c 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -183,6 +183,19 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathPrefix) > 0 { + item := NewProcessPathPrefixItem(options.ProcessPathPrefix) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + 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) diff --git a/route/rule_headless.go b/route/rule_headless.go index d537df57..0425776a 100644 --- a/route/rule_headless.go +++ b/route/rule_headless.go @@ -123,6 +123,19 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathPrefix) > 0 { + item := NewProcessPathPrefixItem(options.ProcessPathPrefix) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + 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) diff --git a/route/rule_item_process_path_prefix.go b/route/rule_item_process_path_prefix.go new file mode 100644 index 00000000..60874ada --- /dev/null +++ b/route/rule_item_process_path_prefix.go @@ -0,0 +1,42 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*ProcessPathPrefixItem)(nil) + +type ProcessPathPrefixItem struct { + processes []string +} + +func NewProcessPathPrefixItem(directories []string) *ProcessPathPrefixItem { + return &ProcessPathPrefixItem{ + processes: directories, + } +} + +func (r *ProcessPathPrefixItem) Match(metadata *adapter.InboundContext) bool { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + return false + } + for _, processe := range r.processes { + if strings.HasPrefix(metadata.ProcessInfo.ProcessPath, processe) { + return true + } + } + return false +} + +func (r *ProcessPathPrefixItem) String() string { + var description string + pLen := len(r.processes) + if pLen == 1 { + description = "process_path_prefix=" + r.processes[0] + } else { + description = "process_path_prefix=[" + strings.Join(r.processes, " ") + "]" + } + return description +} diff --git a/route/rule_item_process_path_regex.go b/route/rule_item_process_path_regex.go new file mode 100644 index 00000000..01b2723c --- /dev/null +++ b/route/rule_item_process_path_regex.go @@ -0,0 +1,54 @@ +package route + +import ( + "regexp" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*ProcessPathRegexItem)(nil) + +type ProcessPathRegexItem struct { + matchers []*regexp.Regexp + description string +} + +func NewProcessPathRegexItem(expressions []string) (*ProcessPathRegexItem, error) { + matchers := make([]*regexp.Regexp, 0, len(expressions)) + for i, regex := range expressions { + matcher, err := regexp.Compile(regex) + if err != nil { + return nil, E.Cause(err, "parse expression ", i) + } + matchers = append(matchers, matcher) + } + description := "process_path_regex=" + eLen := len(expressions) + if eLen == 1 { + description += expressions[0] + } else if eLen > 3 { + description += F.ToString("[", strings.Join(expressions[:3], " "), "]") + } else { + description += F.ToString("[", strings.Join(expressions, " "), "]") + } + return &ProcessPathRegexItem{matchers, description}, nil +} + +func (r *ProcessPathRegexItem) Match(metadata *adapter.InboundContext) bool { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + return false + } + for _, matcher := range r.matchers { + if matcher.MatchString(metadata.ProcessInfo.ProcessPath) { + return true + } + } + return false +} + +func (r *ProcessPathRegexItem) String() string { + return r.description +}