mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-02 19:37:37 +03:00
Add certificate store
This commit is contained in:
parent
511ba9bd6a
commit
ed1ea104eb
30 changed files with 4786 additions and 32 deletions
3
Makefile
3
Makefile
|
@ -61,6 +61,9 @@ proto_install:
|
||||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
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
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
|
update_certificates:
|
||||||
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
|
|
21
adapter/certificate.go
Normal file
21
adapter/certificate.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CertificateStore interface {
|
||||||
|
LifecycleService
|
||||||
|
Pool() *x509.CertPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
||||||
|
store := service.FromContext[CertificateStore](ctx)
|
||||||
|
if store == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return store.Pool()
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +14,20 @@ type ClashServer interface {
|
||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLTestHistory struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLTestHistoryStorage interface {
|
||||||
|
SetHook(hook chan<- struct{})
|
||||||
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
|
DeleteURLTestHistory(tag string)
|
||||||
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServer interface {
|
type V2RayServer interface {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
|
@ -66,12 +68,14 @@ type RuleSetMetadata struct {
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
type HTTPStartContext struct {
|
||||||
|
ctx context.Context
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
httpClientCache map[string]*http.Client
|
httpClientCache map[string]*http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPStartContext() *HTTPStartContext {
|
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
|
||||||
return &HTTPStartContext{
|
return &HTTPStartContext{
|
||||||
|
ctx: ctx,
|
||||||
httpClientCache: make(map[string]*http.Client),
|
httpClientCache: make(map[string]*http.Client),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +93,10 @@ func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Clie
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(c.ctx),
|
||||||
|
RootCAs: RootPoolFromContext(c.ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.httpClientCache[detour] = httpClient
|
c.httpClientCache[detour] = httpClient
|
||||||
|
|
16
box.go
16
box.go
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"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/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
@ -141,6 +142,20 @@ func New(options Options) (*Box, error) {
|
||||||
return nil, E.Cause(err, "create log factory")
|
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)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
|
@ -287,7 +302,6 @@ func New(options Options) (*Box, error) {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var services []adapter.LifecycleService
|
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
|
68
cmd/internal/update_certificates/main.go
Normal file
68
cmd/internal/update_certificates/main.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := updateMozillaIncludedRootCAs()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMozillaIncludedRootCAs() error {
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
geoIndex := slices.Index(header, "Geographic Focus")
|
||||||
|
nameIndex := slices.Index(header, "Common Name or Certificate Name")
|
||||||
|
certIndex := slices.Index(header, "PEM Info")
|
||||||
|
|
||||||
|
generated := strings.Builder{}
|
||||||
|
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||||
|
|
||||||
|
package certificate
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
var mozillaIncluded *x509.CertPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mozillaIncluded = x509.NewCertPool()
|
||||||
|
`)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record[geoIndex] == "China" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
generated.WriteString("\n // ")
|
||||||
|
generated.WriteString(record[nameIndex])
|
||||||
|
generated.WriteString("\n")
|
||||||
|
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
|
generated.WriteString(record[certIndex])
|
||||||
|
generated.WriteString("`))\n")
|
||||||
|
}
|
||||||
|
generated.WriteString("}\n")
|
||||||
|
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||||
|
}
|
4359
common/certificate/mozilla.go
Normal file
4359
common/certificate/mozilla.go
Normal file
File diff suppressed because it is too large
Load diff
185
common/certificate/store.go
Normal file
185
common/certificate/store.go
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
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, "":
|
||||||
|
systemPool = x509.NewCertPool()
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
var systemValid bool
|
||||||
|
if platformInterface != nil {
|
||||||
|
for _, cert := range platformInterface.SystemCertificates() {
|
||||||
|
if systemPool.AppendCertsFromPEM([]byte(cert)) {
|
||||||
|
systemValid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !systemValid {
|
||||||
|
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, "/")
|
||||||
|
}
|
|
@ -100,6 +100,7 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
|
||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (c *echServerConfig) startWatcher() error {
|
||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.credentialsUpdated(path)
|
err := c.credentialsUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(E.Cause(err, "reload credentials from ", path))
|
c.logger.Error(E.Cause(err, "reload credentials"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -27,9 +27,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
|
@ -40,6 +42,7 @@ import (
|
||||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
||||||
|
|
||||||
type RealityClientConfig struct {
|
type RealityClientConfig struct {
|
||||||
|
ctx context.Context
|
||||||
uClient *UTLSClientConfig
|
uClient *UTLSClientConfig
|
||||||
publicKey []byte
|
publicKey []byte
|
||||||
shortID [8]byte
|
shortID [8]byte
|
||||||
|
@ -70,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
|
@ -180,20 +183,24 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verifier.verified {
|
if !verifier.verified {
|
||||||
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
|
||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &realityClientConnWrapper{uConn}, nil
|
return &realityClientConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
defer uConn.Close()
|
defer uConn.Close()
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http2.Transport{
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
return uConn, nil
|
return uConn, nil
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(ctx),
|
||||||
|
RootCAs: adapter.RootPoolFromContext(ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
|
@ -213,6 +220,7 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [
|
||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
|
e.ctx,
|
||||||
e.uClient.Clone().(*UTLSClientConfig),
|
e.uClient.Clone().(*UTLSClientConfig),
|
||||||
e.publicKey,
|
e.publicKey,
|
||||||
e.shortID,
|
e.shortID,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
@ -58,6 +59,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -99,7 +99,7 @@ func (c *STDServerConfig) startWatcher() error {
|
||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.certificateUpdated(path)
|
err := c.certificateUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(err)
|
c.logger.Error(E.Cause(err, "reload certificate"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
@ -130,6 +131,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,32 +2,32 @@ package urltest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type History struct {
|
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Delay uint16 `json:"delay"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*History
|
delayHistory map[string]*adapter.URLTestHistory
|
||||||
updateHook chan<- struct{}
|
updateHook chan<- struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
return &HistoryStorage{
|
return &HistoryStorage{
|
||||||
delayHistory: make(map[string]*History),
|
delayHistory: make(map[string]*adapter.URLTestHistory),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
||||||
s.updateHook = hook
|
s.updateHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||||
s.notifyUpdated()
|
s.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
s.delayHistory[tag] = history
|
s.delayHistory[tag] = history
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
|
@ -110,6 +110,10 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return instance, nil
|
return instance, nil
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(ctx),
|
||||||
|
RootCAs: adapter.RootPoolFromContext(ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
|
|
7
constant/certificate.go
Normal file
7
constant/certificate.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
CertificateStoreSystem = "system"
|
||||||
|
CertificateStoreMozilla = "mozilla"
|
||||||
|
CertificateStoreNone = "none"
|
||||||
|
)
|
|
@ -111,7 +111,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||||
} else {
|
} else {
|
||||||
server.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
server.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||||
server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
|
server.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Delay: t,
|
Delay: t,
|
||||||
})
|
})
|
||||||
|
|
|
@ -72,9 +72,9 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||||
info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP))
|
info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP))
|
||||||
delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour))
|
delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour))
|
||||||
if delayHistory != nil {
|
if delayHistory != nil {
|
||||||
info.Put("history", []*urltest.History{delayHistory})
|
info.Put("history", []*adapter.URLTestHistory{delayHistory})
|
||||||
} else {
|
} else {
|
||||||
info.Put("history", []*urltest.History{})
|
info.Put("history", []*adapter.URLTestHistory{})
|
||||||
}
|
}
|
||||||
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
||||||
info.Put("now", group.Now())
|
info.Put("now", group.Now())
|
||||||
|
@ -116,7 +116,7 @@ func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
"type": "Fallback",
|
"type": "Fallback",
|
||||||
"name": "GLOBAL",
|
"name": "GLOBAL",
|
||||||
"udp": true,
|
"udp": true,
|
||||||
"history": []*urltest.History{},
|
"history": []*adapter.URLTestHistory{},
|
||||||
"all": allProxies,
|
"all": allProxies,
|
||||||
"now": defaultTag,
|
"now": defaultTag,
|
||||||
})
|
})
|
||||||
|
@ -208,7 +208,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||||
} else {
|
} else {
|
||||||
server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
|
server.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Delay: delay,
|
Delay: delay,
|
||||||
})
|
})
|
||||||
|
|
|
@ -48,7 +48,7 @@ type Server struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
trafficManager *trafficontrol.Manager
|
trafficManager *trafficontrol.Manager
|
||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory adapter.URLTestHistoryStorage
|
||||||
mode string
|
mode string
|
||||||
modeList []string
|
modeList []string
|
||||||
modeUpdateHook chan<- struct{}
|
modeUpdateHook chan<- struct{}
|
||||||
|
@ -79,7 +79,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
||||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||||
}
|
}
|
||||||
s.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
|
s.urlTestHistory = service.FromContext[adapter.URLTestHistoryStorage](ctx)
|
||||||
if s.urlTestHistory == nil {
|
if s.urlTestHistory == nil {
|
||||||
s.urlTestHistory = urltest.NewHistoryStorage()
|
s.urlTestHistory = urltest.NewHistoryStorage()
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ func (s *Server) SetMode(newMode string) {
|
||||||
s.logger.Info("updated mode: ", newMode)
|
s.logger.Info("updated mode: ", newMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HistoryStorage() *urltest.HistoryStorage {
|
func (s *Server) HistoryStorage() adapter.URLTestHistoryStorage {
|
||||||
return s.urlTestHistory
|
return s.urlTestHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package clashapi
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,6 +62,10 @@ func (s *Server) downloadExternalUI() error {
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(s.ctx),
|
||||||
|
RootCAs: adapter.RootPoolFromContext(s.ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer httpClient.CloseIdleConnections()
|
defer httpClient.CloseIdleConnections()
|
||||||
|
|
|
@ -76,7 +76,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
historyStorage.DeleteURLTestHistory(outboundTag)
|
historyStorage.DeleteURLTestHistory(outboundTag)
|
||||||
} else {
|
} else {
|
||||||
historyStorage.StoreURLTestHistory(outboundTag, &urltest.History{
|
historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Delay: t,
|
Delay: t,
|
||||||
})
|
})
|
||||||
|
|
|
@ -108,6 +108,10 @@ func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState {
|
||||||
return adapter.WIFIState{}
|
return adapter.WIFIState{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *platformInterfaceStub) SystemCertificates() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ type PlatformInterface interface {
|
||||||
UnderNetworkExtension() bool
|
UnderNetworkExtension() bool
|
||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ReadWIFIState() *WIFIState
|
ReadWIFIState() *WIFIState
|
||||||
|
SystemCertificates() StringIterator
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
SendNotification(notification *Notification) error
|
SendNotification(notification *Notification) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Interface interface {
|
||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
ReadWIFIState() adapter.WIFIState
|
ReadWIFIState() adapter.WIFIState
|
||||||
|
SystemCertificates() []string
|
||||||
process.Searcher
|
process.Searcher
|
||||||
SendNotification(notification *Notification) error
|
SendNotification(notification *Notification) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ import (
|
||||||
type BoxService struct {
|
type BoxService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
urlTestHistoryStorage *urltest.HistoryStorage
|
urlTestHistoryStorage adapter.URLTestHistoryStorage
|
||||||
instance *box.Box
|
instance *box.Box
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
|
@ -233,6 +233,10 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
||||||
return (adapter.WIFIState)(*wifiState)
|
return (adapter.WIFIState)(*wifiState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) SystemCertificates() []string {
|
||||||
|
return iteratorToArray[string](w.iif.SystemCertificates())
|
||||||
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
||||||
var uid int32
|
var uid int32
|
||||||
if w.useProcFS {
|
if w.useProcFS {
|
||||||
|
|
36
option/certificate.go
Normal file
36
option/certificate.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _CertificateOptions struct {
|
||||||
|
Store string `json:"store,omitempty"`
|
||||||
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
|
CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"`
|
||||||
|
CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertificateOptions _CertificateOptions
|
||||||
|
|
||||||
|
func (o CertificateOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
switch o.Store {
|
||||||
|
case C.CertificateStoreSystem:
|
||||||
|
o.Store = ""
|
||||||
|
}
|
||||||
|
return json.Marshal((*_CertificateOptions)(&o))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CertificateOptions) UnmarshalJSON(data []byte) error {
|
||||||
|
err := json.Unmarshal(data, (*_CertificateOptions)(o))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch o.Store {
|
||||||
|
case C.CertificateStoreSystem, "":
|
||||||
|
o.Store = C.CertificateStoreSystem
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ type _Options struct {
|
||||||
Log *LogOptions `json:"log,omitempty"`
|
Log *LogOptions `json:"log,omitempty"`
|
||||||
DNS *DNSOptions `json:"dns,omitempty"`
|
DNS *DNSOptions `json:"dns,omitempty"`
|
||||||
NTP *NTPOptions `json:"ntp,omitempty"`
|
NTP *NTPOptions `json:"ntp,omitempty"`
|
||||||
|
Certificate *CertificateOptions `json:"certificate,omitempty"`
|
||||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
|
|
|
@ -187,7 +187,7 @@ type URLTestGroup struct {
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
tolerance uint16
|
tolerance uint16
|
||||||
idleTimeout time.Duration
|
idleTimeout time.Duration
|
||||||
history *urltest.HistoryStorage
|
history adapter.URLTestHistoryStorage
|
||||||
checking atomic.Bool
|
checking atomic.Bool
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
selectedOutboundTCP adapter.Outbound
|
selectedOutboundTCP adapter.Outbound
|
||||||
|
@ -215,8 +215,9 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
|
||||||
if interval > idleTimeout {
|
if interval > idleTimeout {
|
||||||
return nil, E.New("interval must be less or equal than idle_timeout")
|
return nil, E.New("interval must be less or equal than idle_timeout")
|
||||||
}
|
}
|
||||||
var history *urltest.HistoryStorage
|
var history adapter.URLTestHistoryStorage
|
||||||
if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil {
|
if historyFromCtx := service.PtrFromContext[urltest.HistoryStorage](ctx); historyFromCtx != nil {
|
||||||
|
history = historyFromCtx
|
||||||
} else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil {
|
} else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil {
|
||||||
history = clashServer.HistoryStorage()
|
history = clashServer.HistoryStorage()
|
||||||
} else {
|
} else {
|
||||||
|
@ -379,7 +380,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
|
||||||
g.history.DeleteURLTestHistory(realTag)
|
g.history.DeleteURLTestHistory(realTag)
|
||||||
} else {
|
} else {
|
||||||
g.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
g.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||||
g.history.StoreURLTestHistory(realTag, &urltest.History{
|
g.history.StoreURLTestHistory(realTag, &adapter.URLTestHistory{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Delay: t,
|
Delay: t,
|
||||||
})
|
})
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||||
var cacheContext *adapter.HTTPStartContext
|
var cacheContext *adapter.HTTPStartContext
|
||||||
if len(r.ruleSets) > 0 {
|
if len(r.ruleSets) > 0 {
|
||||||
monitor.Start("initialize rule-set")
|
monitor.Start("initialize rule-set")
|
||||||
cacheContext = adapter.NewHTTPStartContext()
|
cacheContext = adapter.NewHTTPStartContext(r.ctx)
|
||||||
var ruleSetStartGroup task.Group
|
var ruleSetStartGroup task.Group
|
||||||
for i, ruleSet := range r.ruleSets {
|
for i, ruleSet := range r.ruleSets {
|
||||||
ruleSetInPlace := ruleSet
|
ruleSetInPlace := ruleSet
|
||||||
|
|
|
@ -3,6 +3,7 @@ package rule
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -23,6 +24,7 @@ import (
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
@ -235,6 +237,10 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(s.ctx),
|
||||||
|
RootCAs: adapter.RootPoolFromContext(s.ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue