Compare commits

..

40 Commits

Author SHA1 Message Date
世界
003423745e mitm: Refactor & Add url 2025-02-04 15:00:59 +08:00
世界
fb3007fa80 mitm: Minor fixes 2025-02-03 11:32:37 +08:00
世界
5361d2acec mitm: Add /mitm/mobileconfig and /mitm/certificate clash api endpoints 2025-02-03 09:09:41 +08:00
世界
1fe983a81b mitm: Fix HTTP2 support & Add print 2025-02-03 08:20:26 +08:00
世界
b01fe5d364 Fix override address 2025-02-02 23:17:31 +08:00
世界
74920b44ac mitm: Add HTTP2 support 2025-02-02 21:36:09 +08:00
世界
5e28a80e63 Add Surge MITM and scripts 2025-02-02 17:27:29 +08:00
世界
b55bfca7de documentation: Bump version 2025-02-02 07:21:34 +08:00
世界
a0dc394c8f Fix WireGuard panic 2025-02-02 07:21:33 +08:00
世界
87f3423d7e Fix query options leakage 2025-02-02 07:21:33 +08:00
世界
244243f206 Fix domain resolver for DNS server 2025-02-02 07:21:33 +08:00
世界
89855ff548 documentation: Bump version 2025-02-02 07:21:27 +08:00
世界
8a388e9c90 documentation: Fix fakeip example 2025-02-02 07:21:27 +08:00
世界
6bf39156ec release: Skip testflight when another build in review 2025-02-02 07:21:27 +08:00
世界
ab021bee74 Update quic-go to v0.49.0-beta.1 2025-02-02 07:21:27 +08:00
世界
6fd95e157a Fix fakeip not started 2025-02-02 07:21:27 +08:00
世界
6c0e2bf526 Fix missing default domain resolver in route 2025-02-02 07:21:15 +08:00
世界
4f61fc20e0 Fix missing default store value 2025-02-02 07:21:15 +08:00
世界
438405bbf1 documentation: Bump version 2025-02-02 07:21:14 +08:00
世界
b0a2ed9f7e documentation: Remove outdated icons 2025-02-02 07:21:14 +08:00
世界
f4d5823bb2 documentation: Certificate store 2025-02-02 07:21:14 +08:00
世界
e8b43a97f7 documentation: TLS fragment 2025-02-02 07:21:13 +08:00
世界
00460f15f6 documentation: Outbound domain resolver 2025-02-02 07:21:13 +08:00
世界
ca33246c9e documentation: Refactor DNS 2025-02-02 07:21:12 +08:00
世界
e04a4181f4 Add certificate store 2025-02-02 07:21:12 +08:00
世界
ff4f455f25 Add TLS fragment support 2025-02-02 07:21:11 +08:00
世界
9a7c0d9136 refactor: Outbound domain resolver 2025-02-02 07:21:11 +08:00
世界
57de88b9c9 refactor: DNS 2025-02-02 07:21:10 +08:00
世界
7f79458b4f Minor updates 2025-02-01 19:49:33 +08:00
世界
9b4c11ba95 Fix rule-set not closed 2025-02-01 19:49:33 +08:00
世界
27c31eac5d Fix local rule-set not updated 2025-02-01 19:42:21 +08:00
世界
bab8dc0b82 Fix missing handshake for early conn 2025-01-31 12:57:35 +08:00
世界
d09d2fb665 Bump version 2025-01-30 15:09:54 +08:00
世界
e64cf3b7df Do not set address sets to routes on Apple platforms
Network Extension was observed to stop for unknown reasons
2025-01-27 13:40:39 +08:00
世界
9b73222314 documentation: Bump version 2025-01-27 10:53:14 +08:00
世界
3923b57abf release: Update NDK to r28-beta3 2025-01-27 10:53:14 +08:00
世界
4807e64609 documentation: Fix typo 2025-01-27 10:04:07 +08:00
世界
eeb37d89f1 Fix rule-set upgrade command 2025-01-27 09:50:27 +08:00
世界
08c1ec4b7e Fix legacy routes 2025-01-27 09:50:27 +08:00
HystericalDragon
6b4cf67add Fix endpoints not close 2025-01-27 09:50:27 +08:00
185 changed files with 14728 additions and 16555 deletions

View File

@@ -144,7 +144,7 @@ jobs:
~/go/go1.20.14
key: go120
- name: Setup legacy Go
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz
@@ -159,7 +159,7 @@ jobs:
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
version: 2.5.1
install-only: true
- name: Extract signing key
run: |-
@@ -224,7 +224,7 @@ jobs:
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
ndk-version: r28-beta3
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
@@ -299,7 +299,7 @@ jobs:
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
ndk-version: r28-beta3
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
@@ -548,7 +548,7 @@ jobs:
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
version: 2.5.1
install-only: true
- name: Cache ghr
uses: actions/cache@v4

View File

@@ -52,7 +52,7 @@ builds:
env:
- CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go1.20.14
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
targets:
- windows_amd64_v1
- windows_386

View File

@@ -61,6 +61,12 @@ proto_install:
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
update_public_suffix:
go generate common/tlsfragment/public_suffix.go
update_certificates:
go run ./cmd/internal/update_certificates
release:
go run ./cmd/internal/build goreleaser release --clean --skip publish
mkdir dist/release

21
adapter/certificate.go Normal file
View File

@@ -0,0 +1,21 @@
package adapter
import (
"context"
"crypto/x509"
"github.com/sagernet/sing/service"
)
type CertificateStore interface {
LifecycleService
Pool() *x509.CertPool
}
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
store := service.FromContext[CertificateStore](ctx)
if store == nil {
return nil
}
return store.Pool()
}

View File

@@ -6,7 +6,6 @@ import (
"encoding/binary"
"time"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing/common/varbin"
)
@@ -15,7 +14,20 @@ type ClashServer interface {
ConnectionTracker
Mode() string
ModeList() []string
HistoryStorage() *urltest.HistoryStorage
HistoryStorage() URLTestHistoryStorage
}
type URLTestHistory struct {
Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
}
type URLTestHistoryStorage interface {
SetHook(hook chan<- struct{})
LoadURLTestHistory(tag string) *URLTestHistory
DeleteURLTestHistory(tag string)
StoreURLTestHistory(tag string, history *URLTestHistory)
Close() error
}
type V2RayServer interface {
@@ -38,17 +50,21 @@ type CacheFile interface {
StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error
LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedRuleSet) error
LoadRuleSet(tag string) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error
LoadScript(tag string) *SavedBinary
SaveScript(tag string, script *SavedBinary) error
SurgePersistentStoreRead(key string) string
SurgePersistentStoreWrite(key string, value string) error
}
type SavedRuleSet struct {
type SavedBinary struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
@@ -69,7 +85,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
return buffer.Bytes(), nil
}
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)

View File

@@ -2,6 +2,8 @@ package adapter
import (
"context"
"crypto/tls"
"net/http"
"net/netip"
"time"
@@ -57,6 +59,8 @@ type InboundContext struct {
Domain string
Client string
SniffContext any
HTTPRequest *http.Request
ClientHello *tls.ClientHelloInfo
// cache
@@ -73,6 +77,7 @@ type InboundContext struct {
UDPTimeout time.Duration
TLSFragment bool
TLSFragmentFallbackDelay time.Duration
MITM *option.MITMRouteOptions
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType

View File

@@ -1,6 +1,8 @@
package adapter
import E "github.com/sagernet/sing/common/exceptions"
import (
E "github.com/sagernet/sing/common/exceptions"
)
type StartStage uint8
@@ -45,6 +47,9 @@ type LifecycleService interface {
func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services {
if service == nil {
continue
}
err := service.Start(stage)
if err != nil {
return err

15
adapter/mitm.go Normal file
View File

@@ -0,0 +1,15 @@
package adapter
import (
"context"
"crypto/x509"
"net"
N "github.com/sagernet/sing/common/network"
)
type MITMEngine interface {
Lifecycle
ExportCertificate() *x509.Certificate
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

View File

@@ -28,12 +28,14 @@ type NetworkManager interface {
}
type NetworkOptions struct {
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
BindInterface string
RoutingMark uint32
BindInterface string
RoutingMark uint32
DomainResolver string
DomainResolveOptions DNSQueryOptions
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
}
type InterfaceUpdateListener interface {

View File

@@ -2,6 +2,7 @@ package adapter
import (
"context"
"crypto/tls"
"net"
"net/http"
"sync"
@@ -9,6 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/x/list"
"go4.org/netipx"
@@ -66,12 +68,14 @@ type RuleSetMetadata struct {
ContainsIPCIDRRule bool
}
type HTTPStartContext struct {
ctx context.Context
access sync.Mutex
httpClientCache map[string]*http.Client
}
func NewHTTPStartContext() *HTTPStartContext {
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
return &HTTPStartContext{
ctx: ctx,
httpClientCache: make(map[string]*http.Client),
}
}
@@ -89,6 +93,10 @@ func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Clie
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(c.ctx),
RootCAs: RootPoolFromContext(c.ctx),
},
},
}
c.httpClientCache[detour] = httpClient

54
adapter/script.go Normal file
View File

@@ -0,0 +1,54 @@
package adapter
import (
"context"
"net/http"
"sync"
"time"
)
type ScriptManager interface {
Lifecycle
Scripts() []Script
Script(name string) (Script, bool)
SurgeCache() *SurgeInMemoryCache
}
type SurgeInMemoryCache struct {
sync.RWMutex
Data map[string]string
}
type Script interface {
Type() string
Tag() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error
PostStart() error
Close() error
}
type SurgeScript interface {
Script
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
}
type HTTPRequestScriptResult struct {
URL string
Headers http.Header
Body []byte
Response *HTTPRequestScriptResponse
}
type HTTPRequestScriptResponse struct {
Status int
Headers http.Header
Body []byte
}
type HTTPResponseScriptResult struct {
Status int
Headers http.Header
Body []byte
}

55
box.go
View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
@@ -22,9 +23,11 @@ import (
"github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/mitm"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/script"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
@@ -47,6 +50,8 @@ type Box struct {
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
script *script.Manager
mitm adapter.MITMEngine //*mitm.Engine
services []adapter.LifecycleService
done chan struct{}
}
@@ -141,6 +146,20 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create log factory")
}
var services []adapter.LifecycleService
certificateOptions := common.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
}
routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
@@ -158,7 +177,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize network manager")
}
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router)
@@ -166,8 +185,8 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "initialize router")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
ntpOptions := common.PtrValueOrDefault(options.NTP)
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
@@ -187,7 +206,7 @@ func New(options Options) (*Box, error) {
transportOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize inbound[", i, "]")
return nil, E.Cause(err, "initialize DNS server[", i, "]")
}
}
err = dnsRouter.Initialize(dnsOptions.Rules)
@@ -210,7 +229,7 @@ func New(options Options) (*Box, error) {
endpointOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize inbound[", i, "]")
return nil, E.Cause(err, "initialize endpoint[", i, "]")
}
}
for i, inboundOptions := range options.Inbounds {
@@ -274,13 +293,17 @@ func New(options Options) (*Box, error) {
"local",
option.LocalDNSServerOptions{},
)))
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
if err != nil {
return nil, E.Cause(err, "initialize script manager")
}
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
if platformInterface != nil {
err = platformInterface.Initialize(networkManager)
if err != nil {
return nil, E.Cause(err, "initialize platform interface")
}
}
var services []adapter.LifecycleService
if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
@@ -324,6 +347,16 @@ func New(options Options) (*Box, error) {
timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
}
mitmOptions := common.PtrValueOrDefault(options.MITM)
var mitmEngine adapter.MITMEngine
if mitmOptions.Enabled {
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
if err != nil {
return nil, E.Cause(err, "create MITM engine")
}
service.MustRegister[adapter.MITMEngine](ctx, engine)
mitmEngine = engine
}
return &Box{
network: networkManager,
endpoint: endpointManager,
@@ -333,6 +366,8 @@ func New(options Options) (*Box, error) {
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
script: scriptManager,
mitm: mitmEngine,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
@@ -391,11 +426,11 @@ func (s *Box) preStart() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm)
if err != nil {
return err
}
@@ -419,7 +454,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint)
if err != nil {
return err
}
@@ -427,7 +462,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
@@ -446,7 +481,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
)
for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
@@ -194,6 +195,10 @@ func publishTestflight(ctx context.Context) error {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err
}
}

View File

@@ -48,7 +48,7 @@ func FindSDK() {
}
func findNDK() bool {
const fixedVersion = "28.0.12674087"
const fixedVersion = "28.0.12916984"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath

View File

@@ -0,0 +1,68 @@
package main
import (
"encoding/csv"
"io"
"net/http"
"os"
"strings"
"github.com/sagernet/sing-box/log"
"golang.org/x/exp/slices"
)
func main() {
err := updateMozillaIncludedRootCAs()
if err != nil {
log.Error(err)
}
}
func updateMozillaIncludedRootCAs() error {
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
if err != nil {
return err
}
defer response.Body.Close()
reader := csv.NewReader(response.Body)
header, err := reader.Read()
if err != nil {
return err
}
geoIndex := slices.Index(header, "Geographic Focus")
nameIndex := slices.Index(header, "Common Name or Certificate Name")
certIndex := slices.Index(header, "PEM Info")
generated := strings.Builder{}
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
package certificate
import "crypto/x509"
var mozillaIncluded *x509.CertPool
func init() {
mozillaIncluded = x509.NewCertPool()
`)
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return err
}
if record[geoIndex] == "China" {
continue
}
generated.WriteString("\n // ")
generated.WriteString(record[nameIndex])
generated.WriteString("\n")
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
generated.WriteString(record[certIndex])
generated.WriteString("`))\n")
}
generated.WriteString("}\n")
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
}

View File

@@ -30,7 +30,7 @@ func init() {
}
func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}

View File

@@ -61,14 +61,15 @@ func upgradeRuleSet(sourcePath string) error {
log.Info("already up-to-date")
return nil
}
plainRuleSet, err := plainRuleSetCompat.Upgrade()
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
if err != nil {
return err
}
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSet)
err = encoder.Encode(plainRuleSetCompat)
if err != nil {
return E.Cause(err, "encode config")
}

File diff suppressed because it is too large Load Diff

184
common/certificate/store.go Normal file
View File

@@ -0,0 +1,184 @@
package certificate
import (
"context"
"crypto/x509"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
)
var _ adapter.CertificateStore = (*Store)(nil)
type Store struct {
systemPool *x509.CertPool
currentPool *x509.CertPool
certificate string
certificatePaths []string
certificateDirectoryPaths []string
watcher *fswatch.Watcher
}
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
var systemPool *x509.CertPool
switch options.Store {
case C.CertificateStoreSystem, "":
platformInterface := service.FromContext[platform.Interface](ctx)
systemCertificates := platformInterface.SystemCertificates()
if len(systemCertificates) > 0 {
systemPool = x509.NewCertPool()
for _, cert := range systemCertificates {
if !systemPool.AppendCertsFromPEM([]byte(cert)) {
return nil, E.New("invalid system certificate PEM: ", cert)
}
}
} else {
certPool, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
systemPool = certPool
}
case C.CertificateStoreMozilla:
systemPool = mozillaIncluded
case C.CertificateStoreNone:
systemPool = nil
default:
return nil, E.New("unknown certificate store: ", options.Store)
}
store := &Store{
systemPool: systemPool,
certificate: strings.Join(options.Certificate, "\n"),
certificatePaths: options.CertificatePath,
certificateDirectoryPaths: options.CertificateDirectoryPath,
}
var watchPaths []string
for _, target := range options.CertificatePath {
watchPaths = append(watchPaths, target)
}
for _, target := range options.CertificateDirectoryPath {
watchPaths = append(watchPaths, target)
}
if len(watchPaths) > 0 {
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPaths,
Logger: logger,
Callback: func(_ string) {
err := store.update()
if err != nil {
logger.Error(E.Cause(err, "reload certificates"))
}
},
})
if err != nil {
return nil, E.Cause(err, "fswatch: create fsnotify watcher")
}
store.watcher = watcher
}
err := store.update()
if err != nil {
return nil, E.Cause(err, "initializing certificate store")
}
return store, nil
}
func (s *Store) Name() string {
return "certificate"
}
func (s *Store) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
if s.watcher != nil {
return s.watcher.Start()
}
return nil
}
func (s *Store) Close() error {
if s.watcher != nil {
return s.watcher.Close()
}
return nil
}
func (s *Store) Pool() *x509.CertPool {
return s.currentPool
}
func (s *Store) update() error {
var currentPool *x509.CertPool
if s.systemPool == nil {
currentPool = x509.NewCertPool()
} else {
currentPool = s.systemPool.Clone()
}
if s.certificate != "" {
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
return E.New("invalid certificate PEM strings")
}
}
for _, path := range s.certificatePaths {
pemContent, err := os.ReadFile(path)
if err != nil {
return err
}
if !currentPool.AppendCertsFromPEM(pemContent) {
return E.New("invalid certificate PEM file: ", path)
}
}
var firstErr error
for _, directoryPath := range s.certificateDirectoryPaths {
directoryEntries, err := readUniqueDirectoryEntries(directoryPath)
if err != nil {
if firstErr == nil && !os.IsNotExist(err) {
firstErr = E.Cause(err, "invalid certificate directory: ", directoryPath)
}
continue
}
for _, directoryEntry := range directoryEntries {
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
if err == nil {
currentPool.AppendCertsFromPEM(pemContent)
}
}
}
if firstErr != nil {
return firstErr
}
s.currentPool = currentPool
return nil
}
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
uniq := files[:0]
for _, f := range files {
if !isSameDirSymlink(f, dir) {
uniq = append(uniq, f)
}
}
return uniq, nil
}
func isSameDirSymlink(f fs.DirEntry, dir string) bool {
if f.Type()&fs.ModeSymlink == 0 {
return false
}
target, err := os.Readlink(filepath.Join(dir, f.Name()))
return err == nil && !strings.Contains(target, "/")
}

View File

@@ -35,7 +35,6 @@ type DefaultDialer struct {
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
@@ -183,11 +182,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
}
setMultiPathTCP(&dialer4)
}
if options.IsWireGuardListener {
for _, controlFn := range WgControlFns {
listener.Control = control.Append(listener.Control, controlFn)
}
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
@@ -204,7 +198,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpListener: listener,
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
isWireGuardListener: options.IsWireGuardListener,
networkManager: networkManager,
networkStrategy: networkStrategy,
defaultNetworkStrategy: defaultNetworkStrategy,

View File

@@ -29,16 +29,18 @@ func (d *DetourDialer) Start() error {
}
func (d *DetourDialer) Dialer() (N.Dialer, error) {
d.initOnce.Do(func() {
var loaded bool
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
if !loaded {
d.initErr = E.New("outbound detour not found: ", d.detour)
}
})
d.initOnce.Do(d.init)
return d.dialer, d.initErr
}
func (d *DetourDialer) init() {
var loaded bool
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
if !loaded {
d.initErr = E.New("outbound detour not found: ", d.detour)
}
}
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
dialer, err := d.Dialer()
if err != nil {

View File

@@ -16,81 +16,105 @@ import (
"github.com/sagernet/sing/service"
)
type Options struct {
Context context.Context
Options option.DialerOptions
RemoteIsDomain bool
DirectResolver bool
ResolverOnDetour bool
}
// TODO: merge with NewWithOptions
func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
if options.IsWireGuardListener {
return NewDefault(ctx, options)
}
return NewWithOptions(Options{
Context: ctx,
Options: options,
RemoteIsDomain: remoteIsDomain,
})
}
func NewWithOptions(options Options) (N.Dialer, error) {
dialOptions := options.Options
var (
dialer N.Dialer
err error
)
if options.Detour == "" {
dialer, err = NewDefault(ctx, options)
if err != nil {
return nil, err
}
} else {
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
if dialOptions.Detour != "" {
outboundManager := service.FromContext[adapter.OutboundManager](options.Context)
if outboundManager == nil {
return nil, E.New("missing outbound manager")
}
dialer = NewDetour(outboundManager, options.Detour)
}
if remoteIsDomain && options.Detour == "" && options.DomainResolver == "" {
deprecated.Report(ctx, deprecated.OptionMissingDomainResolverInDialOptions)
}
if (options.Detour == "" && remoteIsDomain) || options.DomainResolver != "" {
router := service.FromContext[adapter.DNSRouter](ctx)
if router != nil {
var resolveTransport adapter.DNSTransport
if options.DomainResolver != "" {
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
if !loaded {
return nil, E.New("DNS server not found: " + options.DomainResolver)
}
resolveTransport = transport
}
dialer = NewResolveDialer(
router,
dialer,
options.Detour == "" && !options.TCPFastOpen,
resolveTransport,
C.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay))
dialer = NewDetour(outboundManager, dialOptions.Detour)
} else {
dialer, err = NewDefault(options.Context, dialOptions)
if err != nil {
return nil, err
}
}
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) {
networkManager := service.FromContext[adapter.NetworkManager](options.Context)
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
var defaultOptions adapter.NetworkOptions
if networkManager != nil {
defaultOptions = networkManager.DefaultOptions()
}
var (
server string
dnsQueryOptions adapter.DNSQueryOptions
resolveFallbackDelay time.Duration
)
if dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "" {
var transport adapter.DNSTransport
if !options.DirectResolver {
var loaded bool
transport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server)
if !loaded {
return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server)
}
}
var strategy C.DomainStrategy
if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) {
strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy)
} else if
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
}
server = dialOptions.DomainResolver.Server
dnsQueryOptions = adapter.DNSQueryOptions{
Transport: transport,
Strategy: strategy,
DisableCache: dialOptions.DomainResolver.DisableCache,
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
}
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.DirectResolver {
return nil, E.New("missing domain resolver for domain server address")
} else if defaultOptions.DomainResolver != "" {
dnsQueryOptions = defaultOptions.DomainResolveOptions
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
dialer = NewResolveDialer(
options.Context,
dialer,
dialOptions.Detour == "" && !dialOptions.TCPFastOpen,
server,
dnsQueryOptions,
resolveFallbackDelay,
)
}
return dialer, nil
}
func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) {
if options.Detour != "" {
return nil, E.New("`detour` is not supported in direct context")
}
if options.IsWireGuardListener {
return NewDefault(ctx, options)
}
dialer, err := NewDefault(ctx, options)
if err != nil {
return nil, err
}
var resolveTransport adapter.DNSTransport
if options.DomainResolver != "" {
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
if !loaded {
return nil, E.New("DNS server not found: " + options.DomainResolver)
}
resolveTransport = transport
}
return NewResolveParallelInterfaceDialer(
service.FromContext[adapter.DNSRouter](ctx),
dialer,
true,
resolveTransport,
C.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay),
), nil
}
type ParallelInterfaceDialer interface {
N.Dialer
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)

View File

@@ -3,14 +3,17 @@ package dialer
import (
"context"
"net"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
var (
@@ -18,23 +21,37 @@ var (
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
)
type ResolveDialer interface {
N.Dialer
QueryOptions() adapter.DNSQueryOptions
}
type ParallelInterfaceResolveDialer interface {
ParallelInterfaceDialer
QueryOptions() adapter.DNSQueryOptions
}
type resolveDialer struct {
transport adapter.DNSTransportManager
router adapter.DNSRouter
dialer N.Dialer
parallel bool
router adapter.DNSRouter
transport adapter.DNSTransport
strategy C.DomainStrategy
server string
initOnce sync.Once
initErr error
queryOptions adapter.DNSQueryOptions
fallbackDelay time.Duration
}
func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
return &resolveDialer{
dialer,
parallel,
router,
transport,
strategy,
fallbackDelay,
transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx),
dialer: dialer,
parallel: parallel,
server: server,
queryOptions: queryOptions,
fallbackDelay: fallbackDelay,
}
}
@@ -43,42 +60,68 @@ type resolveParallelNetworkDialer struct {
dialer ParallelInterfaceDialer
}
func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
return &resolveParallelNetworkDialer{
resolveDialer{
dialer,
parallel,
router,
transport,
strategy,
fallbackDelay,
transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx),
dialer: dialer,
parallel: parallel,
server: server,
queryOptions: queryOptions,
fallbackDelay: fallbackDelay,
},
dialer,
}
}
func (d *resolveDialer) initialize() error {
d.initOnce.Do(d.initServer)
return d.initErr
}
func (d *resolveDialer) initServer() {
if d.server == "" {
return
}
transport, loaded := d.transport.Transport(d.server)
if !loaded {
d.initErr = E.New("domain resolver not found: " + d.server)
return
}
d.queryOptions.Transport = transport
}
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
err := d.initialize()
if err != nil {
return nil, err
}
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
if err != nil {
return nil, err
}
if d.parallel {
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
} else {
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
}
}
func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
err := d.initialize()
if err != nil {
return nil, err
}
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
if err != nil {
return nil, err
}
@@ -89,12 +132,24 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions {
return d.queryOptions
}
func (d *resolveDialer) Upstream() any {
return d.dialer
}
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
err := d.initialize()
if err != nil {
return nil, err
}
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
if err != nil {
return nil, err
}
@@ -102,21 +157,28 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
fallbackDelay = d.fallbackDelay
}
if d.parallel {
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
}
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
err := d.initialize()
if err != nil {
return nil, err
}
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
if err != nil {
return nil, err
}
if fallbackDelay == 0 {
fallbackDelay = d.fallbackDelay
}
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil {
return nil, err
@@ -124,6 +186,10 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *resolveDialer) Upstream() any {
func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions {
return d.queryOptions
}
func (d *resolveParallelNetworkDialer) Upstream() any {
return d.dialer
}

View File

@@ -23,6 +23,7 @@ type Config struct {
}
type Info struct {
ProcessID uint32
ProcessPath string
PackageName string
User string

View File

@@ -2,14 +2,11 @@ package process
import (
"context"
"fmt"
"net/netip"
"os"
"syscall"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/winiphlpapi"
"golang.org/x/sys/windows"
)
@@ -26,209 +23,39 @@ func NewSearcher(_ Config) (Searcher, error) {
return &windowsSearcher{}, nil
}
var (
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
procGetExtendedUdpTable = modiphlpapi.NewProc("GetExtendedUdpTable")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
)
func initWin32API() error {
err := modiphlpapi.Load()
if err != nil {
return E.Cause(err, "load iphlpapi.dll")
}
err = procGetExtendedTcpTable.Find()
if err != nil {
return E.Cause(err, "load iphlpapi::GetExtendedTcpTable")
}
err = procGetExtendedUdpTable.Find()
if err != nil {
return E.Cause(err, "load iphlpapi::GetExtendedUdpTable")
}
err = modkernel32.Load()
if err != nil {
return E.Cause(err, "load kernel32.dll")
}
err = procQueryFullProcessImageNameW.Find()
if err != nil {
return E.Cause(err, "load kernel32::QueryFullProcessImageNameW")
}
return nil
return winiphlpapi.LoadExtendedTable()
}
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
pid, err := winiphlpapi.FindPid(network, source)
if err != nil {
return nil, err
}
return &Info{ProcessPath: processName, UserId: -1}, nil
}
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
family := windows.AF_INET
if ip.Is6() {
family = windows.AF_INET6
}
const (
tcpTablePidConn = 4
udpTablePid = 1
)
var class int
var fn uintptr
switch network {
case N.NetworkTCP:
fn = procGetExtendedTcpTable.Addr()
class = tcpTablePidConn
case N.NetworkUDP:
fn = procGetExtendedUdpTable.Addr()
class = udpTablePid
default:
return "", os.ErrInvalid
}
buf, err := getTransportTable(fn, family, class)
path, err := getProcessPath(pid)
if err != nil {
return "", err
return &Info{ProcessID: pid, UserId: -1}, err
}
s := newSearcher(family == windows.AF_INET, network == N.NetworkTCP)
pid, err := s.Search(buf, ip, uint16(srcPort))
if err != nil {
return "", err
}
return getExecPathFromPID(pid)
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
}
type searcher struct {
itemSize int
port int
ip int
ipSize int
pid int
tcpState int
}
func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
n := int(readNativeUint32(b[:4]))
itemSize := s.itemSize
for i := 0; i < n; i++ {
row := b[4+itemSize*i : 4+itemSize*(i+1)]
if s.tcpState >= 0 {
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
if tcpState != 5 {
continue
}
}
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
// this field can be illustrated as follows depends on different machine endianess:
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
// big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB)
// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
if srcPort != port {
continue
}
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue
}
pid := readNativeUint32(row[s.pid : s.pid+4])
return pid, nil
}
return 0, ErrNotFound
}
func newSearcher(isV4, isTCP bool) *searcher {
var itemSize, port, ip, ipSize, pid int
tcpState := -1
switch {
case isV4 && isTCP:
// struct MIB_TCPROW_OWNER_PID
itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
case isV4 && !isTCP:
// struct MIB_UDPROW_OWNER_PID
itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
case !isV4 && isTCP:
// struct MIB_TCP6ROW_OWNER_PID
itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
case !isV4 && !isTCP:
// struct MIB_UDP6ROW_OWNER_PID
itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
}
return &searcher{
itemSize: itemSize,
port: port,
ip: ip,
ipSize: ipSize,
pid: pid,
tcpState: tcpState,
}
}
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
for size, buf := uint32(8), make([]byte, 8); ; {
ptr := unsafe.Pointer(&buf[0])
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
switch err {
case 0:
return buf, nil
case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
buf = make([]byte, size)
default:
return nil, fmt.Errorf("syscall error: %d", err)
}
}
}
func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}
func getExecPathFromPID(pid uint32) (string, error) {
// kernel process starts with a colon in order to distinguish with normal processes
func getProcessPath(pid uint32) (string, error) {
switch pid {
case 0:
// reserved pid for system idle process
return ":System Idle Process", nil
case 4:
// reserved pid for windows kernel image
return ":System", nil
}
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
if err != nil {
return "", err
}
defer windows.CloseHandle(h)
defer windows.CloseHandle(handle)
size := uint32(syscall.MAX_LONG_PATH)
buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(len(buf))
r1, _, err := syscall.SyscallN(
procQueryFullProcessImageNameW.Addr(),
uintptr(h),
uintptr(0),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)),
)
if r1 == 0 {
err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size)
if err != nil {
return "", err
}
return syscall.UTF16ToString(buf[:size]), nil
return windows.UTF16ToString(buf[:size]), nil
}

View File

@@ -18,5 +18,6 @@ func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Rea
}
metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
metadata.HTTPRequest = request
return nil
}

View File

@@ -21,6 +21,7 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
if clientHello != nil {
metadata.Protocol = C.ProtocolTLS
metadata.Domain = clientHello.ServerName
metadata.ClientHello = clientHello
return nil
}
return err

View File

@@ -100,6 +100,7 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
var tlsConfig cftls.Config
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -90,7 +90,7 @@ func (c *echServerConfig) startWatcher() error {
Callback: func(path string) {
err := c.credentialsUpdated(path)
if err != nil {
c.logger.Error(E.Cause(err, "reload credentials from ", path))
c.logger.Error(E.Cause(err, "reload credentials"))
}
},
})

View File

@@ -8,11 +8,14 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"
M "github.com/sagernet/sing/common/metadata"
)
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
if err != nil {
return nil, err
}
@@ -23,7 +26,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer
return &certificate, err
}
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
if timeFunc == nil {
timeFunc = time.Now
}
@@ -35,19 +38,36 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T
if err != nil {
return
}
template := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: serverName,
},
DNSNames: []string{serverName},
var template *x509.Certificate
if serverAddress := M.ParseAddr(serverName); serverAddress.IsValid() {
template = &x509.Certificate{
SerialNumber: serialNumber,
IPAddresses: []net.IP{serverAddress.AsSlice()},
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
} else {
template = &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: serverName,
},
DNSNames: []string{serverName},
}
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if parent == nil {
parent = template
parentKey = key
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
if err != nil {
return
}

View File

@@ -27,9 +27,11 @@ import (
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls"
utls "github.com/sagernet/utls"
@@ -40,6 +42,7 @@ import (
var _ ConfigCompat = (*RealityClientConfig)(nil)
type RealityClientConfig struct {
ctx context.Context
uClient *UTLSClientConfig
publicKey []byte
shortID [8]byte
@@ -70,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
if decodedLen > 8 {
return nil, E.New("invalid short_id")
}
return &RealityClientConfig{uClient, publicKey, shortID}, nil
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
}
func (e *RealityClientConfig) ServerName() string {
@@ -180,20 +183,24 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
}
if !verifier.verified {
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
return nil, E.New("reality verification failed")
}
return &realityClientConnWrapper{uConn}, nil
}
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
defer uConn.Close()
client := &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
return uConn, nil
},
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(ctx),
RootCAs: adapter.RootPoolFromContext(ctx),
},
},
}
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
@@ -213,6 +220,7 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [
func (e *RealityClientConfig) Clone() Config {
return &RealityClientConfig{
e.ctx,
e.uClient.Clone().(*UTLSClientConfig),
e.publicKey,
e.shortID,

View File

@@ -8,6 +8,7 @@ import (
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
@@ -58,6 +59,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
var tlsConfig tls.Config
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -99,7 +99,7 @@ func (c *STDServerConfig) startWatcher() error {
Callback: func(path string) {
err := c.certificateUpdated(path)
if err != nil {
c.logger.Error(err)
c.logger.Error(E.Cause(err, "reload certificate"))
}
},
})
@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -12,6 +12,7 @@ import (
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
@@ -130,6 +131,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
var tlsConfig utls.Config
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -5,26 +5,26 @@ import (
"math/rand"
"net"
"strings"
"syscall"
"time"
"github.com/sagernet/sing/common"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/publicsuffix"
)
type Conn struct {
net.Conn
syscallConn syscall.Conn
tcpConn *net.TCPConn
ctx context.Context
firstPacketWritten bool
fallbackDelay time.Duration
}
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
syscallConn, _ := N.UnwrapReader(conn).(syscall.Conn)
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
return &Conn{
Conn: conn,
syscallConn: syscallConn,
tcpConn: tcpConn,
ctx: ctx,
fallbackDelay: fallbackDelay,
}, nil
@@ -37,39 +37,18 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}()
serverName := indexTLSServerName(b)
if serverName != nil {
tcpConn, isTCPConn := c.syscallConn.(interface {
SetNoDelay(bool) error
})
if isTCPConn {
err = tcpConn.SetNoDelay(true)
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(true)
if err != nil {
return
}
}
splits := strings.Split(string(b[serverName.Index:serverName.Index+serverName.Length]), ".")
splits := strings.Split(serverName.ServerName, ".")
currentIndex := serverName.Index
var striped bool
if len(splits) > 3 {
suffix := splits[len(splits)-3] + "." + splits[len(splits)-2] + "." + splits[len(splits)-1]
if publicSuffixMatcher().Match(suffix) {
splits = splits[:len(splits)-3]
}
striped = true
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
}
if !striped && len(splits) > 2 {
suffix := splits[len(splits)-2] + "." + splits[len(splits)-1]
if publicSuffixMatcher().Match(suffix) {
splits = splits[:len(splits)-2]
}
striped = true
}
if !striped && len(splits) > 1 {
suffix := splits[len(splits)-1]
if publicSuffixMatcher().Match(suffix) {
splits = splits[:len(splits)-1]
}
}
if len(splits) > 1 && common.Contains(publicPrefix, splits[0]) {
if len(splits) > 1 && splits[0] == "..." {
currentIndex += len(splits[0]) + 1
splits = splits[1:]
}
@@ -83,25 +62,28 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}
}
for i := 0; i <= len(splitIndexes); i++ {
var payload []byte
if i == 0 {
_, err = c.Conn.Write(b[:splitIndexes[i]])
payload = b[:splitIndexes[i]]
} else if i == len(splitIndexes) {
_, err = c.Conn.Write(b[splitIndexes[i-1]:])
payload = b[splitIndexes[i-1]:]
} else {
_, err = c.Conn.Write(b[splitIndexes[i-1]:splitIndexes[i]])
payload = b[splitIndexes[i-1]:splitIndexes[i]]
}
if err != nil {
return
}
if c.syscallConn != nil && i != len(splitIndexes) {
err = waitAck(c.ctx, c.syscallConn, c.fallbackDelay)
if c.tcpConn != nil && i != len(splitIndexes) {
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
if err != nil {
return
}
} else {
_, err = c.Conn.Write(payload)
if err != nil {
return
}
}
}
if isTCPConn {
err = tcpConn.SetNoDelay(false)
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(false)
if err != nil {
return
}

View File

@@ -23,9 +23,9 @@ const (
)
type myServerName struct {
Index int
Length int
sex []byte
Index int
Length int
ServerName string
}
func indexTLSServerName(payload []byte) *myServerName {
@@ -68,15 +68,9 @@ func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
return nil
}
csLen := uint16(cs[0])<<8 | uint16(cs[1])
numCiphers := int(csLen / 2)
cipherSuites := make([]uint16, 0, numCiphers)
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
return nil
}
for i := 0; i < numCiphers; i++ {
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
cipherSuites = append(cipherSuites, cipherSuite)
}
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
return nil
@@ -125,9 +119,9 @@ func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
sex = sex[sniExtensionHeaderLen:]
return &myServerName{
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
Length: int(sniLen),
sex: sex,
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
Length: int(sniLen),
ServerName: string(sex),
}
}
exs = exs[4+exLen:]

View File

@@ -1,55 +0,0 @@
package tf
import (
"bufio"
"bytes"
_ "embed"
"io"
"strings"
"sync"
"github.com/sagernet/sing/common/domain"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var publicPrefix = []string{
"www",
}
//go:generate wget -O public_suffix_list.dat https://publicsuffix.org/list/public_suffix_list.dat
//go:embed public_suffix_list.dat
var publicSuffix []byte
var publicSuffixMatcher = sync.OnceValue(func() *domain.Matcher {
matcher, err := initPublicSuffixMatcher()
if err != nil {
panic(F.ToString("error in initialize public suffix matcher"))
}
return matcher
})
func initPublicSuffixMatcher() (*domain.Matcher, error) {
reader := bufio.NewReader(bytes.NewReader(publicSuffix))
var domainList []string
for {
line, isPrefix, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if isPrefix {
return nil, E.New("unexpected prefix line")
}
lineStr := string(line)
lineStr = strings.TrimSpace(lineStr)
if lineStr == "" || strings.HasPrefix(lineStr, "//") {
continue
}
domainList = append(domainList, lineStr)
}
return domain.NewMatcher(domainList, nil, false), nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ package tf
import (
"context"
"syscall"
"net"
"time"
"github.com/sagernet/sing/common/control"
@@ -63,7 +63,11 @@ func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32)
}
*/
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
_, err := conn.Write(payload)
if err != nil {
return err
}
return control.Conn(conn, func(fd uintptr) error {
start := time.Now()
for {

View File

@@ -2,7 +2,7 @@ package tf
import (
"context"
"syscall"
"net"
"time"
"github.com/sagernet/sing/common/control"
@@ -10,7 +10,11 @@ import (
"golang.org/x/sys/unix"
)
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
_, err := conn.Write(payload)
if err != nil {
return err
}
return control.Conn(conn, func(fd uintptr) error {
start := time.Now()
for {

View File

@@ -1,14 +1,14 @@
//go:build !(linux || darwin)
//go:build !(linux || darwin || windows)
package tf
import (
"context"
"syscall"
"net"
"time"
)
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
time.Sleep(fallbackDelay)
return nil
}

View File

@@ -0,0 +1,28 @@
package tf
import (
"context"
"errors"
"net"
"time"
"github.com/sagernet/sing/common/winiphlpapi"
"golang.org/x/sys/windows"
)
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
start := time.Now()
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
if err != nil {
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
time.Sleep(fallbackDelay)
return nil
}
return err
}
if time.Since(start) <= 20*time.Millisecond {
time.Sleep(fallbackDelay)
}
return nil
}

View File

@@ -2,32 +2,32 @@ package urltest
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/url"
"sync"
"time"
"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"
)
type History struct {
Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
}
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
type HistoryStorage struct {
access sync.RWMutex
delayHistory map[string]*History
delayHistory map[string]*adapter.URLTestHistory
updateHook chan<- struct{}
}
func NewHistoryStorage() *HistoryStorage {
return &HistoryStorage{
delayHistory: make(map[string]*History),
delayHistory: make(map[string]*adapter.URLTestHistory),
}
}
@@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
s.updateHook = hook
}
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
if s == nil {
return nil
}
@@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
s.notifyUpdated()
}
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
s.access.Lock()
s.delayHistory[tag] = history
s.access.Unlock()
@@ -110,6 +110,10 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return instance, nil
},
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(ctx),
RootCAs: adapter.RootPoolFromContext(ctx),
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse

7
constant/certificate.go Normal file
View File

@@ -0,0 +1,7 @@
package constant
const (
CertificateStoreSystem = "system"
CertificateStoreMozilla = "mozilla"
CertificateStoreNone = "none"
)

7
constant/script.go Normal file
View File

@@ -0,0 +1,7 @@
package constant
const (
ScriptTypeSurge = "surge"
ScriptSourceTypeLocal = "local"
ScriptSourceTypeRemote = "remote"
)

View File

@@ -252,7 +252,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
ruleIndex = -1
for {
dnsCtx := adapter.OverrideContext(ctx)
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &options)
dnsOptions := options
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
if rule != nil {
switch action := rule.Action().(type) {
case *R.RuleActionReject:
@@ -271,10 +272,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
return rule.MatchAddressLimit(metadata)
}
}
if options.Strategy == C.DomainStrategyAsIS {
options.Strategy = r.defaultDomainStrategy
if dnsOptions.Strategy == C.DomainStrategyAsIS {
dnsOptions.Strategy = r.defaultDomainStrategy
}
response, err = r.client.Exchange(dnsCtx, transport, message, options, responseCheck)
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
var rejected bool
if err != nil {
if errors.Is(err, ErrResponseRejectedCached) {

View File

@@ -36,6 +36,14 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
}, nil
}
func (t *Transport) Start() error {
return t.store.Start()
}
func (t *Transport) Close() error {
return t.store.Close()
}
func (t *Transport) Reset() {
}

View File

@@ -10,6 +10,7 @@ import (
)
func TestHosts(t *testing.T) {
t.Parallel()
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
}

View File

@@ -1,8 +1,17 @@
package hosts
import _ "unsafe"
import (
"path/filepath"
var DefaultPath = getSystemDirectory() + "/Drivers/etc/hosts"
"golang.org/x/sys/windows"
)
//go:linkname getSystemDirectory internal/syscall/windows.GetSystemDirectory
func getSystemDirectory() string
var DefaultPath string
func init() {
systemDirectory, err := windows.GetSystemDirectory()
if err != nil {
systemDirectory = "C:\\Windows\\System32"
}
DefaultPath = filepath.Join(systemDirectory, "Drivers/etc/hosts")
}

View File

@@ -2,6 +2,7 @@ package local
import (
"context"
"math/rand"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -147,7 +148,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
}
request := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: uint16(randInt()),
Id: uint16(rand.Uint32()),
RecursionDesired: true,
AuthenticatedData: ad,
},

View File

@@ -7,7 +7,6 @@ import (
"sync"
"sync/atomic"
"time"
_ "unsafe"
)
const (
@@ -118,7 +117,7 @@ func (conf *dnsConfig) nameList(name string) []string {
hasNdots := strings.Count(name, ".") >= conf.ndots
name += "."
l++
// l++
names := make([]string, 0, 1+len(conf.search))
if hasNdots && !avoidDNS(name) {
@@ -136,13 +135,6 @@ func (conf *dnsConfig) nameList(name string) []string {
return names
}
//go:linkname runtime_rand runtime.rand
func runtime_rand() uint64
func randInt() int {
return int(uint(runtime_rand()) >> 1) // clear sign bit
}
func avoidDNS(name string) bool {
if name == "" {
return true

View File

@@ -27,9 +27,14 @@ func NewTransportAdapter(transportType string, transportTag string, dependencies
}
func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter {
var dependencies []string
if localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != "" {
dependencies = append(dependencies, localOptions.DomainResolver.Server)
}
return TransportAdapter{
transportType: transportType,
transportTag: transportTag,
dependencies: dependencies,
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
clientSubnet: localOptions.LegacyClientSubnet,
}
@@ -37,8 +42,11 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {
var dependencies []string
if remoteOptions.AddressResolver != "" {
dependencies = []string{remoteOptions.AddressResolver}
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
}
if remoteOptions.LegacyAddressResolver != "" {
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
}
return TransportAdapter{
transportType: transportType,

View File

@@ -19,37 +19,39 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
if options.LegacyDefaultDialer {
return dialer.NewDefaultOutbound(ctx), nil
} else {
return dialer.New(ctx, options.DialerOptions, false)
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
DirectResolver: true,
})
}
}
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
var (
transportDialer N.Dialer
err error
)
if options.LegacyDefaultDialer {
transportDialer = dialer.NewDefaultOutbound(ctx)
} else {
transportDialer, err = dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
}
if err != nil {
return nil, err
}
if options.AddressResolver != "" {
transport := service.FromContext[adapter.DNSTransportManager](ctx)
resolverTransport, loaded := transport.Transport(options.AddressResolver)
if !loaded {
return nil, E.New("address resolver not found: ", options.AddressResolver)
transportDialer := dialer.NewDefaultOutbound(ctx)
if options.LegacyAddressResolver != "" {
transport := service.FromContext[adapter.DNSTransportManager](ctx)
resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver)
if !loaded {
return nil, E.New("address resolver not found: ", options.LegacyAddressResolver)
}
transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay))
} else if options.ServerIsDomain() {
return nil, E.New("missing address resolver for server: ", options.Server)
}
transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay))
} else if options.ServerIsDomain() {
return nil, E.New("missing address resolver for server: ", options.Server)
return transportDialer, nil
} else {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(),
DirectResolver: true,
})
}
return transportDialer, nil
}
type TransportDialer struct {
type legacyTransportDialer struct {
dialer N.Dialer
dnsRouter adapter.DNSRouter
transport adapter.DNSTransport
@@ -57,8 +59,8 @@ type TransportDialer struct {
fallbackDelay time.Duration
}
func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer {
return &TransportDialer{
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
return &legacyTransportDialer{
dialer,
dnsRouter,
transport,
@@ -67,7 +69,7 @@ func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport
}
}
func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if destination.IsIP() {
return d.dialer.DialContext(ctx, network, destination)
}
@@ -81,7 +83,7 @@ func (d *TransportDialer) DialContext(ctx context.Context, network string, desti
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
}
func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if destination.IsIP() {
return d.dialer.ListenPacket(ctx, destination)
}
@@ -96,6 +98,6 @@ func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksa
return conn, err
}
func (d *TransportDialer) Upstream() any {
func (d *legacyTransportDialer) Upstream() any {
return d.dialer
}

View File

@@ -2,10 +2,182 @@
icon: material/alert-decagram
---
#### 1.11.0-beta.24
#### 1.12.0-alpha.3
* Fixes and improvements
#### 1.12.0-alpha.2
* Update quic-go to v0.49.0
* Fixes and improvements
#### 1.12.0-alpha.1
* Refactor DNS servers **1**
* Add domain resolver options**2**
* Add TLS fragment route options **3**
* Add certificate options **4**
**1**:
DNS servers are refactored for better performance and scalability.
See [DNS server](/configuration/dns/server/).
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
Compatibility for old formats will be removed in sing-box 1.14.0.
**2**:
Legacy `outbound` DNS rules are deprecated
and can be replaced by the new `domain_resolver` option.
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
[Route](/configuration/route/#default_domain_resolver).
For migration, see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
**3**:
The new TLS fragment route options allow you to fragment TLS handshakes to bypass firewalls.
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
See [Route Action](/configuration/route/rule_action/#tls_fragment).
**4**:
New certificate options allow you to manage the default list of trusted X509 CA certificates.
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
See [Certificate](/configuration/certificate/).
### 1.11.0
Important changes since 1.10:
* Introducing rule actions **1**
* Improve tun compatibility **3**
* Merge route options to route actions **4**
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
* Add multi network dialing **6**
* Add `cache_capacity` DNS option **7**
* Add `override_address` and `override_port` route options **8**
* Upgrade WireGuard outbound to endpoint **9**
* Add UDP GSO support for WireGuard
* Make GSO adaptive **10**
* Add UDP timeout route option **11**
* Add more masquerade options for hysteria2 **12**
* Add `rule-set merge` command
* Add port hopping support for Hysteria2 **13**
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
**1**:
New rule actions replace legacy inbound fields and special outbound fields,
and can be used for pre-matching **2**.
See [Rule](/configuration/route/rule/),
[Rule Action](/configuration/route/rule_action/),
[DNS Rule](/configuration/dns/rule/) and
[DNS Rule Action](/configuration/dns/rule_action/).
For migration, see
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
**2**:
Similar to Surge's pre-matching.
Specifically, new rule actions allow you to reject connections with
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
before connection established to improve tun's compatibility.
See [Rule Action](/configuration/route/rule_action/).
**3**:
When `gvisor` tun stack is enabled, even if the request passes routing,
if the outbound connection establishment fails,
the connection still does not need to be established and a TCP RST is replied.
**4**:
Route options in DNS route actions will no longer be considered deprecated,
see [DNS Route Action](/configuration/dns/rule_action/).
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
see [Route Action](/configuration/route/rule_action/).
**5**:
When using in graphical clients, new routing rule items allow you to match on
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
**6**:
Similar to Surge's strategy.
New options allow you to connect using multiple network interfaces,
prefer or only use one type of interface,
and configure a timeout to fallback to other interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy),
[Rule Action](/configuration/route/rule_action/#network_strategy)
and [Route](/configuration/route/#default_network_strategy).
**7**:
See [DNS](/configuration/dns/#cache_capacity).
**8**:
See [Rule Action](/configuration/route/#override_address) and
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
**9**:
The new WireGuard endpoint combines inbound and outbound capabilities,
and the old outbound will be removed in sing-box 1.13.0.
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
**10**:
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
For TUN, GSO has been removed,
see [Deprecated](/deprecated/#gso-option-in-tun).
**11**:
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
**12**:
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
**13**:
See [Hysteria2](/configuration/outbound/hysteria2/).
**14**:
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
### 1.10.7
* Fixes and improvements

View File

@@ -0,0 +1,54 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# Certificate
### Structure
```json
{
"store": "",
"certificate": [],
"certificate_path": [],
"certificate_directory_path": []
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Fields
#### store
The default X509 trusted CA certificate list.
| Type | Description |
|--------------------|---------------------------------------------------------------------------------------------------------------|
| `system` (default) | System trusted CA certificates |
| `mozilla` | [Mozilla Included List](https://wiki.mozilla.org/CA/Included_Certificates) with China CA certificates removed |
| `none` | Empty list |
#### certificate
The certificate line array to trust, in PEM format.
#### certificate_path
!!! note ""
Will be automatically reloaded if file modified.
The paths to certificates to trust, in PEM format.
#### certificate_directory_path
!!! note ""
Will be automatically reloaded if file modified.
The directory path to search for certificates to trust,in PEM format.

View File

@@ -1,3 +1,11 @@
---
icon: material/delete-clock
---
!!! failure "Deprecated in sing-box 1.12.0"
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
### Structure
```json

View File

@@ -1,3 +1,11 @@
---
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.12.0 废弃"
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
### 结构
```json

View File

@@ -49,8 +49,6 @@ Default domain strategy for resolving the domain names.
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
Take no effect if `server.strategy` is set.
#### disable_cache
Disable dns cache.

View File

@@ -48,8 +48,6 @@ icon: material/new-box
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
如果设置了 `server.strategy`,则不生效。
#### disable_cache
禁用 DNS 缓存。

View File

@@ -1,7 +1,11 @@
---
icon: material/new-box
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.12.0"
:material-delete-clock: [outbound](#outbound)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [action](#action)
@@ -395,6 +399,10 @@ Invert match result.
#### outbound
!!! failure "Deprecated in sing-box 1.12.0"
`outbound` rule items are deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
Match outbound.
`any` can be used as a value to match any outbound.

View File

@@ -1,7 +1,11 @@
---
icon: material/new-box
icon: material/alert-decagram
---
!!! quote "sing-box 1.12.0 中的更改"
:material-delete-clock: [outbound](#outbound)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
@@ -395,6 +399,10 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### outbound
!!! failure "已在 sing-box 1.12.0 废弃"
`outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver)。
匹配出站。
`any` 可作为值用于匹配任意出站。

View File

@@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [strategy](#strategy)
!!! question "Since sing-box 1.11.0"
### route
@@ -10,6 +14,7 @@ icon: material/new-box
{
"action": "route", // default
"server": "",
"strategy": "",
"disable_cache": false,
"rewrite_ttl": 0,
"client_subnet": null
@@ -24,6 +29,12 @@ icon: material/new-box
Tag of target server.
#### strategy
Set domain strategy for this query.
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
#### disable_cache
Disable cache and save cache in this query.

View File

@@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [strategy](#strategy)
!!! question "自 sing-box 1.11.0 起"
### route
@@ -10,8 +14,8 @@ icon: material/new-box
{
"action": "route", // 默认
"server": "",
// 兼容性
"strategy": "",
"disable_cache": false,
"rewrite_ttl": 0,
"client_subnet": null
@@ -26,6 +30,12 @@ icon: material/new-box
目标 DNS 服务器的标签。
#### strategy
为此查询设置域名策略。
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
#### disable_cache
在此查询中禁用缓存。

View File

@@ -0,0 +1,38 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# DHCP
### Structure
```json
{
"dns": {
"servers": [
{
"type": "dhcp",
"tag": "",
"interface": "",
// Dial Fields
}
]
}
}
```
### Fields
#### interface
Interface name to listen on.
Tge default interface will be used by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,33 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# Fake IP
### Structure
```json
{
"dns": {
"servers": {
"type": "fakeip",
"tag": "",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
}
}
```
### Fields
#### inet4_range
IPv4 address range for FakeIP.
#### inet6_address
IPv6 address range for FakeIP.

View File

@@ -0,0 +1,69 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# DNS over HTTP3 (DoH3)
### Structure
```json
{
"dns": {
"servers": {
"type": "h3",
"tag": "",
"server": "",
"server_port": 443,
"path": "",
"headers": {},
"tls": {},
// Dial Fields
}
}
}
```
!!! info "Difference from legacy H3 server"
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
### Fields
#### server
==Required==
The address of the DNS server.
If domain name is used, `domain_resolver` must also be set to resolve IP address.
#### server_port
The port of the DNS server.
`853` will be used by default.
#### path
The path of the DNS server.
`/dns-query` will be used by default.
#### headers
Additional headers to be sent to the DNS server.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,69 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# DNS over HTTPS (DoH)
### Structure
```json
{
"dns": {
"servers": {
"type": "https",
"tag": "",
"server": "",
"server_port": 443,
"path": "",
"headers": {},
"tls": {},
// Dial Fields
}
}
}
```
!!! info "Difference from legacy HTTPS server"
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
### Fields
#### server
==Required==
The address of the DNS server.
If domain name is used, `domain_resolver` must also be set to resolve IP address.
#### server_port
The port of the DNS server.
`853` will be used by default.
#### path
The path of the DNS server.
`/dns-query` will be used by default.
#### headers
Additional headers to be sent to the DNS server.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,46 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [type](#type)
# DNS Server
### Structure
```json
{
"dns": {
"servers": [
{
"type": "",
"tag": ""
}
]
}
}
```
#### type
The type of the DNS server.
| Type | Format |
|-----------------|-----------------------------------------------------|
| empty (default) | [Legacy](/configuration/dns/server/legacy/) |
| `tcp` | [TCP](/configuration/dns/server/tcp/) |
| `udp` | [UDP](/configuration/dns/server/udp/) |
| `tls` | [TLS](/configuration/dns/server/tls/) |
| `https` | [HTTPS](/configuration/dns/server/https/) |
| `quic` | [QUIC](/configuration/dns/server/quic/) |
| `h3` | [HTTP/3](/configuration/dns/server/http3/) |
| `predefined` | [Predefined](/configuration/dns/server/predefined/) |
| `dhcp` | [DHCP](/configuration/dns/server/dhcp/) |
| `fakeip` | [Fake IP](/configuration/dns/server/fakeip/) |
#### tag
The tag of the DNS server.

View File

@@ -1,3 +1,11 @@
---
icon: material/delete-clock
---
!!! failure "Deprecated in sing-box 1.12.0"
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)

View File

@@ -1,3 +1,11 @@
---
icon: material/delete-clock
---
!!! failure "Deprecated in sing-box 1.12.0"
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)

View File

@@ -0,0 +1,33 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# Local
### Structure
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "",
// Dial Fields
}
]
}
}
```
!!! info "Difference from legacy local server"
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,93 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# Predefined
### Structure
```json
{
"dns": {
"servers": [
{
"type": "predefined",
"tag": "",
"responses": []
}
]
}
}
```
### Fields
#### responses
==Required==
List of [Response](#response-structure).
### Response Structure
```json
{
"query": [],
"query_type": [],
"rcode": "",
"answer": [],
"ns": [],
"extra": []
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Response Fields
#### query
List of domain name to match.
#### query_type
List of query type to match.
#### rcode
The response code.
| Value | Value in the legacy rcode server | Description |
|------------|----------------------------------|-----------------|
| `NOERROR` | `success` | Ok |
| `FORMERR` | `format_error` | Bad request |
| `SERVFAIL` | `server_failure` | Server failure |
| `NXDOMAIN` | `name_error` | Not found |
| `NOTIMP` | `not_implemented` | Not implemented |
| `REFUSED` | `refused` | Refused |
`NOERROR` will be used by default.
#### answer
List of text DNS record to respond as answers.
Examples:
| Record Type | Example |
|-------------|-------------------------------|
| `A` | `localhost. IN A 127.0.0.1` |
| `AAAA` | `localhost. IN AAAA ::1` |
| `TXT` | `localhost. IN TXT \"Hello\"` |
#### ns
List of text DNS record to respond as name servers.
#### extra
List of text DNS record to respond as extra records.

View File

@@ -0,0 +1,56 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# DNS over QUIC (DoQ)
### Structure
```json
{
"dns": {
"servers": {
"type": "quic",
"tag": "",
"server": "",
"server_port": 853,
"tls": {},
// Dial Fields
}
}
}
```
!!! info "Difference from legacy QUIC server"
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
### Fields
#### server
==Required==
The address of the DNS server.
If domain name is used, `domain_resolver` must also be set to resolve IP address.
#### server_port
The port of the DNS server.
`853` will be used by default.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,50 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# TCP
### Structure
```json
{
"dns": {
"servers": {
"type": "tcp",
"tag": "",
"server": "",
"server_port": 53,
// Dial Fields
}
}
}
```
!!! info "Difference from legacy TCP server"
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
### Fields
#### server
==Required==
The address of the DNS server.
If domain name is used, `domain_resolver` must also be set to resolve IP address.
#### server_port
The port of the DNS server.
`53` will be used by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,56 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# DNS over TLS (DoT)
### Structure
```json
{
"dns": {
"servers": {
"type": "tls",
"tag": "",
"server": "",
"server_port": 853,
"tls": {},
// Dial Fields
}
}
}
```
!!! info "Difference from legacy TLS server"
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
### Fields
#### server
==Required==
The address of the DNS server.
If domain name is used, `domain_resolver` must also be set to resolve IP address.
#### server_port
The port of the DNS server.
`853` will be used by default.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -0,0 +1,50 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# TCP
### Structure
```json
{
"dns": {
"servers": {
"type": "udp",
"tag": "",
"server": "",
"server_port": 53,
// Dial Fields
}
}
}
```
!!! info "Difference from legacy UDP server"
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
### Fields
#### server
==Required==
The address of the DNS server.
If domain name is used, `domain_resolver` must also be set to resolve IP address.
#### server_port
The port of the DNS server.
`53` will be used by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: [access_control_allow_origin](#access_control_allow_origin)

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: [access_control_allow_origin](#access_control_allow_origin)

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.11.0 废弃"
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
!!! quote "sing-box 1.11.0 中的更改"

View File

@@ -1,10 +1,10 @@
---
icon: material/delete-clock
icon: material/note-remove
---
!!! failure "Deprecated in sing-box 1.8.0"
!!! failure "Removed in sing-box 1.12.0"
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
### Structure

View File

@@ -1,10 +1,10 @@
---
icon: material/delete-clock
icon: material/note-remove
---
!!! failure "已在 sing-box 1.8.0 废弃"
!!! failure "已在 sing-box 1.12.0 中被移除"
GeoIP 已废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
### 结构

View File

@@ -1,10 +1,10 @@
---
icon: material/delete-clock
icon: material/note-remove
---
!!! failure "Deprecated in sing-box 1.8.0"
!!! failure "Removed in sing-box 1.12.0"
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Geosite is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
### Structure

View File

@@ -1,10 +1,10 @@
---
icon: material/delete-clock
icon: material/note-remove
---
!!! failure "已在 sing-box 1.8.0 废弃"
!!! failure "已在 sing-box 1.12.0 中被移除"
Geosite 已废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
Geosite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
### 结构

View File

@@ -1,9 +1,15 @@
---
icon: material/new-box
icon: material/alert-decagram
---
# Route
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [default_domain_resolver](#default_domain_resolver)
:material-note-remove: [geoip](#geoip)
:material-note-remove: [geosite](#geosite)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [default_network_strategy](#default_network_strategy)
@@ -22,8 +28,6 @@ icon: material/new-box
```json
{
"route": {
"geoip": {},
"geosite": {},
"rules": [],
"rule_set": [],
"final": "",
@@ -31,10 +35,16 @@ icon: material/new-box
"override_android_vpn": false,
"default_interface": "",
"default_mark": 0,
"default_domain_resolver": "", // or {}
"default_network_strategy": "",
"default_network_type": [],
"default_fallback_network_type": [],
"default_fallback_delay": ""
"default_fallback_delay": "",
// Removed
"geoip": {},
"geosite": {}
}
}
```
@@ -97,6 +107,14 @@ Set routing mark by default.
Takes no effect if `outbound.routing_mark` is set.
#### default_domain_resolver
!!! question "Since sing-box 1.12.0"
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
Can be overrides by `outbound.domain_resolver`.
#### default_network_strategy
!!! question "Since sing-box 1.11.0"

View File

@@ -1,9 +1,15 @@
---
icon: material/new-box
icon: material/alert-decagram
---
# 路由
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [default_domain_resolver](#default_domain_resolver)
:material-note-remove: [geoip](#geoip)
:material-note-remove: [geosite](#geosite)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [network_strategy](#network_strategy)
@@ -100,6 +106,14 @@ icon: material/new-box
如果设置了 `outbound.routing_mark` 设置,则不生效。
#### default_domain_resolver
!!! question "自 sing-box 1.12.0 起"
详情参阅 [拨号字段](/configuration/shared/dial/#domain_resolver)。
可以被 `outbound.domain_resolver` 覆盖。
#### network_strategy
!!! question "自 sing-box 1.11.0 起"

View File

@@ -2,6 +2,11 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [tls_fragment](#tls_fragment)
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
## Final actions
### route
@@ -31,6 +36,45 @@ Tag of target outbound.
See `route-options` fields below.
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### route-options
```json
@@ -42,7 +86,9 @@ See `route-options` fields below.
"fallback_delay": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false,
"udp_timeout": ""
"udp_timeout": "",
"tls_fragment": false,
"tls_fragment_fallback_delay": ""
}
```
@@ -109,44 +155,27 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
| 443 | `quic` |
| 3478 | `stun` |
### reject
#### tls_fragment
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
!!! question "Since sing-box 1.12.0"
`reject` reject connections
Fragment TLS handshakes to bypass firewalls.
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
For non-tun connections and already established connections, will just be closed.
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
#### method
On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`.
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy.
#### no_drop
#### tls_fragment_fallback_delay
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
!!! question "Since sing-box 1.12.0"
Not available when `method` is set to drop.
The fallback value used when TLS segmentation cannot automatically determine the wait time.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
`500ms` is used by default.
### sniff

View File

@@ -2,6 +2,11 @@
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [tls_fragment](#tls_fragment)
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
## 最终动作
### route
@@ -27,6 +32,45 @@ icon: material/new-box
参阅下方的 `route-options` 字段。
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### route-options
```json
@@ -107,44 +151,27 @@ UDP 连接超时时间。
| 443 | `quic` |
| 3478 | `stun` |
### reject
#### tls_fragment
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
!!! question "自 sing-box 1.12.0 起"
`reject` 拒绝连接
通过分段 TLS 握手数据包来绕过防火墙检测
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查
对于非 tun 连接和已建立的连接,将直接关闭
由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称
#### method
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达
- `drop`: 丢弃数据包。
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后
#### no_drop
#### tls_fragment_fallback_delay
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
!!! question "自 sing-box 1.12.0 起"
`method` 设为 `drop` 时不可用
TLS 分片功能无法自动判定等待时间时使用的回退值
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
默认使用 `500ms`
### sniff

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: QUIC client type detect support for QUIC

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: QUIC 的 客户端类型探测支持

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.10.0"
sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box,

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.10.0 起"
sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: `type: inline`

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: `type: inline`

View File

@@ -2,6 +2,11 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [domain_resolver](#domain_resolver)
:material-delete-clock: [domain_strategy](#domain_strategy)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [network_strategy](#network_strategy)
@@ -23,11 +28,14 @@ icon: material/new-box
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"domain_resolver": "", // or {}
"network_strategy": "default",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms"
"fallback_delay": "300ms",
// Deprecated
"domain_strategy": "prefer_ipv6"
}
```
@@ -92,16 +100,22 @@ decimal numbers, each with optional fraction and a unit suffix,
such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
#### domain_strategy
#### domain_resolver
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
!!! warning ""
If set, the requested domain name will be resolved to IP before connect.
`outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0.
| Outbound | Effected domains | Fallback Value |
|----------|--------------------------|-------------------------------------------|
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
| others | Domain in server address | / |
Set domain resolver to use for resolving domain names.
This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field.
Setting this option directly to a string is equivalent to setting `server` of this options.
| Outbound/Endpoints | Effected domains |
|--------------------|--------------------------|
| `direct` | Domain in request |
| others | Domain in server address |
#### network_strategy
@@ -171,3 +185,19 @@ back to other interfaces.
Only take effect when `domain_strategy` or `network_strategy` is set.
`300ms` is used by default.
#### domain_strategy
!!! failure "Deprecated in sing-box 1.12.0"
`domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0.
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
If set, the requested domain name will be resolved to IP before connect.
| Outbound | Effected domains | Fallback Value |
|----------|--------------------------|-------------------------------------------|
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
| others | Domain in server address | / |

View File

@@ -2,6 +2,11 @@
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [domain_resolver](#domain_resolver)
:material-delete-clock: [domain_strategy](#domain_strategy)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [network_strategy](#network_strategy)
@@ -23,11 +28,15 @@ icon: material/new-box
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"domain_resolver": "", // 或 {}
"network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms"
"fallback_delay": "300ms",
// 废弃的
"domain_strategy": "prefer_ipv6"
}
```
@@ -90,16 +99,22 @@ icon: material/new-box
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
#### domain_strategy
#### domain_resolver
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
!!! warning ""
如果设置,域名将在请求发出之前解析为 IP
`outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项
| 出站 | 受影响的域名 | 默认回退值 |
|----------|-----------|---------------------------|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
| others | 服务器地址中的域名 | / |
用于设置解析域名的域名解析器。
此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。
若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。
| 出站/端点 | 受影响的域名 |
|----------------|---------------------------|
| `direct` | 请求中的域名 |
| 其他类型 | 服务器地址中的域名 |
#### network_strategy
@@ -160,3 +175,14 @@ icon: material/new-box
仅当 `domain_strategy``network_strategy` 已设置时生效。
默认使用 `300ms`
#### domain_strategy
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
如果设置,域名将在请求发出之前解析为 IP。
| 出站 | 受影响的域名 | 默认回退值 |
|----------|-----------|---------------------------|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
| others | 服务器地址中的域名 | / |

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.10.0"
:material-alert-decagram: [utls](#utls)

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.10.0 中的更改"
:material-alert-decagram: [utls](#utls)

View File

@@ -4,6 +4,21 @@ icon: material/delete-alert
# Deprecated Feature List
## 1.12.0
#### Legacy DNS server formats
DNS servers are refactored,
check [Migration](../migration/#migrate-to-new-dns-servers).
Compatibility for old formats will be removed in sing-box 1.14.0.
#### `outbound` DNS rule item
Legacy `outbound` DNS rules are deprecated
and can be replaced by dial fields,
check [Migration](../migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
## 1.11.0
#### Legacy special outbounds

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