Update sing-box core

This commit is contained in:
Shtorm
2026-08-06 14:09:46 +03:00
57 changed files with 1628 additions and 429 deletions

View File

@@ -1 +1 @@
98d539ce67568fb911654e66a14cf4247ed833ec
617d38f41f935b46a68f550d9add2e38abb3f168

View File

@@ -2,7 +2,7 @@
set -euo pipefail
VERSION="1.25.10"
VERSION="1.25.12"
PATCH_COMMITS=(
"afe69d3cec1c6dcf0f1797b20546795730850070"
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"

View File

@@ -2,14 +2,14 @@
set -euo pipefail
VERSION="1.25.10"
VERSION="1.25.12"
PATCH_COMMITS=(
"466f6c7a29bc098b0d4c987b803c779222894a11"
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
"a90777dcf692dd2168577853ba743b4338721b06"
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
"da4094da73b3b419e3f347594d805e2831f65667"
"824aa60e77f06dbae86c20a164c78df722eb7047"
"a3b6ba31c8cc67b6d899b978bba7b53e95afc46b"
"edfa8de63435a409a59f60731b66ab5940d6d3a4"
"284f9b24d6284984966a8431e30fdc2583938f96"
"9864798dee8dd47b55d1d5100d2f1b909a2a6e6c"
)
CURL_ARGS=(
-fL

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: 1.25.12
- name: Clone cronet-go
if: matrix.naive
run: |

View File

@@ -29,7 +29,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: 1.25.12
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -72,7 +72,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: 1.25.12
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -194,6 +194,7 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
echo '%_rpmformat 4' > "$HOME/.rpmmacros"
cp .fpm_systemd .fpm
fpm -t rpm \
--name "${NAME}" \

View File

@@ -27,6 +27,11 @@ type OutboundWithPreferredRoutes interface {
PreferredAddress(address netip.Addr) bool
}
type OutboundWithMultiplex interface {
Outbound
MultiplexEnabled() bool
}
type DirectRouteOutbound interface {
Outbound
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)

View File

@@ -289,7 +289,7 @@ func prepareAppStore(ctx context.Context) error {
return err
}
if len(builds.Data) == 0 {
log.Fatal(platform, " ", tag, " no build found")
log.Fatal(string(platform), " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {

View File

@@ -0,0 +1,165 @@
package main
import (
"archive/zip"
"crypto/sha256"
"flag"
"io"
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
)
var outputPath string
func init() {
flag.StringVar(&outputPath, "output", "", "output AAR path")
}
func main() {
flag.Parse()
err := merge()
if err != nil {
log.Fatal(err)
}
}
func merge() error {
inputPaths := flag.Args()
if outputPath == "" {
return E.New("missing output path")
}
if len(inputPaths) == 0 {
return E.New("missing input AAR paths")
}
archiveReaders := make([]*zip.ReadCloser, 0, len(inputPaths))
for _, inputPath := range inputPaths {
archiveReader, err := zip.OpenReader(inputPath)
if err != nil {
return E.Cause(err, "open input AAR: ", inputPath)
}
archiveReaders = append(archiveReaders, archiveReader)
}
defer func() {
for _, archiveReader := range archiveReaders {
archiveReader.Close()
}
}()
referenceEntries := make(map[string][sha256.Size]byte)
selectedEntries := make([]*zip.File, 0)
selectedJNIEntries := make(map[string]bool)
for inputIndex, archiveReader := range archiveReaders {
seenEntries := make(map[string]bool)
for _, archiveFile := range archiveReader.File {
if strings.HasPrefix(archiveFile.Name, "jni/") {
if archiveFile.FileInfo().IsDir() {
continue
}
if selectedJNIEntries[archiveFile.Name] {
return E.New("duplicate AAR JNI entry: ", archiveFile.Name)
}
selectedJNIEntries[archiveFile.Name] = true
selectedEntries = append(selectedEntries, archiveFile)
continue
}
entryDigest, err := digestEntry(archiveFile)
if err != nil {
return E.Cause(err, "read AAR entry: ", archiveFile.Name)
}
if inputIndex == 0 {
referenceEntries[archiveFile.Name] = entryDigest
selectedEntries = append(selectedEntries, archiveFile)
} else {
referenceDigest, loaded := referenceEntries[archiveFile.Name]
if !loaded {
return E.New("unexpected AAR entry: ", archiveFile.Name)
}
if referenceDigest != entryDigest {
return E.New("AAR entry differs between architectures: ", archiveFile.Name)
}
}
seenEntries[archiveFile.Name] = true
}
if inputIndex > 0 {
for referenceName := range referenceEntries {
if !seenEntries[referenceName] {
return E.New("missing AAR entry: ", referenceName)
}
}
}
}
absoluteOutputPath, err := filepath.Abs(outputPath)
if err != nil {
return E.Cause(err, "resolve output AAR path")
}
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
if err != nil {
return E.Cause(err, "create output AAR directory")
}
temporaryFile, err := os.CreateTemp(filepath.Dir(absoluteOutputPath), ".merge-aar-*.aar")
if err != nil {
return E.Cause(err, "create temporary output AAR")
}
temporaryPath := temporaryFile.Name()
defer os.Remove(temporaryPath)
archiveWriter := zip.NewWriter(temporaryFile)
for _, archiveFile := range selectedEntries {
rawReader, openErr := archiveFile.OpenRaw()
if openErr != nil {
archiveWriter.Close()
temporaryFile.Close()
return E.Cause(openErr, "open raw AAR entry: ", archiveFile.Name)
}
header := archiveFile.FileHeader
rawWriter, createErr := archiveWriter.CreateRaw(&header)
if createErr != nil {
archiveWriter.Close()
temporaryFile.Close()
return E.Cause(createErr, "create output AAR entry: ", archiveFile.Name)
}
_, copyErr := io.Copy(rawWriter, rawReader)
if copyErr != nil {
archiveWriter.Close()
temporaryFile.Close()
return E.Cause(copyErr, "copy output AAR entry: ", archiveFile.Name)
}
}
err = archiveWriter.Close()
if err != nil {
temporaryFile.Close()
return E.Cause(err, "finalize output AAR")
}
err = temporaryFile.Close()
if err != nil {
return E.Cause(err, "close output AAR")
}
err = os.Rename(temporaryPath, absoluteOutputPath)
if err != nil {
return E.Cause(err, "replace output AAR")
}
return nil
}
func digestEntry(archiveFile *zip.File) ([sha256.Size]byte, error) {
entryReader, err := archiveFile.Open()
if err != nil {
return [sha256.Size]byte{}, err
}
digest := sha256.New()
_, err = io.Copy(digest, entryReader)
closeErr := entryReader.Close()
if err != nil {
return [sha256.Size]byte{}, err
}
if closeErr != nil {
return [sha256.Size]byte{}, closeErr
}
var result [sha256.Size]byte
copy(result[:], digest.Sum(nil))
return result, nil
}

View File

@@ -0,0 +1,172 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"howett.net/plist"
)
type xcFrameworkInfo struct {
AvailableLibraries []xcFrameworkLibrary `plist:"AvailableLibraries"`
}
type xcFrameworkLibrary struct {
BinaryPath string `plist:"BinaryPath"`
LibraryIdentifier string `plist:"LibraryIdentifier"`
LibraryPath string `plist:"LibraryPath"`
SupportedArchitectures []string `plist:"SupportedArchitectures"`
SupportedPlatform string `plist:"SupportedPlatform"`
SupportedPlatformVariant string `plist:"SupportedPlatformVariant"`
}
type frameworkSlice struct {
rootPath string
library xcFrameworkLibrary
}
var outputPath string
func init() {
flag.StringVar(&outputPath, "output", "", "output XCFramework path")
}
func main() {
flag.Parse()
err := merge()
if err != nil {
log.Fatal(err)
}
}
func merge() error {
inputPaths := flag.Args()
if outputPath == "" {
return E.New("missing output path")
}
if len(inputPaths) == 0 {
return E.New("missing input XCFramework paths")
}
frameworkGroups := make(map[string][]frameworkSlice)
for _, inputPath := range inputPaths {
infoFile, err := os.Open(filepath.Join(inputPath, "Info.plist"))
if err != nil {
return E.Cause(err, "open XCFramework metadata: ", inputPath)
}
var info xcFrameworkInfo
decoder := plist.NewDecoder(infoFile)
err = decoder.Decode(&info)
closeErr := infoFile.Close()
if err != nil {
return E.Cause(err, "decode XCFramework metadata: ", inputPath)
}
if closeErr != nil {
return E.Cause(closeErr, "close XCFramework metadata: ", inputPath)
}
for _, library := range info.AvailableLibraries {
groupName := library.SupportedPlatform + "|" + library.SupportedPlatformVariant
frameworkGroups[groupName] = append(frameworkGroups[groupName], frameworkSlice{
rootPath: inputPath,
library: library,
})
}
}
groupNames := make([]string, 0, len(frameworkGroups))
for groupName := range frameworkGroups {
groupNames = append(groupNames, groupName)
}
sort.Strings(groupNames)
absoluteOutputPath, err := filepath.Abs(outputPath)
if err != nil {
return E.Cause(err, "resolve output XCFramework path")
}
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
if err != nil {
return E.Cause(err, "create output XCFramework directory")
}
temporaryDirectory, err := os.MkdirTemp(filepath.Dir(absoluteOutputPath), ".merge-xcframework-*")
if err != nil {
return E.Cause(err, "create XCFramework merge directory")
}
defer os.RemoveAll(temporaryDirectory)
frameworkPaths := make([]string, 0, len(groupNames))
for groupIndex, groupName := range groupNames {
frameworkSlices := frameworkGroups[groupName]
firstSlice := frameworkSlices[0]
firstFrameworkPath := filepath.Join(firstSlice.rootPath, firstSlice.library.LibraryIdentifier, firstSlice.library.LibraryPath)
if len(frameworkSlices) == 1 {
frameworkPaths = append(frameworkPaths, firstFrameworkPath)
continue
}
architectures := make(map[string]bool)
binaryPaths := make([]string, 0, len(frameworkSlices))
for _, currentSlice := range frameworkSlices {
if currentSlice.library.LibraryPath != firstSlice.library.LibraryPath || currentSlice.library.BinaryPath != firstSlice.library.BinaryPath {
return E.New("incompatible XCFramework slices for platform: ", currentSlice.library.SupportedPlatform)
}
for _, architecture := range currentSlice.library.SupportedArchitectures {
if architectures[architecture] {
return E.New("duplicate XCFramework architecture: ", architecture)
}
architectures[architecture] = true
}
binaryPaths = append(binaryPaths, filepath.Join(currentSlice.rootPath, currentSlice.library.LibraryIdentifier, currentSlice.library.BinaryPath))
}
mergedFrameworkPath := filepath.Join(temporaryDirectory, "framework-"+strconv.Itoa(groupIndex), filepath.Base(firstSlice.library.LibraryPath))
copyCommand := exec.Command("ditto", firstFrameworkPath, mergedFrameworkPath)
copyCommand.Stdout = os.Stdout
copyCommand.Stderr = os.Stderr
err = copyCommand.Run()
if err != nil {
return E.Cause(err, "copy XCFramework slice")
}
binaryRelativePath, relativeErr := filepath.Rel(firstSlice.library.LibraryPath, firstSlice.library.BinaryPath)
if relativeErr != nil {
return E.Cause(relativeErr, "resolve XCFramework binary path")
}
if binaryRelativePath == "." || strings.HasPrefix(binaryRelativePath, ".."+string(filepath.Separator)) {
return E.New("invalid XCFramework binary path: ", firstSlice.library.BinaryPath)
}
mergedBinaryPath := filepath.Join(mergedFrameworkPath, binaryRelativePath)
temporaryBinaryPath := mergedBinaryPath + ".merged"
lipoArguments := append([]string{"lipo", "-create"}, binaryPaths...)
lipoArguments = append(lipoArguments, "-output", temporaryBinaryPath)
lipoCommand := exec.Command("xcrun", lipoArguments...)
lipoCommand.Stdout = os.Stdout
lipoCommand.Stderr = os.Stderr
err = lipoCommand.Run()
if err != nil {
return E.Cause(err, "merge XCFramework binaries")
}
err = os.Rename(temporaryBinaryPath, mergedBinaryPath)
if err != nil {
return E.Cause(err, "replace merged XCFramework binary")
}
frameworkPaths = append(frameworkPaths, mergedFrameworkPath)
}
err = os.RemoveAll(absoluteOutputPath)
if err != nil {
return E.Cause(err, "remove output XCFramework")
}
xcodebuildArguments := []string{"-create-xcframework"}
for _, frameworkPath := range frameworkPaths {
xcodebuildArguments = append(xcodebuildArguments, "-framework", frameworkPath)
}
xcodebuildArguments = append(xcodebuildArguments, "-output", absoluteOutputPath)
xcodebuildCommand := exec.Command("xcodebuild", xcodebuildArguments...)
xcodebuildCommand.Stdout = os.Stdout
xcodebuildCommand.Stderr = os.Stderr
err = xcodebuildCommand.Run()
if err != nil {
return E.Cause(err, "create XCFramework")
}
return nil
}

View File

@@ -106,6 +106,7 @@ func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent stri
}
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
globalSettings := collectBuildSettings(objectsMap)
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
@@ -116,13 +117,51 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
if bundleIDObject == nil {
continue
}
if common.Contains(bundleIDList, bundleIDObject.(string)) {
bundleID := expandBuildVariables(bundleIDObject.(string), globalSettings)
if common.Contains(bundleIDList, bundleID) {
objectKeyList = append(objectKeyList, objectKey)
}
}
return objectKeyList
}
func collectBuildSettings(objectsMap map[string]any) map[string]string {
settings := make(map[string]string)
for _, object := range objectsMap {
buildSettings, loaded := object.(map[string]any)["buildSettings"].(map[string]any)
if !loaded {
continue
}
for key, value := range buildSettings {
valueString, isString := value.(string)
if !isString {
continue
}
settings[key] = valueString
}
}
return settings
}
var buildVariableRegexp = regexp.MustCompile(`\$[({]([A-Za-z0-9_]+)[)}]`)
func expandBuildVariables(value string, settings map[string]string) string {
for {
expanded := buildVariableRegexp.ReplaceAllStringFunc(value, func(match string) string {
name := buildVariableRegexp.FindStringSubmatch(match)[1]
replacement, loaded := settings[name]
if !loaded {
return match
}
return replacement
})
if expanded == value {
return expanded
}
value = expanded
}
}
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {

View File

@@ -36,6 +36,7 @@ type DefaultDialer struct {
udpAddr4 string
udpAddr6 string
netns string
autoDetectBindFunc control.Func
connectionManager adapter.ConnectionManager
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
@@ -60,6 +61,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
autoDetectBindFunc control.Func
)
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
@@ -130,6 +132,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
autoDetectBindFunc = bindFunc
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
@@ -224,6 +227,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
netns: options.NetNs,
autoDetectBindFunc: autoDetectBindFunc,
connectionManager: connectionManager,
networkManager: networkManager,
networkStrategy: networkStrategy,
@@ -328,12 +332,18 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil {
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
listenConfig := d.udpListener
if d.autoDetectBindFunc != nil && destination.Addr.IsValid() {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return d.autoDetectBindFunc(network, destination.String(), conn)
})
}
if destination.IsIPv6() {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
return listenConfig.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
} else {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
}
}))
} else {

View File

@@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ Searcher = (*androidSearcher)(nil)
@@ -16,6 +17,9 @@ type androidSearcher struct {
}
func NewSearcher(config Config) (Searcher, error) {
if config.PackageManager == nil {
return nil, E.New("missing package manager")
}
return &androidSearcher{config.PackageManager}, nil
}

View File

@@ -165,7 +165,7 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", certificate)
return nil, E.New("failed to parse certificate:\n\n", string(certificate))
}
tlsConfig.RootCAs = certPool
}

View File

@@ -171,7 +171,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
for _, certPath := range c.clientCertificatePath {
content, err := os.ReadFile(certPath)
if err != nil {
c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath))
c.logger.Error(E.Cause(err, "reload certificate from ", certPath))
continue
}
if !clientCertificateCA.AppendCertsFromPEM(content) {

View File

@@ -218,7 +218,7 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", certificate)
return nil, E.New("failed to parse certificate:\n\n", string(certificate))
}
tlsConfig.RootCAs = certPool
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
@@ -72,7 +73,18 @@ func (s *HistoryStorage) Close() error {
return nil
}
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
func URLTest(ctx context.Context, link string, detour N.Dialer) (uint16, error) {
multiplexOutbound, isMultiplexOutbound := common.Cast[adapter.OutboundWithMultiplex](detour)
if isMultiplexOutbound && multiplexOutbound.MultiplexEnabled() {
_, err := urlTest(ctx, link, detour)
if err != nil {
return 0, err
}
}
return urlTest(ctx, link, detour)
}
func urlTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
if link == "" {
link = "https://www.gstatic.com/generate_204"
}

View File

@@ -106,6 +106,18 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
return 0, false
}
func stripDNSPadding(response *dns.Msg) {
for _, record := range response.Extra {
opt, isOpt := record.(*dns.OPT)
if !isOpt {
continue
}
opt.Option = common.Filter(opt.Option, func(it dns.EDNS0) bool {
return it.Option() != dns.EDNS0PADDING
})
}
}
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
transportStack := transportStackFromContext(ctx)
if containsTransport(transportStack, transport.Tag()) {
@@ -198,6 +210,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
} else {
return nil, err
}
} else {
stripDNSPadding(response)
}
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
validResponse := response

View File

@@ -111,6 +111,7 @@ func (t *Transport) Close() error {
func (t *Transport) Reset() {
t.transportLock.Lock()
t.updatedAt = time.Time{}
t.lastError = nil
t.servers = nil
t.transportLock.Unlock()
}
@@ -295,9 +296,14 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
searchList := dhcpPacket.DomainSearch()
if searchList != nil && len(searchList.Labels) > 0 {
t.search = searchList.Labels
t.search = common.Filter(common.Map(searchList.Labels, mDNS.Fqdn), func(it string) bool {
return it != "."
})
} else if dhcpPacket.DomainName() != "" {
t.search = []string{dhcpPacket.DomainName()}
domainName := mDNS.Fqdn(dhcpPacket.DomainName())
if domainName != "." {
t.search = []string{domainName}
}
}
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
return M.SocksaddrFrom(M.AddrFromIP(it), 53)

View File

@@ -120,13 +120,16 @@ func NewHTTPSRaw(
serverAddr M.Socksaddr,
tlsConfig tls.Config,
) *HTTPSTransport {
if tlsConfig != nil {
dialer = tls.NewDialer(dialer, tlsConfig)
}
return &HTTPSTransport{
TransportAdapter: adapter,
logger: logger,
dialer: dialer,
destination: destination,
headers: headers,
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination),
}
}

View File

@@ -5,11 +5,13 @@ import (
"errors"
"net"
"net/http"
"net/url"
"sync/atomic"
"github.com/sagernet/sing-box/common/tls"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/http2"
)
@@ -22,27 +24,36 @@ type HTTPSTransportWrapper struct {
fallback *atomic.Bool
}
func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {
func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destination *url.URL) *HTTPSTransportWrapper {
var fallback atomic.Bool
if destination.Scheme == "http" {
// plain HTTP DoH used by Tailscale
fallback.Store(true)
}
return &HTTPSTransportWrapper{
http2Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
tlsConn, err := dialer.DialTLSContext(ctx, serverAddr)
resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
if err != nil {
return nil, err
}
state := tlsConn.ConnectionState()
if state.NegotiatedProtocol == http2.NextProtoTLS {
return tlsConn, nil
if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn {
state := tlsConn.ConnectionState()
if state.NegotiatedProtocol != http2.NextProtoTLS {
tlsConn.Close()
fallback.Store(true)
return nil, errFallback
}
}
tlsConn.Close()
fallback.Store(true)
return nil, errFallback
return resultConn, nil
},
},
httpTransport: &http.Transport{
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
},
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialTLSContext(ctx, serverAddr)
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
},
},
fallback: &fallback,
@@ -52,16 +63,15 @@ func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPST
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
if h.fallback.Load() {
return h.httpTransport.RoundTrip(request)
} else {
response, err := h.http2Transport.RoundTrip(request)
if err != nil {
if errors.Is(err, errFallback) {
return h.httpTransport.RoundTrip(request)
}
return nil, err
}
return response, nil
}
response, err := h.http2Transport.RoundTrip(request)
if err != nil {
if errors.Is(err, errFallback) {
return h.httpTransport.RoundTrip(request)
}
return nil, err
}
return response, nil
}
func (h *HTTPSTransportWrapper) CloseIdleConnections() {

View File

@@ -126,6 +126,12 @@ func (t *HTTP3Transport) newTransport() *http3.Transport {
conn.Close()
return nil, dialErr
}
// quic-go does not take ownership of the packet conn passed to
// DialEarly: when the connection ends it only stops reading.
go func() {
<-quicConn.Context().Done()
conn.Close()
}()
return quicConn, nil
},
TLSClientConfig: t.tlsConfig,

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"os"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/adapter"
@@ -117,6 +118,12 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
rawConn.Close()
return nil, E.Cause(err, "establish QUIC connection")
}
// quic-go does not take ownership of the packet conn passed to
// DialEarly: when the connection ends it only stops reading.
go func() {
<-earlyConnection.Context().Done()
rawConn.Close()
}()
return earlyConnection, nil
})
if err != nil {
@@ -144,6 +151,11 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.
return nil, E.Cause(err, "open stream")
}
defer stream.CancelRead(0)
stopWatch := context.AfterFunc(ctx, func() {
stream.CancelRead(0)
_ = stream.SetWriteDeadline(time.Now())
})
defer stopWatch()
err = transport.WriteMessage(stream, 0, message)
if err != nil {
stream.Close()

View File

@@ -2,6 +2,23 @@
icon: material/alert-decagram
---
#### 1.13.16
* Remove client metadata from AnyTLS requests by default **1**
* Fixes and improvements
**1**:
We found that the AnyTLS client implementation uploads metadata that is
**not used by the open-source server**, and there are reports of vendors using
it to profile and discriminate against users. We now leave it empty by default
and allow you to customize it, see
[AnyTLS client metadata](/manual/misc/anytls-client-metadata/).
#### 1.13.15
* Fixes and improvements
#### 1.13.14
* Fixes and improvements

View File

@@ -17,6 +17,7 @@ icon: material/new-box
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"min_idle_session": 5,
"client_metadata": "",
"tls": {},
... // Dial Fields
@@ -55,6 +56,12 @@ In the check, close sessions that have been idle for longer than this. Default:
In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0
#### client_metadata
!!! question "Since sing-box 1.13.16"
Check [AnyTLS client metadata](/manual/misc/anytls-client-metadata/).
#### tls
==Required==

View File

@@ -17,6 +17,7 @@ icon: material/new-box
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"min_idle_session": 5,
"client_metadata": "",
"tls": {},
... // 拨号字段
@@ -55,6 +56,12 @@ AnyTLS 密码。
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
#### client_metadata
!!! question "自 sing-box 1.13.16 起"
参阅 [AnyTLS 客户端元数据](/zh/manual/misc/anytls-client-metadata/)。
#### tls
==必填==

View File

@@ -0,0 +1,67 @@
---
icon: material/incognito
---
# AnyTLS client metadata
The AnyTLS protocol has a design flaw: its settings frame requires the client
to send its software name and version to the server, and the protocol
specification requires that clients not disguise this information.
This field serves no protocol purpose — AnyTLS already has a separate version
field for compatibility negotiation, and the open-source server implementation
does not use client metadata. However, the field allows vendors to collect and track client types, and for
platform-specific clients, potentially infer private information such as the operating system type and version range —
something that should not, and is not expected by users to, appear in an
anti-censorship protocol. We have received reports that
commercial proxy providers use this information to identify and block
connections from the official library provided by AnyTLS for sing-box
integration, reportedly because abusive users connect to their servers with
sing-box or with clients using the same official library. This indicates that
client metadata is being collected and used for discrimination in practice.
The protocol specification states that "disguising it has no value." We
disagree: the situation is analogous to browsers implementing TLS ECH GREASE —
without it, privacy-protecting clients can be fingerprinted and treated
differently.
## Status
### 2025-02-20
We merged the
[pull request adding this protocol](https://github.com/SagerNet/sing-box/pull/2615).
Since the metadata was fixed at `sing-anytls/<library version>` in the
implementation provided for our use, and we did not carefully review the
protocol specification and other implementations, we wrongly believed that it
was not private information.
### 2025-04-05
The protocol document
[added](https://github.com/anytls/anytls-go/commit/8812aae7ab29dd88bb89067b9ca676e2e7e29171)
the requirement that third-party implementations fill in the real software
name and version, claiming that "disguising it has no value".
### 2026-07-18
A [pull request submitted to sing-box](https://github.com/SagerNet/sing-box/pull/4311)
was found to additionally upload the `sing-box` name and the actual version;
the change was subsequently reverted and was never released.
### 2026-08-03
sing-box 1.13.16 and 1.14.0-beta.5 have been released; the client metadata in
AnyTLS requests is now empty by default. For compatibility, the
[client_metadata](/configuration/outbound/anytls/#client_metadata) outbound
option allows users to set a custom value.
Since the open-source server implementation does not use this information and
it has no legitimate use, this is not considered a breaking change.
## Recommendations
We recommend that the AnyTLS protocol remove the client metadata, or replace
it with an option that is not sent by default and can be customized by the
user; and that other client implementations also take action, to jointly stop
statistics collection and discrimination based on client metadata.

View File

@@ -0,0 +1,35 @@
---
icon: material/incognito
---
# AnyTLS 客户端元数据
AnyTLS 协议具有设计缺陷:其 settings 帧要求客户端向服务器发送软件名称和版本,且协议规范要求客户端不得伪装此信息。
此字段不承担协议功能——AnyTLS 已有独立的版本字段用于兼容性协商,且开源服务端实现不使用客户端元数据。然而,此字段使得供应商可以收集并统计客户端类型,对于某些平台特定的客户端,还可能推断出操作系统类型与版本范围等隐私信息,而这不应该,也不是被用户预期的,在一个反审查协议中出现。我们收到报告,有商业代理提供商利用此信息识别和阻止来自 sing-box 使用的、由 AnyTLS 提供的用于 sing-box 集成的官方代码库的连接,据传原因是恶意用户使用 sing-box 或使用相同官方代码库的客户端连接到服务器,这表明客户端元数据在实践中已被用于收集和区别对待。
协议规范称「伪装它没有任何意义」。我们不同意:这类似于浏览器实现 TLS ECH GREASE——如果没有这一机制保护隐私的客户端会被识别并受到差别对待。
## 状态
### 2025-02-20
我们合并了[添加此协议的 PR](https://github.com/SagerNet/sing-box/pull/2615)。由于在供我们使用的实现中metadata 被固定在 `sing-anytls/<library version>`,且我们没有仔细审查协议规范和其他实现,我们错误地认为这不是隐私信息。
### 2025-04-05
协议文档[加入](https://github.com/anytls/anytls-go/commit/8812aae7ab29dd88bb89067b9ca676e2e7e29171)了要求第三方实现填写真实软件名称与版本号的条款,并声称「伪装它没有任何意义」。
### 2026-07-18
[向 sing-box 提出的 PR](https://github.com/SagerNet/sing-box/pull/4311) 被发现额外上传了 `sing-box` 和实际版本的字符串,随后此更改被回退,没有发布。
### 2026-08-03
发布了 sing-box 1.13.16 和 1.14.0-beta.5,现在 AnyTLS 请求中的客户端元数据默认为空。出于兼容性考虑,[client_metadata](/zh/configuration/outbound/anytls/#client_metadata) 出站选项允许用户自定义此值。
由于开源服务端实现不使用此信息,且它没有合理用途,这不被视为破坏性更改。
## 建议
我们建议 AnyTLS 协议移除客户端元数据,或将其替换为非默认提供、且用户可以自定义的选项;并建议其他客户端实现也采取行动,共同阻止基于客户端元数据的统计和区别对待。

View File

@@ -3,11 +3,13 @@ package deprecated
import (
"os"
"strconv"
"sync"
"github.com/sagernet/sing/common/logger"
)
type stderrManager struct {
access sync.Mutex
logger logger.Logger
reported map[string]bool
}
@@ -20,6 +22,8 @@ func NewStderrManager(logger logger.Logger) Manager {
}
func (f *stderrManager) ReportDeprecated(feature Note) {
f.access.Lock()
defer f.access.Unlock()
if f.reported[feature.Name] {
return
}

View File

@@ -13,7 +13,6 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task"
mDNS "github.com/miekg/dns"
)
@@ -58,24 +57,23 @@ func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*m
if err != nil {
return nil, err
}
var responseMessage *mDNS.Msg
var group task.Group
group.Append0(func(ctx context.Context) error {
err = p.iif.Exchange(response, messageBytes)
done := make(chan error, 1)
go func() {
exchangeErr := p.iif.Exchange(response, messageBytes)
if exchangeErr == nil {
exchangeErr = response.error
}
done <- exchangeErr
}()
select {
case err = <-done:
if err != nil {
return err
return nil, err
}
if response.error != nil {
return response.error
}
responseMessage = &response.message
return nil
})
err = group.Run(ctx)
if err != nil {
return nil, err
return &response.message, nil
case <-ctx.Done():
return nil, ctx.Err()
}
return responseMessage, nil
} else {
question := message.Question[0]
var network string
@@ -87,24 +85,23 @@ func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*m
default:
return nil, E.New("only IP queries are supported by current version of Android")
}
var responseAddrs []netip.Addr
var group task.Group
group.Append0(func(ctx context.Context) error {
err := p.iif.Lookup(response, network, question.Name)
done := make(chan error, 1)
go func() {
lookupErr := p.iif.Lookup(response, network, question.Name)
if lookupErr == nil {
lookupErr = response.error
}
done <- lookupErr
}()
select {
case err := <-done:
if err != nil {
return err
return nil, err
}
if response.error != nil {
return response.error
}
responseAddrs = response.addresses
return nil
})
err := group.Run(ctx)
if err != nil {
return nil, err
return dns.FixedResponse(message.Id, question, response.addresses, C.DefaultDNSTTL), nil
case <-ctx.Done():
return nil, ctx.Err()
}
return dns.FixedResponse(message.Id, question, responseAddrs, C.DefaultDNSTTL), nil
}
}

78
go.mod
View File

@@ -7,10 +7,11 @@ require (
github.com/Diniboy1123/connect-ip-go v0.0.0-20260409225322-8d7bb0a858a2
github.com/anthropics/anthropic-sdk-go v1.26.0
github.com/anytls/sing-anytls v0.0.11
github.com/caddyserver/certmagic v0.25.2
github.com/caddyserver/certmagic v0.25.3-0.20260421143802-60d9d8b415d6
github.com/coder/websocket v1.8.14
github.com/cretz/bine v0.2.0
github.com/database64128/tfo-go/v2 v2.3.2
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa
github.com/enfein/mieru/v3 v3.33.0
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/render v1.0.3
@@ -36,22 +37,22 @@ require (
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cors v1.2.1
github.com/sagernet/cronet-go v0.0.0-20260620140045-05ab0dc17597
github.com/sagernet/cronet-go/all v0.0.0-20260620140045-05ab0dc17597
github.com/sagernet/cronet-go v0.0.0-20260712143338-d22f2ea3630e
github.com/sagernet/cronet-go/all v0.0.0-20260712143338-d22f2ea3630e
github.com/sagernet/fswatch v0.1.2
github.com/sagernet/gomobile v0.1.12
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
github.com/sagernet/sing v0.8.11
github.com/sagernet/sing v0.8.12-0.20260726145744-ef2df370afca
github.com/sagernet/sing-mux v0.3.5
github.com/sagernet/sing-quic v0.6.1
github.com/sagernet/sing-quic v0.6.4-0.20260803041914-d83826c306d7
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.8.11
github.com/sagernet/sing-shadowtls v0.2.1
github.com/sagernet/sing-tun v0.8.12-0.20260727151122-3a09076491df
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.8
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.0.2
@@ -119,7 +120,6 @@ require (
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/database64128/netx-go v0.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/dolonet/mtg-multi v1.8.0
github.com/ebitengine/purego v0.10.1 // indirect
@@ -162,35 +162,35 @@ require (
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260712142643-1e5048bd5587 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-mod.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
@@ -239,4 +239,4 @@ replace github.com/Diniboy1123/connect-ip-go => github.com/shtorm-7/connect-ip-g
replace github.com/shtorm-7/go-cache/v2 => github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.2.0
replace github.com/sagernet/sing => github.com/shtorm-7/sing v0.8.10-extended-1.2.0
replace github.com/sagernet/sing => github.com/shtorm-7/sing v0.8.12-extended-1.2.0

144
go.sum
View File

@@ -34,8 +34,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc=
github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=
github.com/caddyserver/certmagic v0.25.3-0.20260421143802-60d9d8b415d6 h1:LYSB6VgWzKtNrcxElw3c97BP40Oc7bizKxA9K1Vi/5k=
github.com/caddyserver/certmagic v0.25.3-0.20260421143802-60d9d8b415d6/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@@ -287,68 +287,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/cronet-go v0.0.0-20260620140045-05ab0dc17597 h1:QkwE/ZFnShDuPF+ExmAyZlQaMwFNgkYZMekrabiStfg=
github.com/sagernet/cronet-go v0.0.0-20260620140045-05ab0dc17597/go.mod h1:T/mwtrpC4JlWfScw73CmSBvHzIvc7BatQ1MhRr+cYNw=
github.com/sagernet/cronet-go/all v0.0.0-20260620140045-05ab0dc17597 h1:cLALmGKP9eOS8622gWQIiVbZlOfH29PGNsoxbEloIdk=
github.com/sagernet/cronet-go/all v0.0.0-20260620140045-05ab0dc17597/go.mod h1:zVHZ5tgDTwbNvUGffAgLmouYs4in0grEzhSdaggoZOw=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260620135226-def9ff0fb992 h1:J9l8PP4vb79Wm5zKaMO6LNZ/AiP1FvyAWJBlkKHrRBU=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260620135226-def9ff0fb992 h1:wXDjUNeKuihv85Kg51FomkiEH7xGsDgRcfRLiyZxacQ=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260620135226-def9ff0fb992 h1:IF04nGyY3Q6Nbk9XJwTX1mckwhf12iIx4RhZ4TLOZIU=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260620135226-def9ff0fb992/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260620135226-def9ff0fb992 h1:dlnG1E42xx8ms2fyZigYwsYJ1Gqoj2QT8WeGlpOAWK8=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260620135226-def9ff0fb992 h1:aYAQQN3jZP89MmT1ZzpJz52jAsXx1WApmv5Qidc+ez4=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260620135226-def9ff0fb992 h1:n81+aLphvjLpL2M6lI+BC8Ldw4S/FPA3CFDXTWL4g3I=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260620135226-def9ff0fb992 h1:mOseesF+CxgPG2U1a2Yh2fUUMdOaPxSuk4eIL6g7EU8=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260620135226-def9ff0fb992/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260620135226-def9ff0fb992 h1:JK9kP72IxAoyVQTnh9gGkh8S9RjP4FBkj95WjrLPyKs=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260620135226-def9ff0fb992 h1:3EtgLRsUpmeRaOBynTARxVC8tDegykigutmpAow2ayc=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260620135226-def9ff0fb992/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260620135226-def9ff0fb992 h1:Vr3I2sC9E/1FurpZwJXAL29C7jJROyN3JfulQWjhKuA=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260620135226-def9ff0fb992 h1:flxwC8loz0C4LQ/tLK7LvNMKx4iqaXayPggzzCxCevI=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260620135226-def9ff0fb992 h1:G4vwPmOVR/jXDngIUC9owbEtMKXLZl/BgUHhEWWl8ec=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260620135226-def9ff0fb992 h1:zHIxR2FlJOW5GRmgwyA2Gjgx7potOCtlOmdl8k4mwJI=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260620135226-def9ff0fb992 h1:U1OrR5zP+lkOPqrDpZsn8sPK1XBWZ84isXaFBZLsOfs=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260620135226-def9ff0fb992/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260620135226-def9ff0fb992 h1:F59ptY4AdtKEg73OWaL+lTb5yoxp5b/gTuDbvA6xMyg=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260620135226-def9ff0fb992 h1:Y5axK4sCWXH+2OCpKYPI8nX3OSBuRb/6yC+5xBi9/uo=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260620135226-def9ff0fb992 h1:QCBwCdv9y+RJj7p0b9Db2p9fFt1wtnawD0sn9oV3vRs=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260620135226-def9ff0fb992 h1:6gXFFaAMiGCPZdfUs64qzj9cl7EleVs9HsNfRi8jWWw=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260620135226-def9ff0fb992 h1:VGoIX2u4CWVg9kiyjQdIpFMaFUNCW3yz0pyrEKI5X0o=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260620135226-def9ff0fb992 h1:SjPuqtUNCzIDaEm7iY86JZ7L+ixTmai4i2DIC++eUvw=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260620135226-def9ff0fb992/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260620135226-def9ff0fb992 h1:ao2FrDzTYhu2MYsMri9nzqIdnAL7ooUWQN6/FFr+Lbk=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260620135226-def9ff0fb992/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260620135226-def9ff0fb992 h1:5gPMu6EUlX6gqCRTOJrJk1FMILO6ugtnopQF1c2R/lY=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260620135226-def9ff0fb992 h1:UaLOQKbjnLrO943Sm+ff/jm+NmemRuJXiImmtqShd8s=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260620135226-def9ff0fb992 h1:wxlsDfxDowVk99Ay2hzfuIXPpXH1lGQxSL/2sX/D7jw=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260620135226-def9ff0fb992/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260620135226-def9ff0fb992 h1:pgRpWh2JPE73mtedovPPu3gmmAqHz3Rfz79QVxciu2o=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260620135226-def9ff0fb992/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260620135226-def9ff0fb992 h1:9jtXyxukTS2ZdhMy3u1hg4StkZpgP48BOfgYhXf835w=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260620135226-def9ff0fb992 h1:gKgD1LJZbZzacaaqBQX/YKy4dyomhn8xtfmDKayVLW4=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260620135226-def9ff0fb992/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260620135226-def9ff0fb992 h1:Lv8gtKP4QRn6Yjv3h5L215aGQBgwCyAE5YqejmN9Bqc=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260620135226-def9ff0fb992 h1:2wsRAcqJHOTlj6zGc3oQyxAZHDnGwyB/pTdAOUtLgHY=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260620135226-def9ff0fb992/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
github.com/sagernet/cronet-go v0.0.0-20260712143338-d22f2ea3630e h1:Y5mhsZrYuZ9jraIqg7hg1fw4zoVreae81fWvmozbCsQ=
github.com/sagernet/cronet-go v0.0.0-20260712143338-d22f2ea3630e/go.mod h1:T/mwtrpC4JlWfScw73CmSBvHzIvc7BatQ1MhRr+cYNw=
github.com/sagernet/cronet-go/all v0.0.0-20260712143338-d22f2ea3630e h1:rdNlS1dRSi7jQe/ingFf7QmV9ZCUZMbxAfHnBrdVW7g=
github.com/sagernet/cronet-go/all v0.0.0-20260712143338-d22f2ea3630e/go.mod h1:WNl4xfTNuR+f7SObmuBtrk0p4MhlmvuuiWYoty3U52E=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260712142643-1e5048bd5587 h1:ENmDXbGH92/jsMwhjIxK2a0URkA8ILC3npjqmTGj0Yc=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260712142643-1e5048bd5587 h1:5xn/EZOO5LriSEih91thvTuR+gxb59jNxNQiB/KQPEc=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260712142643-1e5048bd5587 h1:2/UN0LvWnAM0Sc9B/zE2qWylGrX2hQXhknyQyTlvz4k=
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260712142643-1e5048bd5587/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260712142643-1e5048bd5587 h1:SDULc9o0HkneJD38H1G+HRLby69zfdtyQk5qaKtn/tU=
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260712142643-1e5048bd5587 h1:hfcM9YccWN4O2LENHN16Jgm4g1/1PRouV0RmvvrS1f0=
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260712142643-1e5048bd5587 h1:arY9CL3C7lwJfG2Cdz2ZLvHLzT1wnzCVyAomNER6CrY=
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260712142643-1e5048bd5587 h1:HrtCf6KPJsW9KJ4L4T8HMW1vx+3xefLRLObpjZEtzF4=
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260712142643-1e5048bd5587/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260712142643-1e5048bd5587 h1:Inzzp4hyvcC0lawhndaK8iwEN0vGLDJLCThbaKZpksg=
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260712142643-1e5048bd5587 h1:qN6vqr9nFnZqjZXhLez+7tMcbm3FMfhaJRWNMlK3SLI=
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260712142643-1e5048bd5587/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260712142643-1e5048bd5587 h1:Zwzpw6555p3rWw844JxNZ+5iRqli6ZBOEtMH3qq1c7s=
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260712142643-1e5048bd5587 h1:CwsGHfo1HwvEhA2Wnh3O8WxazN3d4Un44LXhiuIvA/w=
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260712142643-1e5048bd5587 h1:Pn6vsVFOJkj9q/XKBOZosfuDLB6luNMLkqq6YLUPXEk=
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260712142643-1e5048bd5587 h1:vgRWcEr2jlgICj28XwDMXPQcy8mTob1ZcO+tPdiZjes=
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260712142643-1e5048bd5587 h1:uiO62HvSAdRJR3d1Jc4duxWig3kkdwHBSQ3TmUFqw48=
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260712142643-1e5048bd5587/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260712142643-1e5048bd5587 h1:mXioRdq9h2YlIr9XGM51kXgIkToywwVfnGmJiaz/uzM=
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260712142643-1e5048bd5587 h1:oOlfOL+sq0KNZRsk06+5KgvtKw2TEXl2EZnLakTx+WM=
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260712142643-1e5048bd5587 h1:UDCa0lYiSUXD4wbxA/G5pGPSXhJaesCQCh5IEiZKb3M=
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260712142643-1e5048bd5587 h1:Yg2Ut7mPs0GK4W6p9LDh8RrDOnhTe4YpvG/WeJyUqMo=
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260712142643-1e5048bd5587 h1:NtagC/YHvucD0Azh86aVghOk4z5f7oOAAy25Uw4knXQ=
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260712142643-1e5048bd5587 h1:INzfLHBKjgJxoUDBeCaiB9hYqjhn5yfudkMz2phYypA=
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260712142643-1e5048bd5587/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260712142643-1e5048bd5587 h1:IX5NCEV9nojHdjpOkxKVN4L5FUuA5nwawXlSTdFXjCE=
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260712142643-1e5048bd5587/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260712142643-1e5048bd5587 h1:pzgA94sR7kvrP+H86/to2oN3efYORQmh0Q3b6AyfRJQ=
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260712142643-1e5048bd5587 h1:nkS6jhF90E24PW37Xemhs6+hUM0l2iRcQcZFhPJL8R8=
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260712142643-1e5048bd5587 h1:EbydHlp6vWdqhb8e8zBPVJv/F3HPob+FgO6rsSdNSr0=
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260712142643-1e5048bd5587/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260712142643-1e5048bd5587 h1:sSfVTswgqQZJqh9wTP0Acvvo5/qYARAoHegRYwZ+gyU=
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260712142643-1e5048bd5587/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260712142643-1e5048bd5587 h1:lHqqbALbKdJdq/1rcI4yyg3zvibHDw7wmhDYVbjy498=
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260712142643-1e5048bd5587 h1:23GyWjb58Nk9a7WXgRfFUiTrMD9kvYSRHx73AaBthYc=
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260712142643-1e5048bd5587/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260712142643-1e5048bd5587 h1:64EMjgVuZMD4TX5b7oWUfWRVp8aVl3hg4aWrriQKOWo=
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260712142643-1e5048bd5587 h1:x8cvgMQUs0EgVwt3iT/isRQ9KImvXvPobLKO5bHTN4o=
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260712142643-1e5048bd5587/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
github.com/sagernet/fswatch v0.1.2 h1:/TT7k4mkce1qFPxamLO842WjqBgbTBiXP2mlUjp9PFk=
github.com/sagernet/fswatch v0.1.2/go.mod h1:5BpGmpUQVd3Mc5r313HRpvADHRg3/rKn5QbwFteB880=
github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg=
@@ -361,16 +361,16 @@ github.com/sagernet/nftables v0.3.0-mod.2 h1:ck2KMU02OxL1eDFgGaWYglMDpoOZ7OHzxje
github.com/sagernet/nftables v0.3.0-mod.2/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw=
github.com/sagernet/sing-quic v0.6.1/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
github.com/sagernet/sing-quic v0.6.4-0.20260803041914-d83826c306d7 h1:D46kmyvKMNVFvL3KXdq3T4vy8r29xCMmSqNuQHJBybE=
github.com/sagernet/sing-quic v0.6.4-0.20260803041914-d83826c306d7/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.11 h1:BFu4+8LNl2JiTQtto5f+5AbkH90qgdoZEAqUbGiEXCg=
github.com/sagernet/sing-tun v0.8.11/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkGCKNXhbaM=
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.12-0.20260727151122-3a09076491df h1:IVG68QmUeO9xlvrBMB/0u/prBot+jIiEjCsNterk7qs=
github.com/sagernet/sing-tun v0.8.12-0.20260727151122-3a09076491df/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
@@ -383,8 +383,8 @@ github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.2.0 h1:aOd9Vy2LGSwgMM+4805AgLB
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.2.0/go.mod h1:Ek4yz5OK6stwhLKgLsRRYDI+FA+ZWvRJiWLjsi/vMM4=
github.com/shtorm-7/mtg-multi v1.11.0-extended-1.0.0 h1:iBLll4ZZG8ULQcHWs6gGslZWtBN72Zo1zjySzMVHF7g=
github.com/shtorm-7/mtg-multi v1.11.0-extended-1.0.0/go.mod h1:3rvdhwdPABkwKBdvgMt3VwMn9uSq8hpoHRezZ5jRJU0=
github.com/shtorm-7/sing v0.8.10-extended-1.2.0 h1:5yw9j0+P2QkRWvxBvb71wvNdpAlHmmpBv4hj2gqvass=
github.com/shtorm-7/sing v0.8.10-extended-1.2.0/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
github.com/shtorm-7/sing v0.8.12-extended-1.2.0 h1:/CYLFBi+Xrj0R6hFvYoqmDfcLe3WB6McZq5t9UvVhrc=
github.com/shtorm-7/sing v0.8.12-extended-1.2.0/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0 h1:a5OoXr3e2ACbM6vDIaaGL44IdHQ6wPjcSoU13vfC0Sw=
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/shtorm-7/sing-vmess v0.2.8-extended-1.0.0 h1:OjvqZOhYSi9eIJGYC0iPDPqvgo/asujvhAGkFzbZ5/Y=

View File

@@ -75,6 +75,7 @@ nav:
- Hysteria 2: manual/proxy-protocol/hysteria2.md
- Misc:
- TunnelVision: manual/misc/tunnelvision.md
- AnyTLS client metadata: manual/misc/anytls-client-metadata.md
- Configuration:
- configuration/index.md
- Log:

View File

@@ -22,4 +22,5 @@ type AnyTLSOutboundOptions struct {
IdleSessionCheckInterval badoption.Duration `json:"idle_session_check_interval,omitempty"`
IdleSessionTimeout badoption.Duration `json:"idle_session_timeout,omitempty"`
MinIdleSession int `json:"min_idle_session,omitempty"`
ClientMetadata string `json:"client_metadata,omitempty"`
}

View File

@@ -90,7 +90,7 @@ type DERPMeshOptions struct {
}
type _DERPSTUNListenOptions struct {
Enabled bool
Enabled bool `json:"enabled,omitempty"`
ListenOptions
}

View File

@@ -0,0 +1,64 @@
package anytls
import (
"encoding/binary"
"net"
"reflect"
"strings"
"sync"
"unsafe"
"github.com/sagernet/sing/common"
anytls "github.com/anytls/sing-anytls"
"github.com/anytls/sing-anytls/session"
)
const (
commandSettings = 4
frameHeaderSize = 7
)
var (
clientSessionField, _ = reflect.TypeFor[anytls.Client]().FieldByName("sessionClient")
streamSessionField, _ = reflect.TypeFor[session.Stream]().FieldByName("sess")
sessionConnLockField, _ = reflect.TypeFor[session.Session]().FieldByName("connLock")
sessionBufferField, _ = reflect.TypeFor[session.Session]().FieldByName("buffer")
)
func sessionClientOf(client *anytls.Client) *session.Client {
return *(**session.Client)(unsafe.Add(unsafe.Pointer(client), clientSessionField.Offset))
}
func (h *Outbound) rewriteClientMetadata(conn net.Conn) {
sess := *(**session.Session)(unsafe.Add(unsafe.Pointer(conn.(*session.Stream)), streamSessionField.Offset))
connLock := (*sync.Mutex)(unsafe.Add(unsafe.Pointer(sess), sessionConnLockField.Offset))
bufferPointer := (*[]byte)(unsafe.Add(unsafe.Pointer(sess), sessionBufferField.Offset))
connLock.Lock()
defer connLock.Unlock()
buffer := *bufferPointer
offset := 0
for offset+frameHeaderSize <= len(buffer) {
dataLength := int(binary.BigEndian.Uint16(buffer[offset+5 : offset+7]))
frameEnd := offset + frameHeaderSize + dataLength
if frameEnd > len(buffer) {
return
}
if buffer[offset] == commandSettings {
data := []byte(strings.Join(common.Map(strings.Split(string(buffer[offset+frameHeaderSize:frameEnd]), "\n"), func(line string) string {
if strings.HasPrefix(line, "client=") {
return "client=" + h.clientMetadata
}
return line
}), "\n"))
newBuffer := make([]byte, 0, offset+frameHeaderSize+len(data)+len(buffer)-frameEnd)
newBuffer = append(newBuffer, buffer[:offset+5]...)
newBuffer = binary.BigEndian.AppendUint16(newBuffer, uint16(len(data)))
newBuffer = append(newBuffer, data...)
newBuffer = append(newBuffer, buffer[frameEnd:]...)
*bufferPointer = newBuffer
return
}
offset = frameEnd
}
}

View File

@@ -19,20 +19,25 @@ import (
"github.com/sagernet/sing/common/uot"
anytls "github.com/anytls/sing-anytls"
"github.com/anytls/sing-anytls/session"
)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.AnyTLSOutboundOptions](registry, C.TypeAnyTLS, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
dialer tls.Dialer
server M.Socksaddr
tlsConfig tls.Config
client *anytls.Client
uotClient *uot.Client
logger log.ContextLogger
dialer tls.Dialer
server M.Socksaddr
tlsConfig tls.Config
clientMetadata string
client *anytls.Client
sessionClient *session.Client
uotClient *uot.Client
logger log.ContextLogger
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) {
@@ -81,14 +86,30 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
outbound.client = client
outbound.clientMetadata = options.ClientMetadata
outbound.sessionClient = sessionClientOf(client)
outbound.uotClient = &uot.Client{
Dialer: anytlsDialer(client.CreateProxy),
Dialer: (anytlsDialer)(outbound.createProxy),
Version: uot.Version,
}
return outbound, nil
}
func (h *Outbound) createProxy(ctx context.Context, destination M.Socksaddr) (net.Conn, error) {
conn, err := h.sessionClient.CreateStream(ctx)
if err != nil {
return nil, err
}
h.rewriteClientMetadata(conn)
err = M.SocksaddrSerializer.WriteAddrPort(conn, destination)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
func (d anytlsDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
@@ -103,6 +124,10 @@ func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
return h.dialer.DialTLSContext(ctx, h.server)
}
func (h *Outbound) MultiplexEnabled() bool {
return true
}
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
@@ -110,7 +135,7 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.CreateProxy(ctx, destination)
return h.createProxy(ctx, destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
return h.uotClient.DialContext(ctx, network, destination)

View File

@@ -108,7 +108,10 @@ func (h *Outbound) fetchMyAddresses() {
func (h *Outbound) isMyLoopbackAddress(addresses ...netip.Addr) bool {
for _, prefix := range h.myAddresses.Load() {
for _, address := range addresses {
if prefix.Addr() != address && prefix.Contains(address) {
if !C.IsDarwin && prefix.Addr() == address {
continue
}
if prefix.Contains(address) {
return true
}
}

View File

@@ -225,7 +225,7 @@ func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net
} else {
done := make(chan struct{})
wrapper := v2rayhttp.NewHTTP2Wrapper(conn)
n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) {
n.router.RouteConnectionEx(ctx, wrapper, metadata, N.OnceClose(func(it error) {
close(done)
}))
<-done

View File

@@ -26,6 +26,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -124,6 +126,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.multiplexDialer != nil {
h.multiplexDialer.Reset()

View File

@@ -103,7 +103,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
for _, hostKey := range options.HostKey {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hostKey))
if err != nil {
return nil, E.New("parse host key ", key)
return nil, E.Cause(err, "parse host key: ", hostKey)
}
outbound.hostKey = append(outbound.hostKey, key)
}

View File

@@ -161,8 +161,9 @@ func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *n
func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) {
serverURL, parseURLErr := url.Parse(resolver.Addr)
isHTTPScheme := parseURLErr == nil && (serverURL.Scheme == "http" || serverURL.Scheme == "https")
var myDialer N.Dialer
if parseURLErr == nil && serverURL.Scheme == "http" {
if isHTTPScheme && serverURL.Scheme == "http" {
myDialer = t.endpoint
} else {
myDialer = directDialer()
@@ -170,36 +171,39 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn
if len(resolver.BootstrapResolution) > 0 {
bootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53))
myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0)
}
if serverAddr := M.ParseSocksaddr(resolver.Addr); serverAddr.IsValid() {
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil
} else if parseURLErr != nil {
return nil, E.Cause(parseURLErr, "parse resolver address")
} else {
myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", t.endpoint.queryOptions, 0)
}
if isHTTPScheme {
serverAddr := M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())
switch serverURL.Scheme {
case "https":
serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())
if serverAddr.Port == 0 {
serverAddr.Port = 443
}
tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.AddrString(), option.OutboundTLSOptions{
ALPN: []string{http2.NextProtoTLS, "http/1.1"},
Enabled: true,
ALPN: []string{http2.NextProtoTLS, "http/1.1"},
}))
return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil
case "http":
serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())
if serverAddr.Port == 0 {
serverAddr.Port = 80
}
return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil
// case "tls":
default:
return nil, E.New("unknown resolver scheme: ", serverURL.Scheme)
}
}
serverAddr := M.ParseSocksaddr(resolver.Addr)
if !serverAddr.IsValid() {
if parseURLErr != nil {
return nil, E.Cause(parseURLErr, "parse resolver address")
}
return nil, E.New("invalid resolver address: ", resolver.Addr)
}
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil
}
func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix {
@@ -279,7 +283,7 @@ func (t *DNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
}
}
for domainSuffix, transports := range routes {
if strings.HasSuffix(question.Name, domainSuffix) {
if mDNS.IsSubDomain(domainSuffix, question.Name) {
if len(transports) == 0 {
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{

View File

@@ -9,7 +9,6 @@ import (
"net"
"net/http"
"net/netip"
"net/url"
"os"
"path/filepath"
"reflect"
@@ -71,7 +70,7 @@ var (
)
func init() {
version.SetVersion(tailscaleroot.VersionDotTxt + " (sing-box " + C.Version + ")")
version.SetVersion(strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-0-(sing-box " + C.Version + ")")
}
func RegisterEndpoint(registry *endpoint.Registry) {
@@ -83,6 +82,7 @@ type Endpoint struct {
ctx context.Context
router adapter.Router
logger logger.ContextLogger
queryOptions adapter.DNSQueryOptions
dnsRouter adapter.DNSRouter
network adapter.NetworkManager
platformInterface adapter.PlatformInterface
@@ -93,6 +93,7 @@ type Endpoint struct {
onReconfigHook wgengine.ReconfigListener
cfg *wgcfg.Config
routerCfg *router.Config
dnsCfg *tsDNS.Config
routeDomains common.TypedValue[map[string]bool]
routePrefixes atomic.Pointer[netipx.IPSet]
@@ -188,27 +189,17 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
} else {
udpTimeout = C.UDPTimeout
}
var remoteIsDomain bool
if options.ControlURL != "" {
controlURL, err := url.Parse(options.ControlURL)
if err != nil {
return nil, E.Cause(err, "parse control URL")
}
remoteIsDomain = M.ParseSocksaddr(controlURL.Hostname()).IsDomain()
} else {
// controlplane.tailscale.com
remoteIsDomain = true
}
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: remoteIsDomain,
RemoteIsDomain: true,
ResolverOnDetour: true,
NewDialer: true,
})
if err != nil {
return nil, err
}
dialerQueryOptions := outboundDialer.(dialer.ResolveDialer).QueryOptions()
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
server := &tsnet.Server{
Dir: stateDirectory,
@@ -225,7 +216,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
AdvertiseTags: options.AdvertiseTags,
Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger},
LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {
return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())
return dnsRouter.Lookup(ctx, host, dialerQueryOptions)
},
DNS: &dnsConfigurtor{},
HTTPClient: &http.Client{
@@ -242,10 +233,11 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
},
}
return &Endpoint{
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
ctx: ctx,
router: router,
logger: logger,
queryOptions: dialerQueryOptions,
dnsRouter: dnsRouter,
network: service.FromContext[adapter.NetworkManager](ctx),
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
@@ -704,12 +696,17 @@ func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.
metadata.Inbound = t.Tag()
metadata.InboundType = t.Type()
metadata.Source = source
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
case addr6:
destination.Addr = netip.IPv6Loopback()
destinationAddress := tsaddr.UnmapVia(destination.Addr)
if destinationAddress != destination.Addr {
destination.Addr = destinationAddress
} else {
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
case addr6:
destination.Addr = netip.IPv6Loopback()
}
}
metadata.Destination = destination
t.logger.InfoContext(ctx, "inbound connection from ", source)
@@ -722,16 +719,22 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
metadata.Inbound = t.Tag()
metadata.InboundType = t.Type()
metadata.Source = source
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
metadata.OriginDestination = destination
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)
case addr6:
metadata.OriginDestination = destination
destination.Addr = netip.IPv6Loopback()
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)
originDestination := destination
destinationAddress := tsaddr.UnmapVia(destination.Addr)
if destinationAddress != destination.Addr {
destination.Addr = destinationAddress
} else {
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
case addr6:
destination.Addr = netip.IPv6Loopback()
}
}
if destination != originDestination {
metadata.OriginDestination = originDestination
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), originDestination, destination)
}
metadata.Destination = destination
t.logger.InfoContext(ctx, "inbound packet connection from ", source)
@@ -797,7 +800,9 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf
if cfg == nil || dnsCfg == nil {
return
}
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
if t.cfg != nil && reflect.DeepEqual(t.cfg, cfg) &&
t.routerCfg != nil && reflect.DeepEqual(t.routerCfg, routerCfg) &&
t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg) {
return
}
var inet4Address, inet6Address netip.Addr
@@ -810,6 +815,7 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf
}
t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
t.cfg = cfg
t.routerCfg = routerCfg
t.dnsCfg = dnsCfg
routeDomains := make(map[string]bool)

View File

@@ -11,6 +11,9 @@ import (
singTun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
wgTun "github.com/sagernet/wireguard-go/tun"
"github.com/dblohm7/wingoes/com"
"golang.org/x/sys/windows/svc"
)
type tunDeviceAdapter struct {
@@ -21,7 +24,23 @@ type tunDeviceAdapter struct {
closeOnce sync.Once
}
func newTunDeviceAdapter(tun singTun.Tun, mtu int, _ logger.ContextLogger) (wgTun.Device, error) {
var comRuntimeOnce sync.Once
func newTunDeviceAdapter(tun singTun.Tun, mtu int, contextLogger logger.ContextLogger) (wgTun.Device, error) {
// wgengine/router/osrouter/ifconfig_windows.go setPrivateNetwork assumes COM
// is initialized process-wide, which upstream performs only in tailscaled's
// main package; library consumers must do it themselves.
comRuntimeOnce.Do(func() {
processType := com.ConsoleApp
isService, serviceErr := svc.IsWindowsService()
if serviceErr == nil && isService {
processType = com.Service
}
err := com.StartRuntime(processType)
if err != nil {
contextLogger.Warn("initialize COM runtime: ", err)
}
})
winTun, ok := tun.(singTun.WinTun)
if !ok {
return nil, errors.New("not a windows tun device")

View File

@@ -26,6 +26,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -107,6 +109,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()

View File

@@ -198,6 +198,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
InterfaceMonitor: networkManager.InterfaceMonitor(),
Logger: logger,
EXP_MultiPendingPackets: multiPendingPackets,
},
udpTimeout: udpTimeout,

View File

@@ -33,6 +33,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -152,6 +154,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()

View File

@@ -27,6 +27,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -105,6 +107,10 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return outbound, nil
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()

View File

@@ -114,6 +114,7 @@ func (s *LocalRuleSet) reloadFile(path string) error {
if err != nil {
return err
}
defer setFile.Close()
ruleSet, err = srs.Read(setFile, false)
if err != nil {
return err

View File

@@ -98,10 +98,11 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
if savedSet := s.cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil {
err := s.loadBytes(savedSet.Content)
if err != nil {
return E.Cause(err, "restore cached rule-set")
s.logger.Warn(E.Cause(err, "restore cached rule-set, will refetch"))
} else {
s.lastUpdated = savedSet.LastUpdated
s.lastEtag = savedSet.LastEtag
}
s.lastUpdated = savedSet.LastUpdated
s.lastEtag = savedSet.LastEtag
}
}
if s.lastUpdated.IsZero() {

View File

@@ -204,10 +204,12 @@ func (i *Service) onNetworkUpdate() {
}
func (conf *TransportLink) nameList(ndots int, name string) []string {
search := common.Map(common.Filter(conf.domain, func(it LinkDomain) bool {
search := common.Filter(common.Map(common.Filter(conf.domain, func(it LinkDomain) bool {
return !it.RoutingOnly
}), func(it LinkDomain) string {
return it.Domain
return mDNS.Fqdn(it.Domain)
}), func(it string) bool {
return it != "."
})
l := len(name)

View File

@@ -6,7 +6,6 @@ import (
"context"
"net/netip"
"os"
"strings"
"sync"
"sync/atomic"
"time"
@@ -200,7 +199,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
if domain.Domain == "." && domain.RoutingOnly && !t.acceptDefaultResolvers {
continue
}
if strings.HasSuffix(question.Name, domain.Domain) {
if mDNS.IsSubDomain(domain.Domain, question.Name) {
selectedLink = link
}
}

View File

@@ -87,22 +87,27 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
request.Header.Set("Upgrade", "websocket")
err = request.Write(conn)
if err != nil {
conn.Close()
return nil, err
}
bufReader := std_bufio.NewReader(conn)
response, err := http.ReadResponse(bufReader, request)
if err != nil {
conn.Close()
return nil, err
}
if response.StatusCode != 101 ||
!strings.EqualFold(response.Header.Get("Connection"), "upgrade") ||
!strings.EqualFold(response.Header.Get("Upgrade"), "websocket") {
conn.Close()
response.Body.Close()
return nil, E.New("v2ray-http-upgrade: unexpected status: ", response.Status)
}
if bufReader.Buffered() > 0 {
buffer := buf.NewSize(bufReader.Buffered())
_, err = buffer.ReadFullFrom(bufReader, buffer.Len())
if err != nil {
conn.Close()
return nil, err
}
conn = bufio.NewCachedConn(conn, buffer)

View File

@@ -78,6 +78,12 @@ func (c *Client) offerNew() (*quic.Conn, error) {
packetConn.Close()
return nil, err
}
// quic-go does not take ownership of the packet conn passed to Dial:
// when the connection ends it only stops reading.
go func() {
<-quicConn.Context().Done()
packetConn.Close()
}()
c.conn.Store(quicConn)
c.rawConn = udpConn
return quicConn, nil

View File

@@ -2,6 +2,7 @@ package v2rayquic
import (
"net"
"time"
"github.com/sagernet/quic-go"
qtls "github.com/sagernet/sing-quic"
@@ -37,5 +38,8 @@ func (s *StreamWrapper) Upstream() any {
func (s *StreamWrapper) Close() error {
s.CancelRead(0)
s.Stream.Close()
// quic-go's Stream.Close does not unblock a Write blocked on flow control,
// but a past write deadline does; buffered data and the FIN are unaffected.
s.Stream.SetWriteDeadline(time.Now())
return nil
}

View File

@@ -93,12 +93,14 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h
reader, _, err := ws.Dialer{Header: ws.HandshakeHeaderHTTP(headers), Protocols: protocols}.Upgrade(deadlineConn, requestURL)
deadlineConn.SetDeadline(time.Time{})
if err != nil {
conn.Close()
return nil, err
}
if reader != nil {
buffer := buf.NewSize(reader.Buffered())
_, err = buffer.ReadFullFrom(reader, buffer.Len())
if err != nil {
conn.Close()
return nil, err
}
conn = bufio.NewCachedConn(conn, buffer)

View File

@@ -187,6 +187,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
if len(lateData) > 0 {
_, err = conn.Write(lateData)
if err != nil {
conn.Close()
return err
}
}