Skip to content

Commit 851546e

Browse files
authored
Merge pull request #219 from SenseUnit/sleep_fix
Improvement: HTTP/2 connections healthcheck
2 parents 4682dbe + d8bf230 commit 851546e

3 files changed

Lines changed: 30 additions & 9 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,13 @@ Supported proxy schemes are:
476476
* `min-tls-version` - minimum TLS version.
477477
* `max-tls-version` - maximum TLS version.
478478
* `fetchrandom` - request server to send random data in the first request via every new HTTP/2 connection. Useful to trick TLS-in-TLS detection. Value format: length as a number or range `x-y`. Example: `fetchrandom=100000-500000`.
479+
* `read-idle-timeout` - timeout after which a health check using a ping frame will be carried out if no frame is received on the connection. If zero, no health check is performed. Default: `5s`.
480+
* `ping-timeout` - timeout after which the connection will be closed if a response to a ping is not received. Default: `5s`.
479481
* `utls-fp` - TLS fingerprint parroting with uTLS library. See the [list](https://pkg.go.dev/github.com/refraction-networking/utls#pkg-variables) of allowed client IDs. Example: `utls-fp=HelloChrome_Auto`.
480482
* `h2c` - HTTP/2 proxy over plaintext connection with the CONNECT method support. Examples: `h2c://example.org:8080`.
481483
* `fetchrandom` - request server to send random data in the first request via every new HTTP/2 connection. Useful to trick TLS-in-TLS detection. Value format: length as a number or range `x-y`. Example: `fetchrandom=100000-500000`.
484+
* `read-idle-timeout` - timeout after which a health check using a ping frame will be carried out if no frame is received on the connection. If zero, no health check is performed. Default: `5s`.
485+
* `ping-timeout` - timeout after which the connection will be closed if a response to a ping is not received. Default: `5s`.
482486
* `socks5`, `socks5h` - SOCKS5 proxy with hostname resolving via remote proxy. Example: `socks5://127.0.0.1:9050`.
483487
* `socks5s`, `socks5hs` - SOCKS5 proxy over TLS with hostname resolving via remote proxy. Example: `socks5s://example.com:10443`. This method also supports additional parameters passed in query string:
484488
* `cafile` - file with CA certificates in PEM format used to verify TLS peer.

dialer/dialer.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,11 @@ func MaybeWrapWithContextDialer(d LegacyDialer) Dialer {
8989
return wrappedDialer{d}
9090
}
9191

92-
func garbageLenFuncFromURL(u *url.URL, paramname string) (func() int, error) {
93-
params, err := url.ParseQuery(u.RawQuery)
94-
if err != nil {
95-
return nil, fmt.Errorf("garbage len param parse failed: %w", err)
96-
}
97-
if !params.Has(paramname) {
92+
func garbageLenFuncFromValues(params url.Values) (func() int, error) {
93+
if !params.Has("fetchrandom") {
9894
return nil, nil
9995
}
100-
left, right, found := strings.Cut(params.Get(paramname), "-")
96+
left, right, found := strings.Cut(params.Get("fetchrandom"), "-")
10197
if found {
10298
lo, err := strconv.Atoi(left)
10399
if err != nil {

dialer/h2.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ type H2ProxyDialer struct {
3030
func H2ProxyDialerFromURL(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error) {
3131
host := u.Hostname()
3232
port := u.Port()
33+
params, err := url.ParseQuery(u.RawQuery)
34+
if err != nil {
35+
return nil, fmt.Errorf("query string parse failed: %w", err)
36+
}
3337

3438
var (
3539
tlsConfig *tls.Config
3640
tlsFactory func(net.Conn, *tls.Config) net.Conn
37-
err error
3841
h2c bool
3942
scheme string
4043
)
@@ -66,13 +69,31 @@ func H2ProxyDialerFromURL(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error)
6669
}
6770

6871
address := net.JoinHostPort(host, port)
69-
garbageLenFunc, err := garbageLenFuncFromURL(u, "fetchrandom")
72+
garbageLenFunc, err := garbageLenFuncFromValues(params)
7073
if err != nil {
7174
return nil, err
7275
}
76+
readIdleTimeout := 5 * time.Second
77+
pingTimeout := 5 * time.Second
78+
if params.Has("read-idle-timeout") {
79+
d, err := time.ParseDuration(params.Get("read-idle-timeout"))
80+
if err != nil {
81+
return nil, fmt.Errorf("unable to parse duration in \"read-idle-timeout\" option: %w", err)
82+
}
83+
readIdleTimeout = d
84+
}
85+
if params.Has("ping-timeout") {
86+
d, err := time.ParseDuration(params.Get("ping-timeout"))
87+
if err != nil {
88+
return nil, fmt.Errorf("unable to parse duration in \"ping-timeout\" option: %w", err)
89+
}
90+
pingTimeout = d
91+
}
7392
t := &http2.Transport{
7493
AllowHTTP: h2c,
7594
TLSClientConfig: tlsConfig,
95+
ReadIdleTimeout: readIdleTimeout,
96+
PingTimeout: pingTimeout,
7697
}
7798
t.ConnPool = &clientConnPool{
7899
t: t,

0 commit comments

Comments
 (0)