Compare commits

..

12 Commits

Author SHA1 Message Date
世界
420d8d144a Add tls fragment support 2025-01-26 19:06:27 +08:00
世界
7ecc62f2e0 Fix local server 2025-01-26 09:00:43 +08:00
世界
3fa417f283 Add hosts transport & Remove bad linkname usages 2025-01-25 19:37:11 +08:00
世界
314b25bccf Fix https DNS transports 2025-01-25 19:03:53 +08:00
世界
1c56213cbe Add ip_accept_any DNS rule action 2025-01-25 19:02:41 +08:00
世界
1947a90878 Add dns.rules[].strategy 2025-01-25 19:01:53 +08:00
世界
63ef6f972f refactor: Outbound domain resolver 2025-01-19 16:04:21 +08:00
世界
eae2631208 refactor: DNS 2025-01-19 15:57:44 +08:00
世界
8cc7734a92 documentation: Bump version 2025-01-19 09:54:44 +08:00
世界
d74a05fdff Fix legacy routes 2025-01-19 09:40:09 +08:00
HystericalDragon
4f453e81ac Fix endpoints not close 2025-01-17 11:21:07 +08:00
世界
bd2e052815 documentation: Bump version 2025-01-13 15:14:30 +08:00
185 changed files with 16553 additions and 14726 deletions

View File

@@ -144,7 +144,7 @@ jobs:
~/go/go1.20.14
key: go120
- name: Setup legacy Go
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
if: matrix.require_legacy_go == 'true' && 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: 2.5.1
version: latest
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-beta3
ndk-version: r28-beta2
- 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-beta3
ndk-version: r28-beta2
- 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: 2.5.1
version: latest
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
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
targets:
- windows_amd64_v1
- windows_386

View File

@@ -61,12 +61,6 @@ 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

View File

@@ -1,21 +0,0 @@
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,6 +6,7 @@ import (
"encoding/binary"
"time"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing/common/varbin"
)
@@ -14,20 +15,7 @@ type ClashServer interface {
ConnectionTracker
Mode() string
ModeList() []string
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
HistoryStorage() *urltest.HistoryStorage
}
type V2RayServer interface {
@@ -50,21 +38,17 @@ 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) *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
LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedRuleSet) error
}
type SavedBinary struct {
type SavedRuleSet struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
@@ -85,7 +69,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
return buffer.Bytes(), nil
}
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)

View File

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

View File

@@ -1,8 +1,6 @@
package adapter
import (
E "github.com/sagernet/sing/common/exceptions"
)
import E "github.com/sagernet/sing/common/exceptions"
type StartStage uint8
@@ -47,9 +45,6 @@ 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

View File

@@ -1,15 +0,0 @@
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,14 +28,12 @@ type NetworkManager interface {
}
type NetworkOptions struct {
BindInterface string
RoutingMark uint32
DomainResolver string
DomainResolveOptions DNSQueryOptions
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
BindInterface string
RoutingMark uint32
}
type InterfaceUpdateListener interface {

View File

@@ -2,7 +2,6 @@ package adapter
import (
"context"
"crypto/tls"
"net"
"net/http"
"sync"
@@ -10,7 +9,6 @@ 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"
@@ -68,14 +66,12 @@ type RuleSetMetadata struct {
ContainsIPCIDRRule bool
}
type HTTPStartContext struct {
ctx context.Context
access sync.Mutex
httpClientCache map[string]*http.Client
}
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
func NewHTTPStartContext() *HTTPStartContext {
return &HTTPStartContext{
ctx: ctx,
httpClientCache: make(map[string]*http.Client),
}
}
@@ -93,10 +89,6 @@ 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

View File

@@ -1,54 +0,0 @@
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,7 +12,6 @@ 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"
@@ -23,11 +22,9 @@ 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"
@@ -50,8 +47,6 @@ 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{}
}
@@ -146,20 +141,6 @@ 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)
@@ -177,7 +158,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize network manager")
}
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router)
@@ -185,8 +166,8 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "initialize router")
}
var timeService *tls.TimeServiceWrapper
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
@@ -206,7 +187,7 @@ func New(options Options) (*Box, error) {
transportOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize DNS server[", i, "]")
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
}
err = dnsRouter.Initialize(dnsOptions.Rules)
@@ -229,7 +210,7 @@ func New(options Options) (*Box, error) {
endpointOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize endpoint[", i, "]")
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
}
for i, inboundOptions := range options.Inbounds {
@@ -293,17 +274,13 @@ 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)
@@ -347,16 +324,6 @@ 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,
@@ -366,8 +333,6 @@ func New(options Options) (*Box, error) {
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
script: scriptManager,
mitm: mitmEngine,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
@@ -426,11 +391,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.script, s.mitm, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, 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, s.script, s.mitm)
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
@@ -454,7 +419,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.script, s.mitm, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
if err != nil {
return err
}
@@ -462,7 +427,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.script, s.mitm, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
@@ -481,7 +446,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
s.inbound, s.outbound, s.endpoint, 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,7 +5,6 @@ import (
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
@@ -195,10 +194,6 @@ 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.12916984"
const fixedVersion = "28.0.12674087"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath

View File

@@ -1,68 +0,0 @@
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.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}

View File

@@ -61,15 +61,14 @@ func upgradeRuleSet(sourcePath string) error {
log.Info("already up-to-date")
return nil
}
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
plainRuleSet, 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(plainRuleSetCompat)
err = encoder.Encode(plainRuleSet)
if err != nil {
return E.Cause(err, "encode config")
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,184 +0,0 @@
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,6 +35,7 @@ type DefaultDialer struct {
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
@@ -182,6 +183,11 @@ 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
@@ -198,6 +204,7 @@ 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,18 +29,16 @@ func (d *DetourDialer) Start() error {
}
func (d *DetourDialer) Dialer() (N.Dialer, error) {
d.initOnce.Do(d.init)
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)
}
})
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,105 +16,81 @@ 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) {
return NewWithOptions(Options{
Context: ctx,
Options: options,
RemoteIsDomain: remoteIsDomain,
})
}
func NewWithOptions(options Options) (N.Dialer, error) {
dialOptions := options.Options
if options.IsWireGuardListener {
return NewDefault(ctx, options)
}
var (
dialer N.Dialer
err error
)
if dialOptions.Detour != "" {
outboundManager := service.FromContext[adapter.OutboundManager](options.Context)
if outboundManager == nil {
return nil, E.New("missing outbound manager")
}
dialer = NewDetour(outboundManager, dialOptions.Detour)
} else {
dialer, err = NewDefault(options.Context, dialOptions)
if options.Detour == "" {
dialer, err = NewDefault(ctx, options)
if err != nil {
return nil, err
}
} else {
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
if outboundManager == nil {
return nil, E.New("missing outbound manager")
}
dialer = NewDetour(outboundManager, options.Detour)
}
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 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("domain resolver not found: " + dialOptions.DomainResolver.Server)
return nil, E.New("DNS server not found: " + options.DomainResolver)
}
resolveTransport = transport
}
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(
router,
dialer,
options.Detour == "" && !options.TCPFastOpen,
resolveTransport,
C.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay))
}
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,17 +3,14 @@ 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 (
@@ -21,37 +18,23 @@ 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
server string
initOnce sync.Once
initErr error
queryOptions adapter.DNSQueryOptions
router adapter.DNSRouter
transport adapter.DNSTransport
strategy C.DomainStrategy
fallbackDelay time.Duration
}
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
return &resolveDialer{
transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx),
dialer: dialer,
parallel: parallel,
server: server,
queryOptions: queryOptions,
fallbackDelay: fallbackDelay,
dialer,
parallel,
router,
transport,
strategy,
fallbackDelay,
}
}
@@ -60,68 +43,42 @@ type resolveParallelNetworkDialer struct {
dialer ParallelInterfaceDialer
}
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
return &resolveParallelNetworkDialer{
resolveDialer{
transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx),
dialer: dialer,
parallel: parallel,
server: server,
queryOptions: queryOptions,
fallbackDelay: fallbackDelay,
dialer,
parallel,
router,
transport,
strategy,
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, d.queryOptions)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
if err != nil {
return nil, err
}
if d.parallel {
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.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, d.queryOptions)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
if err != nil {
return nil, err
}
@@ -132,24 +89,12 @@ 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, d.queryOptions)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
if err != nil {
return nil, err
}
@@ -157,28 +102,21 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
fallbackDelay = d.fallbackDelay
}
if d.parallel {
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.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, d.queryOptions)
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
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
@@ -186,10 +124,6 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions {
return d.queryOptions
}
func (d *resolveParallelNetworkDialer) Upstream() any {
func (d *resolveDialer) Upstream() any {
return d.dialer
}

View File

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

View File

@@ -2,11 +2,14 @@ package process
import (
"context"
"fmt"
"net/netip"
"os"
"syscall"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/winiphlpapi"
N "github.com/sagernet/sing/common/network"
"golang.org/x/sys/windows"
)
@@ -23,39 +26,209 @@ 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 {
return winiphlpapi.LoadExtendedTable()
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
}
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
pid, err := winiphlpapi.FindPid(network, source)
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
if err != nil {
return nil, err
}
path, err := getProcessPath(pid)
if err != nil {
return &Info{ProcessID: pid, UserId: -1}, err
}
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
return &Info{ProcessPath: processName, UserId: -1}, nil
}
func getProcessPath(pid uint32) (string, error) {
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)
if err != nil {
return "", 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)
}
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
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
}
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
if err != nil {
return "", err
}
defer windows.CloseHandle(handle)
size := uint32(syscall.MAX_LONG_PATH)
defer windows.CloseHandle(h)
buf := make([]uint16, syscall.MAX_LONG_PATH)
err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size)
if err != nil {
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 {
return "", err
}
return windows.UTF16ToString(buf[:size]), nil
return syscall.UTF16ToString(buf[:size]), nil
}

View File

@@ -18,6 +18,5 @@ 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,7 +21,6 @@ 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,7 +100,6 @@ 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"))
c.logger.Error(E.Cause(err, "reload credentials from ", path))
}
},
})

View File

@@ -8,14 +8,11 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"
M "github.com/sagernet/sing/common/metadata"
)
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))
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
if err != nil {
return nil, err
}
@@ -26,7 +23,7 @@ func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() ti
return &certificate, err
}
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
if timeFunc == nil {
timeFunc = time.Now
}
@@ -38,36 +35,19 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
if err != nil {
return
}
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},
}
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},
}
if parent == nil {
parent = template
parentKey = key
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if err != nil {
return
}

View File

@@ -27,11 +27,9 @@ 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"
@@ -42,7 +40,6 @@ import (
var _ ConfigCompat = (*RealityClientConfig)(nil)
type RealityClientConfig struct {
ctx context.Context
uClient *UTLSClientConfig
publicKey []byte
shortID [8]byte
@@ -73,7 +70,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
if decodedLen > 8 {
return nil, E.New("invalid short_id")
}
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
return &RealityClientConfig{uClient, publicKey, shortID}, nil
}
func (e *RealityClientConfig) ServerName() string {
@@ -183,24 +180,20 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
}
if !verifier.verified {
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
return nil, E.New("reality verification failed")
}
return &realityClientConnWrapper{uConn}, nil
}
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
func realityClientFallback(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)
@@ -220,7 +213,6 @@ 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,7 +8,6 @@ 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"
@@ -59,7 +58,6 @@ 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(E.Cause(err, "reload certificate"))
c.logger.Error(err)
}
},
})
@@ -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 GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -12,7 +12,6 @@ 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"
@@ -131,7 +130,6 @@ 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
tcpConn *net.TCPConn
syscallConn syscall.Conn
ctx context.Context
firstPacketWritten bool
fallbackDelay time.Duration
}
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
syscallConn, _ := N.UnwrapReader(conn).(syscall.Conn)
return &Conn{
Conn: conn,
tcpConn: tcpConn,
syscallConn: syscallConn,
ctx: ctx,
fallbackDelay: fallbackDelay,
}, nil
@@ -37,18 +37,39 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}()
serverName := indexTLSServerName(b)
if serverName != nil {
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(true)
tcpConn, isTCPConn := c.syscallConn.(interface {
SetNoDelay(bool) error
})
if isTCPConn {
err = tcpConn.SetNoDelay(true)
if err != nil {
return
}
}
splits := strings.Split(serverName.ServerName, ".")
splits := strings.Split(string(b[serverName.Index:serverName.Index+serverName.Length]), ".")
currentIndex := serverName.Index
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
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 len(splits) > 1 && splits[0] == "..." {
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]) {
currentIndex += len(splits[0]) + 1
splits = splits[1:]
}
@@ -62,28 +83,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}
}
for i := 0; i <= len(splitIndexes); i++ {
var payload []byte
if i == 0 {
payload = b[:splitIndexes[i]]
_, err = c.Conn.Write(b[:splitIndexes[i]])
} else if i == len(splitIndexes) {
payload = b[splitIndexes[i-1]:]
_, err = c.Conn.Write(b[splitIndexes[i-1]:])
} else {
payload = b[splitIndexes[i-1]:splitIndexes[i]]
_, err = c.Conn.Write(b[splitIndexes[i-1]:splitIndexes[i]])
}
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 c.syscallConn != nil && i != len(splitIndexes) {
err = waitAck(c.ctx, c.syscallConn, c.fallbackDelay)
if err != nil {
return
}
}
}
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(false)
if isTCPConn {
err = tcpConn.SetNoDelay(false)
if err != nil {
return
}

View File

@@ -23,9 +23,9 @@ const (
)
type myServerName struct {
Index int
Length int
ServerName string
Index int
Length int
sex []byte
}
func indexTLSServerName(payload []byte) *myServerName {
@@ -68,9 +68,15 @@ 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
@@ -119,9 +125,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),
ServerName: string(sex),
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
Length: int(sniLen),
sex: sex,
}
}
exs = exs[4+exLen:]

View File

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

View File

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

View File

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

View File

@@ -1,28 +0,0 @@
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"
)
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
type History struct {
Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
}
type HistoryStorage struct {
access sync.RWMutex
delayHistory map[string]*adapter.URLTestHistory
delayHistory map[string]*History
updateHook chan<- struct{}
}
func NewHistoryStorage() *HistoryStorage {
return &HistoryStorage{
delayHistory: make(map[string]*adapter.URLTestHistory),
delayHistory: make(map[string]*History),
}
}
@@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
s.updateHook = hook
}
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
if s == nil {
return nil
}
@@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
s.notifyUpdated()
}
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
s.access.Lock()
s.delayHistory[tag] = history
s.access.Unlock()
@@ -110,10 +110,6 @@ 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import (
"sync"
"sync/atomic"
"time"
_ "unsafe"
)
const (
@@ -117,7 +118,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) {
@@ -135,6 +136,13 @@ 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,14 +27,9 @@ 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,
}
@@ -42,11 +37,8 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {
var dependencies []string
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
}
if remoteOptions.LegacyAddressResolver != "" {
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
if remoteOptions.AddressResolver != "" {
dependencies = []string{remoteOptions.AddressResolver}
}
return TransportAdapter{
transportType: transportType,

View File

@@ -19,39 +19,37 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
if options.LegacyDefaultDialer {
return dialer.NewDefaultOutbound(ctx), nil
} else {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
DirectResolver: true,
})
return dialer.New(ctx, options.DialerOptions, false)
}
}
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
var (
transportDialer N.Dialer
err error
)
if options.LegacyDefaultDialer {
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)
}
return transportDialer, nil
transportDialer = dialer.NewDefaultOutbound(ctx)
} else {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(),
DirectResolver: true,
})
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 = 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
}
type legacyTransportDialer struct {
type TransportDialer struct {
dialer N.Dialer
dnsRouter adapter.DNSRouter
transport adapter.DNSTransport
@@ -59,8 +57,8 @@ type legacyTransportDialer struct {
fallbackDelay time.Duration
}
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
return &legacyTransportDialer{
func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer {
return &TransportDialer{
dialer,
dnsRouter,
transport,
@@ -69,7 +67,7 @@ func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport
}
}
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if destination.IsIP() {
return d.dialer.DialContext(ctx, network, destination)
}
@@ -83,7 +81,7 @@ func (d *legacyTransportDialer) DialContext(ctx context.Context, network string,
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
}
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if destination.IsIP() {
return d.dialer.ListenPacket(ctx, destination)
}
@@ -98,6 +96,6 @@ func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.
return conn, err
}
func (d *legacyTransportDialer) Upstream() any {
func (d *TransportDialer) Upstream() any {
return d.dialer
}

View File

@@ -2,182 +2,10 @@
icon: material/alert-decagram
---
#### 1.12.0-alpha.3
#### 1.11.0-beta.24
* 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

@@ -1,54 +0,0 @@
---
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,11 +1,3 @@
---
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,11 +1,3 @@
---
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,6 +49,8 @@ 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,6 +48,8 @@ icon: material/new-box
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
如果设置了 `server.strategy`,则不生效。
#### disable_cache
禁用 DNS 缓存。

View File

@@ -1,11 +1,7 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! 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)
@@ -399,10 +395,6 @@ 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,11 +1,7 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-delete-clock: [outbound](#outbound)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
@@ -399,10 +395,6 @@ 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,10 +2,6 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [strategy](#strategy)
!!! question "Since sing-box 1.11.0"
### route
@@ -14,7 +10,6 @@ icon: material/new-box
{
"action": "route", // default
"server": "",
"strategy": "",
"disable_cache": false,
"rewrite_ttl": 0,
"client_subnet": null
@@ -29,12 +24,6 @@ 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,10 +2,6 @@
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [strategy](#strategy)
!!! question "自 sing-box 1.11.0 起"
### route
@@ -14,8 +10,8 @@ icon: material/new-box
{
"action": "route", // 默认
"server": "",
"strategy": "",
// 兼容性
"disable_cache": false,
"rewrite_ttl": 0,
"client_subnet": null
@@ -30,12 +26,6 @@ icon: material/new-box
目标 DNS 服务器的标签。
#### strategy
为此查询设置域名策略。
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
#### disable_cache
在此查询中禁用缓存。

View File

@@ -1,11 +1,3 @@
---
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,11 +1,3 @@
---
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

@@ -1,38 +0,0 @@
---
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

@@ -1,33 +0,0 @@
---
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

@@ -1,69 +0,0 @@
---
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

@@ -1,69 +0,0 @@
---
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

@@ -1,46 +0,0 @@
---
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,33 +0,0 @@
---
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

@@ -1,93 +0,0 @@
---
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

@@ -1,56 +0,0 @@
---
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

@@ -1,50 +0,0 @@
---
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

@@ -1,56 +0,0 @@
---
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

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,9 @@
---
icon: material/alert-decagram
icon: material/new-box
---
# 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)
@@ -28,6 +22,8 @@ icon: material/alert-decagram
```json
{
"route": {
"geoip": {},
"geosite": {},
"rules": [],
"rule_set": [],
"final": "",
@@ -35,16 +31,10 @@ icon: material/alert-decagram
"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": "",
// Removed
"geoip": {},
"geosite": {}
"default_fallback_delay": ""
}
}
```
@@ -107,14 +97,6 @@ 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,15 +1,9 @@
---
icon: material/alert-decagram
icon: material/new-box
---
# 路由
!!! 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)
@@ -106,14 +100,6 @@ icon: material/alert-decagram
如果设置了 `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,11 +2,6 @@
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
@@ -36,45 +31,6 @@ 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
@@ -86,9 +42,7 @@ Not available when `method` is set to drop.
"fallback_delay": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false,
"udp_timeout": "",
"tls_fragment": false,
"tls_fragment_fallback_delay": ""
"udp_timeout": ""
}
```
@@ -155,27 +109,44 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
| 443 | `quic` |
| 3478 | `stun` |
#### tls_fragment
### reject
!!! question "Since sing-box 1.12.0"
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
Fragment TLS handshakes to bypass firewalls.
`reject` reject connections
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
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.
For non-tun connections and already established connections, will just be closed.
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`.
#### method
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.
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### tls_fragment_fallback_delay
#### no_drop
!!! question "Since sing-box 1.12.0"
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
The fallback value used when TLS segmentation cannot automatically determine the wait time.
Not available when `method` is set to drop.
`500ms` is used by default.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### sniff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,6 @@
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)
@@ -28,14 +23,11 @@ icon: material/new-box
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"domain_resolver": "", // or {}
"domain_strategy": "prefer_ipv6",
"network_strategy": "default",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms",
// Deprecated
"domain_strategy": "prefer_ipv6"
"fallback_delay": "300ms"
}
```
@@ -100,22 +92,16 @@ 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_resolver
#### domain_strategy
!!! warning ""
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
`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.
If set, the requested domain name will be resolved to IP before connect.
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 |
| Outbound | Effected domains | Fallback Value |
|----------|--------------------------|-------------------------------------------|
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
| others | Domain in server address | / |
#### network_strategy
@@ -185,19 +171,3 @@ 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,11 +2,6 @@
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)
@@ -28,15 +23,11 @@ icon: material/new-box
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"domain_resolver": "", // 或 {}
"domain_strategy": "prefer_ipv6",
"network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms",
// 废弃的
"domain_strategy": "prefer_ipv6"
"fallback_delay": "300ms"
}
```
@@ -99,22 +90,16 @@ icon: material/new-box
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
#### domain_resolver
#### domain_strategy
!!! warning ""
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
`outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项
如果设置,域名将在请求发出之前解析为 IP
用于设置解析域名的域名解析器。
此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。
若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。
| 出站/端点 | 受影响的域名 |
|----------------|---------------------------|
| `direct` | 请求中的域名 |
| 其他类型 | 服务器地址中的域名 |
| 出站 | 受影响的域名 | 默认回退值 |
|----------|-----------|---------------------------|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
| others | 服务器地址中的域名 | / |
#### network_strategy
@@ -175,14 +160,3 @@ 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,3 +1,7 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.10.0"
:material-alert-decagram: [utls](#utls)

View File

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

View File

@@ -4,21 +4,6 @@ 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