mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-10 02:53:12 +00:00
Add address filter support for DNS rules
This commit is contained in:
parent
830ea46932
commit
0517ceef76
|
@ -51,11 +51,13 @@ type InboundContext struct {
|
||||||
|
|
||||||
// rule cache
|
// rule cache
|
||||||
|
|
||||||
IPCIDRMatchSource bool
|
IPCIDRMatchSource bool
|
||||||
SourceAddressMatch bool
|
SourceAddressMatch bool
|
||||||
SourcePortMatch bool
|
SourcePortMatch bool
|
||||||
DestinationAddressMatch bool
|
DestinationAddressMatch bool
|
||||||
DestinationPortMatch bool
|
DestinationPortMatch bool
|
||||||
|
DidMatch bool
|
||||||
|
IgnoreDestinationIPCIDRMatch bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InboundContext) ResetRuleCache() {
|
func (c *InboundContext) ResetRuleCache() {
|
||||||
|
@ -64,6 +66,7 @@ func (c *InboundContext) ResetRuleCache() {
|
||||||
c.SourcePortMatch = false
|
c.SourcePortMatch = false
|
||||||
c.DestinationAddressMatch = false
|
c.DestinationAddressMatch = false
|
||||||
c.DestinationPortMatch = false
|
c.DestinationPortMatch = false
|
||||||
|
c.DidMatch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
type inboundContextKey struct{}
|
type inboundContextKey struct{}
|
||||||
|
|
|
@ -86,6 +86,8 @@ type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
RewriteTTL() *uint32
|
RewriteTTL() *uint32
|
||||||
|
WithAddressLimit() bool
|
||||||
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
|
@ -99,6 +101,7 @@ type RuleSet interface {
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleSetStartContext interface {
|
type RuleSetStartContext interface {
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|----------|--------------------------------|
|
|----------|---------------------------------|
|
||||||
| `server` | List of [DNS Server](./server/) |
|
| `server` | List of [DNS Server](./server/) |
|
||||||
| `rules` | List of [DNS Rule](./rule/) |
|
| `rules` | List of [DNS Rule](./rule/) |
|
||||||
| `fakeip` | [FakeIP](./fakeip/) |
|
| `fakeip` | [FakeIP](./fakeip/) |
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
|
:material-plus: [geoip](#geoip)
|
||||||
|
:material-plus: [ip_cidr](#ip_cidr)
|
||||||
|
:material-plus: [ip_is_private](#ip_is_private)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
|
@ -53,11 +59,19 @@ icon: material/alert-decagram
|
||||||
"source_geoip": [
|
"source_geoip": [
|
||||||
"private"
|
"private"
|
||||||
],
|
],
|
||||||
|
"geoip": [
|
||||||
|
"cn"
|
||||||
|
],
|
||||||
"source_ip_cidr": [
|
"source_ip_cidr": [
|
||||||
"10.0.0.0/24",
|
"10.0.0.0/24",
|
||||||
"192.168.0.1"
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
"source_ip_is_private": false,
|
"source_ip_is_private": false,
|
||||||
|
"ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"ip_is_private": false,
|
||||||
"source_port": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
|
@ -312,6 +326,32 @@ Disable cache and save cache in this query.
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
Rewrite TTL in DNS responses.
|
||||||
|
|
||||||
|
### Address Filter Fields
|
||||||
|
|
||||||
|
Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped.
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
`ip_cidr` items in included rule sets also takes effect as an address filtering field.
|
||||||
|
|
||||||
|
#### geoip
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
|
Match GeoIP with query response.
|
||||||
|
|
||||||
|
#### ip_cidr
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
|
Match IP CIDR with query response.
|
||||||
|
|
||||||
|
#### ip_is_private
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
|
Match private IP with query response.
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [geoip](#geoip)
|
||||||
|
:material-plus: [ip_cidr](#ip_cidr)
|
||||||
|
:material-plus: [ip_is_private](#ip_is_private)
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
|
@ -53,10 +59,19 @@ icon: material/alert-decagram
|
||||||
"source_geoip": [
|
"source_geoip": [
|
||||||
"private"
|
"private"
|
||||||
],
|
],
|
||||||
|
"geoip": [
|
||||||
|
"cn"
|
||||||
|
],
|
||||||
"source_ip_cidr": [
|
"source_ip_cidr": [
|
||||||
"10.0.0.0/24"
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
"source_ip_is_private": false,
|
"source_ip_is_private": false,
|
||||||
|
"ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"ip_is_private": false,
|
||||||
"source_port": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
|
@ -307,6 +322,32 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
重写 DNS 回应中的 TTL。
|
||||||
|
|
||||||
|
### 地址筛选字段
|
||||||
|
|
||||||
|
仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。
|
||||||
|
|
||||||
|
#### geoip
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.9.0 起"
|
||||||
|
|
||||||
|
与查询响应匹配 GeoIP。
|
||||||
|
|
||||||
|
#### ip_cidr
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.9.0 起"
|
||||||
|
|
||||||
|
与查询相应匹配 IP CIDR。
|
||||||
|
|
||||||
|
#### ip_is_private
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.9.0 起"
|
||||||
|
|
||||||
|
与查询响应匹配非公开 IP。
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-delete-alert: [store_mode](#store_mode)
|
:material-delete-alert: [store_mode](#store_mode)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-delete-alert: [store_mode](#store_mode)
|
:material-delete-alert: [store_mode](#store_mode)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
# Experimental
|
# Experimental
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
# 实验性
|
# 实验性
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-plus: [gso](#gso)
|
:material-plus: [gso](#gso)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [gso](#gso)
|
:material-plus: [gso](#gso)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-plus: [gso](#gso)
|
:material-plus: [gso](#gso)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [gso](#gso)
|
:material-plus: [gso](#gso)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
# Route
|
# Route
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
# 路由
|
# 路由
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# Rule Set
|
# Rule Set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# Source Format
|
# Source Format
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
icon: material/alert-decagram
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|
|
@ -290,52 +290,6 @@ flowchart TB
|
||||||
|
|
||||||
=== ":material-dns: DNS rules"
|
=== ":material-dns: DNS rules"
|
||||||
|
|
||||||
!!! info
|
|
||||||
|
|
||||||
DNS rules are optional if FakeIP is used.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"tag": "google",
|
|
||||||
"address": "tls://8.8.8.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "local",
|
|
||||||
"address": "223.5.5.5",
|
|
||||||
"detour": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"outbound": "any",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"clash_mode": "Direct",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"clash_mode": "Global",
|
|
||||||
"server": "google"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "geolocation-cn",
|
|
||||||
"server": "local"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-dns: DNS rules (1.8.0+)"
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
|
|
||||||
DNS rules are optional if FakeIP is used.
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
|
@ -382,74 +336,78 @@ flowchart TB
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-router-network: Route rules"
|
=== ":material-dns: DNS rules (1.9.0+)"
|
||||||
|
|
||||||
|
!!! warning "DNS leaks"
|
||||||
|
|
||||||
|
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS if using this method.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"outbounds": [
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"tag": "google",
|
||||||
"mode": "or",
|
"address": "tls://8.8.8.8"
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": 53
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbound": "dns"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"geoip": "private",
|
"tag": "local",
|
||||||
"outbound": "direct"
|
"address": "https://223.5.5.5/dns-query",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clash_mode": "Direct",
|
"clash_mode": "Direct",
|
||||||
"outbound": "direct"
|
"server": "local"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clash_mode": "Global",
|
"clash_mode": "Global",
|
||||||
"outbound": "default"
|
"server": "google"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"rule_set": "geosite-geolocation-cn",
|
||||||
"mode": "or",
|
"server": "local"
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"port": 853
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"network": "udp",
|
|
||||||
"port": 443
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"protocol": "stun"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbound": "block"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"geosite": "geolocation-cn",
|
"clash_mode": "Default",
|
||||||
"outbound": "direct"
|
"server": "google"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_set": "geoip-cn",
|
||||||
|
"server": "local"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"rule_set": [
|
||||||
|
{
|
||||||
|
"type": "remote",
|
||||||
|
"tag": "geosite-geolocation-cn",
|
||||||
|
"format": "binary",
|
||||||
|
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "remote",
|
||||||
|
"tag": "geoip-cn",
|
||||||
|
"format": "binary",
|
||||||
|
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"experimental": {
|
||||||
|
"clash_api": {
|
||||||
|
"default_mode": "Leak"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-router-network: Route rules (1.8.0+)"
|
=== ":material-router-network: Route rules"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -24,10 +24,10 @@ require (
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
||||||
github.com/sagernet/gomobile v0.1.3
|
github.com/sagernet/gomobile v0.1.3
|
||||||
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e
|
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e
|
||||||
github.com/sagernet/quic-go v0.40.1
|
github.com/sagernet/quic-go v0.43.0-beta.3
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.4.0-beta.20
|
github.com/sagernet/sing v0.4.0-beta.20
|
||||||
github.com/sagernet/sing-dns v0.1.14
|
github.com/sagernet/sing-dns v0.2.0-beta.18
|
||||||
github.com/sagernet/sing-mux v0.2.0
|
github.com/sagernet/sing-mux v0.2.0
|
||||||
github.com/sagernet/sing-quic v0.1.15
|
github.com/sagernet/sing-quic v0.1.15
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -101,15 +101,15 @@ github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dks
|
||||||
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw=
|
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.40.1 h1:qLeTIJR0d0JWRmDWo346nLsVN6EWihd1kalJYPEd0TM=
|
github.com/sagernet/quic-go v0.43.0-beta.3 h1:qclJbbpgZe76EH62Bdu3LfDSC2zmuxj7zXCpdQBbe7c=
|
||||||
github.com/sagernet/quic-go v0.40.1/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4DwGbNl9UQrU=
|
github.com/sagernet/quic-go v0.43.0-beta.3/go.mod h1:3EtxR1Yaa1FZu6jFPiBHpOAdhOxL4A3EPxmiVgjJvVM=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||||
github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY=
|
github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY=
|
||||||
github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y=
|
github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y=
|
||||||
github.com/sagernet/sing-dns v0.1.14 h1:kxE/Ik3jMXmD3sXsdt9MgrNzLFWt64mghV+MQqzyf40=
|
github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8=
|
||||||
github.com/sagernet/sing-dns v0.1.14/go.mod h1:AA+vZMNovuPN5i/sPnfF6756Nq94nzb5nXodMWbta5w=
|
github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24=
|
||||||
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
||||||
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
||||||
github.com/sagernet/sing-quic v0.1.15 h1:LGWPxQEeg89+68RHP7HtAV0RZeEWQikUqOfE9nYmr2A=
|
github.com/sagernet/sing-quic v0.1.15 h1:LGWPxQEeg89+68RHP7HtAV0RZeEWQikUqOfE9nYmr2A=
|
||||||
|
|
|
@ -77,6 +77,9 @@ type DefaultDNSRule struct {
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
|
|
@ -2,13 +2,13 @@ package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/cache"
|
"github.com/sagernet/sing/common/cache"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
@ -37,41 +37,51 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) {
|
||||||
return domain, loaded
|
return domain, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool) (context.Context, dns.Transport, dns.DomainStrategy) {
|
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) {
|
||||||
metadata := adapter.ContextFrom(ctx)
|
metadata := adapter.ContextFrom(ctx)
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
panic("no context")
|
panic("no context")
|
||||||
}
|
}
|
||||||
for i, rule := range r.dnsRules {
|
if index < len(r.dnsRules) {
|
||||||
metadata.ResetRuleCache()
|
dnsRules := r.dnsRules
|
||||||
if rule.Match(metadata) {
|
if index != -1 {
|
||||||
detour := rule.Outbound()
|
dnsRules = dnsRules[index+1:]
|
||||||
transport, loaded := r.transportMap[detour]
|
}
|
||||||
if !loaded {
|
for ruleIndex, rule := range dnsRules {
|
||||||
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
|
metadata.ResetRuleCache()
|
||||||
continue
|
if rule.Match(metadata) {
|
||||||
}
|
detour := rule.Outbound()
|
||||||
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP {
|
transport, loaded := r.transportMap[detour]
|
||||||
continue
|
if !loaded {
|
||||||
}
|
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
|
||||||
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
continue
|
||||||
if rule.DisableCache() {
|
}
|
||||||
ctx = dns.ContextWithDisableCache(ctx, true)
|
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP {
|
||||||
}
|
continue
|
||||||
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
}
|
||||||
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
displayRuleIndex := ruleIndex
|
||||||
}
|
if index != -1 {
|
||||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
|
displayRuleIndex += index + 1
|
||||||
return ctx, transport, domainStrategy
|
}
|
||||||
} else {
|
r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour)
|
||||||
return ctx, transport, r.defaultDomainStrategy
|
if rule.DisableCache() {
|
||||||
|
ctx = dns.ContextWithDisableCache(ctx, true)
|
||||||
|
}
|
||||||
|
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
||||||
|
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
||||||
|
}
|
||||||
|
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
|
||||||
|
return ctx, transport, domainStrategy, rule, ruleIndex
|
||||||
|
} else {
|
||||||
|
return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
|
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
|
||||||
return ctx, r.defaultTransport, domainStrategy
|
return ctx, r.defaultTransport, domainStrategy, nil, -1
|
||||||
} else {
|
} else {
|
||||||
return ctx, r.defaultTransport, r.defaultDomainStrategy
|
return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +96,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
||||||
)
|
)
|
||||||
response, cached = r.dnsClient.ExchangeCache(ctx, message)
|
response, cached = r.dnsClient.ExchangeCache(ctx, message)
|
||||||
if !cached {
|
if !cached {
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
var metadata *adapter.InboundContext
|
||||||
|
ctx, metadata = adapter.AppendContext(ctx)
|
||||||
if len(message.Question) > 0 {
|
if len(message.Question) > 0 {
|
||||||
metadata.QueryType = message.Question[0].Qtype
|
metadata.QueryType = message.Question[0].Qtype
|
||||||
switch metadata.QueryType {
|
switch metadata.QueryType {
|
||||||
|
@ -97,17 +108,47 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
||||||
}
|
}
|
||||||
metadata.Domain = fqdnToDomain(message.Question[0].Name)
|
metadata.Domain = fqdnToDomain(message.Question[0].Name)
|
||||||
}
|
}
|
||||||
ctx, transport, strategy := r.matchDNS(ctx, true)
|
var (
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
|
transport dns.Transport
|
||||||
defer cancel()
|
strategy dns.DomainStrategy
|
||||||
response, err = r.dnsClient.Exchange(ctx, transport, message, strategy)
|
rule adapter.DNSRule
|
||||||
if err != nil && len(message.Question) > 0 {
|
ruleIndex int
|
||||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
|
)
|
||||||
|
ruleIndex = -1
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
dnsCtx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
addressLimit bool
|
||||||
|
)
|
||||||
|
|
||||||
|
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex)
|
||||||
|
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
|
||||||
|
if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) {
|
||||||
|
addressLimit = true
|
||||||
|
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool {
|
||||||
|
metadata.DestinationAddresses, _ = dns.MessageToAddresses(response)
|
||||||
|
return rule.MatchAddressLimit(metadata)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addressLimit = false
|
||||||
|
response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, dns.ErrResponseRejected) {
|
||||||
|
r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())))
|
||||||
|
} else if len(message.Question) > 0 {
|
||||||
|
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
|
||||||
|
} else {
|
||||||
|
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !addressLimit || err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(message.Question) > 0 && response != nil {
|
|
||||||
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)
|
|
||||||
}
|
|
||||||
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
||||||
for _, answer := range response.Answer {
|
for _, answer := range response.Answer {
|
||||||
switch record := answer.(type) {
|
switch record := answer.(type) {
|
||||||
|
@ -125,22 +166,57 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
||||||
r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
|
r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
metadata.Domain = domain
|
metadata.Domain = domain
|
||||||
ctx, transport, transportStrategy := r.matchDNS(ctx, false)
|
var (
|
||||||
if strategy == dns.DomainStrategyAsIS {
|
transport dns.Transport
|
||||||
strategy = transportStrategy
|
transportStrategy dns.DomainStrategy
|
||||||
|
rule adapter.DNSRule
|
||||||
|
ruleIndex int
|
||||||
|
resultAddrs []netip.Addr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
ruleIndex = -1
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
dnsCtx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
addressLimit bool
|
||||||
|
)
|
||||||
|
metadata.ResetRuleCache()
|
||||||
|
metadata.DestinationAddresses = nil
|
||||||
|
dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex)
|
||||||
|
if strategy == dns.DomainStrategyAsIS {
|
||||||
|
strategy = transportStrategy
|
||||||
|
}
|
||||||
|
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
|
||||||
|
if rule != nil && rule.WithAddressLimit() {
|
||||||
|
addressLimit = true
|
||||||
|
resultAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool {
|
||||||
|
metadata.DestinationAddresses = responseAddrs
|
||||||
|
return rule.MatchAddressLimit(metadata)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addressLimit = false
|
||||||
|
resultAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, dns.ErrResponseRejected) {
|
||||||
|
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain)
|
||||||
|
} else {
|
||||||
|
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||||
|
}
|
||||||
|
} else if len(resultAddrs) == 0 {
|
||||||
|
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||||
|
err = dns.RCodeNameError
|
||||||
|
}
|
||||||
|
if !addressLimit || err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
|
if len(resultAddrs) > 0 {
|
||||||
defer cancel()
|
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(resultAddrs), " "))
|
||||||
addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy)
|
|
||||||
if len(addrs) > 0 {
|
|
||||||
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " "))
|
|
||||||
} else if err != nil {
|
|
||||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
|
||||||
} else {
|
|
||||||
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
|
||||||
err = dns.RCodeNameError
|
|
||||||
}
|
}
|
||||||
return addrs, err
|
return resultAddrs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
|
func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
|
||||||
|
@ -154,10 +230,13 @@ func (r *Router) ClearDNSCache() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) {
|
func isAddressQuery(message *mDNS.Msg) bool {
|
||||||
for _, answer := range answers {
|
for _, question := range message.Question {
|
||||||
logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String()))
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fqdnToDomain(fqdn string) string {
|
func fqdnToDomain(fqdn string) string {
|
||||||
|
|
|
@ -59,7 +59,7 @@ func isGeoIPRule(rule option.DefaultRule) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGeositeRule(rule option.DefaultRule) bool {
|
func isGeositeRule(rule option.DefaultRule) bool {
|
||||||
|
@ -97,3 +97,7 @@ func isWIFIDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||||
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
|
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return len(rule.IPCIDR) > 0 || rule.IPSet != nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ type abstractDefaultRule struct {
|
||||||
sourceAddressItems []RuleItem
|
sourceAddressItems []RuleItem
|
||||||
sourcePortItems []RuleItem
|
sourcePortItems []RuleItem
|
||||||
destinationAddressItems []RuleItem
|
destinationAddressItems []RuleItem
|
||||||
|
destinationIPCIDRItems []RuleItem
|
||||||
destinationPortItems []RuleItem
|
destinationPortItems []RuleItem
|
||||||
allItems []RuleItem
|
allItems []RuleItem
|
||||||
ruleSetItem RuleItem
|
ruleSetItem RuleItem
|
||||||
|
@ -64,6 +65,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
||||||
|
metadata.DidMatch = true
|
||||||
for _, item := range r.sourceAddressItems {
|
for _, item := range r.sourceAddressItems {
|
||||||
if item.Match(metadata) {
|
if item.Match(metadata) {
|
||||||
metadata.SourceAddressMatch = true
|
metadata.SourceAddressMatch = true
|
||||||
|
@ -73,6 +75,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
||||||
|
metadata.DidMatch = true
|
||||||
for _, item := range r.sourcePortItems {
|
for _, item := range r.sourcePortItems {
|
||||||
if item.Match(metadata) {
|
if item.Match(metadata) {
|
||||||
metadata.SourcePortMatch = true
|
metadata.SourcePortMatch = true
|
||||||
|
@ -82,6 +85,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
||||||
|
metadata.DidMatch = true
|
||||||
for _, item := range r.destinationAddressItems {
|
for _, item := range r.destinationAddressItems {
|
||||||
if item.Match(metadata) {
|
if item.Match(metadata) {
|
||||||
metadata.DestinationAddressMatch = true
|
metadata.DestinationAddressMatch = true
|
||||||
|
@ -90,7 +94,18 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch {
|
||||||
|
metadata.DidMatch = true
|
||||||
|
for _, item := range r.destinationIPCIDRItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
metadata.DestinationAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
||||||
|
metadata.DidMatch = true
|
||||||
for _, item := range r.destinationPortItems {
|
for _, item := range r.destinationPortItems {
|
||||||
if item.Match(metadata) {
|
if item.Match(metadata) {
|
||||||
metadata.DestinationPortMatch = true
|
metadata.DestinationPortMatch = true
|
||||||
|
@ -100,6 +115,9 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range r.items {
|
for _, item := range r.items {
|
||||||
|
if _, isRuleSet := item.(*RuleSetItem); !isRuleSet {
|
||||||
|
metadata.DidMatch = true
|
||||||
|
}
|
||||||
if !item.Match(metadata) {
|
if !item.Match(metadata) {
|
||||||
return r.invert
|
return r.invert
|
||||||
}
|
}
|
||||||
|
@ -113,7 +131,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
return r.invert
|
return r.invert
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch {
|
||||||
return r.invert
|
return r.invert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +139,10 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
return r.invert
|
return r.invert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !metadata.DidMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return !r.invert
|
return !r.invert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||||
}
|
}
|
||||||
if len(options.GeoIP) > 0 {
|
if len(options.GeoIP) > 0 {
|
||||||
item := NewGeoIPItem(router, logger, false, options.GeoIP)
|
item := NewGeoIPItem(router, logger, false, options.GeoIP)
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if len(options.SourceIPCIDR) > 0 {
|
if len(options.SourceIPCIDR) > 0 {
|
||||||
|
@ -130,12 +130,12 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "ipcidr")
|
return nil, E.Cause(err, "ipcidr")
|
||||||
}
|
}
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if options.IPIsPrivate {
|
if options.IPIsPrivate {
|
||||||
item := NewIPIsPrivateItem(false)
|
item := NewIPIsPrivateItem(false)
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if len(options.SourcePort) > 0 {
|
if len(options.SourcePort) > 0 {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -111,6 +112,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||||
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if len(options.GeoIP) > 0 {
|
||||||
|
item := NewGeoIPItem(router, logger, false, options.GeoIP)
|
||||||
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.SourceIPCIDR) > 0 {
|
if len(options.SourceIPCIDR) > 0 {
|
||||||
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -119,11 +125,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||||
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
rule.allItems = append(rule.allItems, 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, "ip_cidr")
|
||||||
|
}
|
||||||
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if options.SourceIPIsPrivate {
|
if options.SourceIPIsPrivate {
|
||||||
item := NewIPIsPrivateItem(true)
|
item := NewIPIsPrivateItem(true)
|
||||||
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if options.IPIsPrivate {
|
||||||
|
item := NewIPIsPrivateItem(false)
|
||||||
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.SourcePort) > 0 {
|
if len(options.SourcePort) > 0 {
|
||||||
item := NewPortItem(true, options.SourcePort)
|
item := NewPortItem(true, options.SourcePort)
|
||||||
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
@ -211,6 +230,34 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 {
|
||||||
return r.rewriteTTL
|
return r.rewriteTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DefaultDNSRule) WithAddressLimit() bool {
|
||||||
|
if len(r.destinationIPCIDRItems) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, rawRule := range r.items {
|
||||||
|
ruleSet, isRuleSet := rawRule.(*RuleSetItem)
|
||||||
|
if !isRuleSet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ruleSet.ContainsIPCIDRRule() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
metadata.IgnoreDestinationIPCIDRMatch = true
|
||||||
|
defer func() {
|
||||||
|
metadata.IgnoreDestinationIPCIDRMatch = false
|
||||||
|
}()
|
||||||
|
return r.abstractDefaultRule.Match(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||||
|
return r.abstractDefaultRule.Match(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
type LogicalDNSRule struct {
|
||||||
|
@ -254,3 +301,47 @@ func (r *LogicalDNSRule) DisableCache() bool {
|
||||||
func (r *LogicalDNSRule) RewriteTTL() *uint32 {
|
func (r *LogicalDNSRule) RewriteTTL() *uint32 {
|
||||||
return r.rewriteTTL
|
return r.rewriteTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LogicalDNSRule) WithAddressLimit() bool {
|
||||||
|
for _, rawRule := range r.rules {
|
||||||
|
switch rule := rawRule.(type) {
|
||||||
|
case *DefaultDNSRule:
|
||||||
|
if rule.WithAddressLimit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case *LogicalDNSRule:
|
||||||
|
if rule.WithAddressLimit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
if r.mode == C.LogicalTypeAnd {
|
||||||
|
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||||
|
metadata.ResetRuleCache()
|
||||||
|
return it.(adapter.DNSRule).Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
} else {
|
||||||
|
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||||
|
metadata.ResetRuleCache()
|
||||||
|
return it.(adapter.DNSRule).Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||||
|
if r.mode == C.LogicalTypeAnd {
|
||||||
|
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||||
|
metadata.ResetRuleCache()
|
||||||
|
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
} else {
|
||||||
|
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||||
|
metadata.ResetRuleCache()
|
||||||
|
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -80,11 +80,11 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "ipcidr")
|
return nil, E.Cause(err, "ipcidr")
|
||||||
}
|
}
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
} else if options.IPSet != nil {
|
} else if options.IPSet != nil {
|
||||||
item := NewRawIPCIDRItem(false, options.IPSet)
|
item := NewRawIPCIDRItem(false, options.IPSet)
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if len(options.SourcePort) > 0 {
|
if len(options.SourcePort) > 0 {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
)
|
)
|
||||||
|
@ -13,7 +14,7 @@ var _ RuleItem = (*RuleSetItem)(nil)
|
||||||
type RuleSetItem struct {
|
type RuleSetItem struct {
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
tagList []string
|
tagList []string
|
||||||
setList []adapter.HeadlessRule
|
setList []adapter.RuleSet
|
||||||
ipcidrMatchSource bool
|
ipcidrMatchSource bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +47,12 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuleSetItem) ContainsIPCIDRRule() bool {
|
||||||
|
return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool {
|
||||||
|
return ruleSet.Metadata().ContainsIPCIDRRule
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RuleSetItem) String() string {
|
func (r *RuleSetItem) String() string {
|
||||||
if len(r.tagList) == 1 {
|
if len(r.tagList) == 1 {
|
||||||
return F.ToString("rule_set=", r.tagList[0])
|
return F.ToString("rule_set=", r.tagList[0])
|
||||||
|
|
|
@ -3,12 +3,14 @@ package route
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,6 +57,7 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS
|
||||||
var metadata adapter.RuleSetMetadata
|
var metadata adapter.RuleSetMetadata
|
||||||
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
|
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
return &LocalRuleSet{rules, metadata}, nil
|
return &LocalRuleSet{rules, metadata}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +70,10 @@ func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) String() string {
|
||||||
|
return strings.Join(F.MapToString(s.rules), " ")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
|
func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
@ -68,6 +70,10 @@ func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) String() string {
|
||||||
|
return strings.Join(F.MapToString(s.rules), " ")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
|
func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
|
||||||
var dialer N.Dialer
|
var dialer N.Dialer
|
||||||
if s.options.RemoteOptions.DownloadDetour != "" {
|
if s.options.RemoteOptions.DownloadDetour != "" {
|
||||||
|
@ -150,6 +156,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||||
}
|
}
|
||||||
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
|
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue