Sfoglia il codice sorgente

Add `process_path_regex` rule type

srk24 1 anno fa
parent
commit
a07219d16d

+ 9 - 0
common/srs/binary.go

@@ -37,6 +37,7 @@ const (
 	ruleItemWIFISSID
 	ruleItemWIFIBSSID
 	ruleItemAdGuardDomain
+	ruleItemProcessPathRegex
 	ruleItemFinal uint8 = 0xFF
 )
 
@@ -207,6 +208,8 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
 			rule.ProcessName, err = readRuleItemString(reader)
 		case ruleItemProcessPath:
 			rule.ProcessPath, err = readRuleItemString(reader)
+		case ruleItemProcessPathRegex:
+			rule.ProcessPathRegex, err = readRuleItemString(reader)
 		case ruleItemPackageName:
 			rule.PackageName, err = readRuleItemString(reader)
 		case ruleItemWIFISSID:
@@ -326,6 +329,12 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
 			return err
 		}
 	}
+	if len(rule.ProcessPathRegex) > 0 {
+		err = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex)
+		if err != nil {
+			return err
+		}
+	}
 	if len(rule.PackageName) > 0 {
 		err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
 		if err != nil {

+ 1 - 0
docs/clients/android/features.md

@@ -40,6 +40,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
 |-----------------------|------------------|-----------------------------------|
 | `process_name`        | :material-close: | No permission                     |
 | `process_path`        | :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        |

+ 1 - 0
docs/clients/apple/features.md

@@ -42,6 +42,7 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension
 |-----------------------|------------------|-----------------------|
 | `process_name`        | :material-close: | No permission         |
 | `process_path`        | :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         |

+ 15 - 1
docs/configuration/dns/rule.md

@@ -6,7 +6,8 @@ icon: material/new-box
 
     :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: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
+    :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)  
+    :material-plus: [process_path_regex](#process_path_regex)
 
 !!! quote "Changes in sing-box 1.9.0"
 
@@ -103,6 +104,9 @@ icon: material/new-box
         "process_path": [
           "/usr/bin/curl"
         ],
+        "process_path_regex": [
+          "^/usr/bin/.+"
+        ],
         "package_name": [
           "com.termux"
         ],
@@ -268,6 +272,16 @@ Match process name.
 
 Match process path.
 
+#### 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.

+ 15 - 1
docs/configuration/dns/rule.zh.md

@@ -6,7 +6,8 @@ icon: material/new-box
 
     :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: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
+    :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)  
+    :material-plus: [process_path_regex](#process_path_regex)
 
 !!! quote "sing-box 1.9.0 中的更改"
 
@@ -103,6 +104,9 @@ icon: material/new-box
         "process_path": [
           "/usr/bin/curl"
         ],
+        "process_path_regex": [
+          "^/usr/bin/.+"
+        ],
         "package_name": [
           "com.termux"
         ],
@@ -266,6 +270,16 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
 
 匹配进程路径。
 
+#### process_path_regex
+
+!!! question "自 sing-box 1.10.0 起"
+
+!!! quote ""
+
+    仅支持 Linux、Windows 和 macOS.
+
+使用正则表达式匹配进程路径。
+
 #### package_name
 
 匹配 Android 应用包名。

+ 14 - 0
docs/configuration/route/rule.md

@@ -7,6 +7,7 @@ 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_regex](#process_path_regex)
 
 !!! quote "Changes in sing-box 1.8.0"
 
@@ -101,6 +102,9 @@ icon: material/alert-decagram
         "process_path": [
           "/usr/bin/curl"
         ],
+        "process_path_regex": [
+          "^/usr/bin/.+"
+        ],
         "package_name": [
           "com.termux"
         ],
@@ -277,6 +281,16 @@ Match process name.
 
 Match process path.
 
+#### 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.

+ 15 - 1
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_regex](#process_path_regex)
+
 
 !!! quote "sing-box 1.8.0 中的更改"
 
@@ -99,6 +100,9 @@ icon: material/alert-decagram
         "process_path": [
           "/usr/bin/curl"
         ],
+        "process_path_regex": [
+          "^/usr/bin/.+"
+        ],
         "package_name": [
           "com.termux"
         ],
@@ -275,6 +279,16 @@ icon: material/alert-decagram
 
 匹配进程路径。
 
+#### process_path_regex
+
+!!! question "自 sing-box 1.10.0 起"
+
+!!! quote ""
+
+    仅支持 Linux、Windows 和 macOS.
+
+使用正则表达式匹配进程路径。
+
 #### package_name
 
 匹配 Android 应用包名。

+ 13 - 0
docs/configuration/rule-set/headless-rule.md

@@ -57,6 +57,9 @@
       "process_path": [
         "/usr/bin/curl"
       ],
+      "process_path_regex": [
+        "^/usr/bin/.+"
+      ],
       "package_name": [
         "com.termux"
       ],
@@ -160,6 +163,16 @@ Match process name.
 
 Match process path.
 
+#### 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.

+ 1 - 0
option/rule.go

@@ -88,6 +88,7 @@ type _DefaultRule struct {
 	PortRange                Listable[string] `json:"port_range,omitempty"`
 	ProcessName              Listable[string] `json:"process_name,omitempty"`
 	ProcessPath              Listable[string] `json:"process_path,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"`

+ 1 - 0
option/rule_dns.go

@@ -88,6 +88,7 @@ type _DefaultDNSRule struct {
 	PortRange                Listable[string]       `json:"port_range,omitempty"`
 	ProcessName              Listable[string]       `json:"process_name,omitempty"`
 	ProcessPath              Listable[string]       `json:"process_path,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"`

+ 19 - 18
option/rule_set.go

@@ -144,24 +144,25 @@ 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"`
+	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:"-"`

+ 8 - 0
route/rule_default.go

@@ -179,6 +179,14 @@ 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.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)

+ 8 - 0
route/rule_dns.go

@@ -183,6 +183,14 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
 		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)

+ 8 - 0
route/rule_headless.go

@@ -123,6 +123,14 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles
 		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)

+ 54 - 0
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
+}