Add sniff_override_rules

This commit is contained in:
PuerNya 2023-08-22 19:41:50 +08:00
parent 9be5cd99c1
commit 32ec64f781
10 changed files with 856 additions and 15 deletions

View File

@ -6,8 +6,8 @@ import (
"net/netip" "net/netip"
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-dns" dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun" tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -84,6 +84,10 @@ type DNSRule interface {
RewriteTTL() *uint32 RewriteTTL() *uint32
} }
type SniffOverrideRule interface {
Rule
}
type InterfaceUpdateListener interface { type InterfaceUpdateListener interface {
InterfaceUpdated() InterfaceUpdated()
} }

View File

@ -9,6 +9,7 @@
"udp_fragment": false, "udp_fragment": false,
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_override_rules": [],
"sniff_timeout": "300ms", "sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_timeout": 300,
@ -68,6 +69,14 @@ Override the connection destination address with the sniffed domain.
If the domain name is invalid (like tor), this will not work. If the domain name is invalid (like tor), this will not work.
#### sniff_override_rules
Pick up the connection that will be overrided destination address with the sniffed domain by rules.
If the domain name is invalid (like tor), this will not work.
See [Sniff Override Rule](/configuration/shared/sniff_override_rules/) for details.
#### sniff_timeout #### sniff_timeout
Timeout for sniffing. Timeout for sniffing.

View File

@ -9,6 +9,7 @@
"udp_fragment": false, "udp_fragment": false,
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_override_rules": [],
"sniff_timeout": "300ms", "sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_timeout": 300,
@ -69,6 +70,12 @@
如果域名无效(如 Tor将不生效。 如果域名无效(如 Tor将不生效。
#### sniff_override_rules
根据规则选择处需要用探测出的域名覆盖目标地址的连接。
参阅 [Sniff Override Rule](/zh/configuration/shared/sniff_override_rules/)
#### sniff_timeout #### sniff_timeout
探测超时时间。 探测超时时间。

View File

@ -0,0 +1,242 @@
### Structure
```json
{
"route": {
"rules": [
{
"ip_version": 6,
"network": [
"tcp"
],
"auth_user": [
"usera",
"userb"
],
"protocol": [
"tls",
"http",
"quic"
],
"domain": [
"test.com"
],
"domain_suffix": [
".cn"
],
"domain_keyword": [
"test"
],
"domain_regex": [
"^stun\\..+"
],
"geosite": [
"cn"
],
"source_geoip": [
"private"
],
"geoip": [
"cn"
],
"source_ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"source_port": [
12345
],
"source_port_range": [
"1000:2000",
":3000",
"4000:"
],
"port": [
80,
443
],
"port_range": [
"1000:2000",
":3000",
"4000:"
],
"process_name": [
"curl"
],
"process_path": [
"/usr/bin/curl"
],
"package_name": [
"com.termux"
],
"user": [
"sekai"
],
"user_id": [
1000
],
"clash_mode": "direct",
"invert": false
},
{
"type": "logical",
"mode": "and",
"rules": [],
"invert": false
}
]
}
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Default Fields
!!! note ""
The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
#### ip_version
4 or 6.
Not limited if empty.
#### auth_user
Username, see each inbound for details.
#### protocol
Sniffed protocol, see [Sniff](/configuration/route/sniff/) for details.
#### network
`tcp` or `udp`.
#### domain
Match full domain.
#### domain_suffix
Match domain suffix.
#### domain_keyword
Match domain using keyword.
#### domain_regex
Match domain using regular expression.
#### geosite
Match geosite.
#### source_geoip
Match source geoip.
#### geoip
Match geoip.
#### source_ip_cidr
Match source ip cidr.
#### ip_cidr
Match ip cidr.
#### source_port
Match source port.
#### source_port_range
Match source port range.
#### port
Match port.
#### port_range
Match port range.
#### process_name
!!! error ""
Only supported on Linux, Windows, and macOS.
Match process name.
#### process_path
!!! error ""
Only supported on Linux, Windows, and macOS.
Match process path.
#### package_name
Match android package name.
#### user
!!! error ""
Only supported on Linux.
Match user name.
#### user_id
!!! error ""
Only supported on Linux.
Match user id.
#### clash_mode
Match Clash mode.
#### invert
Invert match result.
### Logical Fields
#### type
`logical`
#### mode
==Required==
`and` or `or`
#### rules
==Required==
Included default rules.

View File

@ -0,0 +1,240 @@
### 结构
```json
{
"route": {
"rules": [
{
"ip_version": 6,
"network": [
"tcp"
],
"auth_user": [
"usera",
"userb"
],
"protocol": [
"tls",
"http",
"quic"
],
"domain": [
"test.com"
],
"domain_suffix": [
".cn"
],
"domain_keyword": [
"test"
],
"domain_regex": [
"^stun\\..+"
],
"geosite": [
"cn"
],
"source_geoip": [
"private"
],
"geoip": [
"cn"
],
"source_ip_cidr": [
"10.0.0.0/24"
],
"ip_cidr": [
"10.0.0.0/24"
],
"source_port": [
12345
],
"source_port_range": [
"1000:2000",
":3000",
"4000:"
],
"port": [
80,
443
],
"port_range": [
"1000:2000",
":3000",
"4000:"
],
"process_name": [
"curl"
],
"process_path": [
"/usr/bin/curl"
],
"package_name": [
"com.termux"
],
"user": [
"sekai"
],
"user_id": [
1000
],
"clash_mode": "direct",
"invert": false
},
{
"type": "logical",
"mode": "and",
"rules": [],
"invert": false
}
]
}
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
### Default Fields
!!! note ""
默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
#### ip_version
4 或 6。
默认不限制。
#### auth_user
认证用户名,参阅入站设置。
#### protocol
探测到的协议, 参阅 [协议探测](/zh/configuration/route/sniff/)。
#### network
`tcp``udp`
#### domain
匹配完整域名。
#### domain_suffix
匹配域名后缀。
#### domain_keyword
匹配域名关键字。
#### domain_regex
匹配域名正则表达式。
#### geosite
匹配 GeoSite。
#### source_geoip
匹配源 GeoIP。
#### geoip
匹配 GeoIP。
#### source_ip_cidr
匹配源 IP CIDR。
#### ip_cidr
匹配 IP CIDR。
#### source_port
匹配源端口。
#### source_port_range
匹配源端口范围。
#### port
匹配端口。
#### port_range
匹配端口范围。
#### process_name
!!! error ""
仅支持 Linux、Windows 和 macOS。
匹配进程名称。
#### process_path
!!! error ""
仅支持 Linux、Windows 和 macOS.
匹配进程路径。
#### package_name
匹配 Android 应用包名。
#### user
!!! error ""
仅支持 Linux.
匹配用户名。
#### user_id
!!! error ""
仅支持 Linux.
匹配用户 ID。
#### clash_mode
匹配 Clash 模式。
#### invert
反选匹配结果。
### 逻辑字段
#### type
`logical`
#### mode
==必填==
`and``or`
#### rules
==必填==
包括的默认规则。

View File

@ -117,6 +117,7 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
type InboundOptions struct { type InboundOptions struct {
SniffEnabled bool `json:"sniff,omitempty"` SniffEnabled bool `json:"sniff,omitempty"`
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
SniffOverrideRules []SniffOverrideRule `json:"sniff_override_rules,omitempty"`
SniffTimeout Duration `json:"sniff_timeout,omitempty"` SniffTimeout Duration `json:"sniff_timeout,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
} }

View File

@ -0,0 +1,97 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type _SniffOverrideRule struct {
Type string `json:"type,omitempty"`
DefaultOptions DefaultSniffOverrideRule `json:"-"`
LogicalOptions LogicalSniffOverrideRule `json:"-"`
}
type SniffOverrideRule _SniffOverrideRule
func (r SniffOverrideRule) MarshalJSON() ([]byte, error) {
var v any
switch r.Type {
case C.RuleTypeDefault:
r.Type = ""
v = r.DefaultOptions
case C.RuleTypeLogical:
v = r.LogicalOptions
default:
return nil, E.New("unknown rule type: " + r.Type)
}
return MarshallObjects((_SniffOverrideRule)(r), v)
}
func (r *SniffOverrideRule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_SniffOverrideRule)(r))
if err != nil {
return err
}
var v any
switch r.Type {
case "", C.RuleTypeDefault:
r.Type = C.RuleTypeDefault
v = &r.DefaultOptions
case C.RuleTypeLogical:
v = &r.LogicalOptions
default:
return E.New("unknown rule type: " + r.Type)
}
err = UnmarshallExcluded(bytes, (*_SniffOverrideRule)(r), v)
if err != nil {
return E.Cause(err, "route rule")
}
return nil
}
type DefaultSniffOverrideRule struct {
IPVersion int `json:"ip_version,omitempty"`
Network Listable[string] `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,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"`
Geosite Listable[string] `json:"geosite,omitempty"`
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
GeoIP Listable[string] `json:"geoip,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"`
User Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
Invert bool `json:"invert,omitempty"`
}
func (r DefaultSniffOverrideRule) IsValid() bool {
var defaultValue DefaultSniffOverrideRule
defaultValue.Invert = r.Invert
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalSniffOverrideRule struct {
Mode string `json:"mode"`
Rules []DefaultSniffOverrideRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
}
func (r LogicalSniffOverrideRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultSniffOverrideRule.IsValid)
}

View File

@ -26,9 +26,9 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-box/transport/fakeip"
"github.com/sagernet/sing-dns" dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun" tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@ -50,6 +50,7 @@ type Router struct {
ctx context.Context ctx context.Context
logger log.ContextLogger logger log.ContextLogger
dnsLogger log.ContextLogger dnsLogger log.ContextLogger
overrideLogger log.ContextLogger
inboundByTag map[string]adapter.Inbound inboundByTag map[string]adapter.Inbound
outbounds []adapter.Outbound outbounds []adapter.Outbound
outboundByTag map[string]adapter.Outbound outboundByTag map[string]adapter.Outbound
@ -101,6 +102,7 @@ func NewRouter(
ctx: ctx, ctx: ctx,
logger: logFactory.NewLogger("router"), logger: logFactory.NewLogger("router"),
dnsLogger: logFactory.NewLogger("dns"), dnsLogger: logFactory.NewLogger("dns"),
overrideLogger: logFactory.NewLogger("override"),
outboundByTag: make(map[string]adapter.Outbound), outboundByTag: make(map[string]adapter.Outbound),
rules: make([]adapter.Rule, 0, len(options.Rules)), rules: make([]adapter.Rule, 0, len(options.Rules)),
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
@ -657,11 +659,14 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
metadata.Protocol = sniffMetadata.Protocol metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain metadata.Domain = sniffMetadata.Domain
if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
overrideFlag := r.matchSniffOverride(ctx, &metadata)
if overrideFlag {
metadata.Destination = M.Socksaddr{ metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain, Fqdn: metadata.Domain,
Port: metadata.Destination.Port, Port: metadata.Destination.Port,
} }
} }
}
if metadata.Domain != "" { if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else { } else {
@ -776,11 +781,14 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
metadata.Protocol = sniffMetadata.Protocol metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain metadata.Domain = sniffMetadata.Domain
if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
overrideFlag := r.matchSniffOverride(ctx, &metadata)
if overrideFlag {
metadata.Destination = M.Socksaddr{ metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain, Fqdn: metadata.Domain,
Port: metadata.Destination.Port, Port: metadata.Destination.Port,
} }
} }
}
if metadata.Domain != "" { if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else { } else {

View File

@ -0,0 +1,31 @@
package route
import (
"context"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
)
func (r *Router) matchSniffOverride(ctx context.Context, metadata *adapter.InboundContext) bool {
rules := make([]adapter.SniffOverrideRule, 0, len(metadata.InboundOptions.SniffOverrideRules))
for i, sniffOverrideRuleOptions := range metadata.InboundOptions.SniffOverrideRules {
sniffOverrideRule, err := NewSniffOverrideRule(r, r.logger, sniffOverrideRuleOptions)
if err != nil {
E.Cause(err, "parse sniff_override rule[", i, "]")
return false
}
rules = append(rules, sniffOverrideRule)
}
if len(rules) == 0 {
r.overrideLogger.DebugContext(ctx, "match all")
return true
}
for i, rule := range rules {
if rule.Match(metadata) {
r.overrideLogger.DebugContext(ctx, "match[", i, "] ", rule.String())
return true
}
}
return false
}

View File

@ -0,0 +1,202 @@
package route
import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewSniffOverrideRule(router adapter.Router, logger log.ContextLogger, options option.SniffOverrideRule) (adapter.SniffOverrideRule, error) {
switch options.Type {
case "", C.RuleTypeDefault:
if !options.DefaultOptions.IsValid() {
return nil, E.New("missing conditions")
}
return NewDefaultSniffOverrideRule(router, logger, options.DefaultOptions)
case C.RuleTypeLogical:
if !options.LogicalOptions.IsValid() {
return nil, E.New("missing conditions")
}
return NewLogicalSniffOverrideRule(router, logger, options.LogicalOptions)
default:
return nil, E.New("unknown rule type: ", options.Type)
}
}
var _ adapter.SniffOverrideRule = (*DefaultSniffOverrideRule)(nil)
type DefaultSniffOverrideRule struct {
abstractDefaultRule
}
func NewDefaultSniffOverrideRule(router adapter.Router, logger log.ContextLogger, options option.DefaultSniffOverrideRule) (*DefaultSniffOverrideRule, error) {
rule := &DefaultSniffOverrideRule{
abstractDefaultRule: abstractDefaultRule{
invert: options.Invert,
},
}
if options.IPVersion > 0 {
switch options.IPVersion {
case 4, 6:
item := NewIPVersionItem(options.IPVersion == 6)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
default:
return nil, E.New("invalid ip version: ", options.IPVersion)
}
}
if len(options.Network) > 0 {
item := NewNetworkItem(options.Network)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.AuthUser) > 0 {
item := NewAuthUserItem(options.AuthUser)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Protocol) > 0 {
item := NewProtocolItem(options.Protocol)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
item := NewDomainItem(options.Domain, options.DomainSuffix)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DomainKeyword) > 0 {
item := NewDomainKeywordItem(options.DomainKeyword)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DomainRegex) > 0 {
item, err := NewDomainRegexItem(options.DomainRegex)
if err != nil {
return nil, E.Cause(err, "domain_regex")
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Geosite) > 0 {
item := NewGeositeItem(router, logger, options.Geosite)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourceGeoIP) > 0 {
item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.GeoIP) > 0 {
item := NewGeoIPItem(router, logger, false, options.GeoIP)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourceIPCIDR) > 0 {
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
if err != nil {
return nil, E.Cause(err, "source_ipcidr")
}
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.IPCIDR) > 0 {
item, err := NewIPCIDRItem(false, options.IPCIDR)
if err != nil {
return nil, E.Cause(err, "ipcidr")
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourcePort) > 0 {
item := NewPortItem(true, options.SourcePort)
rule.sourcePortItems = append(rule.sourcePortItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourcePortRange) > 0 {
item, err := NewPortRangeItem(true, options.SourcePortRange)
if err != nil {
return nil, E.Cause(err, "source_port_range")
}
rule.sourcePortItems = append(rule.sourcePortItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Port) > 0 {
item := NewPortItem(false, options.Port)
rule.destinationPortItems = append(rule.destinationPortItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.PortRange) > 0 {
item, err := NewPortRangeItem(false, options.PortRange)
if err != nil {
return nil, E.Cause(err, "port_range")
}
rule.destinationPortItems = append(rule.destinationPortItems, 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.ProcessPath) > 0 {
item := NewProcessPathItem(options.ProcessPath)
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 options.ClashMode != "" {
item := NewClashModeItem(router, options.ClashMode)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
return rule, nil
}
var _ adapter.SniffOverrideRule = (*LogicalSniffOverrideRule)(nil)
type LogicalSniffOverrideRule struct {
abstractLogicalRule
}
func NewLogicalSniffOverrideRule(router adapter.Router, logger log.ContextLogger, options option.LogicalSniffOverrideRule) (*LogicalSniffOverrideRule, error) {
r := &LogicalSniffOverrideRule{
abstractLogicalRule: abstractLogicalRule{
rules: make([]adapter.Rule, len(options.Rules)),
invert: options.Invert,
},
}
switch options.Mode {
case C.LogicalTypeAnd:
r.mode = C.LogicalTypeAnd
case C.LogicalTypeOr:
r.mode = C.LogicalTypeOr
default:
return nil, E.New("unknown logical mode: ", options.Mode)
}
for i, subRule := range options.Rules {
rule, err := NewDefaultSniffOverrideRule(router, logger, subRule)
if err != nil {
return nil, E.Cause(err, "sub rule[", i, "]")
}
r.rules[i] = rule
}
return r, nil
}