-
Notifications
You must be signed in to change notification settings - Fork 0
/
utls.go
188 lines (159 loc) · 5.36 KB
/
utls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright 2023 Wayback Archiver. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package proxier
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"golang.org/x/net/http2"
"golang.org/x/net/proxy"
utls "github.com/refraction-networking/utls"
)
var defaultClientHelloID = &utls.HelloChrome_102
// A http.RoundTripper that uses uTLS (with a specified Client Hello ID) to make
// TLS connections.
//
// Can only be reused among servers which negotiate the same ALPN.
type UTLSRoundTripper struct {
clientHelloID *utls.ClientHelloID
config *utls.Config
proxyDialer proxy.Dialer
// Transport for HTTP requests, which don't use uTLS.
httpRT *http.Transport
}
// RoundTrip executes a single HTTP transaction, using the UTLS protocol for secure connections.
// It takes an `http.Request` and returns an `http.Response` and an error.
// This method is used in an HTTP client to send a request and receive a response.
func (u *UTLSRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
switch req.URL.Scheme {
case "http":
// If http, we don't invoke uTLS; just pass it to an ordinary http.Transport.
return u.httpRT.RoundTrip(req)
case "https":
return u.httpsRoundTrip(req)
default:
return nil, fmt.Errorf("unsupported URL scheme: %s", req.URL.Scheme)
}
}
func (u *UTLSRoundTripper) httpsRoundTrip(req *http.Request) (*http.Response, error) {
var err error
// Make an http.Transport or http2.Transport as appropriate.
rt, err := u.makeRoundTripper(req.URL)
if err != nil {
return nil, err
}
if req.UserAgent() == "" {
req.Header.Set("User-Agent", useragent)
}
// Forward the request to the internal http.Transport or http2.Transport.
return rt.RoundTrip(req)
}
func (u *UTLSRoundTripper) makeRoundTripper(url *url.URL) (http.RoundTripper, error) {
addr, err := addrForDial(url)
if err != nil {
return nil, err
}
// Connect to the given address, through a proxy if requested, and
// initiate a TLS handshake using the given ClientHelloID. Return the
// resulting connection.
dial := func(network, addr string) (*utls.UConn, error) {
return dialUTLS(network, addr, u.config, u.clientHelloID, u.proxyDialer)
}
bootstrapConn, err := dial("tcp", addr)
if err != nil {
return nil, err
}
// Peek at what protocol we negotiated.
protocol := bootstrapConn.ConnectionState().NegotiatedProtocol
// Protects bootstrapConn.
// This is the callback for future dials done by the internal
// http.Transport or http2.Transport.
dialTLS := func(network, addr string) (net.Conn, error) {
// On the first dial, reuse bootstrapConn.
if bootstrapConn != nil {
uconn := bootstrapConn
bootstrapConn = nil
return uconn, nil
}
// Later dials make a new connection.
uconn, err := dial(network, addr)
if err != nil {
return nil, err
}
if uconn.ConnectionState().NegotiatedProtocol != protocol {
return nil, fmt.Errorf("unexpected switch from ALPN %q to %q",
protocol, uconn.ConnectionState().NegotiatedProtocol)
}
return uconn, nil
}
// Construct an http.Transport or http2.Transport depending on ALPN.
switch protocol {
case http2.NextProtoTLS:
// Unfortunately http2.Transport does not expose the same
// configuration options as http.Transport with regard to
// timeouts, etc., so we are at the mercy of the defaults.
// https://github.com/golang/go/issues/16581
return &http2.Transport{
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
// Ignore the *tls.Config parameter; use our
// static cfg instead.
return dialTLS(network, addr)
},
}, nil
default:
// With http.Transport, copy important default fields from
// http.DefaultTransport, such as TLSHandshakeTimeout and
// IdleConnTimeout, before overriding DialTLS.
tr := httpRoundTripper.Clone()
tr.DialTLS = dialTLS
return tr, nil
}
}
// Dialer returns the underlying *net.Dialer used by the UTLSRoundTripper's proxyDialer.
// This method is useful for accessing additional properties of the dialer,
// such as its proxy settings.
//
// func main() {
// dialer := UTLSRoundTripper.Dialer()
// conn, err := dialer.Dial("tcp", "example.com:443")
// if err != nil {
// panic(err)
// }
// // Optional to wraps conn to a tls client for tls transport.
// conn = tls.Client(conn, &tls.Config{ServerName: "example.com"})
//
// // ...
// }
func (u *UTLSRoundTripper) Dialer() proxy.Dialer {
return u.proxyDialer
}
// NewUTLSRoundTripper creates a new round tripper that can be used in an HTTP
// client to handle secure connections using the UTLS protocol.
//
// It takes an optional list of `UTLSOption` arguments that can be used to
// customize the behavior of the round tripper. It returns an `http.RoundTripper`
// and an error.
func NewUTLSRoundTripper(opts ...UTLSOption) (http.RoundTripper, error) {
u := UTLSOptions(opts...)
var (
err error
proxyURL *url.URL
rt = &UTLSRoundTripper{
clientHelloID: u.clientHello,
config: u.config,
}
)
rt.proxyDialer, proxyURL, err = makeProxyDialer(u.proxy, u.config, u.clientHello)
if err != nil {
return nil, fmt.Errorf("make proxy dialer failed: %w", err)
}
// This special-case RoundTripper is used for HTTP requests, which don't
// use uTLS but should use the specified proxy.
httpRT := httpRoundTripper.Clone()
httpRT.Proxy = http.ProxyURL(proxyURL)
rt.httpRT = httpRT
return rt, nil
}