Compare commits

..

9 Commits

Author SHA1 Message Date
世界
26064a9fdc documentation: Bump version 2024-11-11 20:13:55 +08:00
世界
d8e66b9180 documentation: Add new rule item types 2024-11-11 20:13:55 +08:00
世界
c59f282b7d documentation: Merge route options to route actions 2024-11-11 20:13:55 +08:00
世界
aa35ae1736 Add network_[type/is_expensive/is_constrained] rule items 2024-11-11 20:13:55 +08:00
世界
ef2a2fdd52 Merge route options to route actions 2024-11-11 20:13:55 +08:00
世界
9988144868 Fix decompile rule-set 2024-11-11 20:13:55 +08:00
世界
412701d4c5 refactor: Platform Interfaces 2024-11-11 20:13:55 +08:00
世界
b6c940af61 Fix match rules 2024-11-11 16:06:56 +08:00
世界
1edb80adcc Fix start stage 2024-11-11 16:04:27 +08:00
64 changed files with 1473 additions and 668 deletions

View File

@@ -44,7 +44,7 @@ func (m *Manager) Start(stage adapter.StartStage) error {
for _, inbound := range m.inbounds {
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
}
return nil
@@ -118,7 +118,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
}
}

View File

@@ -1,5 +1,7 @@
package adapter
import E "github.com/sagernet/sing/common/exceptions"
type StartStage uint8
const (
@@ -16,7 +18,7 @@ var ListStartStages = []StartStage{
StartStateStarted,
}
func (s StartStage) Action() string {
func (s StartStage) String() string {
switch s {
case StartStateInitialize:
return "initialize"
@@ -25,7 +27,7 @@ func (s StartStage) Action() string {
case StartStatePostStart:
return "post-start"
case StartStateStarted:
return "start-after-started"
return "finish-start"
default:
panic("unknown stage")
}
@@ -40,3 +42,23 @@ type LifecycleService interface {
Name() string
Lifecycle
}
func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services {
err := service.Start(stage)
if err != nil {
return err
}
}
return nil
}
func StartNamed(stage StartStage, services []LifecycleService) error {
for _, service := range services {
err := service.Start(stage)
if err != nil {
return E.Cause(err, stage.String(), " ", service.Name())
}
}
return nil
}

View File

@@ -14,7 +14,7 @@ func LegacyStart(starter any, stage StartStage) error {
}); isStarter {
return starter.Start()
}
case StartStatePostStart:
case StartStateStarted:
if postStarter, isPostStarter := starter.(interface {
PostStart() error
}); isPostStarter {

View File

@@ -9,6 +9,8 @@ type NetworkManager interface {
Lifecycle
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultNetworkInterface() *NetworkInterface
NetworkInterfaces() []NetworkInterface
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
@@ -21,3 +23,20 @@ type NetworkManager interface {
WIFIState() WIFIState
ResetNetwork()
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}
type NetworkInterface struct {
control.Interface
Type string
DNSServers []string
Expensive bool
Constrained bool
}

View File

@@ -61,7 +61,7 @@ func (m *Manager) Start(stage adapter.StartStage) error {
for _, outbound := range outbounds {
err := adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
}
}
@@ -234,7 +234,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
}
}

View File

@@ -119,12 +119,3 @@ func (c *HTTPStartContext) Close() {
client.CloseIdleConnections()
}
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}

64
box.go
View File

@@ -315,27 +315,17 @@ func (s *Box) preStart() error {
if err != nil {
return E.Cause(err, "start logger")
}
for _, lifecycleService := range s.services {
err = lifecycleService.Start(adapter.StartStateInitialize) // cache-file
if err != nil {
return E.Cause(err, "initialize ", lifecycleService.Name())
}
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
if err != nil {
return err
}
for _, lifecycle := range []adapter.Lifecycle{
s.network, s.router, s.outbound, s.inbound,
} {
err = lifecycle.Start(adapter.StartStateInitialize)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound)
if err != nil {
return err
}
for _, lifecycle := range []adapter.Lifecycle{
s.outbound, s.network, s.router,
} {
err = lifecycle.Start(adapter.StartStateStart)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router)
if err != nil {
return err
}
return nil
}
@@ -345,31 +335,29 @@ func (s *Box) start() error {
if err != nil {
return err
}
for _, lifecycleService := range s.services {
err = lifecycleService.Start(adapter.StartStateStart)
if err != nil {
return E.Cause(err, "initialize ", lifecycleService.Name())
}
err = adapter.StartNamed(adapter.StartStateStart, s.services)
if err != nil {
return err
}
err = s.inbound.Start(adapter.StartStateStart)
if err != nil {
return err
}
for _, lifecycleService := range []adapter.Lifecycle{
s.outbound, s.network, s.router, s.inbound,
} {
err = lifecycleService.Start(adapter.StartStatePostStart)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound)
if err != nil {
return err
}
for _, lifecycleService := range []adapter.Lifecycle{
s.network, s.router, s.outbound, s.inbound,
} {
err = lifecycleService.Start(adapter.StartStateStarted)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
if err != nil {
return err
}
return nil
}

View File

@@ -6,7 +6,6 @@ import (
"strings"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
@@ -56,10 +55,6 @@ func compileRuleSet(sourcePath string) error {
if err != nil {
return err
}
ruleSet, err := plainRuleSet.Upgrade()
if err != nil {
return err
}
var outputPath string
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".json") {
@@ -74,7 +69,7 @@ func compileRuleSet(sourcePath string) error {
if err != nil {
return err
}
err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2)
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
if err != nil {
outputFile.Close()
os.Remove(outputPath)

View File

@@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -77,7 +78,7 @@ func convertRuleSet(sourcePath string) error {
return err
}
defer outputFile.Close()
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true)
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
if err != nil {
outputFile.Close()
os.Remove(outputPath)

View File

@@ -6,9 +6,7 @@ import (
"strings"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
@@ -48,14 +46,10 @@ func decompileRuleSet(sourcePath string) error {
return err
}
}
plainRuleSet, err := srs.Read(reader, true)
ruleSet, err := srs.Read(reader, true)
if err != nil {
return err
}
ruleSet := option.PlainRuleSetCompat{
Version: C.RuleSetVersion1,
Options: plainRuleSet,
}
var outputPath string
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".srs") {

View File

@@ -56,26 +56,25 @@ func ruleSetMatch(sourcePath string, domain string) error {
if err != nil {
return E.Cause(err, "read rule-set")
}
var plainRuleSet option.PlainRuleSet
var ruleSet option.PlainRuleSetCompat
switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource:
var compat option.PlainRuleSetCompat
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet, err = compat.Upgrade()
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
ruleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
}
plainRuleSet, err := ruleSet.Upgrade()
if err != nil {
return err
}
ipAddress := M.ParseAddr(domain)
var metadata adapter.InboundContext
if ipAddress.IsValid() {

View File

@@ -1,6 +1,7 @@
package adguard
import (
"context"
"strings"
"testing"
@@ -26,7 +27,7 @@ example.arpa
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(nil, rules[0])
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
require.NoError(t, err)
matchDomain := []string{
"example.org",
@@ -85,7 +86,7 @@ func TestHosts(t *testing.T) {
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(nil, rules[0])
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
require.NoError(t, err)
matchDomain := []string{
"google.com",
@@ -115,7 +116,7 @@ www.example.org
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(nil, rules[0])
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
require.NoError(t, err)
matchDomain := []string{
"example.com",

View File

@@ -2,7 +2,6 @@ package settings
import (
"context"
"net/netip"
"strconv"
"strings"
@@ -77,14 +76,14 @@ func (p *DarwinSystemProxy) update(event int) {
}
func (p *DarwinSystemProxy) update0() error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
newInterface := p.monitor.DefaultInterface()
if p.interfaceName == newInterface.Name {
return nil
}
if p.interfaceName != "" {
_ = p.Disable()
}
p.interfaceName = newInterfaceName
p.interfaceName = newInterface.Name
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil {
return err

View File

@@ -38,10 +38,13 @@ const (
ruleItemWIFIBSSID
ruleItemAdGuardDomain
ruleItemProcessPathRegex
ruleItemNetworkType
ruleItemNetworkIsExpensive
ruleItemNetworkIsConstrained
ruleItemFinal uint8 = 0xFF
)
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) {
var magicBytes [3]byte
_, err = io.ReadFull(reader, magicBytes[:])
if err != nil {
@@ -54,10 +57,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
var version uint8
err = binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return ruleSet, err
return ruleSetCompat, err
}
if version > C.RuleSetVersion2 {
return ruleSet, E.New("unsupported version: ", version)
if version > C.RuleSetVersionCurrent {
return ruleSetCompat, E.New("unsupported version: ", version)
}
compressReader, err := zlib.NewReader(reader)
if err != nil {
@@ -68,9 +71,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
if err != nil {
return
}
ruleSet.Rules = make([]option.HeadlessRule, length)
ruleSetCompat.Version = version
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ {
ruleSet.Rules[i], err = readRule(bReader, recover)
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
if err != nil {
err = E.Cause(err, "read rule[", i, "]")
return
@@ -79,18 +83,12 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
return
}
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error {
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error {
_, err := writer.Write(MagicBytes[:])
if err != nil {
return err
}
var version uint8
if generateUnstable {
version = C.RuleSetVersion2
} else {
version = C.RuleSetVersion1
}
err = binary.Write(writer, binary.BigEndian, version)
err = binary.Write(writer, binary.BigEndian, generateVersion)
if err != nil {
return err
}
@@ -104,7 +102,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool)
return err
}
for _, rule := range ruleSet.Rules {
err = writeRule(bWriter, rule, generateUnstable)
err = writeRule(bWriter, rule, generateVersion)
if err != nil {
return err
}
@@ -135,12 +133,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err
return
}
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error {
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error {
switch rule.Type {
case C.RuleTypeDefault:
return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable)
return writeDefaultRule(writer, rule.DefaultOptions, generateVersion)
case C.RuleTypeLogical:
return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable)
return writeLogicalRule(writer, rule.LogicalOptions, generateVersion)
default:
panic("unknown rule type: " + rule.Type)
}
@@ -227,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
return
}
rule.AdGuardDomainMatcher = matcher
case ruleItemNetworkType:
rule.NetworkType, err = readRuleItemString(reader)
case ruleItemNetworkIsExpensive:
rule.NetworkIsExpensive = true
case ruleItemNetworkIsConstrained:
rule.NetworkIsConstrained = true
case ruleItemFinal:
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
return
@@ -240,7 +244,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
}
}
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error {
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error {
err := binary.Write(writer, binary.BigEndian, uint8(0))
if err != nil {
return err
@@ -264,7 +268,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
if err != nil {
return err
}
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer)
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer)
if err != nil {
return err
}
@@ -341,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
return err
}
}
if len(rule.NetworkType) > 0 {
if generateVersion < C.RuleSetVersion3 {
return E.New("network_type rule item is only supported in version 3 or later")
}
err = writeRuleItemString(writer, ruleItemNetworkType, rule.NetworkType)
if err != nil {
return err
}
}
if rule.NetworkIsExpensive {
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
if err != nil {
return err
}
}
if rule.NetworkIsConstrained {
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
if err != nil {
return err
}
}
if len(rule.WIFISSID) > 0 {
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
if err != nil {
@@ -354,6 +379,9 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
}
}
if len(rule.AdGuardDomain) > 0 {
if generateVersion < C.RuleSetVersion2 {
return E.New("AdGuard rule items is only supported in version 2 or later")
}
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
if err != nil {
return err
@@ -457,7 +485,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
return
}
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error {
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
@@ -478,7 +506,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
return err
}
for _, rule := range logicalRule.Rules {
err = writeRule(writer, rule, generateUnstable)
err = writeRule(writer, rule, generateVersion)
if err != nil {
return err
}

8
constant/network.go Normal file
View File

@@ -0,0 +1,8 @@
package constant
const (
InterfaceTypeWIFI = "wifi"
InterfaceTypeCellular = "cellular"
InterfaceTypeEthernet = "ethernet"
InterfaceTypeOther = "other"
)

View File

@@ -6,7 +6,7 @@ import (
const IsAndroid = goos.IsAndroid == 1
const IsDarwin = goos.IsDarwin == 1
const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1
const IsDragonfly = goos.IsDragonfly == 1

View File

@@ -21,6 +21,8 @@ const (
const (
RuleSetVersion1 = 1 + iota
RuleSetVersion2
RuleSetVersion3
RuleSetVersionCurrent = RuleSetVersion3
)
const (

View File

@@ -2,10 +2,28 @@
icon: material/alert-decagram
---
#### 1.11.0-alpha.11
#### 1.11.0-alpha.12
* Merge route options to route actions **1**
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2**
* Fixes and improvements
**1**:
Route options in DNS route actions will no longer be considered deprecated,
see [DNS Route Action](/configuration/dns/rule_action/).
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
see [Route Action](/configuration/route/rule_action/).
**2**:
When using in graphical clients, new routing rule items allow you to match on
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
#### 1.11.0-alpha.9
* Improve tun compatibility **1**
@@ -32,7 +50,7 @@ See [Rule](/configuration/route/rule/),
[DNS Rule Action](/configuration/dns/rule_action/).
For migration, see
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).

View File

@@ -8,7 +8,10 @@ icon: material/new-box
:material-alert: [server](#server)
:material-alert: [disable_cache](#disable_cache)
:material-alert: [rewrite_ttl](#rewrite_ttl)
:material-alert: [client_subnet](#client_subnet)
:material-alert: [client_subnet](#client_subnet)
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
!!! quote "Changes in sing-box 1.10.0"
@@ -125,6 +128,11 @@ icon: material/new-box
1000
],
"clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [
"My WIFI"
],
@@ -310,6 +318,39 @@ Match user id.
Match Clash mode.
#### network_type
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match network type.
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match if network is considered Metered (on Android) or considered expensive,
such as Cellular or a Personal Hotspot (on Apple platforms).
#### network_is_constrained
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Apple platforms.
Match if network is in Low Data Mode.
#### wifi_ssid
!!! quote ""

View File

@@ -2,6 +2,17 @@
icon: material/new-box
---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
:material-alert: [server](#server)
:material-alert: [disable_cache](#disable_cache)
:material-alert: [rewrite_ttl](#rewrite_ttl)
:material-alert: [client_subnet](#client_subnet)
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
!!! quote "sing-box 1.10.0 中的更改"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
@@ -117,6 +128,11 @@ icon: material/new-box
1000
],
"clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [
"My WIFI"
],
@@ -135,17 +151,15 @@ icon: material/new-box
"outbound": [
"direct"
],
"server": "local",
"disable_cache": false,
"client_subnet": "127.0.0.1/24"
"action": "route",
"server": "local"
},
{
"type": "logical",
"mode": "and",
"rules": [],
"server": "local",
"disable_cache": false,
"client_subnet": "127.0.0.1/24"
"action": "route",
"server": "local"
}
]
}
@@ -304,6 +318,39 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配 Clash 模式。
#### network_type
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络类型。
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
像蜂窝网络或个人热点 (在 Apple 平台)。
#### network_is_constrained
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Apple 平台图形客户端中支持。
匹配如果网络在低数据模式下。
#### wifi_ssid
!!! quote ""
@@ -352,29 +399,35 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
`any` 可作为值用于匹配任意出站。
#### server
#### action
==必填==
目标 DNS 服务器的标签
参阅 [规则动作](../rule_action/)
#### server
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### disable_cache
在此查询中禁用缓存。
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### rewrite_ttl
重写 DNS 回应中的 TTL。
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### client_subnet
!!! question "自 sing-box 1.9.0 "
!!! failure "已在 sing-box 1.11.0 废弃"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet``servers.[].client_subnet`
已移动到 [DNS 规则动作](../rule_action#route).
### 地址筛选字段
@@ -420,8 +473,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### mode
==必填==
`and``or`
#### rules
包括的规则。
==必填==
包括的规则。

View File

@@ -12,8 +12,6 @@ icon: material/new-box
{
"action": "route", // default
"server": "",
// for compatibility
"disable_cache": false,
"rewrite_ttl": 0,
"client_subnet": null
@@ -28,23 +26,6 @@ icon: material/new-box
Tag of target server.
#### disable_cache/rewrite_ttl/client_subnet
!!! failure "Deprecated in sing-box 1.11.0"
Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
#### disable_cache
Disable cache and save cache in this query.
@@ -61,6 +42,19 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
`route-options` set options for routing.
### reject
```json

View File

@@ -28,24 +28,6 @@ icon: material/new-box
目标 DNS 服务器的标签。
#### disable_cache/rewrite_ttl/client_subnet
!!! failure "自 sing-box 1.11.0 起"
旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
#### disable_cache
在此查询中禁用缓存。
@@ -62,6 +44,19 @@ icon: material/new-box
将覆盖 `dns.client_subnet``servers.[].client_subnet`
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
`route-options` 为路由设置选项。
### reject
```json

View File

@@ -5,7 +5,10 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [action](#action)
:material-alert: [outbound](#outbound)
:material-alert: [outbound](#outbound)
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
!!! quote "Changes in sing-box 1.10.0"
@@ -120,6 +123,11 @@ icon: material/new-box
1000
],
"clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [
"My WIFI"
],
@@ -322,6 +330,39 @@ Match user id.
Match Clash mode.
#### network_type
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match network type.
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match if network is considered Metered (on Android) or considered expensive,
such as Cellular or a Personal Hotspot (on Apple platforms).
#### network_is_constrained
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Apple platforms.
Match if network is in Low Data Mode.
#### wifi_ssid
!!! quote ""

View File

@@ -5,7 +5,10 @@ icon: material/new-box
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
:material-alert: [outbound](#outbound)
:material-alert: [outbound](#outbound)
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
!!! quote "sing-box 1.10.0 中的更改"
@@ -13,7 +16,6 @@ icon: material/new-box
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [process_path_regex](#process_path_regex)
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set)
@@ -118,6 +120,11 @@ icon: material/new-box
1000
],
"clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [
"My WIFI"
],
@@ -153,7 +160,7 @@ icon: material/new-box
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
### Default Fields
### 默认字段
!!! note ""
@@ -320,6 +327,39 @@ icon: material/new-box
匹配 Clash 模式。
#### network_type
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络类型。
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
像蜂窝网络或个人热点 (在 Apple 平台)。
#### network_is_constrained
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Apple 平台图形客户端中支持。
匹配如果网络在低数据模式下。
#### wifi_ssid
!!! quote ""
@@ -366,13 +406,13 @@ icon: material/new-box
==必填==
参阅 [规则](../rule_action/)。
参阅 [规则动](../rule_action/)。
#### outbound
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [规则](../rule_action#route).
已移动到 [规则动](../rule_action#route).
### 逻辑字段

View File

@@ -13,7 +13,9 @@ icon: material/new-box
```json
{
"action": "route", // default
"outbound": ""
"outbound": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
@@ -25,18 +27,6 @@ icon: material/new-box
Tag of target outbound.
### route-options
```json
{
"action": "route-options",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
`route-options` set options for routing.
#### udp_disable_domain_unmapping
If enabled, for UDP proxy requests addressed to a domain,
@@ -49,6 +39,18 @@ do not support receiving UDP packets with domain addresses, such as Surge.
If enabled, attempts to connect UDP connection to the destination instead of listen.
### route-options
```json
{
"action": "route-options",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
`route-options` set options for routing.
### reject
```json

View File

@@ -14,7 +14,8 @@ icon: material/new-box
{
"action": "route", // 默认
"outbound": "",
"udp_disable_domain_unmapping": false
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
@@ -26,16 +27,6 @@ icon: material/new-box
目标出站的标签。
### route-options
```json
{
"action": "route-options",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
#### udp_disable_domain_unmapping
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
@@ -46,6 +37,18 @@ icon: material/new-box
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
### route-options
```json
{
"action": "route-options",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
`route-options` 为路由设置选项。
### reject
```json

View File

@@ -0,0 +1,70 @@
---
icon: material/new-box
---
# AdGuard DNS Filter
!!! question "自 sing-box 1.10.0 起"
sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box
目前只有 AdGuard DNS Filter。
这些格式不直接作为源格式支持,
而是需要将它们转换为二进制规则集。
## 转换
使用 `sing-box rule-set convert --type adguard [--output <file-name>.srs] <file-name>.txt` 以转换为二进制规则集。
## 性能
AdGuard 将所有规则保存在内存中并按顺序匹配,
而 sing-box 选择高性能和较小的内存使用量。
作为权衡,您无法知道匹配了哪个规则项。
## 兼容性
[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter)
中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list)
中列出的规则集中的规则均受支持。
## 支持的格式
### AdGuard Filter
#### 基本规则语法
| 语法 | 支持 |
|--------|------------------|
| `@@` | :material-check: |
| `\|\|` | :material-check: |
| `\|` | :material-check: |
| `^` | :material-check: |
| `*` | :material-check: |
#### 主机语法
| 语法 | 示例 | 支持 |
|-------------|--------------------------|--------------------------|
| Scheme | `https://` | :material-alert: Ignored |
| Domain Host | `example.org` | :material-check: |
| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: |
| Regexp | `/regexp/` | :material-check: |
| Port | `example.org:80` | :material-close: |
| Path | `example.org/path/ad.js` | :material-close: |
#### 描述符语法
| 描述符 | 支持 |
|-----------------------|--------------------------|
| `$important` | :material-check: |
| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored |
| 任何其他描述符 | :material-close: |
### Hosts
只有 IP 地址为 `0.0.0.0` 的条目将被接受。
### 简易
当所有行都是有效域时,它们被视为简单的逐行域规则, 与 hosts 一样,只匹配完全相同的域。

View File

@@ -1,3 +1,13 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
### Structure
!!! question "Since sing-box 1.8.0"
@@ -63,6 +73,11 @@
"package_name": [
"com.termux"
],
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [
"My WIFI"
],
@@ -177,6 +192,39 @@ Match process path using regular expression.
Match android package name.
#### network_type
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match network type.
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match if network is considered Metered (on Android) or considered expensive,
such as Cellular or a Personal Hotspot (on Apple platforms).
#### network_is_constrained
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Apple platforms.
Match if network is in Low Data Mode.
#### wifi_ssid
!!! quote ""

View File

@@ -0,0 +1,258 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [network_type](#network_type)
:material-alert: [network_is_expensive](#network_is_expensive)
:material-alert: [network_is_constrained](#network_is_constrained)
### 结构
!!! question "自 sing-box 1.8.0 起"
```json
{
"rules": [
{
"query_type": [
"A",
"HTTPS",
32768
],
"network": [
"tcp"
],
"domain": [
"test.com"
],
"domain_suffix": [
".cn"
],
"domain_keyword": [
"test"
],
"domain_regex": [
"^stun\\..+"
],
"source_ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"source_port": [
12345
],
"source_port_range": [
"1000:2000",
":3000",
"4000:"
],
"port": [
80,
443
],
"port_range": [
"1000:2000",
":3000",
"4000:"
],
"process_name": [
"curl"
],
"process_path": [
"/usr/bin/curl"
],
"process_path_regex": [
"^/usr/bin/.+"
],
"package_name": [
"com.termux"
],
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [
"My WIFI"
],
"wifi_bssid": [
"00:00:00:00:00:00"
],
"invert": false
},
{
"type": "logical",
"mode": "and",
"rules": [],
"invert": false
}
]
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
### Default Fields
!!! note ""
默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_port` || `source_port_range`) &&
`other fields`
#### query_type
DNS 查询类型。值可以为整数或者类型名称字符串。
#### network
`tcp``udp`
#### domain
匹配完整域名。
#### domain_suffix
匹配域名后缀。
#### domain_keyword
匹配域名关键字。
#### domain_regex
匹配域名正则表达式。
#### source_ip_cidr
匹配源 IP CIDR。
#### ip_cidr
匹配 IP CIDR。
#### source_port
匹配源端口。
#### source_port_range
匹配源端口范围。
#### port
匹配端口。
#### port_range
匹配端口范围。
#### process_name
!!! quote ""
仅支持 Linux、Windows 和 macOS。
匹配进程名称。
#### process_path
!!! quote ""
仅支持 Linux、Windows 和 macOS.
匹配进程路径。
#### process_path_regex
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux、Windows 和 macOS.
使用正则表达式匹配进程路径。
#### package_name
匹配 Android 应用包名。
#### network_type
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络类型。
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
像蜂窝网络或个人热点 (在 Apple 平台)。
#### network_is_constrained
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Apple 平台图形客户端中支持。
匹配如果网络在低数据模式下。
#### wifi_ssid
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配 WiFi SSID。
#### wifi_bssid
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
#### invert
反选匹配结果。
### 逻辑字段
#### type
`logical`
#### mode
==必填==
`and``or`
#### rules
==必填==
包括的规则。

View File

@@ -74,7 +74,7 @@ Tag of rule-set.
==Required==
List of [Headless Rule](./headless-rule.md/).
List of [Headless Rule](../headless-rule/).
### Local or Remote Fields

View File

@@ -0,0 +1,117 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: `type: inline`
# 规则集
!!! question "自 sing-box 1.8.0 起"
### 结构
=== "内联"
!!! question "自 sing-box 1.10.0 起"
```json
{
"type": "inline", // 可选
"tag": "",
"rules": []
}
```
=== "本地文件"
```json
{
"type": "local",
"tag": "",
"format": "source", // or binary
"path": ""
}
```
=== "远程文件"
!!! info ""
远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。
```json
{
"type": "remote",
"tag": "",
"format": "source", // or binary
"url": "",
"download_detour": "", // 可选
"update_interval": "" // 可选
}
```
### 字段
#### type
==必填==
规则集类型, `local` 或 `remote`。
#### tag
==必填==
规则集的标签。
### 内联字段
!!! question "自 sing-box 1.10.0 起"
#### rules
==必填==
一组 [无头规则](../headless-rule/).
### 本地或远程字段
#### format
==必填==
规则集格式, `source` 或 `binary`。
### 本地字段
#### path
==必填==
!!! note ""
自 sing-box 1.10.0 起,文件更改时将自动重新加载。
规则集的文件路径。
### 远程字段
#### url
==必填==
规则集的下载 URL。
#### download_detour
用于下载规则集的出站的标签。
如果为空,将使用默认出站。
#### update_interval
规则集的更新间隔。
默认使用 `1d`。

View File

@@ -4,6 +4,10 @@ icon: material/new-box
# Source Format
!!! quote "Changes in sing-box 1.11.0"
:material-plus: version `3`
!!! quote "Changes in sing-box 1.10.0"
:material-plus: version `2`
@@ -14,7 +18,7 @@ icon: material/new-box
```json
{
"version": 2,
"version": 3,
"rules": []
}
```
@@ -29,19 +33,14 @@ Use `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to c
==Required==
Version of rule-set, one of `1` or `2`.
Version of rule-set.
* 1: Initial rule-set version, since sing-box 1.8.0.
* 2: Optimized memory usages of `domain_suffix` rules.
The new rule-set version `2` does not make any changes to the format, only affecting `binary` rule-sets compiled by command `rule-set compile`
Since 1.10.0, the optimization is always applied to `source` rule-sets even if version is set to `1`.
It is recommended to upgrade to `2` after sing-box 1.10.0 becomes a stable version.
* 1: sing-box 1.8.0: Initial rule-set version.
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
#### rules
==Required==
List of [Headless Rule](./headless-rule.md/).
List of [Headless Rule](../headless-rule/).

View File

@@ -0,0 +1,46 @@
---
icon: material/new-box
---
# 源文件格式
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: version `3`
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: version `2`
!!! question "自 sing-box 1.8.0 起"
### 结构
```json
{
"version": 3,
"rules": []
}
```
### 编译
使用 `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` 以编译源文件为二进制规则集。
### 字段
#### version
==必填==
规则集版本。
* 1: sing-box 1.8.0: 初始规则集版本。
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
* 3: sing-box 1.11.0: 添加了 `network_type``network_is_expensive``network_is_constrainted` 规则项。
#### rules
==必填==
一组 [无头规则](../headless-rule/).

View File

@@ -22,14 +22,6 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
Old fields will be removed in sing-box 1.13.0.
#### Legacy DNS route options
Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated
and can be replaced by rule actions,
check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions).
Old fields will be removed in sing-box 1.12.0.
## 1.10.0
#### TUN address fields are merged

View File

@@ -20,13 +20,6 @@ icon: material/delete-alert
旧字段将在 sing-box 1.13.0 中被移除。
#### 旧的 DNS 路由参数
旧的 DNS 路由参数(`disable_cache``rewrite_ttl``client_subnet`)已废弃且可以通过规则动作替代,
参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。
旧字段将在 sing-box 1.12.0 中被移除。
## 1.10.0
#### Match source 规则项已重命名

View File

@@ -156,58 +156,6 @@ Inbound fields are deprecated and can be replaced by rule actions.
}
```
### Migrate legacy DNS route options to rule actions
Legacy DNS route options are deprecated and can be replaced by rule actions.
!!! info "References"
[DNS Rule](/configuration/dns/rule/) /
[DNS Rule Action](/configuration/dns/rule_action/)
=== ":material-card-remove: Deprecated"
```json
{
"dns": {
"rules": [
{
...,
"server": "local",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
}
]
}
}
```
=== ":material-card-multiple: New"
```json
{
"dns": {
"rules": [
{
...,
"action": "route-options",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
},
{
...,
"server": "local"
}
]
}
}
```
## 1.10.0
### TUN address fields are merged

View File

@@ -156,58 +156,6 @@ icon: material/arrange-bring-forward
}
```
### 迁移旧的 DNS 路由选项到规则动作
旧的 DNS 路由选项已被弃用,且可以被规则动作替代。
!!! info "参考"
[DNS 规则](/zh/configuration/dns/rule/) /
[DNS 规则动作](/zh/configuration/dns/rule_action/)
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
"rules": [
{
...,
"server": "local",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
}
]
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
"rules": [
{
...,
"action": "route-options",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
},
{
...,
"server": "local"
}
]
}
}
```
## 1.10.0
### TUN 地址字段已合并

View File

@@ -100,15 +100,6 @@ var OptionInboundOptions = Note{
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions",
}
var OptionLegacyDNSRouteOptions = Note{
Name: "legacy-dns-route-options",
Description: "legacy dns route options",
DeprecatedVersion: "1.11.0",
ScheduledVersion: "1.12.0",
EnvName: "LEGACY_DNS_ROUTE_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions",
}
var Options = []Note{
OptionBadMatchSource,
OptionGEOIP,
@@ -116,5 +107,4 @@ var Options = []Note{
OptionTUNAddressX,
OptionSpecialOutbounds,
OptionInboundOptions,
OptionLegacyDNSRouteOptions,
}

View File

@@ -74,11 +74,7 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg
return (*interfaceMonitorStub)(nil)
}
func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool {
return true
}
func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) {
func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) {
return nil, os.ErrInvalid
}
@@ -111,16 +107,8 @@ func (s *interfaceMonitorStub) Close() error {
return os.ErrInvalid
}
func (s *interfaceMonitorStub) DefaultInterfaceName(destination netip.Addr) string {
return ""
}
func (s *interfaceMonitorStub) DefaultInterfaceIndex(destination netip.Addr) int {
return -1
}
func (s *interfaceMonitorStub) DefaultInterface(destination netip.Addr) (string, int) {
return "", -1
func (s *interfaceMonitorStub) DefaultInterface() *control.Interface {
return nil
}
func (s *interfaceMonitorStub) OverrideAndroidVPN() bool {

View File

@@ -1,4 +1,4 @@
//go:build !linux
//go:build !unix
package libbox

View File

@@ -1,3 +1,5 @@
//go:build unix
package libbox
import (

View File

@@ -1,15 +1,10 @@
package libbox
import (
"net"
"net/netip"
"sync"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/x/list"
)
@@ -20,19 +15,9 @@ var (
type platformDefaultInterfaceMonitor struct {
*platformInterfaceWrapper
networkAddresses []networkAddress
defaultInterfaceName string
defaultInterfaceIndex int
element *list.Element[tun.NetworkUpdateCallback]
access sync.Mutex
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
logger logger.Logger
}
type networkAddress struct {
interfaceName string
interfaceIndex int
addresses []netip.Prefix
element *list.Element[tun.NetworkUpdateCallback]
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
logger logger.Logger
}
func (m *platformDefaultInterfaceMonitor) Start() error {
@@ -43,37 +28,10 @@ func (m *platformDefaultInterfaceMonitor) Close() error {
return m.iif.CloseDefaultInterfaceMonitor(m)
}
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string {
for _, address := range m.networkAddresses {
for _, prefix := range address.addresses {
if prefix.Contains(destination) {
return address.interfaceName
}
}
}
return m.defaultInterfaceName
}
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int {
for _, address := range m.networkAddresses {
for _, prefix := range address.addresses {
if prefix.Contains(destination) {
return address.interfaceIndex
}
}
}
return m.defaultInterfaceIndex
}
func (m *platformDefaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) {
for _, address := range m.networkAddresses {
for _, prefix := range address.addresses {
if prefix.Contains(destination) {
return address.interfaceName, address.interfaceIndex
}
}
}
return m.defaultInterfaceName, m.defaultInterfaceIndex
func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface {
m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock()
return m.defaultInterface
}
func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool {
@@ -85,96 +43,49 @@ func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool {
}
func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {
m.access.Lock()
defer m.access.Unlock()
m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock()
return m.callbacks.PushBack(callback)
}
func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
m.access.Lock()
defer m.access.Unlock()
m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock()
m.callbacks.Remove(element)
}
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
if interfaceName == "" || interfaceIndex32 == -1 {
m.defaultInterfaceName = ""
m.defaultInterfaceIndex = -1
m.access.Lock()
callbacks := m.callbacks.Array()
m.access.Unlock()
for _, callback := range callbacks {
callback(tun.EventNoRoute)
}
return
}
var err error
if m.iif.UsePlatformInterfaceGetter() {
err = m.updateInterfacesPlatform()
} else {
err = m.updateInterfaces()
}
if err == nil {
err = m.networkManager.UpdateInterfaces()
}
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
m.isExpensive = isExpensive
m.isConstrained = isConstrained
err := m.networkManager.UpdateInterfaces()
if err != nil {
m.logger.Error(E.Cause(err, "update interfaces"))
}
interfaceIndex := int(interfaceIndex32)
if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex {
m.defaultInterfaceAccess.Lock()
if interfaceIndex32 == -1 {
m.defaultInterface = nil
callbacks := m.callbacks.Array()
m.defaultInterfaceAccess.Unlock()
for _, callback := range callbacks {
callback(tun.EventInterfaceUpdate)
}
return
}
oldInterface := m.defaultInterface
newInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32))
if err != nil {
m.defaultInterfaceAccess.Unlock()
m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName))
return
}
m.defaultInterface = newInterface
if oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index {
m.defaultInterfaceAccess.Unlock()
return
}
m.defaultInterfaceName = interfaceName
m.defaultInterfaceIndex = interfaceIndex
m.access.Lock()
callbacks := m.callbacks.Array()
m.access.Unlock()
m.defaultInterfaceAccess.Unlock()
for _, callback := range callbacks {
callback(tun.EventInterfaceUpdate)
}
}
func (m *platformDefaultInterfaceMonitor) updateInterfaces() error {
interfaces, err := net.Interfaces()
if err != nil {
return err
}
var addresses []networkAddress
for _, iif := range interfaces {
var netAddresses []net.Addr
netAddresses, err = iif.Addrs()
if err != nil {
return err
}
var address networkAddress
address.interfaceName = iif.Name
address.interfaceIndex = iif.Index
address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) {
value, loaded := it.(*net.IPNet)
return value, loaded
}), func(it *net.IPNet) netip.Prefix {
bits, _ := it.Mask.Size()
return netip.PrefixFrom(M.AddrFromIP(it.IP), bits)
})
addresses = append(addresses, address)
}
m.networkAddresses = addresses
return nil
}
func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error {
interfaces, err := m.Interfaces()
if err != nil {
return err
}
var addresses []networkAddress
for _, iif := range interfaces {
var address networkAddress
address.interfaceName = iif.Name
address.interfaceIndex = iif.Index
// address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix)
addresses = append(addresses, address)
}
m.networkAddresses = addresses
return nil
}

View File

@@ -1,6 +1,7 @@
package libbox
import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
@@ -13,10 +14,8 @@ type PlatformInterface interface {
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
PackageNameByUid(uid int32) (string, error)
UIDByPackageName(packageName string) (int32, error)
UsePlatformDefaultInterfaceMonitor() bool
StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
UsePlatformInterfaceGetter() bool
GetInterfaces() (NetworkInterfaceIterator, error)
UnderNetworkExtension() bool
IncludeAllNetworks() bool
@@ -31,15 +30,26 @@ type TunInterface interface {
}
type InterfaceUpdateListener interface {
UpdateDefaultInterface(interfaceName string, interfaceIndex int32)
UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool)
}
const (
InterfaceTypeWIFI = C.InterfaceTypeWIFI
InterfaceTypeCellular = C.InterfaceTypeCellular
InterfaceTypeEthernet = C.InterfaceTypeEthernet
InterfaceTypeOther = C.InterfaceTypeOther
)
type NetworkInterface struct {
Index int32
MTU int32
Name string
Addresses StringIterator
Flags int32
Type string
DNSServer StringIterator
Metered bool
}
type WIFIState struct {

View File

@@ -5,7 +5,6 @@ import (
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/logger"
)
@@ -14,10 +13,8 @@ type Interface interface {
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
UsePlatformDefaultInterfaceMonitor() bool
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
UsePlatformInterfaceGetter() bool
Interfaces() ([]control.Interface, error)
Interfaces() ([]adapter.NetworkInterface, error)
UnderNetworkExtension() bool
IncludeAllNetworks() bool
ClearDNSCache()

View File

@@ -6,6 +6,7 @@ import (
"os"
"runtime"
runtimeDebug "runtime/debug"
"sync"
"syscall"
"time"
@@ -54,7 +55,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
ctx, cancel := context.WithCancel(ctx)
urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
platformWrapper := &platformInterfaceWrapper{
iif: platformInterface,
useProcFS: platformInterface.UseProcFS(),
}
service.MustRegister[platform.Interface](ctx, platformWrapper)
instance, err := box.New(box.Options{
Context: ctx,
@@ -106,9 +110,14 @@ var (
)
type platformInterfaceWrapper struct {
iif PlatformInterface
useProcFS bool
networkManager adapter.NetworkManager
iif PlatformInterface
useProcFS bool
networkManager adapter.NetworkManager
myTunName string
defaultInterfaceAccess sync.Mutex
defaultInterface *control.Interface
isExpensive bool
isConstrained bool
}
func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error {
@@ -148,38 +157,42 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
return nil, E.Cause(err, "dup tun file descriptor")
}
options.FileDescriptor = dupFd
w.myTunName = options.Name
return tun.New(*options)
}
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
return w.iif.UsePlatformDefaultInterfaceMonitor()
}
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
return &platformDefaultInterfaceMonitor{
platformInterfaceWrapper: w,
defaultInterfaceIndex: -1,
logger: logger,
}
}
func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool {
return w.iif.UsePlatformInterfaceGetter()
}
func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) {
interfaceIterator, err := w.iif.GetInterfaces()
if err != nil {
return nil, err
}
var interfaces []control.Interface
var interfaces []adapter.NetworkInterface
for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {
interfaces = append(interfaces, control.Interface{
Index: int(netInterface.Index),
MTU: int(netInterface.MTU),
Name: netInterface.Name,
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
Flags: linkFlags(uint32(netInterface.Flags)),
if netInterface.Name == w.myTunName {
continue
}
w.defaultInterfaceAccess.Lock()
isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index
w.defaultInterfaceAccess.Unlock()
interfaces = append(interfaces, adapter.NetworkInterface{
Interface: control.Interface{
Index: int(netInterface.Index),
MTU: int(netInterface.MTU),
Name: netInterface.Name,
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
Flags: linkFlags(uint32(netInterface.Flags)),
},
Type: netInterface.Type,
DNSServers: iteratorToArray[string](netInterface.DNSServer),
Expensive: netInterface.Metered || isDefault && w.isExpensive,
Constrained: isDefault && w.isConstrained,
})
}
return interfaces, nil

6
go.mod
View File

@@ -25,14 +25,14 @@ require (
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
github.com/sagernet/quic-go v0.48.1-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-alpha.3
github.com/sagernet/sing v0.6.0-alpha.4
github.com/sagernet/sing-dns v0.4.0-alpha.1
github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.3.0-rc.2
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.6.0-alpha.3
github.com/sagernet/sing-tun v0.6.0-alpha.5
github.com/sagernet/sing-vmess v0.1.12
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7
@@ -53,8 +53,6 @@ require (
howett.net/plist v1.0.1
)
//replace github.com/sagernet/sing => ../sing
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect

8
go.sum
View File

@@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
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/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-alpha.3 h1:GLp9d6Gbt+Ioeplauuzojz1nY2J6moceVGYIOv/h5gA=
github.com/sagernet/sing v0.6.0-alpha.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.0-alpha.4 h1:h9oshzhaY0ESPC9HERcXtT9MhK7Oyo/IWXVu1uIiw3Y=
github.com/sagernet/sing v0.6.0-alpha.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg=
github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
@@ -124,8 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.6.0-alpha.3 h1:KddmFF9ZYC1n+HZzpAZ1StMC5v38ir6hH0PJ7uGpAGE=
github.com/sagernet/sing-tun v0.6.0-alpha.3/go.mod h1:R/UaKB1oFIvAeMIH2btQCaDVsfCNomEjfYBSCSA94sw=
github.com/sagernet/sing-tun v0.6.0-alpha.5 h1:N2m+tzpVgeDxsQzc3tq3Bge2uSRhJ72KXy4Zr03U+Cg=
github.com/sagernet/sing-tun v0.6.0-alpha.5/go.mod h1:5FKJNou4ZfW3HhLoSpRRUc8RT+nsdFTvhJc+4MlBrOo=
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=

View File

@@ -95,6 +95,9 @@ type RawDefaultRule struct {
User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`

View File

@@ -7,8 +7,7 @@ import (
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
@@ -137,18 +136,10 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v)
}
type _RouteActionOptions struct {
Outbound string `json:"outbound,omitempty"`
}
type RouteActionOptions _RouteActionOptions
func (r *RouteActionOptions) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, (*_RouteActionOptions)(r))
if err != nil {
return err
}
return nil
type RouteActionOptions struct {
Outbound string `json:"outbound,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"`
}
type _RouteOptionsActionOptions struct {
@@ -169,29 +160,13 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
return nil
}
type _DNSRouteActionOptions struct {
Server string `json:"server,omitempty"`
// Deprecated: Use DNSRouteOptionsActionOptions instead.
DisableCache bool `json:"disable_cache,omitempty"`
// Deprecated: Use DNSRouteOptionsActionOptions instead.
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
// Deprecated: Use DNSRouteOptionsActionOptions instead.
type DNSRouteActionOptions struct {
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
}
type DNSRouteActionOptions _DNSRouteActionOptions
func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data []byte) error {
err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r))
if err != nil {
return err
}
if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil {
deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions)
}
return nil
}
type _DNSRouteOptionsActionOptions struct {
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`

View File

@@ -97,6 +97,9 @@ type RawDefaultDNSRule struct {
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`

View File

@@ -146,25 +146,28 @@ func (r HeadlessRule) IsValid() bool {
}
type DefaultHeadlessRule struct {
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"`
Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
Invert bool `json:"invert,omitempty"`
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"`
Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
Invert bool `json:"invert,omitempty"`
DomainMatcher *domain.Matcher `json:"-"`
SourceIPSet *netipx.IPSet `json:"-"`
@@ -191,7 +194,7 @@ func (r LogicalHeadlessRule) IsValid() bool {
}
type _PlainRuleSetCompat struct {
Version int `json:"version"`
Version uint8 `json:"version"`
Options PlainRuleSet `json:"-"`
}
@@ -200,7 +203,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
var v any
switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2:
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
v = r.Options
default:
return nil, E.New("unknown rule-set version: ", r.Version)
@@ -215,7 +218,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
}
var v any
switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2:
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
v = &r.Options
case 0:
return E.New("missing rule-set version")
@@ -231,7 +234,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2:
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
default:
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
}

View File

@@ -33,7 +33,7 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn {
}
if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn {
if !source.Addr().IsLoopback() {
_, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr())
_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())
if err != nil {
return conn
}
@@ -59,7 +59,7 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc
return conn
}
if !source.Addr().IsLoopback() {
_, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr())
_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())
if err != nil {
return conn
}
@@ -82,7 +82,7 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad
return false
}
if !source.Addr().IsLoopback() {
_, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr())
_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())
if err != nil {
return false
}

View File

@@ -3,9 +3,10 @@ package route
import (
"context"
"errors"
"net/netip"
"net"
"os"
"runtime"
"strings"
"syscall"
"github.com/sagernet/sing-box/adapter"
@@ -15,33 +16,41 @@ import (
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/winpowrprof"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
"golang.org/x/exp/slices"
)
var _ adapter.NetworkManager = (*NetworkManager)(nil)
type NetworkManager struct {
logger logger.ContextLogger
interfaceFinder *control.DefaultInterfaceFinder
logger logger.ContextLogger
interfaceFinder *control.DefaultInterfaceFinder
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface]
autoDetectInterface bool
defaultInterface string
defaultMark uint32
autoRedirectOutputMark uint32
networkMonitor tun.NetworkUpdateMonitor
interfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager
powerListener winpowrprof.EventListener
pauseManager pause.Manager
platformInterface platform.Interface
outboundManager adapter.OutboundManager
wifiState adapter.WIFIState
started bool
networkMonitor tun.NetworkUpdateMonitor
interfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager
powerListener winpowrprof.EventListener
pauseManager pause.Manager
platformInterface platform.Interface
outboundManager adapter.OutboundManager
wifiState adapter.WIFIState
started bool
}
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) {
@@ -55,7 +64,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
platformInterface: service.FromContext[platform.Interface](ctx),
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
}
usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor()
usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil
enforceInterfaceMonitor := routeOptions.AutoDetectInterface
if !usePlatformDefaultInterfaceMonitor {
networkMonitor, err := tun.NewNetworkUpdateMonitor(logger)
@@ -90,17 +99,17 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error {
monitor := taskmonitor.New(r.logger, C.StartTimeout)
switch stage {
case adapter.StartStateInitialize:
if r.interfaceMonitor != nil {
monitor.Start("initialize interface monitor")
err := r.interfaceMonitor.Start()
if r.networkMonitor != nil {
monitor.Start("initialize network monitor")
err := r.networkMonitor.Start()
monitor.Finish()
if err != nil {
return err
}
}
if r.networkMonitor != nil {
monitor.Start("initialize network monitor")
err := r.networkMonitor.Start()
if r.interfaceMonitor != nil {
monitor.Start("initialize interface monitor")
err := r.interfaceMonitor.Start()
monitor.Finish()
if err != nil {
return err
@@ -151,20 +160,6 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error {
func (r *NetworkManager) Close() error {
monitor := taskmonitor.New(r.logger, C.StopTimeout)
var err error
if r.interfaceMonitor != nil {
monitor.Start("close interface monitor")
err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error {
return E.Cause(err, "close interface monitor")
})
monitor.Finish()
}
if r.networkMonitor != nil {
monitor.Start("close network monitor")
err = E.Append(err, r.networkMonitor.Close(), func(err error) error {
return E.Cause(err, "close network monitor")
})
monitor.Finish()
}
if r.packageManager != nil {
monitor.Start("close package manager")
err = E.Append(err, r.packageManager.Close(), func(err error) error {
@@ -179,6 +174,20 @@ func (r *NetworkManager) Close() error {
})
monitor.Finish()
}
if r.interfaceMonitor != nil {
monitor.Start("close interface monitor")
err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error {
return E.Cause(err, "close interface monitor")
})
monitor.Finish()
}
if r.networkMonitor != nil {
monitor.Start("close network monitor")
err = E.Append(err, r.networkMonitor.Close(), func(err error) error {
return E.Cause(err, "close network monitor")
})
monitor.Finish()
}
return nil
}
@@ -187,18 +196,75 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder {
}
func (r *NetworkManager) UpdateInterfaces() error {
if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() {
if r.platformInterface == nil {
return r.interfaceFinder.Update()
} else {
interfaces, err := r.platformInterface.Interfaces()
if err != nil {
return err
}
r.interfaceFinder.UpdateInterfaces(interfaces)
if C.IsDarwin {
err = r.interfaceFinder.Update()
if err != nil {
return err
}
// NEInterface only provides name,index and type
interfaces = common.Map(interfaces, func(it adapter.NetworkInterface) adapter.NetworkInterface {
iif, _ := r.interfaceFinder.ByIndex(it.Index)
if iif != nil {
it.Interface = *iif
}
return it
})
} else {
r.interfaceFinder.UpdateInterfaces(common.Map(interfaces, func(it adapter.NetworkInterface) control.Interface { return it.Interface }))
}
oldInterfaces := r.networkInterfaces.Load()
newInterfaces := common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return it.Flags&net.FlagUp != 0
})
r.networkInterfaces.Store(newInterfaces)
if !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool {
return oldInterface.Interface.Index == newInterface.Interface.Index &&
oldInterface.Interface.Name == newInterface.Interface.Name &&
oldInterface.Interface.Flags == newInterface.Interface.Flags &&
oldInterface.Type == newInterface.Type &&
oldInterface.Expensive == newInterface.Expensive &&
oldInterface.Constrained == newInterface.Constrained
}) {
r.logger.Info("updated available networks: ", strings.Join(common.Map(newInterfaces, func(it adapter.NetworkInterface) string {
var options []string
options = append(options, F.ToString(it.Type))
if it.Expensive {
options = append(options, "expensive")
}
if it.Constrained {
options = append(options, "constrained")
}
return F.ToString(it.Name, " (", strings.Join(options, ", "), ")")
}), ", "))
}
return nil
}
}
func (r *NetworkManager) DefaultNetworkInterface() *adapter.NetworkInterface {
iif := r.interfaceMonitor.DefaultInterface()
if iif == nil {
return nil
}
for _, it := range r.networkInterfaces.Load() {
if it.Interface.Index == iif.Index {
return &it
}
}
return &adapter.NetworkInterface{Interface: *iif}
}
func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface {
return r.networkInterfaces.Load()
}
func (r *NetworkManager) DefaultInterface() string {
return r.defaultInterface
}
@@ -220,18 +286,17 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
}
return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
remoteAddr := M.ParseSocksaddr(address).Addr
if C.IsLinux {
interfaceName, interfaceIndex = r.interfaceMonitor.DefaultInterface(remoteAddr)
if interfaceIndex == -1 {
err = tun.ErrNoRoute
}
} else {
interfaceIndex = r.interfaceMonitor.DefaultInterfaceIndex(remoteAddr)
if interfaceIndex == -1 {
err = tun.ErrNoRoute
if remoteAddr.IsValid() {
iif, err := r.interfaceFinder.ByAddr(remoteAddr)
if err == nil {
return iif.Name, iif.Index, nil
}
}
return
defaultInterface := r.interfaceMonitor.DefaultInterface()
if defaultInterface == nil {
return "", -1, tun.ErrNoRoute
}
return defaultInterface.Name, defaultInterface.Index, nil
})
}
}
@@ -285,6 +350,12 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) {
r.logger.Error("missing default interface")
} else {
r.pauseManager.NetworkWake()
defaultInterface := r.DefaultNetworkInterface()
if defaultInterface == nil {
panic("invalid interface context")
}
var options []string
options = append(options, F.ToString("index ", defaultInterface.Index))
if C.IsAndroid && r.platformInterface == nil {
var vpnStatus string
if r.interfaceMonitor.AndroidVPNEnabled() {
@@ -292,17 +363,24 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) {
} else {
vpnStatus = "disabled"
}
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus)
options = append(options, "vpn "+vpnStatus)
} else {
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
if defaultInterface.Type != "" {
options = append(options, F.ToString("type ", defaultInterface.Type))
}
if defaultInterface.Expensive {
options = append(options, "expensive")
}
if defaultInterface.Constrained {
options = append(options, "constrained")
}
}
r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", "))
if r.platformInterface != nil {
state := r.platformInterface.ReadWIFIState()
if state != r.wifiState {
r.wifiState = state
if state.SSID == "" && state.BSSID == "" {
r.logger.Info("updated WIFI state: disconnected")
} else {
if state.SSID != "" {
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
}
}
@@ -312,7 +390,6 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) {
if !r.started {
return
}
r.ResetNetwork()
}

View File

@@ -437,6 +437,12 @@ match:
}
}
switch action := currentRule.Action().(type) {
case *rule.RuleActionRoute:
metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping
metadata.UDPConnect = action.UDPConnect
selectedRule = currentRule
selectedRuleIndex = currentRuleIndex
break match
case *rule.RuleActionRouteOptions:
metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping
metadata.UDPConnect = action.UDPConnect
@@ -467,7 +473,12 @@ match:
selectedRuleIndex = currentRuleIndex
break match
}
ruleIndex = currentRuleIndex
if ruleIndex == -1 {
ruleIndex = currentRuleIndex
} else {
ruleIndex += currentRuleIndex
}
ruleIndex++
}
if !preMatch && metadata.Destination.Addr.IsUnspecified() {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn)

View File

@@ -29,6 +29,10 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
case C.RuleActionTypeRoute:
return &RuleActionRoute{
Outbound: action.RouteOptions.Outbound,
RuleActionRouteOptions: RuleActionRouteOptions{
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptions.UDPConnect,
},
}, nil
case C.RuleActionTypeRouteOptions:
return &RuleActionRouteOptions{
@@ -85,10 +89,12 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
return nil
case C.RuleActionTypeRoute:
return &RuleActionDNSRoute{
Server: action.RouteOptions.Server,
DisableCache: action.RouteOptions.DisableCache,
RewriteTTL: action.RouteOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
Server: action.RouteOptions.Server,
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
DisableCache: action.RouteOptions.DisableCache,
RewriteTTL: action.RouteOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
},
}
case C.RuleActionTypeRouteOptions:
return &RuleActionDNSRouteOptions{
@@ -109,6 +115,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
type RuleActionRoute struct {
Outbound string
RuleActionRouteOptions
}
func (r *RuleActionRoute) Type() string {
@@ -116,7 +123,15 @@ func (r *RuleActionRoute) Type() string {
}
func (r *RuleActionRoute) String() string {
return F.ToString("route(", r.Outbound, ")")
var descriptions []string
descriptions = append(descriptions, r.Outbound)
if r.UDPDisableDomainUnmapping {
descriptions = append(descriptions, "udp-disable-domain-unmapping")
}
if r.UDPConnect {
descriptions = append(descriptions, "udp-connect")
}
return F.ToString("route(", strings.Join(descriptions, ","), ")")
}
type RuleActionRouteOptions struct {
@@ -140,10 +155,8 @@ func (r *RuleActionRouteOptions) String() string {
}
type RuleActionDNSRoute struct {
Server string
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
Server string
RuleActionDNSRouteOptions
}
func (r *RuleActionDNSRoute) Type() string {
@@ -151,7 +164,18 @@ func (r *RuleActionDNSRoute) Type() string {
}
func (r *RuleActionDNSRoute) String() string {
return F.ToString("route(", r.Server, ")")
var descriptions []string
descriptions = append(descriptions, r.Server)
if r.DisableCache {
descriptions = append(descriptions, "disable-cache")
}
if r.RewriteTTL != nil {
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
}
if r.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
}
return F.ToString("route(", strings.Join(descriptions, ","), ")")
}
type RuleActionDNSRouteOptions struct {
@@ -170,10 +194,10 @@ func (r *RuleActionDNSRouteOptions) String() string {
descriptions = append(descriptions, "disable-cache")
}
if r.RewriteTTL != nil {
descriptions = append(descriptions, F.ToString("rewrite-ttl(", *r.RewriteTTL, ")"))
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
}
if r.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet(", r.ClientSubnet, ")"))
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
}
return F.ToString("route-options(", strings.Join(descriptions, ","), ")")
}

View File

@@ -224,6 +224,21 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.NetworkType) > 0 {
item := NewNetworkTypeItem(networkManager, options.NetworkType)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkIsExpensive {
item := NewNetworkIsExpensiveItem(networkManager)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkIsConstrained {
item := NewNetworkIsConstrainedItem(networkManager)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.WIFISSID) > 0 {
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
rule.items = append(rule.items, item)

View File

@@ -221,6 +221,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.NetworkType) > 0 {
item := NewNetworkTypeItem(networkManager, options.NetworkType)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkIsExpensive {
item := NewNetworkIsExpensiveItem(networkManager)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkIsConstrained {
item := NewNetworkIsConstrainedItem(networkManager)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.WIFISSID) > 0 {
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
rule.items = append(rule.items, item)

View File

@@ -140,18 +140,33 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.WIFISSID) > 0 {
if networkManager != nil {
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
if networkManager != nil {
if len(options.NetworkType) > 0 {
item := NewNetworkTypeItem(networkManager, options.NetworkType)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
}
if len(options.WIFIBSSID) > 0 {
if networkManager != nil {
if options.NetworkIsExpensive {
item := NewNetworkIsExpensiveItem(networkManager)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkIsConstrained {
item := NewNetworkIsConstrainedItem(networkManager)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.WIFISSID) > 0 {
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.WIFIBSSID) > 0 {
item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
}
if len(options.AdGuardDomain) > 0 {

View File

@@ -0,0 +1,29 @@
package rule
import (
"github.com/sagernet/sing-box/adapter"
)
var _ RuleItem = (*NetworkIsConstrainedItem)(nil)
type NetworkIsConstrainedItem struct {
networkManager adapter.NetworkManager
}
func NewNetworkIsConstrainedItem(networkManager adapter.NetworkManager) *NetworkIsConstrainedItem {
return &NetworkIsConstrainedItem{
networkManager: networkManager,
}
}
func (r *NetworkIsConstrainedItem) Match(metadata *adapter.InboundContext) bool {
networkInterface := r.networkManager.DefaultNetworkInterface()
if networkInterface == nil {
return false
}
return networkInterface.Constrained
}
func (r *NetworkIsConstrainedItem) String() string {
return "network_is_expensive=true"
}

View File

@@ -0,0 +1,29 @@
package rule
import (
"github.com/sagernet/sing-box/adapter"
)
var _ RuleItem = (*NetworkIsExpensiveItem)(nil)
type NetworkIsExpensiveItem struct {
networkManager adapter.NetworkManager
}
func NewNetworkIsExpensiveItem(networkManager adapter.NetworkManager) *NetworkIsExpensiveItem {
return &NetworkIsExpensiveItem{
networkManager: networkManager,
}
}
func (r *NetworkIsExpensiveItem) Match(metadata *adapter.InboundContext) bool {
networkInterface := r.networkManager.DefaultNetworkInterface()
if networkInterface == nil {
return false
}
return networkInterface.Expensive
}
func (r *NetworkIsExpensiveItem) String() string {
return "network_is_expensive=true"
}

View File

@@ -0,0 +1,39 @@
package rule
import (
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
)
var _ RuleItem = (*NetworkTypeItem)(nil)
type NetworkTypeItem struct {
networkManager adapter.NetworkManager
networkType []string
}
func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem {
return &NetworkTypeItem{
networkManager: networkManager,
networkType: networkType,
}
}
func (r *NetworkTypeItem) Match(metadata *adapter.InboundContext) bool {
networkInterface := r.networkManager.DefaultNetworkInterface()
if networkInterface == nil {
return false
}
return common.Contains(r.networkType, networkInterface.Type)
}
func (r *NetworkTypeItem) String() string {
if len(r.networkType) == 1 {
return F.ToString("network_type=", r.networkType[0])
} else {
return F.ToString("network_type=", "["+strings.Join(F.MapToString(r.networkType), " ")+"]")
}
}

View File

@@ -95,33 +95,34 @@ func (s *LocalRuleSet) StartContext(ctx context.Context, startContext *adapter.H
}
func (s *LocalRuleSet) reloadFile(path string) error {
var plainRuleSet option.PlainRuleSet
var ruleSet option.PlainRuleSetCompat
switch s.fileFormat {
case C.RuleSetFormatSource, "":
content, err := os.ReadFile(path)
if err != nil {
return err
}
compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet, err = compat.Upgrade()
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
case C.RuleSetFormatBinary:
setFile, err := os.Open(path)
if err != nil {
return err
}
plainRuleSet, err = srs.Read(setFile, false)
ruleSet, err = srs.Read(setFile, false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", s.fileFormat)
}
plainRuleSet, err := ruleSet.Upgrade()
if err != nil {
return err
}
return s.reloadRules(plainRuleSet.Rules)
}

View File

@@ -155,28 +155,27 @@ func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSet
func (s *RemoteRuleSet) loadBytes(content []byte) error {
var (
plainRuleSet option.PlainRuleSet
err error
ruleSet option.PlainRuleSetCompat
err error
)
switch s.options.Format {
case C.RuleSetFormatSource:
var compat option.PlainRuleSetCompat
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet, err = compat.Upgrade()
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
ruleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", s.options.Format)
}
plainRuleSet, err := ruleSet.Upgrade()
if err != nil {
return err
}
rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules))
for i, ruleOptions := range plainRuleSet.Rules {
rules[i], err = NewHeadlessRule(s.ctx, ruleOptions)

View File

@@ -119,18 +119,19 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
return nil, err
}
func (t *Transport) fetchInterface() (*net.Interface, error) {
interfaceName := t.interfaceName
func (t *Transport) fetchInterface() (*control.Interface, error) {
if t.autoInterface {
if t.networkManager.InterfaceMonitor() == nil {
return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface")
}
interfaceName = t.networkManager.InterfaceMonitor().DefaultInterfaceName(netip.Addr{})
defaultInterface := t.networkManager.InterfaceMonitor().DefaultInterface()
if defaultInterface == nil {
return nil, E.New("missing default interface")
}
return defaultInterface, nil
} else {
return t.networkManager.InterfaceFinder().ByName(t.interfaceName)
}
if interfaceName == "" {
return nil, E.New("missing default interface")
}
return net.InterfaceByName(interfaceName)
}
func (t *Transport) fetchServers() error {
@@ -172,7 +173,7 @@ func (t *Transport) interfaceUpdated(int) {
}
}
func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error {
func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) error {
var listener net.ListenConfig
listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index))
listener.Control = control.Append(listener.Control, control.ReuseAddr())
@@ -206,7 +207,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err
return group.Run(ctx)
}
func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error {
func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error {
buffer := buf.NewSize(dhcpv4.MaxMessageSize)
defer buffer.Release()
@@ -246,7 +247,7 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa
}
}
func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error {
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []netip.Addr) error {
if len(serverAddrs) > 0 {
t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string {
return it.String()