mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
Refactor rules
This commit is contained in:
parent
52b776b561
commit
aa94cfb876
|
@ -27,7 +27,7 @@ type InjectableInbound interface {
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion int
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
|
|
|
@ -77,6 +77,7 @@ type Rule interface {
|
||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
|
RewriteTTL() *uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestMergeJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: N.NetworkTCP,
|
Network: []string{N.NetworkTCP},
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -42,7 +42,7 @@ func TestMergeJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: N.NetworkUDP,
|
Network: []string{N.NetworkUDP},
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,7 +27,12 @@ type slowOpenConn struct {
|
||||||
|
|
||||||
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
return dialer.DialContext(ctx, network, destination.String(), nil)
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP, N.NetworkUDP:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.String())
|
||||||
|
default:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &slowOpenConn{
|
return &slowOpenConn{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": "tcp",
|
"network": [
|
||||||
|
"tcp"
|
||||||
|
],
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
|
@ -244,18 +246,12 @@ Tag of the target outbound.
|
||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
`and` or `or`
|
`and` or `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included default rules.
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
Invert match result.
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Tag of the target outbound.
|
Included default rules.
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": "tcp",
|
"network": [
|
||||||
|
"tcp"
|
||||||
|
],
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
|
@ -242,18 +244,12 @@
|
||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
`and` 或 `or`
|
`and` 或 `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
包括的默认规则。
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
反选匹配结果。
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
目标出站的标签。
|
包括的默认规则。
|
|
@ -38,10 +38,6 @@ type Tun struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||||
tunName := options.InterfaceName
|
|
||||||
if tunName == "" {
|
|
||||||
tunName = tun.CalculateInterfaceName("")
|
|
||||||
}
|
|
||||||
tunMTU := options.MTU
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
|
@ -75,7 +71,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||||
logger: logger,
|
logger: logger,
|
||||||
inboundOptions: options.InboundOptions,
|
inboundOptions: options.InboundOptions,
|
||||||
tunOptions: tun.Options{
|
tunOptions: tun.Options{
|
||||||
Name: tunName,
|
Name: options.InterfaceName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
||||||
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
||||||
|
@ -141,12 +137,17 @@ func (t *Tun) Tag() string {
|
||||||
|
|
||||||
func (t *Tun) Start() error {
|
func (t *Tun) Start() error {
|
||||||
if C.IsAndroid && t.platformInterface == nil {
|
if C.IsAndroid && t.platformInterface == nil {
|
||||||
|
t.logger.Trace("building android rules")
|
||||||
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
}
|
}
|
||||||
|
if t.tunOptions.Name == "" {
|
||||||
|
t.tunOptions.Name = tun.CalculateInterfaceName("")
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
tunInterface tun.Tun
|
tunInterface tun.Tun
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
t.logger.Trace("opening interface")
|
||||||
if t.platformInterface != nil {
|
if t.platformInterface != nil {
|
||||||
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
|
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,6 +156,7 @@ func (t *Tun) Start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "configure tun interface")
|
return E.Cause(err, "configure tun interface")
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("creating stack")
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunInterface
|
||||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||||
Context: t.ctx,
|
Context: t.ctx,
|
||||||
|
@ -172,6 +174,7 @@ func (t *Tun) Start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("starting stack")
|
||||||
err = t.tunStack.Start()
|
err = t.tunStack.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
103
option/dns.go
103
option/dns.go
|
@ -1,14 +1,5 @@
|
||||||
package option
|
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 DNSOptions struct {
|
type DNSOptions struct {
|
||||||
Servers []DNSServerOptions `json:"servers,omitempty"`
|
Servers []DNSServerOptions `json:"servers,omitempty"`
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
|
@ -32,97 +23,3 @@ type DNSServerOptions struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultDNSRule `json:"-"`
|
|
||||||
LogicalOptions LogicalDNSRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule _DNSRule
|
|
||||||
|
|
||||||
func (r DNSRule) 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((_DNSRule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_DNSRule)(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, (*_DNSRule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "dns route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
|
||||||
Network 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"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultDNSRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultDNSRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Server = r.Server
|
|
||||||
defaultValue.DisableCache = r.DisableCache
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalDNSRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
|
||||||
}
|
|
||||||
|
|
100
option/route.go
100
option/route.go
|
@ -1,14 +1,5 @@
|
||||||
package option
|
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 RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
||||||
|
@ -32,94 +23,3 @@ type GeositeOptions struct {
|
||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _Rule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultRule `json:"-"`
|
|
||||||
LogicalOptions LogicalRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule _Rule
|
|
||||||
|
|
||||||
func (r Rule) 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((_Rule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_Rule)(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, (*_Rule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
Network 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"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Outbound = r.Outbound
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
|
||||||
}
|
|
||||||
|
|
101
option/rule.go
Normal file
101
option/rule.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
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 _Rule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultRule `json:"-"`
|
||||||
|
LogicalOptions LogicalRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule _Rule
|
||||||
|
|
||||||
|
func (r Rule) 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((_Rule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_Rule)(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, (*_Rule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
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"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
||||||
|
}
|
107
option/rule_dns.go
Normal file
107
option/rule_dns.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
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 _DNSRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultDNSRule `json:"-"`
|
||||||
|
LogicalOptions LogicalDNSRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule _DNSRule
|
||||||
|
|
||||||
|
func (r DNSRule) 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((_DNSRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_DNSRule)(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, (*_DNSRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "dns route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
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 {
|
||||||
|
var defaultValue DefaultDNSRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Server = r.Server
|
||||||
|
defaultValue.DisableCache = r.DisableCache
|
||||||
|
defaultValue.RewriteTTL = r.RewriteTTL
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalDNSRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultDNSRule `json:"rules,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 LogicalDNSRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
||||||
|
}
|
266
route/router.go
266
route/router.go
|
@ -2,14 +2,11 @@ package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -38,7 +35,6 @@ import (
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -127,6 +123,7 @@ func NewRouter(
|
||||||
}
|
}
|
||||||
router.dnsRules = append(router.dnsRules, dnsRule)
|
router.dnsRules = append(router.dnsRules, dnsRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
||||||
dummyTransportMap := make(map[string]dns.Transport)
|
dummyTransportMap := make(map[string]dns.Transport)
|
||||||
transportMap := make(map[string]dns.Transport)
|
transportMap := make(map[string]dns.Transport)
|
||||||
|
@ -523,27 +520,6 @@ func (r *Router) Close() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) GeoIPReader() *geoip.Reader {
|
|
||||||
return r.geoIPReader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
|
||||||
rule, cached := r.geositeCache[code]
|
|
||||||
if cached {
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
items, err := r.geositeReader.Read(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.geositeCache[code] = rule
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
|
@ -725,6 +701,13 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||||
}
|
}
|
||||||
conn = bufio.NewCachedPacketConn(conn, buffer, destination)
|
conn = bufio.NewCachedPacketConn(conn, buffer, destination)
|
||||||
}
|
}
|
||||||
|
if r.dnsReverseMapping != nil && metadata.Domain == "" {
|
||||||
|
domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
|
||||||
|
if loaded {
|
||||||
|
metadata.Domain = domain
|
||||||
|
r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
||||||
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -898,239 +881,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
|
||||||
r.v2rayServer = server
|
r.v2rayServer = server
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func notPrivateNode(code string) bool {
|
|
||||||
return code != "private"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeoIPDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geoIPOptions.Path != "" {
|
|
||||||
geoPath = r.geoIPOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geoip.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geoip database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeoIPDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geoip database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geoip.Open(geoPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open geoip database")
|
|
||||||
}
|
|
||||||
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
|
||||||
r.geoIPReader = geoReader
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeositeDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geositeOptions.Path != "" {
|
|
||||||
geoPath = r.geositeOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geosite.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geosite database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeositeDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geosite database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geosite.Open(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
|
||||||
r.geositeReader = geoReader
|
|
||||||
} else {
|
|
||||||
return E.Cause(err, "open geosite database")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geoIPOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geoIPOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geoip database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geoIPOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geositeOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geositeOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geosite database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geositeOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||||
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport,
|
||||||
if rule.DisableCache() {
|
if rule.DisableCache() {
|
||||||
ctx = dns.ContextWithDisableCache(ctx, true)
|
ctx = dns.ContextWithDisableCache(ctx, true)
|
||||||
}
|
}
|
||||||
|
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
||||||
|
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
||||||
|
}
|
||||||
detour := rule.Outbound()
|
detour := rule.Outbound()
|
||||||
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||||
if transport, loaded := r.transportMap[detour]; loaded {
|
if transport, loaded := r.transportMap[detour]; loaded {
|
||||||
|
|
283
route/router_geo_resources.go
Normal file
283
route/router_geo_resources.go
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Router) GeoIPReader() *geoip.Reader {
|
||||||
|
return r.geoIPReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
||||||
|
rule, cached := r.geositeCache[code]
|
||||||
|
if cached {
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
items, err := r.geositeReader.Read(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.geositeCache[code] = rule
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeoIPDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geoIPOptions.Path != "" {
|
||||||
|
geoPath = r.geoIPOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geoip.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if rw.FileExists(geoPath) {
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geoip database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeoIPDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geoip database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open geoip database")
|
||||||
|
}
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeositeDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geositeOptions.Path != "" {
|
||||||
|
geoPath = r.geositeOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geosite.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geosite database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeositeDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geosite database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geosite.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
||||||
|
r.geositeReader = geoReader
|
||||||
|
} else {
|
||||||
|
return E.Cause(err, "open geosite database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geoIPOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geoIPOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geoip database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geoIPOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geositeOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geositeOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geosite database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geositeOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func notPrivateNode(code string) bool {
|
||||||
|
return code != "private"
|
||||||
|
}
|
203
route/rule_abstract.go
Normal file
203
route/rule_abstract.go
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type abstractDefaultRule struct {
|
||||||
|
items []RuleItem
|
||||||
|
sourceAddressItems []RuleItem
|
||||||
|
sourcePortItems []RuleItem
|
||||||
|
destinationAddressItems []RuleItem
|
||||||
|
destinationPortItems []RuleItem
|
||||||
|
allItems []RuleItem
|
||||||
|
invert bool
|
||||||
|
outbound string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Type() string {
|
||||||
|
return C.RuleTypeDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Start() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
err := common.Start(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Close() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
err := common.Close(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) UpdateGeosite() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
||||||
|
err := geositeItem.Update()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
for _, item := range r.items {
|
||||||
|
if !item.Match(metadata) {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.sourceAddressItems) > 0 {
|
||||||
|
var sourceAddressMatch bool
|
||||||
|
for _, item := range r.sourceAddressItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
sourceAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sourceAddressMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.sourcePortItems) > 0 {
|
||||||
|
var sourcePortMatch bool
|
||||||
|
for _, item := range r.sourcePortItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
sourcePortMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sourcePortMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.destinationAddressItems) > 0 {
|
||||||
|
var destinationAddressMatch bool
|
||||||
|
for _, item := range r.destinationAddressItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
destinationAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !destinationAddressMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.destinationPortItems) > 0 {
|
||||||
|
var destinationPortMatch bool
|
||||||
|
for _, item := range r.destinationPortItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
destinationPortMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !destinationPortMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !r.invert
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Outbound() string {
|
||||||
|
return r.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) String() string {
|
||||||
|
if !r.invert {
|
||||||
|
return strings.Join(F.MapToString(r.allItems), " ")
|
||||||
|
} else {
|
||||||
|
return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type abstractLogicalRule struct {
|
||||||
|
rules []adapter.Rule
|
||||||
|
mode string
|
||||||
|
invert bool
|
||||||
|
outbound string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Type() string {
|
||||||
|
return C.RuleTypeLogical
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) UpdateGeosite() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.UpdateGeosite()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Start() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Close() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
if r.mode == C.LogicalTypeAnd {
|
||||||
|
return common.All(r.rules, func(it adapter.Rule) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
} else {
|
||||||
|
return common.Any(r.rules, func(it adapter.Rule) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Outbound() string {
|
||||||
|
return r.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) String() string {
|
||||||
|
var op string
|
||||||
|
switch r.mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
op = "&&"
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
op = "||"
|
||||||
|
}
|
||||||
|
if !r.invert {
|
||||||
|
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
||||||
|
} else {
|
||||||
|
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,11 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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-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"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
|
func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
|
||||||
|
@ -39,14 +34,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul
|
||||||
var _ adapter.Rule = (*DefaultRule)(nil)
|
var _ adapter.Rule = (*DefaultRule)(nil)
|
||||||
|
|
||||||
type DefaultRule struct {
|
type DefaultRule struct {
|
||||||
items []RuleItem
|
abstractDefaultRule
|
||||||
sourceAddressItems []RuleItem
|
|
||||||
sourcePortItems []RuleItem
|
|
||||||
destinationAddressItems []RuleItem
|
|
||||||
destinationPortItems []RuleItem
|
|
||||||
allItems []RuleItem
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleItem interface {
|
type RuleItem interface {
|
||||||
|
@ -56,8 +44,10 @@ type RuleItem interface {
|
||||||
|
|
||||||
func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
|
func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
|
||||||
rule := &DefaultRule{
|
rule := &DefaultRule{
|
||||||
invert: options.Invert,
|
abstractDefaultRule{
|
||||||
outbound: options.Outbound,
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if len(options.Inbound) > 0 {
|
if len(options.Inbound) > 0 {
|
||||||
item := NewInboundRule(options.Inbound)
|
item := NewInboundRule(options.Inbound)
|
||||||
|
@ -74,15 +64,10 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||||
return nil, E.New("invalid ip version: ", options.IPVersion)
|
return nil, E.New("invalid ip version: ", options.IPVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Network != "" {
|
if len(options.Network) > 0 {
|
||||||
switch options.Network {
|
item := NewNetworkItem(options.Network)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
rule.items = append(rule.items, item)
|
||||||
item := NewNetworkItem(options.Network)
|
rule.allItems = append(rule.allItems, item)
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
default:
|
|
||||||
return nil, E.New("invalid network: ", options.Network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(options.AuthUser) > 0 {
|
if len(options.AuthUser) > 0 {
|
||||||
item := NewAuthUserItem(options.AuthUser)
|
item := NewAuthUserItem(options.AuthUser)
|
||||||
|
@ -202,130 +187,19 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultRule) Type() string {
|
|
||||||
return C.RuleTypeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Start() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Start(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Close() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Close(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) UpdateGeosite() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
|
||||||
err := geositeItem.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
for _, item := range r.items {
|
|
||||||
if !item.Match(metadata) {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 {
|
|
||||||
var sourceAddressMatch bool
|
|
||||||
for _, item := range r.sourceAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourceAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 {
|
|
||||||
var sourcePortMatch bool
|
|
||||||
for _, item := range r.sourcePortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourcePortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourcePortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 {
|
|
||||||
var destinationAddressMatch bool
|
|
||||||
for _, item := range r.destinationAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 {
|
|
||||||
var destinationPortMatch bool
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationPortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !r.invert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) String() string {
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.allItems), " ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.Rule = (*LogicalRule)(nil)
|
var _ adapter.Rule = (*LogicalRule)(nil)
|
||||||
|
|
||||||
type LogicalRule struct {
|
type LogicalRule struct {
|
||||||
mode string
|
abstractLogicalRule
|
||||||
rules []*DefaultRule
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
||||||
r := &LogicalRule{
|
r := &LogicalRule{
|
||||||
rules: make([]*DefaultRule, len(options.Rules)),
|
abstractLogicalRule{
|
||||||
invert: options.Invert,
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
outbound: options.Outbound,
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
switch options.Mode {
|
switch options.Mode {
|
||||||
case C.LogicalTypeAnd:
|
case C.LogicalTypeAnd:
|
||||||
|
@ -344,68 +218,3 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalRule) Type() string {
|
|
||||||
return C.RuleTypeLogical
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) UpdateGeosite() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.UpdateGeosite()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Start() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Close() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
if r.mode == C.LogicalTypeAnd {
|
|
||||||
return common.All(r.rules, func(it *DefaultRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
} else {
|
|
||||||
return common.Any(r.rules, func(it *DefaultRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) String() string {
|
|
||||||
var op string
|
|
||||||
switch r.mode {
|
|
||||||
case C.LogicalTypeAnd:
|
|
||||||
op = "&&"
|
|
||||||
case C.LogicalTypeOr:
|
|
||||||
op = "||"
|
|
||||||
}
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,11 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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-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"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
|
func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
|
||||||
|
@ -39,22 +34,19 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.
|
||||||
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
type DefaultDNSRule struct {
|
||||||
items []RuleItem
|
abstractDefaultRule
|
||||||
sourceAddressItems []RuleItem
|
disableCache bool
|
||||||
sourcePortItems []RuleItem
|
rewriteTTL *uint32
|
||||||
destinationAddressItems []RuleItem
|
|
||||||
destinationPortItems []RuleItem
|
|
||||||
allItems []RuleItem
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
disableCache bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
||||||
rule := &DefaultDNSRule{
|
rule := &DefaultDNSRule{
|
||||||
invert: options.Invert,
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
outbound: options.Server,
|
invert: options.Invert,
|
||||||
|
outbound: options.Server,
|
||||||
|
},
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
|
rewriteTTL: options.RewriteTTL,
|
||||||
}
|
}
|
||||||
if len(options.Inbound) > 0 {
|
if len(options.Inbound) > 0 {
|
||||||
item := NewInboundRule(options.Inbound)
|
item := NewInboundRule(options.Inbound)
|
||||||
|
@ -76,15 +68,10 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if options.Network != "" {
|
if len(options.Network) > 0 {
|
||||||
switch options.Network {
|
item := NewNetworkItem(options.Network)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
rule.items = append(rule.items, item)
|
||||||
item := NewNetworkItem(options.Network)
|
rule.allItems = append(rule.allItems, item)
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
default:
|
|
||||||
return nil, E.New("invalid network: ", options.Network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(options.AuthUser) > 0 {
|
if len(options.AuthUser) > 0 {
|
||||||
item := NewAuthUserItem(options.AuthUser)
|
item := NewAuthUserItem(options.AuthUser)
|
||||||
|
@ -196,132 +183,31 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Type() string {
|
|
||||||
return C.RuleTypeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Start() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Start(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Close() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Close(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) UpdateGeosite() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
|
||||||
err := geositeItem.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
for _, item := range r.items {
|
|
||||||
if !item.Match(metadata) {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 {
|
|
||||||
var sourceAddressMatch bool
|
|
||||||
for _, item := range r.sourceAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourceAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 {
|
|
||||||
var sourcePortMatch bool
|
|
||||||
for _, item := range r.sourcePortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourcePortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourcePortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 {
|
|
||||||
var destinationAddressMatch bool
|
|
||||||
for _, item := range r.destinationAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 {
|
|
||||||
var destinationPortMatch bool
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationPortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !r.invert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) DisableCache() bool {
|
func (r *DefaultDNSRule) DisableCache() bool {
|
||||||
return r.disableCache
|
return r.disableCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) String() string {
|
func (r *DefaultDNSRule) RewriteTTL() *uint32 {
|
||||||
return strings.Join(F.MapToString(r.allItems), " ")
|
return r.rewriteTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
type LogicalDNSRule struct {
|
||||||
mode string
|
abstractLogicalRule
|
||||||
rules []*DefaultDNSRule
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
disableCache bool
|
disableCache bool
|
||||||
|
rewriteTTL *uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
||||||
r := &LogicalDNSRule{
|
r := &LogicalDNSRule{
|
||||||
rules: make([]*DefaultDNSRule, len(options.Rules)),
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
invert: options.Invert,
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
outbound: options.Server,
|
invert: options.Invert,
|
||||||
|
outbound: options.Server,
|
||||||
|
},
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
|
rewriteTTL: options.RewriteTTL,
|
||||||
}
|
}
|
||||||
switch options.Mode {
|
switch options.Mode {
|
||||||
case C.LogicalTypeAnd:
|
case C.LogicalTypeAnd:
|
||||||
|
@ -341,71 +227,10 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Type() string {
|
|
||||||
return C.RuleTypeLogical
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) UpdateGeosite() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.UpdateGeosite()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Start() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Close() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
if r.mode == C.LogicalTypeAnd {
|
|
||||||
return common.All(r.rules, func(it *DefaultDNSRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
} else {
|
|
||||||
return common.Any(r.rules, func(it *DefaultDNSRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) DisableCache() bool {
|
func (r *LogicalDNSRule) DisableCache() bool {
|
||||||
return r.disableCache
|
return r.disableCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) String() string {
|
func (r *LogicalDNSRule) RewriteTTL() *uint32 {
|
||||||
var op string
|
return r.rewriteTTL
|
||||||
switch r.mode {
|
|
||||||
case C.LogicalTypeAnd:
|
|
||||||
op = "&&"
|
|
||||||
case C.LogicalTypeOr:
|
|
||||||
op = "||"
|
|
||||||
}
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
42
route/rule_item_network.go
Normal file
42
route/rule_item_network.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*NetworkItem)(nil)
|
||||||
|
|
||||||
|
type NetworkItem struct {
|
||||||
|
networks []string
|
||||||
|
networkMap map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetworkItem(networks []string) *NetworkItem {
|
||||||
|
networkMap := make(map[string]bool)
|
||||||
|
for _, network := range networks {
|
||||||
|
networkMap[network] = true
|
||||||
|
}
|
||||||
|
return &NetworkItem{
|
||||||
|
networks: networks,
|
||||||
|
networkMap: networkMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return r.networkMap[metadata.Network]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkItem) String() string {
|
||||||
|
description := "network="
|
||||||
|
|
||||||
|
pLen := len(r.networks)
|
||||||
|
if pLen == 1 {
|
||||||
|
description += F.ToString(r.networks[0])
|
||||||
|
} else {
|
||||||
|
description += "[" + strings.Join(F.MapToString(r.networks), " ") + "]"
|
||||||
|
}
|
||||||
|
return description
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ RuleItem = (*NetworkItem)(nil)
|
|
||||||
|
|
||||||
type NetworkItem struct {
|
|
||||||
network string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNetworkItem(network string) *NetworkItem {
|
|
||||||
return &NetworkItem{network}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
return r.network == metadata.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *NetworkItem) String() string {
|
|
||||||
return "network=" + r.network
|
|
||||||
}
|
|
Loading…
Reference in a new issue