Skip to content

Commit d036d98

Browse files
committed
fix: http server does not handle http2 logic correctly
1 parent d900c71 commit d036d98

File tree

7 files changed

+125
-49
lines changed

7 files changed

+125
-49
lines changed

component/tls/httpserver.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package tls
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/http"
7+
"time"
8+
9+
N "github.com/metacubex/mihomo/common/net"
10+
"github.com/metacubex/mihomo/log"
11+
12+
"golang.org/x/net/http2"
13+
)
14+
15+
func extractTlsHandshakeTimeoutFromServer(s *http.Server) time.Duration {
16+
var ret time.Duration
17+
for _, v := range [...]time.Duration{
18+
s.ReadHeaderTimeout,
19+
s.ReadTimeout,
20+
s.WriteTimeout,
21+
} {
22+
if v <= 0 {
23+
continue
24+
}
25+
if ret == 0 || v < ret {
26+
ret = v
27+
}
28+
}
29+
return ret
30+
}
31+
32+
// NewListenerForHttps returns a net.Listener for (*http.Server).Serve()
33+
// the "func (c *conn) serve(ctx context.Context)" in http\server.go
34+
// only do tls handshake and check NegotiatedProtocol with std's *tls.Conn
35+
// so we do the same logic to let http2 (not h2c) work fine
36+
func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Config) net.Listener {
37+
http2Server := &http2.Server{}
38+
_ = http2.ConfigureServer(httpServer, http2Server)
39+
return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) {
40+
c := Server(conn, tlsConfig)
41+
42+
tlsTO := extractTlsHandshakeTimeoutFromServer(httpServer)
43+
if tlsTO > 0 {
44+
dl := time.Now().Add(tlsTO)
45+
_ = conn.SetReadDeadline(dl)
46+
_ = conn.SetWriteDeadline(dl)
47+
}
48+
49+
err := c.HandshakeContext(ctx)
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
// Restore Conn-level deadlines.
55+
if tlsTO > 0 {
56+
_ = conn.SetReadDeadline(time.Time{})
57+
_ = conn.SetWriteDeadline(time.Time{})
58+
}
59+
60+
if c.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
61+
http2Server.ServeConn(c, &http2.ServeConnOpts{BaseConfig: httpServer})
62+
return nil, net.ErrClosed
63+
}
64+
return c, nil
65+
}, func(a any) {
66+
log.Errorln("https server panic: %s", a)
67+
})
68+
}

hub/route/server.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import (
2828
"github.com/gobwas/ws"
2929
"github.com/gobwas/ws/wsutil"
3030
"github.com/sagernet/cors"
31-
"golang.org/x/net/http2"
32-
"golang.org/x/net/http2/h2c"
3331
)
3432

3533
var (
@@ -215,11 +213,10 @@ func startTLS(cfg *Config) {
215213
}
216214
}
217215
server := &http.Server{
218-
// using h2c.NewHandler to ensure we can work in plain http2 and some tls conn is not *tls.Conn
219-
Handler: h2c.NewHandler(router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), &http2.Server{}),
216+
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
220217
}
221218
tlsServer = server
222-
if err = server.Serve(tlsC.NewListener(l, tlsConfig)); err != nil {
219+
if err = server.Serve(tlsC.NewListenerForHttps(l, server, tlsConfig)); err != nil {
223220
log.Errorln("External controller tls serve error: %s", err)
224221
}
225222
}

listener/inbound/common_test.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import (
2020
"github.com/metacubex/mihomo/component/dialer"
2121
"github.com/metacubex/mihomo/component/ech"
2222
"github.com/metacubex/mihomo/component/generater"
23+
tlsC "github.com/metacubex/mihomo/component/tls"
2324
C "github.com/metacubex/mihomo/constant"
2425

2526
"github.com/go-chi/chi/v5"
2627
"github.com/go-chi/render"
2728
"github.com/stretchr/testify/assert"
29+
"golang.org/x/net/http2"
2830
)
2931

3032
var httpPath = "/inbound_test"
@@ -134,7 +136,10 @@ func NewHttpTestTunnel() *TestTunnel {
134136
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
135137
render.Data(w, r, httpData)
136138
})
137-
go http.Serve(ln, r)
139+
h2Server := &http2.Server{}
140+
server := http.Server{Handler: r}
141+
_ = http2.ConfigureServer(&server, h2Server)
142+
go server.Serve(ln)
138143
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
139144
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
140145
if !assert.NoError(t, err) {
@@ -208,23 +213,27 @@ func NewHttpTestTunnel() *TestTunnel {
208213
ch: make(chan struct{}),
209214
}
210215
if metadata.DstPort == 443 {
211-
tlsConn := tls.Server(c, tlsConfig.Clone())
216+
tlsConn := tlsC.Server(c, tlsC.UConfig(tlsConfig))
212217
if metadata.Host == realityDest { // ignore the tls handshake error for realityDest
213218
if realityRealDial {
214219
rconn, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
215220
if err != nil {
216221
panic(err)
217222
}
218-
N.Relay(rconn, tlsConn)
219-
return
220-
}
221-
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
222-
defer cancel()
223-
if err := tlsConn.HandshakeContext(ctx); err != nil {
223+
N.Relay(rconn, conn)
224224
return
225225
}
226226
}
227-
ln.ch <- tlsConn
227+
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
228+
defer cancel()
229+
if err := tlsConn.HandshakeContext(ctx); err != nil {
230+
return
231+
}
232+
if tlsConn.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
233+
h2Server.ServeConn(tlsConn, &http2.ServeConnOpts{BaseConfig: &server})
234+
} else {
235+
ln.ch <- tlsConn
236+
}
228237
} else {
229238
ln.ch <- c
230239
}

listener/inbound/vless_test.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,9 @@ func TestInboundVless_Reality(t *testing.T) {
237237
outboundOptions.Flow = "xtls-rprx-vision"
238238
testInboundVless(t, inboundOptions, outboundOptions)
239239
})
240-
t.Run("ECH", func(t *testing.T) {
241-
inboundOptions := inboundOptions
240+
t.Run("X25519MLKEM768", func(t *testing.T) {
242241
outboundOptions := outboundOptions
243-
inboundOptions.EchKey = echKeyPem
244-
outboundOptions.ECHOpts = outbound.ECHOptions{
245-
Enable: true,
246-
Config: echConfigBase64,
247-
}
242+
outboundOptions.RealityOpts.SupportX25519MLKEM768 = true
248243
testInboundVless(t, inboundOptions, outboundOptions)
249244
t.Run("xtls-rprx-vision", func(t *testing.T) {
250245
outboundOptions := outboundOptions
@@ -276,14 +271,9 @@ func TestInboundVless_Reality_Grpc(t *testing.T) {
276271
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
277272
}
278273
testInboundVless(t, inboundOptions, outboundOptions)
279-
t.Run("ECH", func(t *testing.T) {
280-
inboundOptions := inboundOptions
274+
t.Run("X25519MLKEM768", func(t *testing.T) {
281275
outboundOptions := outboundOptions
282-
inboundOptions.EchKey = echKeyPem
283-
outboundOptions.ECHOpts = outbound.ECHOptions{
284-
Enable: true,
285-
Config: echConfigBase64,
286-
}
276+
outboundOptions.RealityOpts.SupportX25519MLKEM768 = true
287277
testInboundVless(t, inboundOptions, outboundOptions)
288278
})
289279
}

listener/sing_vless/server.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
8484

8585
tlsConfig := &tlsC.Config{}
8686
var realityBuilder *reality.Builder
87-
var httpHandler http.Handler
87+
var httpServer http.Server
8888

8989
if config.Certificate != "" && config.PrivateKey != "" {
9090
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
@@ -119,16 +119,16 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
119119
}
120120
sl.HandleConn(conn, tunnel, additions...)
121121
})
122-
httpHandler = httpMux
122+
httpServer.Handler = httpMux
123123
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
124124
}
125125
if config.GrpcServiceName != "" {
126-
httpHandler = gun.NewServerHandler(gun.ServerOption{
126+
httpServer.Handler = gun.NewServerHandler(gun.ServerOption{
127127
ServiceName: config.GrpcServiceName,
128128
ConnHandler: func(conn net.Conn) {
129129
sl.HandleConn(conn, tunnel, additions...)
130130
},
131-
HttpHandler: httpHandler,
131+
HttpHandler: httpServer.Handler,
132132
})
133133
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
134134
}
@@ -144,15 +144,19 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
144144
if realityBuilder != nil {
145145
l = realityBuilder.NewListener(l)
146146
} else if len(tlsConfig.Certificates) > 0 {
147-
l = tlsC.NewListener(l, tlsConfig)
147+
if httpServer.Handler != nil {
148+
l = tlsC.NewListenerForHttps(l, &httpServer, tlsConfig)
149+
} else {
150+
l = tlsC.NewListener(l, tlsConfig)
151+
}
148152
} else {
149153
return nil, errors.New("disallow using Vless without both certificates/reality config")
150154
}
151155
sl.listeners = append(sl.listeners, l)
152156

153157
go func() {
154-
if httpHandler != nil {
155-
_ = http.Serve(l, httpHandler)
158+
if httpServer.Handler != nil {
159+
_ = httpServer.Serve(l)
156160
return
157161
}
158162
for {

listener/sing_vmess/server.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
7878

7979
tlsConfig := &tlsC.Config{}
8080
var realityBuilder *reality.Builder
81-
var httpHandler http.Handler
81+
var httpServer http.Server
8282

8383
if config.Certificate != "" && config.PrivateKey != "" {
8484
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
@@ -113,16 +113,16 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
113113
}
114114
sl.HandleConn(conn, tunnel, additions...)
115115
})
116-
httpHandler = httpMux
116+
httpServer.Handler = httpMux
117117
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
118118
}
119119
if config.GrpcServiceName != "" {
120-
httpHandler = gun.NewServerHandler(gun.ServerOption{
120+
httpServer.Handler = gun.NewServerHandler(gun.ServerOption{
121121
ServiceName: config.GrpcServiceName,
122122
ConnHandler: func(conn net.Conn) {
123123
sl.HandleConn(conn, tunnel, additions...)
124124
},
125-
HttpHandler: httpHandler,
125+
HttpHandler: httpServer.Handler,
126126
})
127127
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
128128
}
@@ -138,13 +138,17 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
138138
if realityBuilder != nil {
139139
l = realityBuilder.NewListener(l)
140140
} else if len(tlsConfig.Certificates) > 0 {
141-
l = tlsC.NewListener(l, tlsConfig)
141+
if httpServer.Handler != nil {
142+
l = tlsC.NewListenerForHttps(l, &httpServer, tlsConfig)
143+
} else {
144+
l = tlsC.NewListener(l, tlsConfig)
145+
}
142146
}
143147
sl.listeners = append(sl.listeners, l)
144148

145149
go func() {
146-
if httpHandler != nil {
147-
_ = http.Serve(l, httpHandler)
150+
if httpServer.Handler != nil {
151+
_ = httpServer.Serve(l)
148152
return
149153
}
150154
for {

listener/trojan/server.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
7272

7373
tlsConfig := &tlsC.Config{}
7474
var realityBuilder *reality.Builder
75-
var httpHandler http.Handler
75+
var httpServer http.Server
7676

7777
if config.Certificate != "" && config.PrivateKey != "" {
7878
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
@@ -107,16 +107,16 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
107107
}
108108
sl.HandleConn(conn, tunnel, additions...)
109109
})
110-
httpHandler = httpMux
110+
httpServer.Handler = httpMux
111111
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
112112
}
113113
if config.GrpcServiceName != "" {
114-
httpHandler = gun.NewServerHandler(gun.ServerOption{
114+
httpServer.Handler = gun.NewServerHandler(gun.ServerOption{
115115
ServiceName: config.GrpcServiceName,
116116
ConnHandler: func(conn net.Conn) {
117117
sl.HandleConn(conn, tunnel, additions...)
118118
},
119-
HttpHandler: httpHandler,
119+
HttpHandler: httpServer.Handler,
120120
})
121121
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
122122
}
@@ -132,15 +132,19 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
132132
if realityBuilder != nil {
133133
l = realityBuilder.NewListener(l)
134134
} else if len(tlsConfig.Certificates) > 0 {
135-
l = tlsC.NewListener(l, tlsConfig)
135+
if httpServer.Handler != nil {
136+
l = tlsC.NewListenerForHttps(l, &httpServer, tlsConfig)
137+
} else {
138+
l = tlsC.NewListener(l, tlsConfig)
139+
}
136140
} else if !config.TrojanSSOption.Enabled {
137141
return nil, errors.New("disallow using Trojan without both certificates/reality/ss config")
138142
}
139143
sl.listeners = append(sl.listeners, l)
140144

141145
go func() {
142-
if httpHandler != nil {
143-
_ = http.Serve(l, httpHandler)
146+
if httpServer.Handler != nil {
147+
_ = httpServer.Serve(l)
144148
return
145149
}
146150
for {

0 commit comments

Comments
 (0)