Skip to content

Commit 407405e

Browse files
committed
Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
1 parent 369f04e commit 407405e

113 files changed

Lines changed: 13601 additions & 8032 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

adapter/http.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package adapter
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"sync"
7+
8+
"github.com/sagernet/sing-box/option"
9+
"github.com/sagernet/sing/common/logger"
10+
)
11+
12+
type HTTPTransport interface {
13+
http.RoundTripper
14+
CloseIdleConnections()
15+
Reset()
16+
}
17+
18+
type HTTPClientManager interface {
19+
ResolveTransport(ctx context.Context, logger logger.ContextLogger, options option.HTTPClientOptions) (HTTPTransport, error)
20+
DefaultTransport() HTTPTransport
21+
ResetNetwork()
22+
}
23+
24+
type HTTPStartContext struct {
25+
access sync.Mutex
26+
transports []HTTPTransport
27+
}
28+
29+
func NewHTTPStartContext() *HTTPStartContext {
30+
return &HTTPStartContext{}
31+
}
32+
33+
func (c *HTTPStartContext) Register(transport HTTPTransport) {
34+
c.access.Lock()
35+
defer c.access.Unlock()
36+
c.transports = append(c.transports, transport)
37+
}
38+
39+
func (c *HTTPStartContext) Close() {
40+
for _, transport := range c.transports {
41+
transport.CloseIdleConnections()
42+
}
43+
}

adapter/router.go

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@ package adapter
22

33
import (
44
"context"
5-
"crypto/tls"
65
"net"
7-
"net/http"
8-
"sync"
96
"time"
107

11-
C "github.com/sagernet/sing-box/constant"
128
"github.com/sagernet/sing-tun"
13-
M "github.com/sagernet/sing/common/metadata"
149
N "github.com/sagernet/sing/common/network"
15-
"github.com/sagernet/sing/common/ntp"
1610
"github.com/sagernet/sing/common/x/list"
1711

1812
"go4.org/netipx"
@@ -77,46 +71,3 @@ type RuleSetMetadata struct {
7771
ContainsIPCIDRRule bool
7872
ContainsDNSQueryTypeRule bool
7973
}
80-
type HTTPStartContext struct {
81-
ctx context.Context
82-
access sync.Mutex
83-
httpClientCache map[string]*http.Client
84-
}
85-
86-
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
87-
return &HTTPStartContext{
88-
ctx: ctx,
89-
httpClientCache: make(map[string]*http.Client),
90-
}
91-
}
92-
93-
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
94-
c.access.Lock()
95-
defer c.access.Unlock()
96-
if httpClient, loaded := c.httpClientCache[detour]; loaded {
97-
return httpClient
98-
}
99-
httpClient := &http.Client{
100-
Transport: &http.Transport{
101-
ForceAttemptHTTP2: true,
102-
TLSHandshakeTimeout: C.TCPTimeout,
103-
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
104-
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
105-
},
106-
TLSClientConfig: &tls.Config{
107-
Time: ntp.TimeFuncFromContext(c.ctx),
108-
RootCAs: RootPoolFromContext(c.ctx),
109-
},
110-
},
111-
}
112-
c.httpClientCache[detour] = httpClient
113-
return httpClient
114-
}
115-
116-
func (c *HTTPStartContext) Close() {
117-
c.access.Lock()
118-
defer c.access.Unlock()
119-
for _, client := range c.httpClientCache {
120-
client.CloseIdleConnections()
121-
}
122-
}

box.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import (
1616
boxService "github.com/sagernet/sing-box/adapter/service"
1717
"github.com/sagernet/sing-box/common/certificate"
1818
"github.com/sagernet/sing-box/common/dialer"
19+
"github.com/sagernet/sing-box/common/httpclient"
1920
"github.com/sagernet/sing-box/common/taskmonitor"
2021
"github.com/sagernet/sing-box/common/tls"
2122
C "github.com/sagernet/sing-box/constant"
2223
"github.com/sagernet/sing-box/dns"
2324
"github.com/sagernet/sing-box/experimental"
2425
"github.com/sagernet/sing-box/experimental/cachefile"
26+
"github.com/sagernet/sing-box/experimental/deprecated"
2527
"github.com/sagernet/sing-box/log"
2628
"github.com/sagernet/sing-box/option"
2729
"github.com/sagernet/sing-box/protocol/direct"
@@ -50,6 +52,7 @@ type Box struct {
5052
dnsRouter *dns.Router
5153
connection *route.ConnectionManager
5254
router *route.Router
55+
httpClientService adapter.LifecycleService
5356
internalService []adapter.LifecycleService
5457
done chan struct{}
5558
}
@@ -169,6 +172,7 @@ func New(options Options) (*Box, error) {
169172
}
170173

171174
var internalServices []adapter.LifecycleService
175+
routeOptions := common.PtrValueOrDefault(options.Route)
172176
certificateOptions := common.PtrValueOrDefault(options.Certificate)
173177
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
174178
len(certificateOptions.Certificate) > 0 ||
@@ -181,8 +185,6 @@ func New(options Options) (*Box, error) {
181185
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
182186
internalServices = append(internalServices, certificateStore)
183187
}
184-
185-
routeOptions := common.PtrValueOrDefault(options.Route)
186188
dnsOptions := common.PtrValueOrDefault(options.DNS)
187189
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
188190
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
@@ -209,6 +211,10 @@ func New(options Options) (*Box, error) {
209211
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
210212
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
211213
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
214+
// Must register after ConnectionManager: the Apple HTTP engine's proxy bridge reads it from the context when Manager.Start resolves the default client.
215+
httpClientManager := httpclient.NewManager(ctx, logFactory.NewLogger("httpclient"), options.HTTPClients, routeOptions.DefaultHTTPClient)
216+
service.MustRegister[adapter.HTTPClientManager](ctx, httpClientManager)
217+
httpClientService := adapter.LifecycleService(httpClientManager)
212218
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
213219
service.MustRegister[adapter.Router](ctx, router)
214220
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
@@ -368,6 +374,12 @@ func New(options Options) (*Box, error) {
368374
&option.LocalDNSServerOptions{},
369375
)
370376
})
377+
httpClientManager.Initialize(func() (*httpclient.ManagedTransport, error) {
378+
deprecated.Report(ctx, deprecated.OptionImplicitDefaultHTTPClient)
379+
var httpClientOptions option.HTTPClientOptions
380+
httpClientOptions.DefaultOutbound = true
381+
return httpclient.NewTransport(ctx, logFactory.NewLogger("httpclient"), "", httpClientOptions)
382+
})
371383
if platformInterface != nil {
372384
err = platformInterface.Initialize(networkManager)
373385
if err != nil {
@@ -428,6 +440,7 @@ func New(options Options) (*Box, error) {
428440
dnsRouter: dnsRouter,
429441
connection: connectionManager,
430442
router: router,
443+
httpClientService: httpClientService,
431444
createdAt: createdAt,
432445
logFactory: logFactory,
433446
logger: logFactory.Logger(),
@@ -490,7 +503,15 @@ func (s *Box) preStart() error {
490503
if err != nil {
491504
return err
492505
}
493-
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
506+
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection)
507+
if err != nil {
508+
return err
509+
}
510+
err = adapter.StartNamed(s.logger, adapter.StartStateStart, []adapter.LifecycleService{s.httpClientService})
511+
if err != nil {
512+
return err
513+
}
514+
err = adapter.Start(s.logger, adapter.StartStateStart, s.router, s.dnsRouter)
494515
if err != nil {
495516
return err
496517
}
@@ -567,6 +588,14 @@ func (s *Box) Close() error {
567588
})
568589
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
569590
}
591+
if s.httpClientService != nil {
592+
s.logger.Trace("close ", s.httpClientService.Name())
593+
startTime := time.Now()
594+
err = E.Append(err, s.httpClientService.Close(), func(err error) error {
595+
return E.Cause(err, "close ", s.httpClientService.Name())
596+
})
597+
s.logger.Trace("close ", s.httpClientService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
598+
}
570599
for _, lifecycleService := range s.internalService {
571600
s.logger.Trace("close ", lifecycleService.Name())
572601
startTime := time.Now()

cmd/internal/build_libbox/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ func buildApple() {
204204
"-target", bindTarget,
205205
"-libname=box",
206206
"-tags-not-macos=with_low_memory",
207+
"-iosversion=15.0",
208+
"-macosversion=13.0",
209+
"-tvosversion=17.0",
207210
}
208211
//if !withTailscale {
209212
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))

cmd/internal/update_certificates/main.go

Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"net/http"
77
"os"
8+
"path/filepath"
89
"strings"
910

1011
"github.com/sagernet/sing-box/log"
@@ -35,21 +36,9 @@ func updateMozillaIncludedRootCAs() error {
3536
return err
3637
}
3738
geoIndex := slices.Index(header, "Geographic Focus")
38-
nameIndex := slices.Index(header, "Common Name or Certificate Name")
3939
certIndex := slices.Index(header, "PEM Info")
4040

41-
generated := strings.Builder{}
42-
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
43-
44-
package certificate
45-
46-
import "crypto/x509"
47-
48-
var mozillaIncluded *x509.CertPool
49-
50-
func init() {
51-
mozillaIncluded = x509.NewCertPool()
52-
`)
41+
pemBundle := strings.Builder{}
5342
for {
5443
record, err := reader.Read()
5544
if err == io.EOF {
@@ -60,18 +49,12 @@ func init() {
6049
if record[geoIndex] == "China" {
6150
continue
6251
}
63-
generated.WriteString("\n // ")
64-
generated.WriteString(record[nameIndex])
65-
generated.WriteString("\n")
66-
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
6752
cert := record[certIndex]
68-
// Remove single quotes
6953
cert = cert[1 : len(cert)-1]
70-
generated.WriteString(cert)
71-
generated.WriteString("`))\n")
54+
pemBundle.WriteString(cert)
55+
pemBundle.WriteString("\n")
7256
}
73-
generated.WriteString("}\n")
74-
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
57+
return writeGeneratedCertificateBundle("mozilla", "mozillaIncluded", pemBundle.String())
7558
}
7659

7760
func fetchChinaFingerprints() (map[string]bool, error) {
@@ -119,23 +102,11 @@ func updateChromeIncludedRootCAs() error {
119102
if err != nil {
120103
return err
121104
}
122-
subjectIndex := slices.Index(header, "Subject")
123105
statusIndex := slices.Index(header, "Google Chrome Status")
124106
certIndex := slices.Index(header, "X.509 Certificate (PEM)")
125107
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
126108

127-
generated := strings.Builder{}
128-
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
129-
130-
package certificate
131-
132-
import "crypto/x509"
133-
134-
var chromeIncluded *x509.CertPool
135-
136-
func init() {
137-
chromeIncluded = x509.NewCertPool()
138-
`)
109+
pemBundle := strings.Builder{}
139110
for {
140111
record, err := reader.Read()
141112
if err == io.EOF {
@@ -149,18 +120,39 @@ func init() {
149120
if chinaFingerprints[record[fingerprintIndex]] {
150121
continue
151122
}
152-
generated.WriteString("\n // ")
153-
generated.WriteString(record[subjectIndex])
154-
generated.WriteString("\n")
155-
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`")
156123
cert := record[certIndex]
157-
// Remove single quotes if present
158124
if len(cert) > 0 && cert[0] == '\'' {
159125
cert = cert[1 : len(cert)-1]
160126
}
161-
generated.WriteString(cert)
162-
generated.WriteString("`))\n")
127+
pemBundle.WriteString(cert)
128+
pemBundle.WriteString("\n")
129+
}
130+
return writeGeneratedCertificateBundle("chrome", "chromeIncluded", pemBundle.String())
131+
}
132+
133+
func writeGeneratedCertificateBundle(name string, variableName string, pemBundle string) error {
134+
goSource := `// Code generated by 'make update_certificates'. DO NOT EDIT.
135+
136+
package certificate
137+
138+
import (
139+
"crypto/x509"
140+
_ "embed"
141+
)
142+
143+
//go:embed ` + name + `.pem
144+
var ` + variableName + `PEM string
145+
146+
var ` + variableName + ` *x509.CertPool
147+
148+
func init() {
149+
` + variableName + ` = x509.NewCertPool()
150+
` + variableName + `.AppendCertsFromPEM([]byte(` + variableName + `PEM))
151+
}
152+
`
153+
err := os.WriteFile(filepath.Join("common/certificate", name+".pem"), []byte(pemBundle), 0o644)
154+
if err != nil {
155+
return err
163156
}
164-
generated.WriteString("}\n")
165-
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
157+
return os.WriteFile(filepath.Join("common/certificate", name+".go"), []byte(goSource), 0o644)
166158
}

0 commit comments

Comments
 (0)