Compare commits

..

8 Commits

Author SHA1 Message Date
世界
a4c600d94a documentation: Bump version 2025-08-15 15:06:47 +08:00
xchacha20-poly1305
900a2c1ef1 Fix rule set version 2025-08-15 15:06:47 +08:00
世界
4b3861232b documentation: Add preferred_by route rule item 2025-08-15 15:06:47 +08:00
世界
31a0bc94ae Add preferred_by route rule item 2025-08-15 15:06:47 +08:00
世界
8c1d1e9b0e documentation: Add interface address rule items 2025-08-15 15:05:53 +08:00
世界
735b52fee5 Add interface address rule items 2025-08-15 15:05:52 +08:00
neletor
9fc9dccf46 Add support for ech retry configs 2025-08-15 15:05:52 +08:00
Zephyruso
c64f7fd8a1 Add /dns/flush-clash meta api 2025-08-15 15:05:52 +08:00
102 changed files with 1556 additions and 1109 deletions

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -110,7 +110,7 @@ jobs:
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Setup Go 1.24
if: matrix.legacy_go124
uses: actions/setup-go@v5
@@ -300,7 +300,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -380,7 +380,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -432,8 +432,7 @@ jobs:
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
build_apple:
name: Build Apple clients
runs-on: macos-26
if: false
runs-on: macos-15
needs:
- calculate_version
strategy:
@@ -479,7 +478,15 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
- name: Set tag
if: matrix.if
run: |-

View File

@@ -30,7 +30,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -71,7 +71,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.0
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1

View File

@@ -17,10 +17,6 @@ build:
export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN)
race:
export GOTOOLCHAIN=local && \
go build -race $(MAIN_PARAMS) $(MAIN)
ci_build:
export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \

View File

@@ -57,7 +57,6 @@ type InboundContext struct {
Domain string
Client string
SniffContext any
SnifferNames []string
SniffError error
// cache

View File

@@ -20,7 +20,6 @@ type NetworkManager interface {
DefaultOptions() NetworkOptions
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
AutoRedirectOutputMarkFunc() control.Func
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager

View File

@@ -2,6 +2,7 @@ package adapter
import (
"context"
"net/netip"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -18,6 +19,11 @@ type Outbound interface {
N.Dialer
}
type OutboundWithPreferredRoutes interface {
PreferredDomain(domain string) bool
PreferredAddress(address netip.Addr) bool
}
type OutboundRegistry interface {
option.OutboundOptionsRegistry
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)

View File

@@ -30,7 +30,7 @@ type Manager struct {
outboundByTag map[string]adapter.Outbound
dependByTag map[string][]string
defaultOutbound adapter.Outbound
defaultOutboundFallback func() (adapter.Outbound, error)
defaultOutboundFallback adapter.Outbound
}
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
@@ -44,7 +44,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry,
}
}
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
m.defaultOutboundFallback = defaultOutboundFallback
}
@@ -55,31 +55,18 @@ func (m *Manager) Start(stage adapter.StartStage) error {
}
m.started = true
m.stage = stage
outbounds := m.outbounds
m.access.Unlock()
if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultOutbound == nil {
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
if !loaded {
m.access.Unlock()
return E.New("default outbound not found: ", m.defaultTag)
}
m.defaultOutbound = defaultEndpoint
}
if m.defaultOutbound == nil {
directOutbound, err := m.defaultOutboundFallback()
if err != nil {
m.access.Unlock()
return E.Cause(err, "create direct outbound for fallback")
}
m.outbounds = append(m.outbounds, directOutbound)
m.outboundByTag[directOutbound.Tag()] = directOutbound
m.defaultOutbound = directOutbound
}
outbounds := m.outbounds
m.access.Unlock()
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
} else {
outbounds := m.outbounds
m.access.Unlock()
for _, outbound := range outbounds {
err := adapter.LegacyStart(outbound, stage)
if err != nil {
@@ -200,7 +187,11 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
func (m *Manager) Default() adapter.Outbound {
m.access.RLock()
defer m.access.RUnlock()
return m.defaultOutbound
if m.defaultOutbound != nil {
return m.defaultOutbound
} else {
return m.defaultOutboundFallback
}
}
func (m *Manager) Remove(tag string) error {

View File

@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
// Deprecated: removed
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source.Unwrap(),
Destination: metadata.Destination.Unwrap(),
Source: metadata.Source,
Destination: metadata.Destination,
}
}

8
box.go
View File

@@ -314,15 +314,15 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize service[", i, "]")
}
}
outboundManager.Initialize(func() (adapter.Outbound, error) {
return direct.NewOutbound(
outboundManager.Initialize(common.Must1(
direct.NewOutbound(
ctx,
router,
logFactory.NewLogger("outbound/direct"),
"direct",
option.DirectOutboundOptions{},
)
})
),
))
dnsTransportManager.Initialize(common.Must1(
local.NewTransport(
ctx,

View File

@@ -6,8 +6,10 @@ 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-box/route/rule"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
@@ -69,7 +71,7 @@ func compileRuleSet(sourcePath string) error {
if err != nil {
return err
}
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
if err != nil {
outputFile.Close()
os.Remove(outputPath)
@@ -78,3 +80,18 @@ func compileRuleSet(sourcePath string) error {
outputFile.Close()
return nil
}
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
len(rule.DefaultInterfaceAddress) > 0
}) {
version = C.RuleSetVersion3
}
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
}) {
version = C.RuleSetVersion2
}
return version
}

View File

@@ -128,10 +128,6 @@ func (c *ReadWaitConn) Upstream() any {
return c.Conn
}
func (c *ReadWaitConn) ReaderReplaceable() bool {
return true
}
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
func init() {

View File

@@ -6,26 +6,22 @@ import (
"net"
_ "unsafe"
"github.com/sagernet/sing/common"
"github.com/metacubex/utls"
)
func init() {
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
switch tlsConn := conn.(type) {
case *tls.UConn:
return true, func() error {
return utlsReadRecord(tlsConn.Conn)
}, func() error {
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
}
case *tls.Conn:
return true, func() error {
return utlsReadRecord(tlsConn)
}, func() error {
return utlsHandlePostHandshakeMessage(tlsConn)
}
tlsConn, loaded := common.Cast[*tls.UConn](conn)
if !loaded {
return
}
return
return true, func() error {
return utlsReadRecord(tlsConn.Conn)
}, func() error {
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
}
})
}

View File

@@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -22,7 +21,6 @@ import (
var _ adapter.CertificateStore = (*Store)(nil)
type Store struct {
access sync.RWMutex
systemPool *x509.CertPool
currentPool *x509.CertPool
certificate string
@@ -117,14 +115,10 @@ func (s *Store) Close() error {
}
func (s *Store) Pool() *x509.CertPool {
s.access.RLock()
defer s.access.RUnlock()
return s.currentPool
}
func (s *Store) update() error {
s.access.Lock()
defer s.access.Unlock()
var currentPool *x509.CertPool
if s.systemPool == nil {
currentPool = x509.NewCertPool()

View File

@@ -15,6 +15,7 @@ import (
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -42,7 +43,7 @@ type DefaultDialer struct {
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback common.TypedValue[time.Time]
networkLastFallback atomic.TypedValue[time.Time]
}
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
@@ -88,35 +89,37 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if networkManager != nil {
defaultOptions := networkManager.DefaultOptions()
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
if !disableDefaultBind {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
@@ -124,11 +127,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
}
}
if networkManager != nil {
markFunc := networkManager.AutoRedirectOutputMarkFunc()
dialer.Control = control.Append(dialer.Control, markFunc)
listener.Control = control.Append(listener.Control, markFunc)
}
if options.ReuseAddr {
listener.Control = control.Append(listener.Control, control.ReuseAddr())
}

View File

@@ -8,10 +8,10 @@ import (
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"

View File

@@ -3,7 +3,6 @@ package listener
import (
"net"
"net/netip"
"strings"
"syscall"
"time"
@@ -57,7 +56,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if l.tproxy {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), false)
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
})
})
}

View File

@@ -5,7 +5,6 @@ import (
"net"
"net/netip"
"os"
"strings"
"syscall"
"github.com/sagernet/sing-box/adapter"
@@ -42,7 +41,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
if l.tproxy {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), true)
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
})
})
}

View File

@@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.NoError(t, err)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium)
require.Equal(t, metadata.Client, C.ClientQUICGo)
require.Equal(t, metadata.Domain, "www.google.com")
}

View File

@@ -12,6 +12,8 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/domain"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
"github.com/sagernet/sing/common/varbin"
"go4.org/netipx"
@@ -41,6 +43,8 @@ const (
ruleItemNetworkType
ruleItemNetworkIsExpensive
ruleItemNetworkIsConstrained
ruleItemNetworkInterfaceAddress
ruleItemDefaultInterfaceAddress
ruleItemFinal uint8 = 0xFF
)
@@ -230,6 +234,51 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
rule.NetworkIsExpensive = true
case ruleItemNetworkIsConstrained:
rule.NetworkIsConstrained = true
case ruleItemNetworkInterfaceAddress:
rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]])
var size uint64
size, err = binary.ReadUvarint(reader)
if err != nil {
return
}
for i := uint64(0); i < size; i++ {
var key uint8
err = binary.Read(reader, binary.BigEndian, &key)
if err != nil {
return
}
var value []badoption.Prefixable
var prefixCount uint64
prefixCount, err = binary.ReadUvarint(reader)
if err != nil {
return
}
for j := uint64(0); j < prefixCount; j++ {
var prefix netip.Prefix
prefix, err = readPrefix(reader)
if err != nil {
return
}
value = append(value, badoption.Prefixable(prefix))
}
rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value)
}
case ruleItemDefaultInterfaceAddress:
var value []badoption.Prefixable
var prefixCount uint64
prefixCount, err = binary.ReadUvarint(reader)
if err != nil {
return
}
for j := uint64(0); j < prefixCount; j++ {
var prefix netip.Prefix
prefix, err = readPrefix(reader)
if err != nil {
return
}
value = append(value, badoption.Prefixable(prefix))
}
rule.DefaultInterfaceAddress = value
case ruleItemFinal:
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
return
@@ -346,7 +395,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
}
if len(rule.NetworkType) > 0 {
if generateVersion < C.RuleSetVersion3 {
return E.New("network_type rule item is only supported in version 3 or later")
return E.New("`network_type` rule item is only supported in version 3 or later")
}
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
if err != nil {
@@ -354,17 +403,67 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
}
}
if rule.NetworkIsExpensive {
if generateVersion < C.RuleSetVersion3 {
return E.New("`network_is_expensive` rule item is only supported in version 3 or later")
}
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
if err != nil {
return err
}
}
if rule.NetworkIsConstrained {
if generateVersion < C.RuleSetVersion3 {
return E.New("`network_is_constrained` rule item is only supported in version 3 or later")
}
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
if err != nil {
return err
}
}
if rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 {
if generateVersion < C.RuleSetVersion4 {
return E.New("`network_interface_address` rule item is only supported in version 4 or later")
}
err = writer.WriteByte(ruleItemNetworkInterfaceAddress)
if err != nil {
return err
}
_, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size()))
if err != nil {
return err
}
for _, entry := range rule.NetworkInterfaceAddress.Entries() {
err = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build()))
if err != nil {
return err
}
for _, rawPrefix := range entry.Value {
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
if err != nil {
return err
}
}
}
}
if len(rule.DefaultInterfaceAddress) > 0 {
if generateVersion < C.RuleSetVersion4 {
return E.New("`default_interface_address` rule item is only supported in version 4 or later")
}
err = writer.WriteByte(ruleItemDefaultInterfaceAddress)
if err != nil {
return err
}
_, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress)))
if err != nil {
return err
}
for _, rawPrefix := range rule.DefaultInterfaceAddress {
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
if err != nil {
return err
}
}
}
if len(rule.WIFISSID) > 0 {
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
if err != nil {

33
common/srs/ip_cidr.go Normal file
View File

@@ -0,0 +1,33 @@
package srs
import (
"encoding/binary"
"net/netip"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/varbin"
)
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
if err != nil {
return netip.Prefix{}, err
}
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
if err != nil {
return netip.Prefix{}, err
}
return netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil
}
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
if err != nil {
return err
}
return nil
}

View File

@@ -2,10 +2,11 @@ package tls
import (
"context"
"crypto/tls"
"errors"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
@@ -14,7 +15,7 @@ import (
aTLS "github.com/sagernet/sing/common/tls"
)
func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
func NewDialerFromOptions(ctx context.Context, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
return dialer, nil
}
@@ -53,26 +54,57 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
return tlsConn, nil
}
type Dialer struct {
type Dialer interface {
N.Dialer
DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error)
}
type defaultDialer struct {
dialer N.Dialer
config Config
}
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
return &Dialer{dialer, config}
func NewDialer(dialer N.Dialer, config Config) Dialer {
return &defaultDialer{dialer, config}
}
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if network != N.NetworkTCP {
func (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if N.NetworkName(network) != N.NetworkTCP {
return nil, os.ErrInvalid
}
conn, err := d.dialer.DialContext(ctx, network, destination)
return d.DialTLSContext(ctx, destination)
}
func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}
func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {
return d.dialContext(ctx, destination, true)
}
func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) {
conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination)
if err != nil {
return nil, err
}
return ClientHandshake(ctx, conn, d.config)
tlsConn, err := ClientHandshake(ctx, conn, d.config)
if err == nil {
return tlsConn, nil
}
conn.Close()
if echRetry {
var echErr *tls.ECHRejectionError
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
echConfig.SetECHConfigList(echErr.RetryConfigList)
}
}
return d.dialContext(ctx, destination, false)
}
return nil, err
}
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
func (d *defaultDialer) Upstream() any {
return d.dialer
}

View File

@@ -69,7 +69,11 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
} else {
return E.New("missing ECH keys")
}
echKeys, err := parseECHKeys(echKey)
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return E.Cause(err, "parse ECH keys")
}
@@ -81,29 +85,21 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
return nil
}
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
echKeys, err := parseECHKeys(echKey)
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
echKey, err := os.ReadFile(echKeyPath)
if err != nil {
return err
return E.Cause(err, "reload ECH keys from ", echKeyPath)
}
c.access.Lock()
config := c.config.Clone()
config.EncryptedClientHelloKeys = echKeys
c.config = config
c.access.Unlock()
return nil
}
func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
block, _ := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" {
return nil, E.New("invalid ECH keys pem")
return E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return nil, E.Cause(err, "parse ECH keys")
return E.Cause(err, "parse ECH keys")
}
return echKeys, nil
tlsConfig.EncryptedClientHelloKeys = echKeys
return nil
}
type ECHClientConfig struct {

View File

@@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
return E.New("ECH requires go1.24, please recompile your binary.")
}
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
panic("unreachable")
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
return E.New("ECH requires go1.24, please recompile your binary.")
}

View File

@@ -307,11 +307,3 @@ func (c *realityClientConnWrapper) Upstream() any {
func (c *realityClientConnWrapper) CloseWrite() error {
return c.Close()
}
func (c *realityClientConnWrapper) ReaderReplaceable() bool {
return true
}
func (c *realityClientConnWrapper) WriterReplaceable() bool {
return true
}

View File

@@ -206,11 +206,3 @@ func (c *realityConnWrapper) Upstream() any {
func (c *realityConnWrapper) CloseWrite() error {
return c.Close()
}
func (c *realityConnWrapper) ReaderReplaceable() bool {
return true
}
func (c *realityConnWrapper) WriterReplaceable() bool {
return true
}

View File

@@ -6,7 +6,6 @@ import (
"net"
"os"
"strings"
"sync"
"time"
"github.com/sagernet/fswatch"
@@ -21,7 +20,6 @@ import (
var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct {
access sync.RWMutex
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
@@ -34,22 +32,14 @@ type STDServerConfig struct {
}
func (c *STDServerConfig) ServerName() string {
c.access.RLock()
defer c.access.RUnlock()
return c.config.ServerName
}
func (c *STDServerConfig) SetServerName(serverName string) {
c.access.Lock()
defer c.access.Unlock()
config := c.config.Clone()
config.ServerName = serverName
c.config = config
c.config.ServerName = serverName
}
func (c *STDServerConfig) NextProtos() []string {
c.access.RLock()
defer c.access.RUnlock()
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
return c.config.NextProtos[1:]
} else {
@@ -58,15 +48,11 @@ func (c *STDServerConfig) NextProtos() []string {
}
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
c.access.Lock()
defer c.access.Unlock()
config := c.config.Clone()
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
} else {
config.NextProtos = nextProto
c.config.NextProtos = nextProto
}
c.config = config
}
func (c *STDServerConfig) Config() (*STDConfig, error) {
@@ -91,6 +77,9 @@ func (c *STDServerConfig) Start() error {
if c.acmeService != nil {
return c.acmeService.Start()
} else {
if c.certificatePath == "" && c.keyPath == "" {
return nil
}
err := c.startWatcher()
if err != nil {
c.logger.Warn("create fsnotify watcher: ", err)
@@ -110,9 +99,6 @@ func (c *STDServerConfig) startWatcher() error {
if c.echKeyPath != "" {
watchPath = append(watchPath, c.echKeyPath)
}
if len(watchPath) == 0 {
return nil
}
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPath,
Callback: func(path string) {
@@ -152,18 +138,10 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
if err != nil {
return E.Cause(err, "reload key pair")
}
c.access.Lock()
config := c.config.Clone()
config.Certificates = []tls.Certificate{keyPair}
c.config = config
c.access.Unlock()
c.config.Certificates = []tls.Certificate{keyPair}
c.logger.Info("reloaded TLS certificate")
} else if path == c.echKeyPath {
echKey, err := os.ReadFile(c.echKeyPath)
if err != nil {
return E.Cause(err, "reload ECH keys from ", c.echKeyPath)
}
err = c.setECHServerConfig(echKey)
err := reloadECHKeys(c.echKeyPath, c.config)
if err != nil {
return err
}
@@ -284,7 +262,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
return nil, err
}
}
serverConfig := &STDServerConfig{
return &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
@@ -293,11 +271,5 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
certificatePath: options.CertificatePath,
keyPath: options.KeyPath,
echKeyPath: echKeyPath,
}
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
serverConfig.access.Lock()
defer serverConfig.access.Unlock()
return serverConfig.config, nil
}
return serverConfig, nil
}, nil
}

View File

@@ -106,14 +106,6 @@ func (c *utlsConnWrapper) Upstream() any {
return c.UConn
}
func (c *utlsConnWrapper) ReaderReplaceable() bool {
return true
}
func (c *utlsConnWrapper) WriterReplaceable() bool {
return true
}
type utlsALPNWrapper struct {
utlsConnWrapper
nextProtocols []string

View File

@@ -109,9 +109,6 @@ func (c *Conn) Write(b []byte) (n int, err error) {
if err != nil {
return
}
if i != len(splitIndexes) {
time.Sleep(c.fallbackDelay)
}
}
}
}

View File

@@ -9,10 +9,6 @@ import (
)
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
_, err := conn.Write(payload)
if err != nil {
return err
}
time.Sleep(fallbackDelay)
return nil
}

View File

@@ -16,9 +16,6 @@ func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fal
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
if err != nil {
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
if _, err := conn.Write(payload); err != nil {
return err
}
time.Sleep(fallbackDelay)
return nil
}

View File

@@ -47,15 +47,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
s.access.Lock()
delete(s.delayHistory, tag)
s.notifyUpdated()
s.access.Unlock()
s.notifyUpdated()
}
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
s.access.Lock()
s.delayHistory[tag] = history
s.notifyUpdated()
s.access.Unlock()
s.notifyUpdated()
}
func (s *HistoryStorage) notifyUpdated() {
@@ -69,8 +69,6 @@ func (s *HistoryStorage) notifyUpdated() {
}
func (s *HistoryStorage) Close() error {
s.access.Lock()
defer s.access.Unlock()
s.updateHook = nil
return nil
}

View File

@@ -22,7 +22,8 @@ const (
RuleSetVersion1 = 1 + iota
RuleSetVersion2
RuleSetVersion3
RuleSetVersionCurrent = RuleSetVersion3
RuleSetVersion4
RuleSetVersionCurrent = RuleSetVersion4
)
const (

View File

@@ -2,14 +2,12 @@ package dns
import (
"context"
"errors"
"net"
"net/netip"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/compatible"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
@@ -19,7 +17,7 @@ import (
"github.com/sagernet/sing/contrab/freelru"
"github.com/sagernet/sing/contrab/maphash"
"github.com/miekg/dns"
dns "github.com/miekg/dns"
)
var (
@@ -32,18 +30,16 @@ var (
var _ adapter.DNSClient = (*Client)(nil)
type Client struct {
timeout time.Duration
disableCache bool
disableExpire bool
independentCache bool
clientSubnet netip.Prefix
rdrc adapter.RDRCStore
initRDRCFunc func() adapter.RDRCStore
logger logger.ContextLogger
cache freelru.Cache[dns.Question, *dns.Msg]
cacheLock compatible.Map[dns.Question, chan struct{}]
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
transportCacheLock compatible.Map[dns.Question, chan struct{}]
timeout time.Duration
disableCache bool
disableExpire bool
independentCache bool
clientSubnet netip.Prefix
rdrc adapter.RDRCStore
initRDRCFunc func() adapter.RDRCStore
logger logger.ContextLogger
cache freelru.Cache[dns.Question, *dns.Msg]
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
}
type ClientOptions struct {
@@ -100,15 +96,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if c.logger != nil {
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
}
return FixedResponseStatus(message, dns.RcodeFormatError), nil
responseMessage := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: message.Id,
Response: true,
Rcode: dns.RcodeFormatError,
},
Question: message.Question,
}
return &responseMessage, nil
}
question := message.Question[0]
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
if c.logger != nil {
c.logger.DebugContext(ctx, "strategy rejected")
}
return FixedResponseStatus(message, dns.RcodeSuccess), nil
}
clientSubnet := options.ClientSubnet
if !clientSubnet.IsValid() {
clientSubnet = c.clientSubnet
@@ -116,38 +114,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if clientSubnet.IsValid() {
message = SetClientSubnet(message, clientSubnet)
}
isSimpleRequest := len(message.Question) == 1 &&
len(message.Ns) == 0 &&
(len(message.Extra) == 0 || len(message.Extra) == 1 &&
message.Extra[0].Header().Rrtype == dns.TypeOPT &&
message.Extra[0].Header().Class > 0 &&
message.Extra[0].Header().Ttl == 0 &&
len(message.Extra[0].(*dns.OPT).Option) == 0) &&
len(message.Extra) == 0 &&
!options.ClientSubnet.IsValid()
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
if !disableCache {
if c.cache != nil {
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
<-cond
} else {
defer func() {
c.cacheLock.Delete(question)
close(cond)
}()
}
} else if c.transportCache != nil {
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
<-cond
} else {
defer func() {
c.transportCacheLock.Delete(question)
close(cond)
}()
}
}
response, ttl := c.loadResponse(question, transport)
if response != nil {
logCachedResponse(c.logger, ctx, response, ttl)
@@ -155,14 +127,27 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return response, nil
}
}
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
responseMessage := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: message.Id,
Response: true,
Rcode: dns.RcodeSuccess,
},
Question: []dns.Question{question},
}
if c.logger != nil {
c.logger.DebugContext(ctx, "strategy rejected")
}
return &responseMessage, nil
}
messageId := message.Id
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
if clientSubnetLoaded && transport.Tag() == contextTransport {
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
}
ctx = contextWithTransportTag(ctx, transport.Tag())
if !disableCache && responseChecker != nil && c.rdrc != nil {
if responseChecker != nil && c.rdrc != nil {
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
if rejected {
return nil, ErrResponseRejectedCached
@@ -172,12 +157,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
response, err := transport.Exchange(ctx, message)
cancel()
if err != nil {
var rcodeError RcodeError
if errors.As(err, &rcodeError) {
response = FixedResponseStatus(message, int(rcodeError))
} else {
return nil, err
}
return nil, err
}
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
validResponse := response
@@ -214,17 +194,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
response.Answer = append(response.Answer, validResponse.Answer...)
}
}*/
disableCache = disableCache || response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0
if responseChecker != nil {
var rejected bool
// TODO: add accept_any rule and support to check response instead of addresses
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
rejected = true
} else {
rejected = !responseChecker(MessageToAddresses(response))
}
if rejected {
if !disableCache && c.rdrc != nil {
if c.rdrc != nil {
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
}
logRejectedResponse(c.logger, ctx, response)
@@ -281,7 +259,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
}
logExchangedResponse(c.logger, ctx, response, timeToLive)
return response, nil
return response, err
}
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
@@ -327,7 +305,8 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
func (c *Client) ClearCache() {
if c.cache != nil {
c.cache.Purge()
} else if c.transportCache != nil {
}
if c.transportCache != nil {
c.transportCache.Purge()
}
}
@@ -341,36 +320,36 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
}
dnsName := dns.Fqdn(domain)
if strategy == C.DomainStrategyIPv4Only {
addresses, err := c.questionCache(dns.Question{
response, err := c.questionCache(dns.Question{
Name: dnsName,
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}, nil)
if err != ErrNotCached {
return addresses, true
return response, true
}
} else if strategy == C.DomainStrategyIPv6Only {
addresses, err := c.questionCache(dns.Question{
response, err := c.questionCache(dns.Question{
Name: dnsName,
Qtype: dns.TypeAAAA,
Qclass: dns.ClassINET,
}, nil)
if err != ErrNotCached {
return addresses, true
return response, true
}
} else {
response4, _ := c.loadResponse(dns.Question{
response4, _ := c.questionCache(dns.Question{
Name: dnsName,
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}, nil)
response6, _ := c.loadResponse(dns.Question{
response6, _ := c.questionCache(dns.Question{
Name: dnsName,
Qtype: dns.TypeAAAA,
Qclass: dns.ClassINET,
}, nil)
if response4 != nil || response6 != nil {
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
if len(response4) > 0 || len(response6) > 0 {
return sortAddresses(response4, response6, strategy), true
}
}
return nil, false
@@ -411,15 +390,15 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
transportTag: transport.Tag(),
}, message)
}
return
}
if !c.independentCache {
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
} else {
if !c.independentCache {
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
} else {
c.transportCache.AddWithLifetime(transportCacheKey{
Question: question,
transportTag: transport.Tag(),
}, message, time.Second*time.Duration(timeToLive))
}
c.transportCache.AddWithLifetime(transportCacheKey{
Question: question,
transportTag: transport.Tag(),
}, message, time.Second*time.Duration(timeToLive))
}
}
@@ -538,9 +517,6 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
}
func MessageToAddresses(response *dns.Msg) []netip.Addr {
if response == nil || response.Rcode != dns.RcodeSuccess {
return nil
}
addresses := make([]netip.Addr, 0, len(response.Answer))
for _, rawAnswer := range response.Answer {
switch answer := rawAnswer.(type) {
@@ -585,12 +561,9 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
return &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: message.Id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: rcode,
Id: message.Id,
Rcode: rcode,
Response: true,
},
Question: message.Question,
}

View File

@@ -5,7 +5,6 @@ import (
)
const (
RcodeSuccess RcodeError = mDNS.RcodeSuccess
RcodeFormatError RcodeError = mDNS.RcodeFormatError
RcodeNameError RcodeError = mDNS.RcodeNameError
RcodeRefused RcodeError = mDNS.RcodeRefused

View File

@@ -293,7 +293,12 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
} else if errors.Is(err, ErrResponseRejected) {
rejected = true
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
rejected = true
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
*/
} else if len(message.Question) > 0 {
rejected = true
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
} else {
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))

View File

@@ -2,18 +2,17 @@ package dhcp
import (
"context"
"errors"
"io"
"net"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
@@ -30,7 +29,6 @@ import (
"github.com/insomniacslk/dhcp/dhcpv4"
mDNS "github.com/miekg/dns"
"golang.org/x/exp/slices"
)
func RegisterTransport(registry *dns.TransportRegistry) {
@@ -47,12 +45,9 @@ type Transport struct {
networkManager adapter.NetworkManager
interfaceName string
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
transportLock sync.RWMutex
transports []adapter.DNSTransport
updateAccess sync.Mutex
updatedAt time.Time
servers []M.Socksaddr
search []string
ndots int
attempts int
}
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
@@ -67,40 +62,27 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
logger: logger,
networkManager: service.FromContext[adapter.NetworkManager](ctx),
interfaceName: options.Interface,
ndots: 1,
attempts: 2,
}, nil
}
func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) *Transport {
return &Transport{
TransportAdapter: transportAdapter,
ctx: ctx,
dialer: dialer,
logger: logger,
networkManager: service.FromContext[adapter.NetworkManager](ctx),
ndots: 1,
attempts: 2,
}
}
func (t *Transport) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
err := t.fetchServers()
if err != nil {
return err
}
if t.interfaceName == "" {
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
}
go func() {
_, err := t.Fetch()
if err != nil {
t.logger.Error(E.Cause(err, "fetch DNS servers"))
}
}()
return nil
}
func (t *Transport) Close() error {
for _, transport := range t.transports {
transport.Close()
}
if t.interfaceCallback != nil {
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
}
@@ -108,44 +90,23 @@ func (t *Transport) Close() error {
}
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
servers, err := t.Fetch()
err := t.fetchServers()
if err != nil {
return nil, err
}
if len(servers) == 0 {
if len(t.transports) == 0 {
return nil, E.New("dhcp: empty DNS servers from response")
}
return t.Exchange0(ctx, message, servers)
}
func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) {
question := message.Question[0]
domain := dns.FqdnToDomain(question.Name)
if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
return t.exchangeSingleRequest(ctx, servers, message, domain)
} else {
return t.exchangeParallel(ctx, servers, message, domain)
var response *mDNS.Msg
for _, transport := range t.transports {
response, err = transport.Exchange(ctx, message)
if err == nil {
return response, nil
}
}
}
func (t *Transport) Fetch() ([]M.Socksaddr, error) {
t.transportLock.RLock()
updatedAt := t.updatedAt
servers := t.servers
t.transportLock.RUnlock()
if time.Since(updatedAt) < C.DHCPTTL {
return servers, nil
}
t.transportLock.Lock()
defer t.transportLock.Unlock()
if time.Since(t.updatedAt) < C.DHCPTTL {
return t.servers, nil
}
err := t.updateServers()
if err != nil {
return nil, err
}
return t.servers, nil
return nil, err
}
func (t *Transport) fetchInterface() (*control.Interface, error) {
@@ -163,6 +124,18 @@ func (t *Transport) fetchInterface() (*control.Interface, error) {
}
}
func (t *Transport) fetchServers() error {
if time.Since(t.updatedAt) < C.DHCPTTL {
return nil
}
t.updateAccess.Lock()
defer t.updateAccess.Unlock()
if time.Since(t.updatedAt) < C.DHCPTTL {
return nil
}
return t.updateServers()
}
func (t *Transport) updateServers() error {
iface, err := t.fetchInterface()
if err != nil {
@@ -175,7 +148,7 @@ func (t *Transport) updateServers() error {
cancel()
if err != nil {
return err
} else if len(t.servers) == 0 {
} else if len(t.transports) == 0 {
return E.New("dhcp: empty DNS servers response")
} else {
t.updatedAt = time.Now()
@@ -198,27 +171,13 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
listenAddr = "255.255.255.255:68"
}
var (
packetConn net.PacketConn
err error
)
for i := 0; i < 5; i++ {
packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr)
if err == nil || !errors.Is(err, syscall.EADDRINUSE) {
break
}
time.Sleep(time.Second)
}
packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr)
if err != nil {
return err
}
defer packetConn.Close()
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(
dhcpv4.OptionDomainName,
dhcpv4.OptionDomainNameServer,
dhcpv4.OptionDNSDomainSearchList,
))
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
if err != nil {
return err
}
@@ -245,9 +204,6 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
for {
_, _, err := buffer.ReadPacketFrom(packetConn)
if err != nil {
if errors.Is(err, io.ErrShortBuffer) {
continue
}
return err
}
@@ -267,23 +223,31 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
continue
}
return t.recreateServers(iface, dhcpPacket)
dns := dhcpPacket.DNS()
if len(dns) == 0 {
return nil
}
return t.recreateServers(iface, common.Map(dns, func(it net.IP) M.Socksaddr {
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
}))
}
}
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
searchList := dhcpPacket.DomainSearch()
if searchList != nil && len(searchList.Labels) > 0 {
t.search = searchList.Labels
} else if dhcpPacket.DomainName() != "" {
t.search = []string{dhcpPacket.DomainName()}
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error {
if len(serverAddrs) > 0 {
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]")
}
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
})
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
BindInterface: iface.Name,
UDPFragmentDefault: true,
}))
var transports []adapter.DNSTransport
for _, serverAddr := range serverAddrs {
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
}
t.servers = serverAddrs
for _, transport := range t.transports {
transport.Close()
}
t.transports = transports
return nil
}

View File

@@ -1,213 +0,0 @@
package dhcp
import (
"context"
"errors"
"math/rand"
"strings"
"syscall"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
mDNS "github.com/miekg/dns"
)
func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
var lastErr error
for _, fqdn := range t.nameList(domain) {
response, err := t.tryOneName(ctx, servers, fqdn, message)
if err != nil {
lastErr = err
continue
}
return response, nil
}
return nil, lastErr
}
func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
returned := make(chan struct{})
defer close(returned)
type queryResult struct {
response *mDNS.Msg
err error
}
results := make(chan queryResult)
startRacer := func(ctx context.Context, fqdn string) {
response, err := t.tryOneName(ctx, servers, fqdn, message)
if err == nil {
if response.Rcode != mDNS.RcodeSuccess {
err = dns.RcodeError(response.Rcode)
} else if len(dns.MessageToAddresses(response)) == 0 {
err = dns.RcodeSuccess
}
}
select {
case results <- queryResult{response, err}:
case <-returned:
}
}
queryCtx, queryCancel := context.WithCancel(ctx)
defer queryCancel()
var nameCount int
for _, fqdn := range t.nameList(domain) {
nameCount++
go startRacer(queryCtx, fqdn)
}
var errors []error
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case result := <-results:
if result.err == nil {
return result.response, nil
}
errors = append(errors, result.err)
if len(errors) == nameCount {
return nil, E.Errors(errors...)
}
}
}
}
func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
sLen := len(servers)
var lastErr error
for i := 0; i < t.attempts; i++ {
for j := 0; j < sLen; j++ {
server := servers[j]
question := message.Question[0]
question.Name = fqdn
response, err := t.exchangeOne(ctx, server, question)
if err != nil {
lastErr = err
continue
}
return response, nil
}
}
return nil, E.Cause(lastErr, fqdn)
}
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) {
if server.Port == 0 {
server.Port = 53
}
request := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: uint16(rand.Uint32()),
RecursionDesired: true,
AuthenticatedData: true,
},
Question: []mDNS.Question{question},
Compress: true,
}
request.SetEdns0(buf.UDPBufferSize, false)
return t.exchangeUDP(ctx, server, request)
}
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
buffer := buf.Get(1 + request.Len())
defer buf.Put(buffer)
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
_, err = conn.Write(rawMessage)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request)
}
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request)
}
return nil, E.Cause(err, "read response")
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated {
return t.exchangeTCP(ctx, server, request)
}
return &response, nil
}
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
err = transport.WriteMessage(conn, 0, request)
if err != nil {
return nil, err
}
return transport.ReadMessage(conn)
}
func (t *Transport) nameList(name string) []string {
l := len(name)
rooted := l > 0 && name[l-1] == '.'
if l > 254 || l == 254 && !rooted {
return nil
}
if rooted {
if avoidDNS(name) {
return nil
}
return []string{name}
}
hasNdots := strings.Count(name, ".") >= t.ndots
name += "."
// l++
names := make([]string, 0, 1+len(t.search))
if hasNdots && !avoidDNS(name) {
names = append(names, name)
}
for _, suffix := range t.search {
fqdn := name + suffix
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
names = append(names, fqdn)
}
}
if !hasNdots && !avoidDNS(name) {
names = append(names, name)
}
return names
}
func avoidDNS(name string) bool {
if name == "" {
return true
}
if name[len(name)-1] == '.' {
name = name[:len(name)-1]
}
return strings.HasSuffix(name, ".onion")
}

View File

@@ -8,6 +8,7 @@ import (
"net"
"net/http"
"net/url"
"os"
"strconv"
"sync"
"time"
@@ -177,7 +178,7 @@ func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS
startAt := time.Now()
response, err := t.exchange(ctx, message)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
if errors.Is(err, os.ErrDeadlineExceeded) {
t.transportAccess.Lock()
defer t.transportAccess.Unlock()
if t.transportResetAt.After(startAt) {

View File

@@ -2,15 +2,12 @@ package local
import (
"context"
"errors"
"math/rand"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing-box/dns/transport/hosts"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -96,7 +93,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
if response.Rcode != mDNS.RcodeSuccess {
err = dns.RcodeError(response.Rcode)
} else if len(dns.MessageToAddresses(response)) == 0 {
err = dns.RcodeSuccess
err = E.New(fqdn, ": empty result")
}
}
select {
@@ -152,6 +149,12 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
if server.Port == 0 {
server.Port = 53
}
var networks []string
if useTCP {
networks = []string{N.NetworkTCP}
} else {
networks = []string{N.NetworkUDP, N.NetworkTCP}
}
request := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: uint16(rand.Uint32()),
@@ -161,74 +164,41 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
Question: []mDNS.Question{question},
Compress: true,
}
request.SetEdns0(buf.UDPBufferSize, false)
if !useTCP {
return t.exchangeUDP(ctx, server, request, timeout)
} else {
return t.exchangeTCP(ctx, server, request, timeout)
}
}
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
newDeadline := time.Now().Add(timeout)
if deadline.After(newDeadline) {
deadline = newDeadline
}
conn.SetDeadline(deadline)
}
buffer := buf.Get(1 + request.Len())
request.SetEdns0(maxDNSPacketSize, false)
buffer := buf.Get(buf.UDPBufferSize)
defer buf.Put(buffer)
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
_, err = conn.Write(rawMessage)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request, timeout)
for _, network := range networks {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
conn, err := t.dialer.DialContext(ctx, network, server)
if err != nil {
return nil, err
}
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request, timeout)
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
return nil, E.Cause(err, "read response")
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
_, err = conn.Write(rawMessage)
if err != nil {
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
return nil, E.Cause(err, "read response")
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated && network == N.NetworkUDP {
continue
}
return &response, nil
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated {
return t.exchangeTCP(ctx, server, request, timeout)
}
return &response, nil
}
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
newDeadline := time.Now().Add(timeout)
if deadline.After(newDeadline) {
deadline = newDeadline
}
conn.SetDeadline(deadline)
}
err = transport.WriteMessage(conn, 0, request)
if err != nil {
return nil, err
}
return transport.ReadMessage(conn)
panic("unexpected")
}

View File

@@ -10,6 +10,11 @@ import (
"time"
)
const (
// net.maxDNSPacketSize
maxDNSPacketSize = 1232
)
type resolverConfig struct {
initOnce sync.Once
ch chan struct{}

View File

@@ -30,7 +30,7 @@ func RegisterTLS(registry *dns.TransportRegistry) {
type TLSTransport struct {
dns.TransportAdapter
logger logger.ContextLogger
dialer N.Dialer
dialer tls.Dialer
serverAddr M.Socksaddr
tlsConfig tls.Config
access sync.Mutex
@@ -67,7 +67,7 @@ func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
return &TLSTransport{
TransportAdapter: adapter,
logger: logger,
dialer: dialer,
dialer: tls.NewDialer(dialer, tlsConfig),
serverAddr: serverAddr,
tlsConfig: tlsConfig,
}
@@ -100,15 +100,10 @@ func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
return response, nil
}
}
tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)
tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr)
if err != nil {
return nil, err
}
tlsConn, err := tls.ClientHandshake(ctx, tcpConn, t.tlsConfig)
if err != nil {
tcpConn.Close()
return nil, err
}
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
}

View File

@@ -2,31 +2,35 @@
icon: material/alert-decagram
---
#### 1.12.7
#### 1.13.0-alpha.2
* Add `preferred_by` rule item **1**
* Fixes and improvements
#### 1.12.5
**1**:
The new `preferred_by` routing rule item allows you to
match preferred domains and addresses for specific outbounds.
See [Route Rule](/configuration/route/rule/#preferred_by).
#### 1.13.0-alpha.1
* Add interface address rule items **1**
* Fixes and improvements
#### 1.12.4
**1**:
* Fixes and improvements
New interface address rules allow you to dynamically adjust rules based on your network environment.
#### 1.12.3
* Fixes and improvements
#### 1.12.2
* Fixes and improvements
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
#### 1.12.1
* Fixes and improvements
#### 1.12.0
### 1.12.0
* Refactor DNS servers **1**
* Add domain resolver options**2**
@@ -177,7 +181,7 @@ We continue to experience issues updating our sing-box apps on the App Store and
Until we rewrite and resubmit the apps, they are considered irrecoverable.
Therefore, after this release, we will not be repeating this notice unless there is new information.
### 1.11.15
#### 1.11.15
* Fixes and improvements
@@ -193,7 +197,7 @@ violated the rules (TestFlight users are not affected)._
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
### 1.11.14
#### 1.11.14
* Fixes and improvements
@@ -243,7 +247,7 @@ You can now choose what the DERP home page shows, just like with derper's `-home
See [DERP](/configuration/service/derp/#home).
### 1.11.13
#### 1.11.13
* Fixes and improvements
@@ -281,7 +285,7 @@ SSM API service is a RESTful API server for managing Shadowsocks servers.
See [SSM API Service](/configuration/service/ssm-api/).
### 1.11.11
#### 1.11.11
* Fixes and improvements
@@ -313,7 +317,7 @@ You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fiel
See [Listen Fields](/configuration/shared/listen/).
### 1.11.10
#### 1.11.10
* Undeprecate the `block` outbound **1**
* Fixes and improvements
@@ -331,7 +335,7 @@ violated the rules (TestFlight users are not affected)._
* Update quic-go to v0.51.0
* Fixes and improvements
### 1.11.9
#### 1.11.9
* Fixes and improvements
@@ -342,7 +346,7 @@ violated the rules (TestFlight users are not affected)._
* Fixes and improvements
### 1.11.8
#### 1.11.8
* Improve `auto_redirect` **1**
* Fixes and improvements
@@ -359,7 +363,7 @@ violated the rules (TestFlight users are not affected)._
* Fixes and improvements
### 1.11.7
#### 1.11.7
* Fixes and improvements
@@ -375,7 +379,7 @@ violated the rules (TestFlight users are not affected)._
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
### 1.11.6
#### 1.11.6
* Fixes and improvements
@@ -416,7 +420,7 @@ See [Protocol Sniff](/configuration/route/sniff/).
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
### 1.11.5
#### 1.11.5
* Fixes and improvements
@@ -432,7 +436,7 @@ violated the rules (TestFlight users are not affected)._
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
### 1.11.4
#### 1.11.4
* Fixes and improvements
@@ -488,7 +492,7 @@ Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to co
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
### 1.11.3
#### 1.11.3
* Fixes and improvements
@@ -499,7 +503,7 @@ process._
* Fixes and improvements
### 1.11.1
#### 1.11.1
* Fixes and improvements
@@ -678,7 +682,7 @@ See [Hysteria2](/configuration/outbound/hysteria2/).
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
### 1.10.7
#### 1.10.7
* Fixes and improvements
@@ -773,7 +777,7 @@ and the old outbound will be removed in sing-box 1.13.0.
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
### 1.10.2
#### 1.10.2
* Add deprecated warnings
* Fix proxying websocket connections in HTTP/mixed inbounds
@@ -910,7 +914,7 @@ See [Rule Action](/configuration/route/rule_action/).
* Update quic-go to v0.48.0
* Fixes and improvements
### 1.10.1
#### 1.10.1
* Fixes and improvements

View File

@@ -2,6 +2,12 @@
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [interface_address](#interface_address)
:material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address)
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [ip_accept_any](#ip_accept_any)
@@ -130,6 +136,19 @@ icon: material/alert-decagram
],
"network_is_expensive": false,
"network_is_constrained": false,
"interface_address": {
"en0": [
"2000::/3"
]
},
"network_interface_address": {
"wifi": [
"2000::/3"
]
},
"default_interface_address": [
"2000::/3"
],
"wifi_ssid": [
"My WIFI"
],
@@ -359,6 +378,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
Match if network is in Low Data Mode.
#### interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux, Windows, and macOS.
Match interface address.
#### network_interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Matches network interface (same values as `network_type`) address.
#### default_interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux, Windows, and macOS.
Match default interface address.
#### wifi_ssid
!!! quote ""

View File

@@ -2,6 +2,12 @@
icon: material/alert-decagram
---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [interface_address](#interface_address)
:material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address)
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [ip_accept_any](#ip_accept_any)
@@ -130,6 +136,19 @@ icon: material/alert-decagram
],
"network_is_expensive": false,
"network_is_constrained": false,
"interface_address": {
"en0": [
"2000::/3"
]
},
"network_interface_address": {
"wifi": [
"2000::/3"
]
},
"default_interface_address": [
"2000::/3"
],
"wifi_ssid": [
"My WIFI"
],
@@ -358,6 +377,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
匹配如果网络在低数据模式下。
#### interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux、Windows 和 macOS.
匹配接口地址。
#### network_interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络接口(可用值同 `network_type`)地址。
#### default_interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux、Windows 和 macOS.
匹配默认接口地址。
#### wifi_ssid
!!! quote ""

View File

@@ -2,6 +2,13 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [interface_address](#interface_address)
:material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address)
:material-plus: [preferred_by](#preferred_by)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [action](#action)
@@ -128,12 +135,29 @@ icon: material/new-box
],
"network_is_expensive": false,
"network_is_constrained": false,
"interface_address": {
"en0": [
"2000::/3"
]
},
"network_interface_address": {
"wifi": [
"2000::/3"
]
},
"default_interface_address": [
"2000::/3"
],
"wifi_ssid": [
"My WIFI"
],
"wifi_bssid": [
"00:00:00:00:00:00"
],
"preferred_by": [
"tailscale",
"wireguard"
],
"rule_set": [
"geoip-cn",
"geosite-cn"
@@ -363,6 +387,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
Match if network is in Low Data Mode.
#### interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux, Windows, and macOS.
Match interface address.
#### network_interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Matches network interface (same values as `network_type`) address.
#### default_interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux, Windows, and macOS.
Match default interface address.
#### wifi_ssid
!!! quote ""
@@ -379,6 +433,17 @@ Match WiFi SSID.
Match WiFi BSSID.
#### preferred_by
!!! question "Since sing-box 1.13.0"
Match specified outbounds' preferred routes.
| Type | Match |
|-------------|-----------------------------------------------|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
| `wireguard` | Match peers's allowed IPs |
#### rule_set
!!! question "Since sing-box 1.8.0"

View File

@@ -2,6 +2,13 @@
icon: material/new-box
---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [interface_address](#interface_address)
:material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address)
:material-plus: [preferred_by](#preferred_by)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
@@ -125,12 +132,29 @@ icon: material/new-box
],
"network_is_expensive": false,
"network_is_constrained": false,
"interface_address": {
"en0": [
"2000::/3"
]
},
"network_interface_address": {
"wifi": [
"2000::/3"
]
},
"default_interface_address": [
"2000::/3"
],
"wifi_ssid": [
"My WIFI"
],
"wifi_bssid": [
"00:00:00:00:00:00"
],
"preferred_by": [
"tailscale",
"wireguard"
],
"rule_set": [
"geoip-cn",
"geosite-cn"
@@ -337,7 +361,7 @@ icon: material/new-box
匹配网络类型。
Available values: `wifi`, `cellular`, `ethernet` and `other`.
可用值: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
@@ -360,6 +384,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
匹配如果网络在低数据模式下。
#### interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux、Windows 和 macOS.
匹配接口地址。
#### network_interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络接口(可用值同 `network_type`)地址。
#### default_interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux、Windows 和 macOS.
匹配默认接口地址。
#### wifi_ssid
!!! quote ""
@@ -376,6 +430,17 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
匹配 WiFi BSSID。
#### preferred_by
!!! question "自 sing-box 1.13.0 起"
匹配制定出站的首选路由。
| 类型 | 匹配 |
|-------------|--------------------------------|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
| `wireguard` | 匹配对端的 allowed IPs |
#### rule_set
!!! question "自 sing-box 1.8.0 起"

View File

@@ -2,6 +2,11 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [network_type](#network_type)
@@ -78,6 +83,14 @@ icon: material/new-box
],
"network_is_expensive": false,
"network_is_constrained": false,
"network_interface_address": {
"wifi": [
"2000::/3"
]
},
"default_interface_address": [
"2000::/3"
],
"wifi_ssid": [
"My WIFI"
],
@@ -225,6 +238,26 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
Match if network is in Low Data Mode.
#### network_interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Matches network interface (same values as `network_type`) address.
#### default_interface_address
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux, Windows, and macOS.
Match default interface address.
#### wifi_ssid
!!! quote ""

View File

@@ -2,6 +2,11 @@
icon: material/new-box
---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [network_type](#network_type)
@@ -78,6 +83,14 @@ icon: material/new-box
],
"network_is_expensive": false,
"network_is_constrained": false,
"network_interface_address": {
"wifi": [
"2000::/3"
]
},
"default_interface_address": [
"2000::/3"
],
"wifi_ssid": [
"My WIFI"
],
@@ -221,6 +234,26 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
匹配如果网络在低数据模式下。
#### network_interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络接口(可用值同 `network_type`)地址。
#### default_interface_address
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux、Windows 和 macOS.
匹配默认接口地址。
#### wifi_ssid
!!! quote ""

View File

@@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.13.0"
:material-plus: version `4`
!!! quote "Changes in sing-box 1.11.0"
:material-plus: version `3`
@@ -36,6 +40,7 @@ Version of rule-set.
* 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.
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
#### rules

View File

@@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: version `4`
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: version `3`
@@ -36,6 +40,7 @@ icon: material/new-box
* 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` 规则项。
* 4: sing-box 1.13.0: 添加了 `network_interface_address``default_interface_address` 规则项。
#### rules

View File

@@ -14,6 +14,7 @@ import (
func cacheRouter(ctx context.Context) http.Handler {
r := chi.NewRouter()
r.Post("/fakeip/flush", flushFakeip(ctx))
r.Post("/dns/flush", flushDNS(ctx))
return r
}
@@ -31,3 +32,13 @@ func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Reques
render.NoContent(w, r)
}
}
func flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
if dnsRouter != nil {
dnsRouter.ClearCache()
}
render.NoContent(w, r)
}
}

View File

@@ -3,12 +3,12 @@ package trafficontrol
import (
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/common/compatible"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/x/list"

View File

@@ -2,11 +2,11 @@ package trafficontrol
import (
"net"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"

View File

@@ -7,11 +7,11 @@ import (
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"

12
go.mod
View File

@@ -15,7 +15,7 @@ require (
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/metacubex/utls v1.8.0
github.com/mholt/acmez/v3 v3.1.2
github.com/miekg/dns v1.1.67
@@ -27,16 +27,16 @@ require (
github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.7.10
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
github.com/sagernet/sing-mux v0.3.3
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb
github.com/sagernet/sing-quic v0.5.0
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.7.2
github.com/sagernet/sing-vmess v0.2.7
github.com/sagernet/sing-tun v0.7.0-beta.1
github.com/sagernet/sing-vmess v0.2.6
github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1
github.com/sagernet/tailscale v1.80.3-mod.5
github.com/sagernet/wireguard-go v0.0.1-beta.7
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.9.1

24
go.sum
View File

@@ -122,8 +122,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
@@ -167,26 +167,26 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw=
github.com/sagernet/sing v0.7.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U=
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM=
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.7.2 h1:uJkAZM0KBqIYzrq077QGqdvj/+4i/pMOx6Pnx0jYqAs=
github.com/sagernet/sing-tun v0.7.2/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM=
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY=
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
github.com/sagernet/sing-vmess v0.2.6 h1:1c4dGzeGy0kpBXXrT1sgiMZtHhdJylIT8eWrGhJYZec=
github.com/sagernet/sing-vmess v0.2.6/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 h1:gMC0q+0VvZBotZMZ9G0R8ZMEIT/Q6KnXbw0/OgMjmdk=
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=

View File

@@ -67,42 +67,46 @@ func (r Rule) IsValid() bool {
}
type RawDefaultRule struct {
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
Client badoption.Listable[string] `json:"client,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"`
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,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"`
User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[InterfaceType] `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"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"`
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
Client badoption.Listable[string] `json:"client,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"`
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,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"`
User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[InterfaceType] `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"`
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`

View File

@@ -68,45 +68,48 @@ func (r DNSRule) IsValid() bool {
}
type RawDefaultDNSRule struct {
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol badoption.Listable[string] `json:"protocol,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"`
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,omitempty"`
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,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"`
User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[InterfaceType] `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"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
Invert bool `json:"invert,omitempty"`
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol badoption.Listable[string] `json:"protocol,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"`
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,omitempty"`
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,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"`
User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[InterfaceType] `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"`
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
Invert bool `json:"invert,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`

View File

@@ -182,28 +182,31 @@ 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"`
NetworkType badoption.Listable[InterfaceType] `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"`
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[InterfaceType] `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"`
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
Invert bool `json:"invert,omitempty"`
DomainMatcher *domain.Matcher `json:"-"`
SourceIPSet *netipx.IPSet `json:"-"`
@@ -240,7 +243,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
var v any
switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
v = r.Options
default:
return nil, E.New("unknown rule-set version: ", r.Version)
@@ -255,7 +258,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
}
var v any
switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
v = &r.Options
case 0:
return E.New("missing rule-set version")
@@ -272,7 +275,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
switch r.Version {
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
default:
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
}

View File

@@ -124,7 +124,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.Source = source
metadata.Destination = destination.Unwrap()
metadata.Destination = destination
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
metadata.User = userName
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)

View File

@@ -26,7 +26,7 @@ func RegisterOutbound(registry *outbound.Registry) {
type Outbound struct {
outbound.Adapter
dialer N.Dialer
dialer tls.Dialer
server M.Socksaddr
tlsConfig tls.Config
client *anytls.Client
@@ -58,7 +58,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
outbound.dialer = outboundDialer
outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig)
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
Password: options.Password,
@@ -91,16 +92,7 @@ func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
}
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.server)
if err != nil {
return nil, err
}
tlsConn, err := tls.ClientHandshake(ctx, conn, h.tlsConfig)
if err != nil {
common.Close(tlsConn, conn)
return nil, err
}
return tlsConn, nil
return h.dialer.DialTLSContext(ctx, h.server)
}
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {

View File

@@ -13,6 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
@@ -163,7 +164,26 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)
var domainStrategy C.DomainStrategy
if h.domainStrategy != C.DomainStrategyAsIS {
domainStrategy = h.domainStrategy
} else {
//nolint:staticcheck
domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy)
}
switch domainStrategy {
case C.DomainStrategyIPv4Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv4 address available for ", destination)
}
case C.DomainStrategyIPv6Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay)
}
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
@@ -184,7 +204,26 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
var domainStrategy C.DomainStrategy
if h.domainStrategy != C.DomainStrategyAsIS {
domainStrategy = h.domainStrategy
} else {
//nolint:staticcheck
domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy)
}
switch domainStrategy {
case C.DomainStrategyIPv4Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv4 address available for ", destination)
}
case C.DomainStrategyIPv6Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
}
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {

View File

@@ -10,7 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
@@ -37,7 +37,7 @@ type Selector struct {
tags []string
defaultTag string
outbounds map[string]adapter.Outbound
selected common.TypedValue[adapter.Outbound]
selected atomic.TypedValue[adapter.Outbound]
interruptGroup *interrupt.Group
interruptExternalConnections bool
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"net"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -15,6 +14,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -192,7 +192,7 @@ type URLTestGroup struct {
ticker *time.Ticker
close chan struct{}
started bool
lastActive common.TypedValue[time.Time]
lastActive atomic.TypedValue[time.Time]
}
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {

View File

@@ -34,7 +34,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
detour, err := tls.NewDialerFromOptions(ctx, router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
detour, err := tls.NewDialerFromOptions(ctx, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}

View File

@@ -8,12 +8,10 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -35,7 +33,6 @@ type Inbound struct {
logger log.ContextLogger
listener *listener.Listener
authenticator *auth.Authenticator
tlsConfig tls.ServerConfig
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) {
@@ -45,13 +42,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
logger: logger,
authenticator: auth.NewAuthenticator(options.Users),
}
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
inbound.tlsConfig = tlsConfig
}
inbound.listener = listener.New(listener.Options{
Context: ctx,
Logger: logger,
@@ -68,20 +58,11 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
if h.tlsConfig != nil {
err := h.tlsConfig.Start()
if err != nil {
return E.Cause(err, "create TLS config")
}
}
return h.listener.Start()
}
func (h *Inbound) Close() error {
return common.Close(
h.listener,
h.tlsConfig,
)
return h.listener.Close()
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
@@ -97,13 +78,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
}
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
if h.tlsConfig != nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
return E.Cause(err, "TLS handshake")
}
conn = tlsConn
}
reader := std_bufio.NewReader(conn)
headerBytes, err := reader.Peek(1)
if err != nil {

View File

@@ -170,7 +170,7 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
hostPort = request.Host
}
source := sHttp.SourceAddress(request)
destination := M.ParseSocksaddr(hostPort).Unwrap()
destination := M.ParseSocksaddr(hostPort)
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
conn, _, err := hijacker.Hijack()

View File

@@ -7,7 +7,6 @@ import (
"net/netip"
"net/url"
"os"
"reflect"
"strings"
"sync"
@@ -47,8 +46,6 @@ type DNSTransport struct {
acceptDefaultResolvers bool
dnsRouter adapter.DNSRouter
endpointManager adapter.EndpointManager
cfg *wgcfg.Config
dnsCfg *nDNS.Config
endpoint *Endpoint
routePrefixes []netip.Prefix
routes map[string][]adapter.DNSTransport
@@ -83,10 +80,10 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error {
if !isTailscale {
return E.New("endpoint is not Tailscale: ", t.endpointTag)
}
if ep.onReconfig != nil {
if ep.onReconfigHook != nil {
return E.New("only one Tailscale DNS server is allowed for single endpoint")
}
ep.onReconfig = t.onReconfig
ep.onReconfigHook = t.onReconfig
t.endpoint = ep
return nil
}
@@ -95,14 +92,6 @@ func (t *DNSTransport) Reset() {
}
func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {
if cfg == nil || dnsCfg == nil {
return
}
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
return
}
t.cfg = cfg
t.dnsCfg = dnsCfg
err := t.updateDNSServers(routerCfg, dnsCfg)
if err != nil {
t.logger.Error(E.Cause(err, "update DNS servers"))

View File

@@ -10,9 +10,9 @@ import (
"net/url"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"sync/atomic"
"syscall"
"time"
@@ -31,6 +31,7 @@ import (
"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/bufio"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
@@ -49,8 +50,14 @@ import (
"github.com/sagernet/tailscale/version"
"github.com/sagernet/tailscale/wgengine"
"github.com/sagernet/tailscale/wgengine/filter"
"github.com/sagernet/tailscale/wgengine/router"
"github.com/sagernet/tailscale/wgengine/wgcfg"
"go4.org/netipx"
)
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
func init() {
version.SetVersion("sing-box " + C.Version)
}
@@ -70,7 +77,12 @@ type Endpoint struct {
server *tsnet.Server
stack *stack.Stack
filter *atomic.Pointer[filter.Filter]
onReconfig wgengine.ReconfigListener
onReconfigHook wgengine.ReconfigListener
cfg *wgcfg.Config
dnsCfg *tsDNS.Config
routeDomains atomic.TypedValue[map[string]bool]
routePrefixes atomic.Pointer[netipx.IPSet]
acceptRoutes bool
exitNode string
@@ -216,9 +228,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
if err != nil {
return err
}
if t.onReconfig != nil {
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
}
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
ipStack := t.server.ExportNetstack().ExportIPStack()
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
@@ -253,8 +263,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
if err != nil {
return E.Cause(err, "update prefs")
}
t.filter = localBackend.ExportFilter()
t.filter = atomic.PointerForm(localBackend.ExportFilter())
go t.watchState()
return nil
}
@@ -473,10 +482,58 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}
func (t *Endpoint) PreferredDomain(domain string) bool {
routeDomains := t.routeDomains.Load()
if routeDomains == nil {
return false
}
return routeDomains[strings.ToLower(domain)]
}
func (t *Endpoint) PreferredAddress(address netip.Addr) bool {
routePrefixes := t.routePrefixes.Load()
if routePrefixes == nil {
return false
}
return routePrefixes.Contains(address)
}
func (t *Endpoint) Server() *tsnet.Server {
return t.server
}
func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) {
if cfg == nil || dnsCfg == nil {
return
}
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
return
}
t.cfg = cfg
t.dnsCfg = dnsCfg
routeDomains := make(map[string]bool)
for fqdn := range dnsCfg.Routes {
routeDomains[fqdn.WithoutTrailingDot()] = true
}
for _, fqdn := range dnsCfg.SearchDomains {
routeDomains[fqdn.WithoutTrailingDot()] = true
}
t.routeDomains.Store(routeDomains)
var builder netipx.IPSetBuilder
for _, peer := range cfg.Peers {
for _, allowedIP := range peer.AllowedIPs {
builder.AddPrefix(allowedIP)
}
}
t.routePrefixes.Store(common.Must1(builder.IPSet()))
if t.onReconfigHook != nil {
t.onReconfigHook(cfg, routerCfg, dnsCfg)
}
}
func addressFromAddr(destination netip.Addr) tcpip.Address {
if destination.Is6() {
return tcpip.AddrFrom16(destination.As16())

View File

@@ -34,6 +34,7 @@ type Outbound struct {
key [56]byte
multiplexDialer *mux.Client
tlsConfig tls.Config
tlsDialer tls.Dialer
transport adapter.V2RayClientTransport
}
@@ -54,6 +55,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
}
if options.Transport != nil {
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
@@ -121,11 +123,10 @@ func (h *trojanDialer) DialContext(ctx context.Context, network string, destinat
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else if h.tlsDialer != nil {
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
common.Close(conn)

View File

@@ -35,6 +35,7 @@ type Outbound struct {
serverAddr M.Socksaddr
multiplexDialer *mux.Client
tlsConfig tls.Config
tlsDialer tls.Dialer
transport adapter.V2RayClientTransport
packetAddr bool
xudp bool
@@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
}
if options.Transport != nil {
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
@@ -140,11 +142,10 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else if h.tlsDialer != nil {
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
return nil, err
@@ -183,11 +184,10 @@ func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else if h.tlsDialer != nil {
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
common.Close(conn)

View File

@@ -35,6 +35,7 @@ type Outbound struct {
serverAddr M.Socksaddr
multiplexDialer *mux.Client
tlsConfig tls.Config
tlsDialer tls.Dialer
transport adapter.V2RayClientTransport
packetAddr bool
xudp bool
@@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
}
if options.Transport != nil {
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
@@ -154,11 +156,10 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else if h.tlsDialer != nil {
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
common.Close(conn)
@@ -182,11 +183,10 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else if h.tlsDialer != nil {
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
return nil, err

View File

@@ -22,6 +22,8 @@ import (
"github.com/sagernet/sing/service"
)
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
func RegisterEndpoint(registry *endpoint.Registry) {
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
}
@@ -210,3 +212,11 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
return w.endpoint.ListenPacket(ctx, destination)
}
func (w *Endpoint) PreferredDomain(domain string) bool {
return false
}
func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
return w.endpoint.Lookup(address) != nil
}

View File

@@ -21,6 +21,8 @@ import (
"github.com/sagernet/sing/service"
)
var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
}
@@ -158,3 +160,11 @@ func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
return o.endpoint.ListenPacket(ctx, destination)
}
func (o *Outbound) PreferredDomain(domain string) bool {
return false
}
func (o *Outbound) PreferredAddress(address netip.Addr) bool {
return o.endpoint.Lookup(address) != nil
}

View File

@@ -19,6 +19,7 @@ import (
"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"
@@ -36,7 +37,7 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil)
type NetworkManager struct {
logger logger.ContextLogger
interfaceFinder *control.DefaultInterfaceFinder
networkInterfaces common.TypedValue[[]adapter.NetworkInterface]
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface]
autoDetectInterface bool
defaultOptions adapter.NetworkOptions
@@ -311,7 +312,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
if r.interfaceMonitor == nil {
return nil
}
return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
remoteAddr := M.ParseSocksaddr(address).Addr
if remoteAddr.IsValid() {
iif, err := r.interfaceFinder.ByAddr(remoteAddr)
@@ -325,6 +326,16 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
}
return defaultInterface.Name, defaultInterface.Index, nil
})
return func(network, address string, conn syscall.RawConn) error {
err := bindFunc(network, address, conn)
if err != nil {
return err
}
if r.autoRedirectOutputMark > 0 {
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
}
return nil
}
}
}
@@ -355,15 +366,6 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 {
return r.autoRedirectOutputMark
}
func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {
return func(network, address string, conn syscall.RawConn) error {
if r.autoRedirectOutputMark == 0 {
return nil
}
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
}
}
func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor {
return r.networkMonitor
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"net"
"net/netip"
"os"
"strings"
"time"
@@ -26,8 +27,6 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot"
"golang.org/x/exp/slices"
)
// Deprecated: use RouteConnectionEx instead.
@@ -346,16 +345,16 @@ func (r *Router) matchRule(
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
}, inputConn, inputPacketConn, nil, nil)
}, inputConn, inputPacketConn, nil)
if newErr != nil {
fatalErr = newErr
return
}
if newBuffer != nil {
buffers = []*buf.Buffer{newBuffer}
} else if len(newPackerBuffers) > 0 {
packetBuffers = newPackerBuffers
}
if newErr != nil {
fatalErr = newErr
return
}
}
if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS {
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
@@ -454,16 +453,16 @@ match:
switch action := currentRule.Action().(type) {
case *R.RuleActionSniff:
if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
if newErr != nil {
fatalErr = newErr
return
}
if newBuffer != nil {
buffers = append(buffers, newBuffer)
} else if len(newPacketBuffers) > 0 {
packetBuffers = append(packetBuffers, newPacketBuffers...)
}
if newErr != nil {
fatalErr = newErr
return
}
} else {
selectedRule = currentRule
selectedRuleIndex = currentRuleIndex
@@ -490,7 +489,7 @@ match:
func (r *Router) actionSniff(
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer,
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
if sniff.Skip(metadata) {
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
@@ -502,7 +501,7 @@ func (r *Router) actionSniff(
if inputConn != nil {
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
return
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return
}
@@ -529,7 +528,6 @@ func (r *Router) actionSniff(
action.Timeout,
streamSniffers...,
)
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err
if err == nil {
//goland:noinspection GoDeprecation
@@ -555,13 +553,10 @@ func (r *Router) actionSniff(
} else if inputPacketConn != nil {
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
return
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return
}
quicMoreData := func() bool {
return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData)
}
var packetSniffers []sniff.PacketSniffer
if len(action.PacketSniffers) > 0 {
packetSniffers = action.PacketSniffers
@@ -576,37 +571,12 @@ func (r *Router) actionSniff(
sniff.NTP,
}
}
var err error
for _, packetBuffer := range inputPacketBuffers {
if quicMoreData() {
err = sniff.PeekPacket(
ctx,
metadata,
packetBuffer.Buffer.Bytes(),
sniff.QUICClientHello,
)
} else {
err = sniff.PeekPacket(
ctx, metadata,
packetBuffer.Buffer.Bytes(),
packetSniffers...,
)
}
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue
}
goto finally
}
packetBuffers = inputPacketBuffers
for {
var (
sniffBuffer = buf.NewPacket()
destination M.Socksaddr
done = make(chan struct{})
err error
)
go func() {
sniffTimeout := C.ReadPayloadTimeout
@@ -627,12 +597,12 @@ func (r *Router) actionSniff(
}
if err != nil {
sniffBuffer.Release()
if !errors.Is(err, context.DeadlineExceeded) {
if !errors.Is(err, os.ErrDeadlineExceeded) {
fatalErr = err
return
}
} else {
if quicMoreData() {
if len(packetBuffers) > 0 || metadata.SniffError != nil {
err = sniff.PeekPacket(
ctx,
metadata,
@@ -652,34 +622,32 @@ func (r *Router) actionSniff(
Destination: destination,
}
packetBuffers = append(packetBuffers, packetBuffer)
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue
}
}
goto finally
}
finally:
if err == nil {
//goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain,
Port: metadata.Destination.Port,
if metadata.Protocol != "" {
//goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain,
Port: metadata.Destination.Port,
}
}
if metadata.Domain != "" && metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
} else if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else if metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
} else {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
}
}
if metadata.Domain != "" && metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
} else if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else if metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
} else {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
break
}
}
return
@@ -707,6 +675,11 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
}
metadata.DestinationAddresses = addresses
r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
if metadata.Destination.IsIPv4() {
metadata.IPVersion = 4
} else if metadata.Destination.IsIPv6() {
metadata.IPVersion = 6
}
}
return nil
}

View File

@@ -87,7 +87,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
return &RuleActionHijackDNS{}, nil
case C.RuleActionTypeSniff:
sniffAction := &RuleActionSniff{
SnifferNames: action.SniffOptions.Sniffer,
snifferNames: action.SniffOptions.Sniffer,
Timeout: time.Duration(action.SniffOptions.Timeout),
}
return sniffAction, sniffAction.build()
@@ -361,7 +361,7 @@ func (r *RuleActionHijackDNS) String() string {
}
type RuleActionSniff struct {
SnifferNames []string
snifferNames []string
StreamSniffers []sniff.StreamSniffer
PacketSniffers []sniff.PacketSniffer
Timeout time.Duration
@@ -374,7 +374,7 @@ func (r *RuleActionSniff) Type() string {
}
func (r *RuleActionSniff) build() error {
for _, name := range r.SnifferNames {
for _, name := range r.snifferNames {
switch name {
case C.ProtocolTLS:
r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)
@@ -407,14 +407,14 @@ func (r *RuleActionSniff) build() error {
}
func (r *RuleActionSniff) String() string {
if len(r.SnifferNames) == 0 && r.Timeout == 0 {
if len(r.snifferNames) == 0 && r.Timeout == 0 {
return "sniff"
} else if len(r.SnifferNames) > 0 && r.Timeout == 0 {
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ")")
} else if len(r.SnifferNames) == 0 && r.Timeout > 0 {
} else if len(r.snifferNames) > 0 && r.Timeout == 0 {
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")")
} else if len(r.snifferNames) == 0 && r.Timeout > 0 {
return F.ToString("sniff(", r.Timeout.String(), ")")
} else {
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ",", r.Timeout.String(), ")")
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")")
}
}

View File

@@ -117,7 +117,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
if len(options.DomainRegex) > 0 {
item, err := NewDomainRegexItem(options.DomainRegex)
if err != nil {
return nil, E.Cause(err, "domain_regex")
return nil, err
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
@@ -246,6 +246,26 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {
item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DefaultInterfaceAddress) > 0 {
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.PreferredBy) > 0 {
item := NewPreferredByItem(ctx, options.PreferredBy)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.RuleSet) > 0 {
var matchSource bool
if options.RuleSetIPCIDRMatchSource {

View File

@@ -0,0 +1,56 @@
package rule
import (
"net/netip"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/json/badoption"
)
var _ RuleItem = (*DefaultInterfaceAddressItem)(nil)
type DefaultInterfaceAddressItem struct {
interfaceMonitor tun.DefaultInterfaceMonitor
interfaceAddresses []netip.Prefix
}
func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[badoption.Prefixable]) *DefaultInterfaceAddressItem {
item := &DefaultInterfaceAddressItem{
interfaceMonitor: networkManager.InterfaceMonitor(),
interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)),
}
for _, prefixable := range interfaceAddresses {
item.interfaceAddresses = append(item.interfaceAddresses, prefixable.Build(netip.Prefix{}))
}
return item
}
func (r *DefaultInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
defaultInterface := r.interfaceMonitor.DefaultInterface()
if defaultInterface == nil {
return false
}
for _, address := range r.interfaceAddresses {
if common.All(defaultInterface.Addresses, func(it netip.Prefix) bool {
return !address.Overlaps(it)
}) {
return false
}
}
return true
}
func (r *DefaultInterfaceAddressItem) String() string {
addressLen := len(r.interfaceAddresses)
switch {
case addressLen == 1:
return "default_interface_address=" + r.interfaceAddresses[0].String()
case addressLen > 3:
return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses[:3], netip.Prefix.String), " ") + "...]"
default:
return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses, netip.Prefix.String), " ") + "]"
}
}

View File

@@ -247,6 +247,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {
item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DefaultInterfaceAddress) > 0 {
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.RuleSet) > 0 {
var matchSource bool
if options.RuleSetIPCIDRMatchSource {

View File

@@ -164,13 +164,21 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
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 options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DefaultInterfaceAddress) > 0 {
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
}
if len(options.AdGuardDomain) > 0 {

View File

@@ -0,0 +1,62 @@
package rule
import (
"net/netip"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
)
var _ RuleItem = (*InterfaceAddressItem)(nil)
type InterfaceAddressItem struct {
networkManager adapter.NetworkManager
interfaceAddresses map[string][]netip.Prefix
description string
}
func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]]) *InterfaceAddressItem {
item := &InterfaceAddressItem{
networkManager: networkManager,
interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()),
}
var entryDescriptions []string
for _, entry := range interfaceAddresses.Entries() {
prefixes := make([]netip.Prefix, 0, len(entry.Value))
for _, prefixable := range entry.Value {
prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
}
item.interfaceAddresses[entry.Key] = prefixes
entryDescriptions = append(entryDescriptions, entry.Key+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
}
item.description = "interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
return item
}
func (r *InterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
interfaces := r.networkManager.InterfaceFinder().Interfaces()
for ifName, addresses := range r.interfaceAddresses {
iface := common.Find(interfaces, func(it control.Interface) bool {
return it.Name == ifName
})
if iface.Name == "" {
return false
}
if common.All(addresses, func(address netip.Prefix) bool {
return common.All(iface.Addresses, func(it netip.Prefix) bool {
return !address.Overlaps(it)
})
}) {
return false
}
}
return true
}
func (r *InterfaceAddressItem) String() string {
return r.description
}

View File

@@ -0,0 +1,86 @@
package rule
import (
"context"
"strings"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/service"
)
var _ RuleItem = (*PreferredByItem)(nil)
type PreferredByItem struct {
ctx context.Context
outboundTags []string
outbounds []adapter.OutboundWithPreferredRoutes
}
func NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem {
return &PreferredByItem{
ctx: ctx,
outboundTags: outboundTags,
}
}
func (r *PreferredByItem) Start() error {
outboundManager := service.FromContext[adapter.OutboundManager](r.ctx)
for _, outboundTag := range r.outboundTags {
rawOutbound, loaded := outboundManager.Outbound(outboundTag)
if !loaded {
return E.New("outbound not found: ", outboundTag)
}
outboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes)
if !withRoutes {
return E.New("outbound type does not support preferred routes: ", rawOutbound.Type())
}
r.outbounds = append(r.outbounds, outboundWithPreferredRoutes)
}
return nil
}
func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {
var domainHost string
if metadata.Domain != "" {
domainHost = metadata.Domain
} else {
domainHost = metadata.Destination.Fqdn
}
if domainHost != "" {
for _, outbound := range r.outbounds {
if outbound.PreferredDomain(domainHost) {
return true
}
}
}
if metadata.Destination.IsIP() {
for _, outbound := range r.outbounds {
if outbound.PreferredAddress(metadata.Destination.Addr) {
return true
}
}
}
if len(metadata.DestinationAddresses) > 0 {
for _, address := range metadata.DestinationAddresses {
for _, outbound := range r.outbounds {
if outbound.PreferredAddress(address) {
return true
}
}
}
}
return false
}
func (r *PreferredByItem) String() string {
description := "preferred_by="
pLen := len(r.outboundTags)
if pLen == 1 {
description += F.ToString(r.outboundTags[0])
} else {
description += "[" + strings.Join(F.MapToString(r.outboundTags), " ") + "]"
}
return description
}

View File

@@ -0,0 +1,64 @@
package rule
import (
"net/netip"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
)
var _ RuleItem = (*NetworkInterfaceAddressItem)(nil)
type NetworkInterfaceAddressItem struct {
networkManager adapter.NetworkManager
interfaceAddresses map[C.InterfaceType][]netip.Prefix
description string
}
func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) *NetworkInterfaceAddressItem {
item := &NetworkInterfaceAddressItem{
networkManager: networkManager,
interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()),
}
var entryDescriptions []string
for _, entry := range interfaceAddresses.Entries() {
prefixes := make([]netip.Prefix, 0, len(entry.Value))
for _, prefixable := range entry.Value {
prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
}
item.interfaceAddresses[entry.Key.Build()] = prefixes
entryDescriptions = append(entryDescriptions, entry.Key.Build().String()+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
}
item.description = "network_interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
return item
}
func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
interfaces := r.networkManager.NetworkInterfaces()
match:
for ifType, addresses := range r.interfaceAddresses {
for _, networkInterface := range interfaces {
if networkInterface.Type != ifType {
continue
}
if common.Any(networkInterface.Addresses, func(it netip.Prefix) bool {
return common.Any(addresses, func(prefix netip.Prefix) bool {
return prefix.Overlaps(it)
})
}) {
continue match
}
}
return false
}
return true
}
func (r *NetworkInterfaceAddressItem) String() string {
return r.description
}

View File

@@ -42,7 +42,7 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet {
}
}
func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
func HasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
for _, rule := range rules {
switch rule.Type {
case C.RuleTypeDefault:
@@ -50,7 +50,7 @@ func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultH
return true
}
case C.RuleTypeLogical:
if hasHeadlessRule(rule.LogicalOptions.Rules, cond) {
if HasHeadlessRule(rule.LogicalOptions.Rules, cond) {
return true
}
}

View File

@@ -6,7 +6,6 @@ import (
"path/filepath"
"strings"
"sync"
"sync/atomic"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -14,6 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
@@ -27,16 +27,16 @@ import (
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
type LocalRuleSet struct {
ctx context.Context
logger logger.Logger
tag string
access sync.RWMutex
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
ctx context.Context
logger logger.Logger
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
callbackAccess sync.Mutex
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
}
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
@@ -138,14 +138,14 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
}
}
var metadata adapter.RuleSetMetadata
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
s.access.Lock()
metadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule)
metadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
s.rules = rules
s.metadata = metadata
s.callbackAccess.Lock()
callbacks := s.callbacks.Array()
s.access.Unlock()
s.callbackAccess.Unlock()
for _, callback := range callbacks {
callback(s)
}
@@ -157,14 +157,10 @@ func (s *LocalRuleSet) PostStart() error {
}
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
s.access.RLock()
defer s.access.RUnlock()
return s.metadata
}
func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {
s.access.RLock()
defer s.access.RUnlock()
return common.FlatMap(s.rules, extractIPSetFromRule)
}
@@ -185,14 +181,14 @@ func (s *LocalRuleSet) Cleanup() {
}
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
s.access.Lock()
defer s.access.Unlock()
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
return s.callbacks.PushBack(callback)
}
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
s.access.Lock()
defer s.access.Unlock()
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
s.callbacks.Remove(element)
}

View File

@@ -10,7 +10,6 @@ import (
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -18,6 +17,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
@@ -40,16 +40,16 @@ type RemoteRuleSet struct {
logger logger.ContextLogger
outbound adapter.OutboundManager
options option.RuleSet
metadata adapter.RuleSetMetadata
updateInterval time.Duration
dialer N.Dialer
access sync.RWMutex
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
lastUpdated time.Time
lastEtag string
updateTicker *time.Ticker
cacheFile adapter.CacheFile
pauseManager pause.Manager
callbackAccess sync.Mutex
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
}
@@ -120,14 +120,10 @@ func (s *RemoteRuleSet) PostStart() error {
}
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
s.access.RLock()
defer s.access.RUnlock()
return s.metadata
}
func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {
s.access.RLock()
defer s.access.RUnlock()
return common.FlatMap(s.rules, extractIPSetFromRule)
}
@@ -148,14 +144,14 @@ func (s *RemoteRuleSet) Cleanup() {
}
func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
s.access.Lock()
defer s.access.Unlock()
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
return s.callbacks.PushBack(callback)
}
func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
s.access.Lock()
defer s.access.Unlock()
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
s.callbacks.Remove(element)
}
@@ -189,13 +185,13 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
}
s.access.Lock()
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
s.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
s.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
s.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
s.rules = rules
s.callbackAccess.Lock()
callbacks := s.callbacks.Array()
s.access.Unlock()
s.callbackAccess.Unlock()
for _, callback := range callbacks {
callback(s)
}

View File

@@ -182,9 +182,9 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context
} else if metadata.ProcessInfo.UserId != 0 {
prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
}
t.logger.InfoContext(ctx, "(", prefix, ") ", strings.Join(F.MapToString(message), " "))
t.logger.InfoContext(ctx, "(", prefix, ") ", F.ToString(message...))
} else {
t.logger.InfoContext(ctx, strings.Join(F.MapToString(message), " "))
t.logger.InfoContext(ctx, F.ToString(message...))
}
return adapter.WithContext(ctx, &metadata)
}
@@ -280,10 +280,7 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
},
}
ctx := t.logRequest(sender, "ResolveAddress ", link.iif.Name, familyToString(family), addr, flags)
var metadata adapter.InboundContext
metadata.InboundType = t.Type()
metadata.Inbound = t.Tag()
response, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
response, lookupErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{})
if lookupErr != nil {
err = wrapError(err)
return
@@ -304,7 +301,7 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
return
}
func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, family int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
t.linkAccess.Lock()
link, err := t.getLink(ifIndex)
if err != nil {
@@ -323,11 +320,8 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostn
},
},
}
ctx := t.logRequest(sender, "ResolveRecord", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
var metadata adapter.InboundContext
metadata.InboundType = t.Type()
metadata.Inbound = t.Tag()
response, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
ctx := t.logRequest(sender, "ResolveRecord ", link.iif.Name, familyToString(family), hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
response, exchangeErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{})
if exchangeErr != nil {
err = wrapError(exchangeErr)
return
@@ -347,7 +341,6 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostn
err = wrapError(unpackErr)
}
record.Data = data
records = append(records, record)
}
return
}
@@ -387,10 +380,8 @@ func (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, host
},
},
}
var metadata adapter.InboundContext
metadata.InboundType = t.Type()
metadata.Inbound = t.Tag()
srvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{})
srvResponse, exchangeErr := t.dnsRouter.Exchange(ctx, srvRequest, adapter.DNSQueryOptions{})
if exchangeErr != nil {
err = wrapError(exchangeErr)
return

View File

@@ -91,6 +91,11 @@ func (i *Service) Start(stage adapter.StartStage) error {
return E.New("multiple resolved service are not supported")
}
}
case adapter.StartStateStart:
err := i.listener.Start()
if err != nil {
return err
}
systemBus, err := dbus.SystemBus()
if err != nil {
return err
@@ -112,11 +117,6 @@ func (i *Service) Start(stage adapter.StartStage) error {
return E.New("unknown request name reply: ", reply)
}
i.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate)
case adapter.StartStateStart:
err := i.listener.Start()
if err != nil {
return err
}
}
return nil
}
@@ -167,8 +167,6 @@ func (i *Service) exchangePacket0(ctx context.Context, buffer *buf.Buffer, oob [
}
var metadata adapter.InboundContext
metadata.Source = source
metadata.InboundType = i.Type()
metadata.Inbound = i.Tag()
response, err := i.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{})
if err != nil {
return err

View File

@@ -5,8 +5,8 @@ import (
"os"
"path/filepath"
"sort"
"sync/atomic"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/service/filemanager"

View File

@@ -3,9 +3,9 @@ package ssmapi
import (
"net"
"sync"
"sync/atomic"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
)

View File

@@ -292,7 +292,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {
}
_, err = buffer.ReadFullFrom(conn, int(length))
return destination, err
return destination.Unwrap(), err
}
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {

View File

@@ -29,7 +29,6 @@ var defaultClientHeader = http.Header{
type Client struct {
ctx context.Context
dialer N.Dialer
serverAddr M.Socksaddr
transport *http2.Transport
options option.V2RayGRPCOptions
@@ -46,7 +45,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
}
client := &Client{
ctx: ctx,
dialer: dialer,
serverAddr: serverAddr,
options: options,
transport: &http2.Transport{
@@ -62,7 +60,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
},
host: host,
}
if tlsConfig == nil {
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
@@ -71,12 +68,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
}
tlsDialer := tls.NewDialer(dialer, tlsConfig)
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
if err != nil {
return nil, err
}
return tls.ClientHandshake(ctx, conn, tlsConfig)
return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))
}
}

View File

@@ -47,15 +47,12 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
}
tlsDialer := tls.NewDialer(dialer, tlsConfig)
transport = &http2.Transport{
ReadIdleTimeout: time.Duration(options.IdleTimeout),
PingTimeout: time.Duration(options.PingTimeout),
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
if err != nil {
return nil, err
}
return tls.ClientHandshake(ctx, conn, tlsConfig)
return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))
},
}
}

View File

@@ -23,7 +23,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct {
dialer N.Dialer
tlsConfig tls.Config
serverAddr M.Socksaddr
requestURL url.URL
headers http.Header
@@ -35,6 +34,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"})
}
dialer = tls.NewDialer(dialer, tlsConfig)
}
var host string
if options.Host != "" {
@@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
}
return &Client{
dialer: dialer,
tlsConfig: tlsConfig,
serverAddr: serverAddr,
requestURL: requestURL,
headers: headers,
@@ -78,12 +77,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if err != nil {
return nil, err
}
if c.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
if err != nil {
return nil, err
}
}
request := &http.Request{
Method: http.MethodGet,
URL: &c.requestURL,

View File

@@ -15,6 +15,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -29,7 +30,7 @@ type Client struct {
tlsConfig tls.Config
quicConfig *quic.Config
connAccess sync.Mutex
conn common.TypedValue[quic.Connection]
conn atomic.TypedValue[quic.Connection]
rawConn net.Conn
}

View File

@@ -26,7 +26,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct {
dialer N.Dialer
tlsConfig tls.Config
serverAddr M.Socksaddr
requestURL url.URL
headers http.Header
@@ -39,6 +38,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"})
}
dialer = tls.NewDialer(dialer, tlsConfig)
}
var requestURL url.URL
if tlsConfig == nil {
@@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
}
return &Client{
dialer,
tlsConfig,
serverAddr,
requestURL,
headers,
@@ -79,12 +78,6 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h
if err != nil {
return nil, err
}
if c.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
if err != nil {
return nil, err
}
}
var deadlineConn net.Conn
if deadline.NeedAdditionalReadDeadline(conn) {
deadlineConn = deadline.NewConn(conn)

Some files were not shown because too many files have changed in this diff Show More