From 218567be4637c3cb2e5039188365a55261af2442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 30 Nov 2023 15:58:00 +0800 Subject: [PATCH] Independent `source_ip_is_private` and `ip_is_private` rules --- docs/configuration/dns/rule.md | 8 ++++ docs/configuration/dns/rule.zh.md | 8 ++++ docs/configuration/route/rule.md | 16 ++++++++ docs/configuration/route/rule.zh.md | 16 ++++++++ docs/migration.md | 8 ++++ option/rule.go | 2 + option/rule_dns.go | 63 +++++++++++++++-------------- route/rule_default.go | 10 +++++ route/rule_dns.go | 5 +++ route/rule_item_ip_is_private.go | 44 ++++++++++++++++++++ 10 files changed, 149 insertions(+), 31 deletions(-) create mode 100644 route/rule_item_ip_is_private.go diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 896a3b4468..5c3e6086b6 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -5,6 +5,7 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) + :material-plus: [source_ip_is_private](#source_ip_is_private) :material-delete-clock: [geoip](#geoip) :material-delete-clock: [geosite](#geosite) @@ -56,6 +57,7 @@ icon: material/alert-decagram "10.0.0.0/24", "192.168.0.1" ], + "source_ip_is_private": false, "source_port": [ 12345 ], @@ -198,6 +200,12 @@ Match source geoip. Match source IP CIDR. +#### source_ip_is_private + +!!! question "Since sing-box 1.8.0" + +Match non-public source IP. + #### source_port Match source port. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index f990ed3ec5..362e7ce4fb 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -5,6 +5,7 @@ icon: material/alert-decagram !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) + :material-plus: [source_ip_is_private](#source_ip_is_private) :material-delete-clock: [geoip](#geoip) :material-delete-clock: [geosite](#geosite) @@ -55,6 +56,7 @@ icon: material/alert-decagram "source_ip_cidr": [ "10.0.0.0/24" ], + "source_ip_is_private": false, "source_port": [ 12345 ], @@ -195,6 +197,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配源 IP CIDR。 +#### source_ip_is_private + +!!! question "自 sing-box 1.8.0 起" + +匹配非公开源 IP。 + #### source_port 匹配源端口。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 35c1d8fc24..2eee990935 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -6,6 +6,8 @@ icon: material/alert-decagram :material-plus: [rule_set](#rule_set) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [source_ip_is_private](#source_ip_is_private) + :material-plus: [ip_is_private](#ip_is_private) :material-delete-clock: [source_geoip](#source_geoip) :material-delete-clock: [geoip](#geoip) :material-delete-clock: [geosite](#geosite) @@ -58,10 +60,12 @@ icon: material/alert-decagram "10.0.0.0/24", "192.168.0.1" ], + "source_ip_is_private": false, "ip_cidr": [ "10.0.0.0/24", "192.168.0.1" ], + "ip_is_private": false, "source_port": [ 12345 ], @@ -202,10 +206,22 @@ Match geoip. Match source IP CIDR. +#### ip_is_private + +!!! question "Since sing-box 1.8.0" + +Match non-public IP. + #### ip_cidr Match IP CIDR. +#### source_ip_is_private + +!!! question "Since sing-box 1.8.0" + +Match non-public source IP. + #### source_port Match source port. diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 31ee08d13a..3bac97e6fb 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -6,6 +6,8 @@ icon: material/alert-decagram :material-plus: [rule_set](#rule_set) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [source_ip_is_private](#source_ip_is_private) + :material-plus: [ip_is_private](#ip_is_private) :material-delete-clock: [source_geoip](#source_geoip) :material-delete-clock: [geoip](#geoip) :material-delete-clock: [geosite](#geosite) @@ -57,9 +59,11 @@ icon: material/alert-decagram "source_ip_cidr": [ "10.0.0.0/24" ], + "source_ip_is_private": false, "ip_cidr": [ "10.0.0.0/24" ], + "ip_is_private": false, "source_port": [ 12345 ], @@ -200,10 +204,22 @@ icon: material/alert-decagram 匹配源 IP CIDR。 +#### source_ip_is_private + +!!! question "自 sing-box 1.8.0 起" + +匹配非公开源 IP。 + #### ip_cidr 匹配 IP CIDR。 +#### ip_is_private + +!!! question "自 sing-box 1.8.0 起" + +匹配非公开 IP。 + #### source_port 匹配源端口。 diff --git a/docs/migration.md b/docs/migration.md index 8eb3daee42..aec9b36007 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -68,6 +68,10 @@ icon: material/arrange-bring-forward { "route": { "rules": [ + { + "geoip": "private", + "outbound": "direct" + }, { "geoip": "cn", "outbound": "direct" @@ -90,6 +94,10 @@ icon: material/arrange-bring-forward { "route": { "rules": [ + { + "ip_is_private": true, + "outbound": "direct" + }, { "rule_set": "geoip-cn", "outbound": "direct" diff --git a/option/rule.go b/option/rule.go index bad605a0fc..1201d123ed 100644 --- a/option/rule.go +++ b/option/rule.go @@ -78,7 +78,9 @@ type DefaultRule struct { SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` GeoIP Listable[string] `json:"geoip,omitempty"` SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` SourcePort Listable[uint16] `json:"source_port,omitempty"` SourcePortRange Listable[string] `json:"source_port_range,omitempty"` Port Listable[uint16] `json:"port,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index c02d09f761..50d9e61266 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -65,37 +65,38 @@ func (r DNSRule) IsValid() bool { } type DefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType Listable[DNSQueryType] `json:"query_type,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"` - SourceIPCIDR Listable[string] `json:"source_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"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet Listable[string] `json:"rule_set,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + Inbound Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType Listable[DNSQueryType] `json:"query_type,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"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,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"` + Outbound Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` + RuleSet Listable[string] `json:"rule_set,omitempty"` + Invert bool `json:"invert,omitempty"` + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` } func (r DefaultDNSRule) IsValid() bool { diff --git a/route/rule_default.go b/route/rule_default.go index c0ef9eef60..1a190ce04b 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -120,6 +120,11 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.allItems = append(rule.allItems, item) } + if options.SourceIPIsPrivate { + item := NewIPIsPrivateItem(true) + 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 { @@ -128,6 +133,11 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } + if options.IPIsPrivate { + item := NewIPIsPrivateItem(false) + 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) diff --git a/route/rule_dns.go b/route/rule_dns.go index f5f9fd3583..1f55d50edb 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -119,6 +119,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.allItems = append(rule.allItems, item) } + if options.SourceIPIsPrivate { + item := NewIPIsPrivateItem(true) + rule.sourceAddressItems = append(rule.sourceAddressItems, item) + rule.allItems = append(rule.allItems, item) + } if len(options.SourcePort) > 0 { item := NewPortItem(true, options.SourcePort) rule.sourcePortItems = append(rule.sourcePortItems, item) diff --git a/route/rule_item_ip_is_private.go b/route/rule_item_ip_is_private.go new file mode 100644 index 0000000000..4d511fdfb1 --- /dev/null +++ b/route/rule_item_ip_is_private.go @@ -0,0 +1,44 @@ +package route + +import ( + "net/netip" + + "github.com/sagernet/sing-box/adapter" + N "github.com/sagernet/sing/common/network" +) + +var _ RuleItem = (*IPIsPrivateItem)(nil) + +type IPIsPrivateItem struct { + isSource bool +} + +func NewIPIsPrivateItem(isSource bool) *IPIsPrivateItem { + return &IPIsPrivateItem{isSource} +} + +func (r *IPIsPrivateItem) Match(metadata *adapter.InboundContext) bool { + var destination netip.Addr + if r.isSource { + destination = metadata.Source.Addr + } else { + destination = metadata.Destination.Addr + } + if destination.IsValid() && !N.IsPublicAddr(destination) { + return true + } + for _, destinationAddress := range metadata.DestinationAddresses { + if !N.IsPublicAddr(destinationAddress) { + return true + } + } + return false +} + +func (r *IPIsPrivateItem) String() string { + if r.isSource { + return "source_ip_is_private=true" + } else { + return "ip_is_private=true" + } +}