mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Add sniff_override_rules
This commit is contained in:
parent
9be5cd99c1
commit
32ec64f781
@ -6,8 +6,8 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-tun"
|
||||
dns "github.com/sagernet/sing-dns"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@ -84,6 +84,10 @@ type DNSRule interface {
|
||||
RewriteTTL() *uint32
|
||||
}
|
||||
|
||||
type SniffOverrideRule interface {
|
||||
Rule
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
InterfaceUpdated()
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
"udp_fragment": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_override_rules": [],
|
||||
"sniff_timeout": "300ms",
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"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.
|
||||
|
||||
#### 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
|
||||
|
||||
Timeout for sniffing.
|
||||
|
@ -9,6 +9,7 @@
|
||||
"udp_fragment": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_override_rules": [],
|
||||
"sniff_timeout": "300ms",
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
@ -69,6 +70,12 @@
|
||||
|
||||
如果域名无效(如 Tor),将不生效。
|
||||
|
||||
#### sniff_override_rules
|
||||
|
||||
根据规则选择处需要用探测出的域名覆盖目标地址的连接。
|
||||
|
||||
参阅 [Sniff Override Rule](/zh/configuration/shared/sniff_override_rules/)
|
||||
|
||||
#### sniff_timeout
|
||||
|
||||
探测超时时间。
|
||||
|
242
docs/configuration/shared/sniff_override_rule.md
Normal file
242
docs/configuration/shared/sniff_override_rule.md
Normal 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.
|
240
docs/configuration/shared/sniff_override_rule.zh.md
Normal file
240
docs/configuration/shared/sniff_override_rule.zh.md
Normal 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
|
||||
|
||||
==必填==
|
||||
|
||||
包括的默认规则。
|
@ -115,10 +115,11 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
|
||||
type InboundOptions struct {
|
||||
SniffEnabled bool `json:"sniff,omitempty"`
|
||||
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
|
||||
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
|
||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||
SniffEnabled bool `json:"sniff,omitempty"`
|
||||
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
|
||||
SniffOverrideRules []SniffOverrideRule `json:"sniff_override_rules,omitempty"`
|
||||
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
|
||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||
}
|
||||
|
||||
type ListenOptions struct {
|
||||
|
97
option/rule_sniff_override.go
Normal file
97
option/rule_sniff_override.go
Normal 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)
|
||||
}
|
@ -26,9 +26,9 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/transport/fakeip"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
dns "github.com/sagernet/sing-dns"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@ -50,6 +50,7 @@ type Router struct {
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
dnsLogger log.ContextLogger
|
||||
overrideLogger log.ContextLogger
|
||||
inboundByTag map[string]adapter.Inbound
|
||||
outbounds []adapter.Outbound
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
@ -101,6 +102,7 @@ func NewRouter(
|
||||
ctx: ctx,
|
||||
logger: logFactory.NewLogger("router"),
|
||||
dnsLogger: logFactory.NewLogger("dns"),
|
||||
overrideLogger: logFactory.NewLogger("override"),
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
||||
@ -657,9 +659,12 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
overrideFlag := r.matchSniffOverride(ctx, &metadata)
|
||||
if overrideFlag {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
}
|
||||
}
|
||||
}
|
||||
if metadata.Domain != "" {
|
||||
@ -776,9 +781,12 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
overrideFlag := r.matchSniffOverride(ctx, &metadata)
|
||||
if overrideFlag {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
}
|
||||
}
|
||||
}
|
||||
if metadata.Domain != "" {
|
||||
|
31
route/router_sniff_override.go
Normal file
31
route/router_sniff_override.go
Normal 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
|
||||
}
|
202
route/rule_sniff_override.go
Normal file
202
route/rule_sniff_override.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user