2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE file.
4
4
5
- // Package tcpproxy lets users build TCP, HTTP/1, and TLS+SNI proxies.
5
+ // Package tcpproxy lets users build TCP proxies, optionally making
6
+ // routing decisions based on HTTP/1 Host headers and the SNI hostname
7
+ // in TLS connections.
6
8
//
7
9
// Typical usage:
8
10
//
14
16
// p.AddSNIHostRoute(":443", "bar.com", tcpproxy.To("10.0.0.2:4432"))
15
17
// p.AddRoute(":443", tcpproxy.To("10.0.0.1:4431")) // fallback
16
18
// log.Fatal(p.Run())
19
+ //
20
+ // Calling Run (or Start) on a proxy also starts all the necessary
21
+ // listeners.
22
+ //
23
+ // For each accepted connection, the rules for that ipPort are
24
+ // matched, in order. If one matches (currently HTTP Host, SNI, or
25
+ // always), then the connection is handed to the target.
26
+ //
27
+ // The two predefined Target implementations are:
28
+ //
29
+ // 1) DialProxy, proxying to another address (use the To func to return a
30
+ // DialProxy value),
31
+ //
32
+ // 2) TargetListener, making the matched connection available via a
33
+ // net.Listener.Accept call.
34
+ //
35
+ // But Target is an interface, so you can also write your own.
36
+ //
37
+ // Note that tcpproxy does not do any TLS encryption or decryption. It
38
+ // only (via DialProxy) copies bytes around. The SNI hostname in the TLS
39
+ // header is unencrypted, for better or worse.
17
40
package tcpproxy
18
41
19
42
import (
20
43
"bufio"
21
- "bytes"
22
44
"context"
23
45
"errors"
24
46
"io"
@@ -28,7 +50,10 @@ import (
28
50
)
29
51
30
52
// Proxy is a proxy. Its zero value is a valid proxy that does
31
- // nothing. Call methods to add routes before calling Run.
53
+ // nothing. Call methods to add routes before calling Start or Run.
54
+ //
55
+ // The order that routes are added in matters; each is matched in the order
56
+ // registered.
32
57
type Proxy struct {
33
58
routes map [string ][]route // ip:port => route
34
59
@@ -156,11 +181,14 @@ func (p *Proxy) serveConn(c net.Conn, routes []route) bool {
156
181
br := bufio .NewReader (c )
157
182
for _ , route := range routes {
158
183
if route .matcher .match (br ) {
159
- buffered , _ := br .Peek (br .Buffered ())
160
- route .target .HandleConn (changeReaderConn {
161
- r : io .MultiReader (bytes .NewReader (buffered ), c ),
162
- Conn : c ,
163
- }, c )
184
+ if n := br .Buffered (); n > 0 {
185
+ peeked , _ := br .Peek (br .Buffered ())
186
+ c = & Conn {
187
+ Peeked : peeked ,
188
+ Conn : c ,
189
+ }
190
+ }
191
+ route .target .HandleConn (c )
164
192
return true
165
193
}
166
194
}
@@ -170,32 +198,48 @@ func (p *Proxy) serveConn(c net.Conn, routes []route) bool {
170
198
return false
171
199
}
172
200
173
- // changeReaderConn is a net.Conn wrapper with a separate reader function.
174
- type changeReaderConn struct {
175
- r io.Reader
201
+ // Conn is an incoming connection that has had some bytes read from it
202
+ // to determine how to route the connection. The Read method stitches
203
+ // the peeked bytes and unread bytes back together.
204
+ type Conn struct {
205
+ // Peeked are the bytes that have been read from Conn for the
206
+ // purposes of route matching, but have not yet been consumed
207
+ // by Read calls. It set to nil by Read when fully consumed.
208
+ Peeked []byte
209
+
210
+ // Conn is the underlying connection.
211
+ // It can be type asserted against *net.TCPConn or other types
212
+ // as needed. It should not be read from directly unless
213
+ // Peeked is nil.
176
214
net.Conn
177
215
}
178
216
179
- func (c changeReaderConn ) Read (p []byte ) (int , error ) { return c .r .Read (p ) }
217
+ func (c * Conn ) Read (p []byte ) (n int , err error ) {
218
+ if len (c .Peeked ) > 0 {
219
+ n = copy (p , c .Peeked )
220
+ c .Peeked = c .Peeked [n :]
221
+ if len (c .Peeked ) == 0 {
222
+ c .Peeked = nil
223
+ }
224
+ return n , nil
225
+ }
226
+ return c .Conn .Read (p )
227
+ }
180
228
181
229
// Target is what an incoming matched connection is sent to.
182
230
type Target interface {
183
231
// HandleConn is called when an incoming connection is
184
232
// matched. After the call to HandleConn, the tcpproxy
185
233
// package never touches the conn again. Implementations are
186
- // responsible for closing the conn when needed.
187
- //
188
- // The c Conn acts like a new conn, without any bytes consumed,
189
- // but it has an unexported concrete type and cannot be type
190
- // asserted to *net.TCPConn, etc.
234
+ // responsible for closing the connection when needed.
191
235
//
192
- // The rawConn represents the underlying connections (with
193
- // some bytes removed) and should only be used for type
194
- // assertions and setting deadlines, not reading .
195
- HandleConn (c net. Conn , rawConn net.Conn )
236
+ // The concrete type of conn will be of type *Conn if any
237
+ // bytes have been consumed for the purposes of route
238
+ // matching .
239
+ HandleConn (net.Conn )
196
240
}
197
241
198
- // To is shorthand way of writing &DialProxy{Addr: addr}.
242
+ // To is shorthand way of writing &tlsproxy. DialProxy{Addr: addr}.
199
243
func To (addr string ) * DialProxy {
200
244
return & DialProxy {Addr : addr }
201
245
}
@@ -229,7 +273,16 @@ type DialProxy struct {
229
273
OnDialError func (src net.Conn , dstDialErr error )
230
274
}
231
275
232
- func (dp * DialProxy ) HandleConn (src net.Conn , rawSrc net.Conn ) {
276
+ // UnderlyingConn returns c.Conn if c of type *Conn,
277
+ // otherwise it returns c.
278
+ func UnderlyingConn (c net.Conn ) net.Conn {
279
+ if wrap , ok := c .(* Conn ); ok {
280
+ return wrap .Conn
281
+ }
282
+ return c
283
+ }
284
+
285
+ func (dp * DialProxy ) HandleConn (src net.Conn ) {
233
286
ctx := context .Background ()
234
287
var cancel context.CancelFunc
235
288
if dp .DialTimeout >= 0 {
@@ -246,7 +299,7 @@ func (dp *DialProxy) HandleConn(src net.Conn, rawSrc net.Conn) {
246
299
defer src .Close ()
247
300
defer dst .Close ()
248
301
if ka := dp .keepAlivePeriod (); ka > 0 {
249
- if c , ok := rawSrc .(* net.TCPConn ); ok {
302
+ if c , ok := UnderlyingConn ( src ) .(* net.TCPConn ); ok {
250
303
c .SetKeepAlive (true )
251
304
c .SetKeepAlivePeriod (ka )
252
305
}
0 commit comments