Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae852e0be4 | ||
|
|
1955002ed8 | ||
|
|
44559fb7b9 | ||
|
|
0977c5cf73 | ||
|
|
07697bf931 | ||
|
|
5d1d1a1456 | ||
|
|
146383499e | ||
|
|
e81a76fdf9 | ||
|
|
de13137418 | ||
|
|
e42b818c2a | ||
|
|
fcde0c94e0 | ||
|
|
1af83e997d | ||
|
|
59ee7be72a | ||
|
|
c331ee3d5c | ||
|
|
36babe4bef | ||
|
|
c5f2cea802 | ||
|
|
8a200bf913 | ||
|
|
f16468e74f | ||
|
|
79c0b9f51d | ||
|
|
f98a3a4f65 | ||
|
|
b14cecaeb2 | ||
|
|
2594745ef8 | ||
|
|
cc3041322e | ||
|
|
f352f84483 | ||
|
|
cbf48e9b8c | ||
|
|
0ef7e8eca2 | ||
|
|
1a18e43a88 | ||
|
|
6849288d6d | ||
|
|
2edfed7d91 | ||
|
|
30c069f5b7 | ||
|
|
649163cb7b | ||
|
|
980e96250b | ||
|
|
963bc4b647 | ||
|
|
031f25c1c1 | ||
|
|
b40f642fa4 | ||
|
|
22782ca6fc | ||
|
|
1468d83895 |
21
.github/workflows/build.yml
vendored
21
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- name: Setup Go 1.24
|
- name: Setup Go 1.24
|
||||||
if: matrix.legacy_go124
|
if: matrix.legacy_go124
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
@@ -300,7 +300,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -380,7 +380,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -432,7 +432,8 @@ jobs:
|
|||||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-15
|
runs-on: macos-26
|
||||||
|
if: false
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
@@ -478,15 +479,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- 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
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
|||||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.25.1
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -17,6 +17,10 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
|
race:
|
||||||
|
export GOTOOLCHAIN=local && \
|
||||||
|
go build -race $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(PARAMS) $(MAIN) && \
|
go build $(PARAMS) $(MAIN) && \
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
|
SnifferNames []string
|
||||||
SniffError error
|
SniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
if stage == adapter.StartStateStart {
|
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 {
|
if m.defaultOutbound == nil {
|
||||||
directOutbound, err := m.defaultOutboundFallback()
|
directOutbound, err := m.defaultOutboundFallback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,14 +74,6 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
||||||
m.defaultOutbound = directOutbound
|
m.defaultOutbound = directOutbound
|
||||||
}
|
}
|
||||||
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
|
|
||||||
}
|
|
||||||
outbounds := m.outbounds
|
outbounds := m.outbounds
|
||||||
m.access.Unlock()
|
m.access.Unlock()
|
||||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
|||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
return M.Metadata{
|
return M.Metadata{
|
||||||
Source: metadata.Source,
|
Source: metadata.Source.Unwrap(),
|
||||||
Destination: metadata.Destination,
|
Destination: metadata.Destination.Unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Submodule clients/android updated: 597c18482f...9fe53a952e
Submodule clients/apple updated: c5734677bd...e0e928b8d9
@@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -21,6 +22,7 @@ import (
|
|||||||
var _ adapter.CertificateStore = (*Store)(nil)
|
var _ adapter.CertificateStore = (*Store)(nil)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
|
access sync.RWMutex
|
||||||
systemPool *x509.CertPool
|
systemPool *x509.CertPool
|
||||||
currentPool *x509.CertPool
|
currentPool *x509.CertPool
|
||||||
certificate string
|
certificate string
|
||||||
@@ -115,10 +117,14 @@ func (s *Store) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Pool() *x509.CertPool {
|
func (s *Store) Pool() *x509.CertPool {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
return s.currentPool
|
return s.currentPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) update() error {
|
func (s *Store) update() error {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
var currentPool *x509.CertPool
|
var currentPool *x509.CertPool
|
||||||
if s.systemPool == nil {
|
if s.systemPool == nil {
|
||||||
currentPool = x509.NewCertPool()
|
currentPool = x509.NewCertPool()
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -43,7 +42,7 @@ type DefaultDialer struct {
|
|||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback atomic.TypedValue[time.Time]
|
networkLastFallback common.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
@@ -89,43 +88,41 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if !disableDefaultBind {
|
if defaultOptions.BindInterface != "" {
|
||||||
if defaultOptions.BindInterface != "" {
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
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()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.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 {
|
|
||||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
|
||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
markFunc := networkManager.AutoRedirectOutputMarkFunc()
|
markFunc := networkManager.AutoRedirectOutputMarkFunc()
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package listener
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.tproxy {
|
if l.tproxy {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
|
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -41,7 +42,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
if l.tproxy {
|
if l.tproxy {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
|
return redir.TProxy(fd, !strings.HasSuffix(network, "4"), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientQUICGo)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.Equal(t, metadata.Domain, "www.google.com")
|
require.Equal(t, metadata.Domain, "www.google.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,11 +69,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
} else {
|
} else {
|
||||||
return E.New("missing ECH keys")
|
return E.New("missing ECH keys")
|
||||||
}
|
}
|
||||||
block, rest := pem.Decode(echKey)
|
echKeys, err := parseECHKeys(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 {
|
if err != nil {
|
||||||
return E.Cause(err, "parse ECH keys")
|
return E.Cause(err, "parse ECH keys")
|
||||||
}
|
}
|
||||||
@@ -85,21 +81,29 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
||||||
echKey, err := os.ReadFile(echKeyPath)
|
echKeys, err := parseECHKeys(echKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "reload ECH keys from ", echKeyPath)
|
return err
|
||||||
}
|
}
|
||||||
|
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)
|
block, _ := pem.Decode(echKey)
|
||||||
if block == nil || block.Type != "ECH KEYS" {
|
if block == nil || block.Type != "ECH KEYS" {
|
||||||
return E.New("invalid ECH keys pem")
|
return nil, E.New("invalid ECH keys pem")
|
||||||
}
|
}
|
||||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse ECH keys")
|
return nil, E.Cause(err, "parse ECH keys")
|
||||||
}
|
}
|
||||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
return echKeys, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type ECHClientConfig struct {
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
||||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,3 +307,11 @@ func (c *realityClientConnWrapper) Upstream() any {
|
|||||||
func (c *realityClientConnWrapper) CloseWrite() error {
|
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||||
return c.Close()
|
return c.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
@@ -20,6 +21,7 @@ import (
|
|||||||
var errInsecureUnused = E.New("tls: insecure unused")
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
|
|
||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
|
access sync.RWMutex
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.SimpleLifecycle
|
||||||
@@ -32,14 +34,22 @@ type STDServerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) ServerName() string {
|
func (c *STDServerConfig) ServerName() string {
|
||||||
|
c.access.RLock()
|
||||||
|
defer c.access.RUnlock()
|
||||||
return c.config.ServerName
|
return c.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetServerName(serverName string) {
|
func (c *STDServerConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
config := c.config.Clone()
|
||||||
|
config.ServerName = serverName
|
||||||
|
c.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) NextProtos() []string {
|
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 {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
return c.config.NextProtos[1:]
|
return c.config.NextProtos[1:]
|
||||||
} else {
|
} else {
|
||||||
@@ -48,11 +58,15 @@ func (c *STDServerConfig) NextProtos() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetNextProtos(nextProto []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 {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||||
} else {
|
} else {
|
||||||
c.config.NextProtos = nextProto
|
config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
c.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
||||||
@@ -77,9 +91,6 @@ func (c *STDServerConfig) Start() error {
|
|||||||
if c.acmeService != nil {
|
if c.acmeService != nil {
|
||||||
return c.acmeService.Start()
|
return c.acmeService.Start()
|
||||||
} else {
|
} else {
|
||||||
if c.certificatePath == "" && c.keyPath == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := c.startWatcher()
|
err := c.startWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
@@ -99,6 +110,9 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
if c.echKeyPath != "" {
|
if c.echKeyPath != "" {
|
||||||
watchPath = append(watchPath, c.echKeyPath)
|
watchPath = append(watchPath, c.echKeyPath)
|
||||||
}
|
}
|
||||||
|
if len(watchPath) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||||
Path: watchPath,
|
Path: watchPath,
|
||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
@@ -138,10 +152,18 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "reload key pair")
|
return E.Cause(err, "reload key pair")
|
||||||
}
|
}
|
||||||
c.config.Certificates = []tls.Certificate{keyPair}
|
c.access.Lock()
|
||||||
|
config := c.config.Clone()
|
||||||
|
config.Certificates = []tls.Certificate{keyPair}
|
||||||
|
c.config = config
|
||||||
|
c.access.Unlock()
|
||||||
c.logger.Info("reloaded TLS certificate")
|
c.logger.Info("reloaded TLS certificate")
|
||||||
} else if path == c.echKeyPath {
|
} else if path == c.echKeyPath {
|
||||||
err := reloadECHKeys(c.echKeyPath, c.config)
|
echKey, err := os.ReadFile(c.echKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload ECH keys from ", c.echKeyPath)
|
||||||
|
}
|
||||||
|
err = c.setECHServerConfig(echKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -262,7 +284,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &STDServerConfig{
|
serverConfig := &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
@@ -271,5 +293,11 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
certificatePath: options.CertificatePath,
|
certificatePath: options.CertificatePath,
|
||||||
keyPath: options.KeyPath,
|
keyPath: options.KeyPath,
|
||||||
echKeyPath: echKeyPath,
|
echKeyPath: echKeyPath,
|
||||||
}, nil
|
}
|
||||||
|
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
|
serverConfig.access.Lock()
|
||||||
|
defer serverConfig.access.Unlock()
|
||||||
|
return serverConfig.config, nil
|
||||||
|
}
|
||||||
|
return serverConfig, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,15 +47,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory
|
|||||||
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
delete(s.delayHistory, tag)
|
delete(s.delayHistory, tag)
|
||||||
s.access.Unlock()
|
|
||||||
s.notifyUpdated()
|
s.notifyUpdated()
|
||||||
|
s.access.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
s.delayHistory[tag] = history
|
s.delayHistory[tag] = history
|
||||||
s.access.Unlock()
|
|
||||||
s.notifyUpdated()
|
s.notifyUpdated()
|
||||||
|
s.access.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) notifyUpdated() {
|
func (s *HistoryStorage) notifyUpdated() {
|
||||||
@@ -69,6 +69,8 @@ func (s *HistoryStorage) notifyUpdated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) Close() error {
|
func (s *HistoryStorage) Close() error {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
s.updateHook = nil
|
s.updateHook = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
149
dns/client.go
149
dns/client.go
@@ -2,12 +2,14 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/compatible"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -17,7 +19,7 @@ import (
|
|||||||
"github.com/sagernet/sing/contrab/freelru"
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
|
|
||||||
dns "github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -30,16 +32,18 @@ var (
|
|||||||
var _ adapter.DNSClient = (*Client)(nil)
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
clientSubnet netip.Prefix
|
clientSubnet netip.Prefix
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||||
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
cacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
|
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||||
|
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
@@ -96,17 +100,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
}
|
}
|
||||||
responseMessage := dns.Msg{
|
return FixedResponseStatus(message, dns.RcodeFormatError), nil
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Response: true,
|
|
||||||
Rcode: dns.RcodeFormatError,
|
|
||||||
},
|
|
||||||
Question: message.Question,
|
|
||||||
}
|
|
||||||
return &responseMessage, nil
|
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
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
|
clientSubnet := options.ClientSubnet
|
||||||
if !clientSubnet.IsValid() {
|
if !clientSubnet.IsValid() {
|
||||||
clientSubnet = c.clientSubnet
|
clientSubnet = c.clientSubnet
|
||||||
@@ -114,12 +116,38 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if clientSubnet.IsValid() {
|
if clientSubnet.IsValid() {
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
len(message.Extra) == 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) &&
|
||||||
!options.ClientSubnet.IsValid()
|
!options.ClientSubnet.IsValid()
|
||||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
if !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)
|
response, ttl := c.loadResponse(question, transport)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
logCachedResponse(c.logger, ctx, response, ttl)
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
@@ -127,27 +155,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, nil
|
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
|
messageId := message.Id
|
||||||
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
||||||
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
||||||
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
||||||
}
|
}
|
||||||
ctx = contextWithTransportTag(ctx, transport.Tag())
|
ctx = contextWithTransportTag(ctx, transport.Tag())
|
||||||
if responseChecker != nil && c.rdrc != nil {
|
if !disableCache && responseChecker != nil && c.rdrc != nil {
|
||||||
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||||
if rejected {
|
if rejected {
|
||||||
return nil, ErrResponseRejectedCached
|
return nil, ErrResponseRejectedCached
|
||||||
@@ -157,7 +172,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
response, err := transport.Exchange(ctx, message)
|
response, err := transport.Exchange(ctx, message)
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
var rcodeError RcodeError
|
||||||
|
if errors.As(err, &rcodeError) {
|
||||||
|
response = FixedResponseStatus(message, int(rcodeError))
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||||
validResponse := response
|
validResponse := response
|
||||||
@@ -194,15 +214,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
response.Answer = append(response.Answer, validResponse.Answer...)
|
response.Answer = append(response.Answer, validResponse.Answer...)
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
disableCache = disableCache || response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
|
// TODO: add accept_any rule and support to check response instead of addresses
|
||||||
|
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
|
||||||
rejected = true
|
rejected = true
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(MessageToAddresses(response))
|
rejected = !responseChecker(MessageToAddresses(response))
|
||||||
}
|
}
|
||||||
if rejected {
|
if rejected {
|
||||||
if c.rdrc != nil {
|
if !disableCache && c.rdrc != nil {
|
||||||
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
||||||
}
|
}
|
||||||
logRejectedResponse(c.logger, ctx, response)
|
logRejectedResponse(c.logger, ctx, response)
|
||||||
@@ -259,7 +281,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||||
return response, err
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
@@ -305,8 +327,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
if c.cache != nil {
|
if c.cache != nil {
|
||||||
c.cache.Purge()
|
c.cache.Purge()
|
||||||
}
|
} else if c.transportCache != nil {
|
||||||
if c.transportCache != nil {
|
|
||||||
c.transportCache.Purge()
|
c.transportCache.Purge()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,36 +341,36 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
|
|||||||
}
|
}
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
if strategy == C.DomainStrategyIPv4Only {
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
response, err := c.questionCache(dns.Question{
|
addresses, err := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != ErrNotCached {
|
if err != ErrNotCached {
|
||||||
return response, true
|
return addresses, true
|
||||||
}
|
}
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
response, err := c.questionCache(dns.Question{
|
addresses, err := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeAAAA,
|
Qtype: dns.TypeAAAA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != ErrNotCached {
|
if err != ErrNotCached {
|
||||||
return response, true
|
return addresses, true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response4, _ := c.questionCache(dns.Question{
|
response4, _ := c.loadResponse(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
response6, _ := c.questionCache(dns.Question{
|
response6, _ := c.loadResponse(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeAAAA,
|
Qtype: dns.TypeAAAA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if len(response4) > 0 || len(response6) > 0 {
|
if response4 != nil || response6 != nil {
|
||||||
return sortAddresses(response4, response6, strategy), true
|
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -390,15 +411,15 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
transportTag: transport.Tag(),
|
transportTag: transport.Tag(),
|
||||||
}, message)
|
}, message)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
if !c.independentCache {
|
|
||||||
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
|
||||||
} else {
|
} else {
|
||||||
c.transportCache.AddWithLifetime(transportCacheKey{
|
if !c.independentCache {
|
||||||
Question: question,
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
transportTag: transport.Tag(),
|
} else {
|
||||||
}, message, time.Second*time.Duration(timeToLive))
|
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, message, time.Second*time.Duration(timeToLive))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,6 +538,9 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||||
|
if response == nil || response.Rcode != dns.RcodeSuccess {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
for _, rawAnswer := range response.Answer {
|
for _, rawAnswer := range response.Answer {
|
||||||
switch answer := rawAnswer.(type) {
|
switch answer := rawAnswer.(type) {
|
||||||
@@ -561,9 +585,12 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
|||||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||||
return &dns.Msg{
|
return &dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: message.Id,
|
Id: message.Id,
|
||||||
Rcode: rcode,
|
Response: true,
|
||||||
Response: true,
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: rcode,
|
||||||
},
|
},
|
||||||
Question: message.Question,
|
Question: message.Question,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
||||||
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
||||||
RcodeNameError RcodeError = mDNS.RcodeNameError
|
RcodeNameError RcodeError = mDNS.RcodeNameError
|
||||||
RcodeRefused RcodeError = mDNS.RcodeRefused
|
RcodeRefused RcodeError = mDNS.RcodeRefused
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ package dhcp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@@ -29,6 +30,7 @@ import (
|
|||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
@@ -45,9 +47,12 @@ type Transport struct {
|
|||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
interfaceName string
|
interfaceName string
|
||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
transports []adapter.DNSTransport
|
transportLock sync.RWMutex
|
||||||
updateAccess sync.Mutex
|
|
||||||
updatedAt time.Time
|
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) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@@ -62,27 +67,40 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
interfaceName: options.Interface,
|
interfaceName: options.Interface,
|
||||||
|
ndots: 1,
|
||||||
|
attempts: 2,
|
||||||
}, nil
|
}, 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 {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
if stage != adapter.StartStateStart {
|
if stage != adapter.StartStateStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := t.fetchServers()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t.interfaceName == "" {
|
if t.interfaceName == "" {
|
||||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
for _, transport := range t.transports {
|
|
||||||
transport.Close()
|
|
||||||
}
|
|
||||||
if t.interfaceCallback != nil {
|
if t.interfaceCallback != nil {
|
||||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
}
|
}
|
||||||
@@ -90,23 +108,44 @@ func (t *Transport) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
err := t.fetchServers()
|
servers, err := t.Fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(servers) == 0 {
|
||||||
if len(t.transports) == 0 {
|
|
||||||
return nil, E.New("dhcp: empty DNS servers from response")
|
return nil, E.New("dhcp: empty DNS servers from response")
|
||||||
}
|
}
|
||||||
|
return t.Exchange0(ctx, message, servers)
|
||||||
|
}
|
||||||
|
|
||||||
var response *mDNS.Msg
|
func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) {
|
||||||
for _, transport := range t.transports {
|
question := message.Question[0]
|
||||||
response, err = transport.Exchange(ctx, message)
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
if err == nil {
|
if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
return response, nil
|
return t.exchangeSingleRequest(ctx, servers, message, domain)
|
||||||
}
|
} else {
|
||||||
|
return t.exchangeParallel(ctx, servers, message, domain)
|
||||||
}
|
}
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
||||||
@@ -124,18 +163,6 @@ 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 {
|
func (t *Transport) updateServers() error {
|
||||||
iface, err := t.fetchInterface()
|
iface, err := t.fetchInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -148,7 +175,7 @@ func (t *Transport) updateServers() error {
|
|||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(t.transports) == 0 {
|
} else if len(t.servers) == 0 {
|
||||||
return E.New("dhcp: empty DNS servers response")
|
return E.New("dhcp: empty DNS servers response")
|
||||||
} else {
|
} else {
|
||||||
t.updatedAt = time.Now()
|
t.updatedAt = time.Now()
|
||||||
@@ -171,13 +198,27 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
|
|||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||||
listenAddr = "255.255.255.255:68"
|
listenAddr = "255.255.255.255:68"
|
||||||
}
|
}
|
||||||
packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr)
|
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)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer packetConn.Close()
|
defer packetConn.Close()
|
||||||
|
|
||||||
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
|
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(
|
||||||
|
dhcpv4.OptionDomainName,
|
||||||
|
dhcpv4.OptionDomainNameServer,
|
||||||
|
dhcpv4.OptionDNSDomainSearchList,
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -204,6 +245,9 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
for {
|
for {
|
||||||
_, _, err := buffer.ReadPacketFrom(packetConn)
|
_, _, err := buffer.ReadPacketFrom(packetConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, io.ErrShortBuffer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,31 +267,23 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dns := dhcpPacket.DNS()
|
return t.recreateServers(iface, dhcpPacket)
|
||||||
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, serverAddrs []M.Socksaddr) error {
|
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
|
||||||
if len(serverAddrs) > 0 {
|
searchList := dhcpPacket.DomainSearch()
|
||||||
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]")
|
if searchList != nil && len(searchList.Labels) > 0 {
|
||||||
|
t.search = searchList.Labels
|
||||||
|
} else if dhcpPacket.DomainName() != "" {
|
||||||
|
t.search = []string{dhcpPacket.DomainName()}
|
||||||
}
|
}
|
||||||
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
||||||
BindInterface: iface.Name,
|
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||||
UDPFragmentDefault: true,
|
})
|
||||||
}))
|
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
||||||
var transports []adapter.DNSTransport
|
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
||||||
for _, serverAddr := range serverAddrs {
|
|
||||||
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
|
||||||
}
|
}
|
||||||
for _, transport := range t.transports {
|
t.servers = serverAddrs
|
||||||
transport.Close()
|
|
||||||
}
|
|
||||||
t.transports = transports
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
213
dns/transport/dhcp/dhcp_shared.go
Normal file
213
dns/transport/dhcp/dhcp_shared.go
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -178,7 +177,7 @@ func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS
|
|||||||
startAt := time.Now()
|
startAt := time.Now()
|
||||||
response, err := t.exchange(ctx, message)
|
response, err := t.exchange(ctx, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
t.transportAccess.Lock()
|
t.transportAccess.Lock()
|
||||||
defer t.transportAccess.Unlock()
|
defer t.transportAccess.Unlock()
|
||||||
if t.transportResetAt.After(startAt) {
|
if t.transportResetAt.After(startAt) {
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"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/dns/transport/hosts"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -93,7 +96,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
if response.Rcode != mDNS.RcodeSuccess {
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
err = dns.RcodeError(response.Rcode)
|
err = dns.RcodeError(response.Rcode)
|
||||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||||
err = E.New(fqdn, ": empty result")
|
err = dns.RcodeSuccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
@@ -149,12 +152,6 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
|||||||
if server.Port == 0 {
|
if server.Port == 0 {
|
||||||
server.Port = 53
|
server.Port = 53
|
||||||
}
|
}
|
||||||
var networks []string
|
|
||||||
if useTCP {
|
|
||||||
networks = []string{N.NetworkTCP}
|
|
||||||
} else {
|
|
||||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
|
||||||
}
|
|
||||||
request := &mDNS.Msg{
|
request := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
Id: uint16(rand.Uint32()),
|
Id: uint16(rand.Uint32()),
|
||||||
@@ -164,41 +161,74 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
|||||||
Question: []mDNS.Question{question},
|
Question: []mDNS.Question{question},
|
||||||
Compress: true,
|
Compress: true,
|
||||||
}
|
}
|
||||||
request.SetEdns0(maxDNSPacketSize, false)
|
request.SetEdns0(buf.UDPBufferSize, false)
|
||||||
buffer := buf.Get(buf.UDPBufferSize)
|
if !useTCP {
|
||||||
defer buf.Put(buffer)
|
return t.exchangeUDP(ctx, server, request, timeout)
|
||||||
for _, network := range networks {
|
} else {
|
||||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
return t.exchangeTCP(ctx, server, request, timeout)
|
||||||
defer cancel()
|
|
||||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
|
||||||
conn.SetDeadline(deadline)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
panic("unexpected")
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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, 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// net.maxDNSPacketSize
|
|
||||||
maxDNSPacketSize = 1232
|
|
||||||
)
|
|
||||||
|
|
||||||
type resolverConfig struct {
|
type resolverConfig struct {
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
ch chan struct{}
|
ch chan struct{}
|
||||||
|
|||||||
@@ -2,6 +2,22 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.12.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.5
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.2
|
#### 1.12.2
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package trafficontrol
|
|||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/compatible"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
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"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package trafficontrol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -27,16 +27,16 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.1.8
|
github.com/sagernet/gomobile v0.1.8
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
|
github.com/sagernet/sing v0.7.10
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.3
|
||||||
github.com/sagernet/sing-quic v0.5.0
|
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1
|
github.com/sagernet/sing-tun v0.7.2
|
||||||
github.com/sagernet/sing-vmess v0.2.7
|
github.com/sagernet/sing-vmess v0.2.7
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -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 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
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.6.9/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.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw=
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.10/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 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
||||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM=
|
||||||
github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
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-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 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
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 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY=
|
github.com/sagernet/sing-tun v0.7.2 h1:uJkAZM0KBqIYzrq077QGqdvj/+4i/pMOx6Pnx0jYqAs=
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
|
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 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
|
||||||
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
github.com/sagernet/sing-vmess v0.2.7/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 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
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-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1/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 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
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=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
|
|||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
|
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination.Unwrap()
|
||||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
||||||
metadata.User = userName
|
metadata.User = userName
|
||||||
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
@@ -164,26 +163,7 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
|
|||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
}
|
}
|
||||||
var domainStrategy C.DomainStrategy
|
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)
|
||||||
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) {
|
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) {
|
||||||
@@ -204,26 +184,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
|
|||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
}
|
}
|
||||||
var domainStrategy C.DomainStrategy
|
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
|
||||||
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) {
|
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) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -37,7 +37,7 @@ type Selector struct {
|
|||||||
tags []string
|
tags []string
|
||||||
defaultTag string
|
defaultTag string
|
||||||
outbounds map[string]adapter.Outbound
|
outbounds map[string]adapter.Outbound
|
||||||
selected atomic.TypedValue[adapter.Outbound]
|
selected common.TypedValue[adapter.Outbound]
|
||||||
interruptGroup *interrupt.Group
|
interruptGroup *interrupt.Group
|
||||||
interruptExternalConnections bool
|
interruptExternalConnections bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -14,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -192,7 +192,7 @@ type URLTestGroup struct {
|
|||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
started bool
|
started bool
|
||||||
lastActive atomic.TypedValue[time.Time]
|
lastActive common.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) {
|
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) {
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/common/listener"
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing-box/common/uot"
|
"github.com/sagernet/sing-box/common/uot"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -33,6 +35,7 @@ type Inbound struct {
|
|||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
listener *listener.Listener
|
listener *listener.Listener
|
||||||
authenticator *auth.Authenticator
|
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) {
|
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) {
|
||||||
@@ -42,6 +45,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
authenticator: auth.NewAuthenticator(options.Users),
|
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{
|
inbound.listener = listener.New(listener.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
@@ -58,11 +68,20 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
|
|||||||
if stage != adapter.StartStateStart {
|
if stage != adapter.StartStateStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if h.tlsConfig != nil {
|
||||||
|
err := h.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create TLS config")
|
||||||
|
}
|
||||||
|
}
|
||||||
return h.listener.Start()
|
return h.listener.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Inbound) Close() error {
|
func (h *Inbound) Close() error {
|
||||||
return h.listener.Close()
|
return common.Close(
|
||||||
|
h.listener,
|
||||||
|
h.tlsConfig,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
@@ -78,6 +97,13 @@ 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 {
|
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)
|
reader := std_bufio.NewReader(conn)
|
||||||
headerBytes, err := reader.Peek(1)
|
headerBytes, err := reader.Peek(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
hostPort = request.Host
|
hostPort = request.Host
|
||||||
}
|
}
|
||||||
source := sHttp.SourceAddress(request)
|
source := sHttp.SourceAddress(request)
|
||||||
destination := M.ParseSocksaddr(hostPort)
|
destination := M.ParseSocksaddr(hostPort).Unwrap()
|
||||||
|
|
||||||
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
|
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
|
||||||
conn, _, err := hijacker.Hijack()
|
conn, _, err := hijacker.Hijack()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
@@ -37,7 +36,7 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil)
|
|||||||
type NetworkManager struct {
|
type NetworkManager struct {
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
interfaceFinder *control.DefaultInterfaceFinder
|
interfaceFinder *control.DefaultInterfaceFinder
|
||||||
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface]
|
networkInterfaces common.TypedValue[[]adapter.NetworkInterface]
|
||||||
|
|
||||||
autoDetectInterface bool
|
autoDetectInterface bool
|
||||||
defaultOptions adapter.NetworkOptions
|
defaultOptions adapter.NetworkOptions
|
||||||
|
|||||||
107
route/route.go
107
route/route.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,6 +26,8 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deprecated: use RouteConnectionEx instead.
|
// Deprecated: use RouteConnectionEx instead.
|
||||||
@@ -345,16 +346,16 @@ func (r *Router) matchRule(
|
|||||||
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
|
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
|
||||||
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
||||||
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||||
}, inputConn, inputPacketConn, nil)
|
}, inputConn, inputPacketConn, nil, nil)
|
||||||
if newErr != nil {
|
|
||||||
fatalErr = newErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if newBuffer != nil {
|
if newBuffer != nil {
|
||||||
buffers = []*buf.Buffer{newBuffer}
|
buffers = []*buf.Buffer{newBuffer}
|
||||||
} else if len(newPackerBuffers) > 0 {
|
} else if len(newPackerBuffers) > 0 {
|
||||||
packetBuffers = newPackerBuffers
|
packetBuffers = newPackerBuffers
|
||||||
}
|
}
|
||||||
|
if newErr != nil {
|
||||||
|
fatalErr = newErr
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS {
|
if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS {
|
||||||
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
|
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
|
||||||
@@ -453,16 +454,16 @@ match:
|
|||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *R.RuleActionSniff:
|
case *R.RuleActionSniff:
|
||||||
if !preMatch {
|
if !preMatch {
|
||||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)
|
||||||
if newErr != nil {
|
|
||||||
fatalErr = newErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if newBuffer != nil {
|
if newBuffer != nil {
|
||||||
buffers = append(buffers, newBuffer)
|
buffers = append(buffers, newBuffer)
|
||||||
} else if len(newPacketBuffers) > 0 {
|
} else if len(newPacketBuffers) > 0 {
|
||||||
packetBuffers = append(packetBuffers, newPacketBuffers...)
|
packetBuffers = append(packetBuffers, newPacketBuffers...)
|
||||||
}
|
}
|
||||||
|
if newErr != nil {
|
||||||
|
fatalErr = newErr
|
||||||
|
return
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedRule = currentRule
|
selectedRule = currentRule
|
||||||
selectedRuleIndex = currentRuleIndex
|
selectedRuleIndex = currentRuleIndex
|
||||||
@@ -489,7 +490,7 @@ match:
|
|||||||
|
|
||||||
func (r *Router) actionSniff(
|
func (r *Router) actionSniff(
|
||||||
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
|
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
|
||||||
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer,
|
||||||
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
||||||
if sniff.Skip(metadata) {
|
if sniff.Skip(metadata) {
|
||||||
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
|
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
|
||||||
@@ -501,7 +502,7 @@ func (r *Router) actionSniff(
|
|||||||
if inputConn != nil {
|
if inputConn != nil {
|
||||||
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
||||||
return
|
return
|
||||||
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -528,6 +529,7 @@ func (r *Router) actionSniff(
|
|||||||
action.Timeout,
|
action.Timeout,
|
||||||
streamSniffers...,
|
streamSniffers...,
|
||||||
)
|
)
|
||||||
|
metadata.SnifferNames = action.SnifferNames
|
||||||
metadata.SniffError = err
|
metadata.SniffError = err
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//goland:noinspection GoDeprecation
|
//goland:noinspection GoDeprecation
|
||||||
@@ -553,10 +555,13 @@ func (r *Router) actionSniff(
|
|||||||
} else if inputPacketConn != nil {
|
} else if inputPacketConn != nil {
|
||||||
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
||||||
return
|
return
|
||||||
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
quicMoreData := func() bool {
|
||||||
|
return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData)
|
||||||
|
}
|
||||||
var packetSniffers []sniff.PacketSniffer
|
var packetSniffers []sniff.PacketSniffer
|
||||||
if len(action.PacketSniffers) > 0 {
|
if len(action.PacketSniffers) > 0 {
|
||||||
packetSniffers = action.PacketSniffers
|
packetSniffers = action.PacketSniffers
|
||||||
@@ -571,12 +576,37 @@ func (r *Router) actionSniff(
|
|||||||
sniff.NTP,
|
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 {
|
for {
|
||||||
var (
|
var (
|
||||||
sniffBuffer = buf.NewPacket()
|
sniffBuffer = buf.NewPacket()
|
||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
done = make(chan struct{})
|
done = make(chan struct{})
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
sniffTimeout := C.ReadPayloadTimeout
|
sniffTimeout := C.ReadPayloadTimeout
|
||||||
@@ -597,12 +627,12 @@ func (r *Router) actionSniff(
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sniffBuffer.Release()
|
sniffBuffer.Release()
|
||||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
if !errors.Is(err, context.DeadlineExceeded) {
|
||||||
fatalErr = err
|
fatalErr = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(packetBuffers) > 0 || metadata.SniffError != nil {
|
if quicMoreData() {
|
||||||
err = sniff.PeekPacket(
|
err = sniff.PeekPacket(
|
||||||
ctx,
|
ctx,
|
||||||
metadata,
|
metadata,
|
||||||
@@ -622,32 +652,34 @@ func (r *Router) actionSniff(
|
|||||||
Destination: destination,
|
Destination: destination,
|
||||||
}
|
}
|
||||||
packetBuffers = append(packetBuffers, packetBuffer)
|
packetBuffers = append(packetBuffers, packetBuffer)
|
||||||
|
metadata.SnifferNames = action.SnifferNames
|
||||||
metadata.SniffError = err
|
metadata.SniffError = err
|
||||||
if errors.Is(err, sniff.ErrNeedMoreData) {
|
if errors.Is(err, sniff.ErrNeedMoreData) {
|
||||||
// TODO: replace with generic message when there are more multi-packet protocols
|
// TODO: replace with generic message when there are more multi-packet protocols
|
||||||
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if metadata.Protocol != "" {
|
}
|
||||||
//goland:noinspection GoDeprecation
|
goto finally
|
||||||
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
}
|
||||||
metadata.Destination = M.Socksaddr{
|
finally:
|
||||||
Fqdn: metadata.Domain,
|
if err == nil {
|
||||||
Port: metadata.Destination.Port,
|
//goland:noinspection GoDeprecation
|
||||||
}
|
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||||
}
|
metadata.Destination = M.Socksaddr{
|
||||||
if metadata.Domain != "" && metadata.Client != "" {
|
Fqdn: metadata.Domain,
|
||||||
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
|
Port: metadata.Destination.Port,
|
||||||
} 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
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -675,11 +707,6 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
|
|||||||
}
|
}
|
||||||
metadata.DestinationAddresses = addresses
|
metadata.DestinationAddresses = addresses
|
||||||
r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
|||||||
return &RuleActionHijackDNS{}, nil
|
return &RuleActionHijackDNS{}, nil
|
||||||
case C.RuleActionTypeSniff:
|
case C.RuleActionTypeSniff:
|
||||||
sniffAction := &RuleActionSniff{
|
sniffAction := &RuleActionSniff{
|
||||||
snifferNames: action.SniffOptions.Sniffer,
|
SnifferNames: action.SniffOptions.Sniffer,
|
||||||
Timeout: time.Duration(action.SniffOptions.Timeout),
|
Timeout: time.Duration(action.SniffOptions.Timeout),
|
||||||
}
|
}
|
||||||
return sniffAction, sniffAction.build()
|
return sniffAction, sniffAction.build()
|
||||||
@@ -361,7 +361,7 @@ func (r *RuleActionHijackDNS) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuleActionSniff struct {
|
type RuleActionSniff struct {
|
||||||
snifferNames []string
|
SnifferNames []string
|
||||||
StreamSniffers []sniff.StreamSniffer
|
StreamSniffers []sniff.StreamSniffer
|
||||||
PacketSniffers []sniff.PacketSniffer
|
PacketSniffers []sniff.PacketSniffer
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
@@ -374,7 +374,7 @@ func (r *RuleActionSniff) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleActionSniff) build() error {
|
func (r *RuleActionSniff) build() error {
|
||||||
for _, name := range r.snifferNames {
|
for _, name := range r.SnifferNames {
|
||||||
switch name {
|
switch name {
|
||||||
case C.ProtocolTLS:
|
case C.ProtocolTLS:
|
||||||
r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)
|
r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)
|
||||||
@@ -407,14 +407,14 @@ func (r *RuleActionSniff) build() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleActionSniff) String() string {
|
func (r *RuleActionSniff) String() string {
|
||||||
if len(r.snifferNames) == 0 && r.Timeout == 0 {
|
if len(r.SnifferNames) == 0 && r.Timeout == 0 {
|
||||||
return "sniff"
|
return "sniff"
|
||||||
} 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, ","), ")")
|
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(", r.Timeout.String(), ")")
|
return F.ToString("sniff(", r.Timeout.String(), ")")
|
||||||
} else {
|
} else {
|
||||||
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")")
|
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ",", r.Timeout.String(), ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -13,7 +14,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -27,16 +27,16 @@ import (
|
|||||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||||
|
|
||||||
type LocalRuleSet struct {
|
type LocalRuleSet struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
tag string
|
tag string
|
||||||
rules []adapter.HeadlessRule
|
access sync.RWMutex
|
||||||
metadata adapter.RuleSetMetadata
|
rules []adapter.HeadlessRule
|
||||||
fileFormat string
|
metadata adapter.RuleSetMetadata
|
||||||
watcher *fswatch.Watcher
|
fileFormat string
|
||||||
callbackAccess sync.Mutex
|
watcher *fswatch.Watcher
|
||||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||||
refs atomic.Int32
|
refs atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
||||||
@@ -141,11 +141,11 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
|||||||
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||||
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||||
|
s.access.Lock()
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.metadata = metadata
|
s.metadata = metadata
|
||||||
s.callbackAccess.Lock()
|
|
||||||
callbacks := s.callbacks.Array()
|
callbacks := s.callbacks.Array()
|
||||||
s.callbackAccess.Unlock()
|
s.access.Unlock()
|
||||||
for _, callback := range callbacks {
|
for _, callback := range callbacks {
|
||||||
callback(s)
|
callback(s)
|
||||||
}
|
}
|
||||||
@@ -157,10 +157,14 @@ func (s *LocalRuleSet) PostStart() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
return s.metadata
|
return s.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {
|
func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
return common.FlatMap(s.rules, extractIPSetFromRule)
|
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,14 +185,14 @@ func (s *LocalRuleSet) Cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||||
s.callbackAccess.Lock()
|
s.access.Lock()
|
||||||
defer s.callbackAccess.Unlock()
|
defer s.access.Unlock()
|
||||||
return s.callbacks.PushBack(callback)
|
return s.callbacks.PushBack(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||||
s.callbackAccess.Lock()
|
s.access.Lock()
|
||||||
defer s.callbackAccess.Unlock()
|
defer s.access.Unlock()
|
||||||
s.callbacks.Remove(element)
|
s.callbacks.Remove(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -17,7 +18,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -40,16 +40,16 @@ type RemoteRuleSet struct {
|
|||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
outbound adapter.OutboundManager
|
outbound adapter.OutboundManager
|
||||||
options option.RuleSet
|
options option.RuleSet
|
||||||
metadata adapter.RuleSetMetadata
|
|
||||||
updateInterval time.Duration
|
updateInterval time.Duration
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
|
access sync.RWMutex
|
||||||
rules []adapter.HeadlessRule
|
rules []adapter.HeadlessRule
|
||||||
|
metadata adapter.RuleSetMetadata
|
||||||
lastUpdated time.Time
|
lastUpdated time.Time
|
||||||
lastEtag string
|
lastEtag string
|
||||||
updateTicker *time.Ticker
|
updateTicker *time.Ticker
|
||||||
cacheFile adapter.CacheFile
|
cacheFile adapter.CacheFile
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
callbackAccess sync.Mutex
|
|
||||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||||
refs atomic.Int32
|
refs atomic.Int32
|
||||||
}
|
}
|
||||||
@@ -120,10 +120,14 @@ func (s *RemoteRuleSet) PostStart() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
|
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
return s.metadata
|
return s.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {
|
func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
return common.FlatMap(s.rules, extractIPSetFromRule)
|
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +148,14 @@ func (s *RemoteRuleSet) Cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||||
s.callbackAccess.Lock()
|
s.access.Lock()
|
||||||
defer s.callbackAccess.Unlock()
|
defer s.access.Unlock()
|
||||||
return s.callbacks.PushBack(callback)
|
return s.callbacks.PushBack(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||||
s.callbackAccess.Lock()
|
s.access.Lock()
|
||||||
defer s.callbackAccess.Unlock()
|
defer s.access.Unlock()
|
||||||
s.callbacks.Remove(element)
|
s.callbacks.Remove(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,13 +189,13 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
|||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.access.Lock()
|
||||||
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.callbackAccess.Lock()
|
|
||||||
callbacks := s.callbacks.Array()
|
callbacks := s.callbacks.Array()
|
||||||
s.callbackAccess.Unlock()
|
s.access.Unlock()
|
||||||
for _, callback := range callbacks {
|
for _, callback := range callbacks {
|
||||||
callback(s)
|
callback(s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package ssmapi
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = buffer.ReadFullFrom(conn, int(length))
|
_, err = buffer.ReadFullFrom(conn, int(length))
|
||||||
return destination.Unwrap(), err
|
return destination, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {
|
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-quic"
|
"github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -30,7 +29,7 @@ type Client struct {
|
|||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
conn atomic.TypedValue[quic.Connection]
|
conn common.TypedValue[quic.Connection]
|
||||||
rawConn net.Conn
|
rawConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
|
|||||||
b := packets[0]
|
b := packets[0]
|
||||||
common.ClearArray(b[1:4])
|
common.ClearArray(b[1:4])
|
||||||
}
|
}
|
||||||
eps[0] = remoteEndpoint(M.AddrPortFromNet(addr))
|
eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort())
|
||||||
count = 1
|
count = 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user