Skip to content

Commit 2eb0155

Browse files
committed
Start of tcpproxy. No Listener or reverse dialing yet.
0 parents  commit 2eb0155

File tree

6 files changed

+761
-0
lines changed

6 files changed

+761
-0
lines changed

LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2017 The Go Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions are
5+
met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above
10+
copyright notice, this list of conditions and the following disclaimer
11+
in the documentation and/or other materials provided with the
12+
distribution.
13+
* Neither the name of Google Inc. nor the names of its
14+
contributors may be used to endorse or promote products derived from
15+
this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# tcpproxy
2+
3+
See https://godoc.org/github.com/bradfitz/tcpproxy/

http.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tcpproxy
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"net/http"
11+
)
12+
13+
// AddHTTPHostRoute appends a route to the ipPort listener that says
14+
// if the incoming HTTP/1.x Host header name is httpHost, the
15+
// connection is given to dest. If it doesn't match, rule processing
16+
// continues for any additional routes on ipPort.
17+
//
18+
// The ipPort is any valid net.Listen TCP address.
19+
func (p *Proxy) AddHTTPHostRoute(ipPort, httpHost string, dest Target) {
20+
p.addRoute(ipPort, httpHostMatch(httpHost), dest)
21+
}
22+
23+
type httpHostMatch string
24+
25+
func (host httpHostMatch) match(br *bufio.Reader) bool {
26+
return httpHostHeader(br) == string(host)
27+
}
28+
29+
// httpHostHeader returns the HTTP Host header from br without
30+
// consuming any of its bytes. It returns "" if it can't find one.
31+
func httpHostHeader(br *bufio.Reader) string {
32+
const maxPeek = 4 << 10
33+
peekSize := 0
34+
for {
35+
peekSize++
36+
if peekSize > maxPeek {
37+
b, _ := br.Peek(br.Buffered())
38+
return httpHostHeaderFromBytes(b)
39+
}
40+
b, err := br.Peek(peekSize)
41+
if n := br.Buffered(); n > peekSize {
42+
b, _ = br.Peek(n)
43+
peekSize = n
44+
}
45+
if len(b) > 0 {
46+
if b[0] < 'A' || b[0] > 'Z' {
47+
// Doesn't look like an HTTP verb
48+
// (GET, POST, etc).
49+
return ""
50+
}
51+
if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 {
52+
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b)))
53+
if err != nil {
54+
return ""
55+
}
56+
if len(req.Header["Host"]) > 1 {
57+
// TODO(bradfitz): what does
58+
// ReadRequest do if there are
59+
// multiple Host headers?
60+
return ""
61+
}
62+
return req.Host
63+
}
64+
}
65+
if err != nil {
66+
return httpHostHeaderFromBytes(b)
67+
}
68+
}
69+
}
70+
71+
var (
72+
lfHostColon = []byte("\nHost:")
73+
lfhostColon = []byte("\nhost:")
74+
crlf = []byte("\r\n")
75+
lf = []byte("\n")
76+
crlfcrlf = []byte("\r\n\r\n")
77+
lflf = []byte("\n\n")
78+
)
79+
80+
func httpHostHeaderFromBytes(b []byte) string {
81+
if i := bytes.Index(b, lfHostColon); i != -1 {
82+
return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):])))
83+
}
84+
if i := bytes.Index(b, lfhostColon); i != -1 {
85+
return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):])))
86+
}
87+
return ""
88+
}
89+
90+
// untilEOL returns v, truncated before the first '\n' byte, if any.
91+
// The returned slice may include a '\r' at the end.
92+
func untilEOL(v []byte) []byte {
93+
if i := bytes.IndexByte(v, '\n'); i != -1 {
94+
return v[:i]
95+
}
96+
return v
97+
}

sni.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tcpproxy
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"crypto/tls"
11+
"io"
12+
"net"
13+
)
14+
15+
// AddSNIRoute appends a route to the ipPort listener that says if the
16+
// incoming TLS SNI server name is sni, the connection is given to
17+
// dest. If it doesn't match, rule processing continues for any
18+
// additional routes on ipPort.
19+
//
20+
// The ipPort is any valid net.Listen TCP address.
21+
func (p *Proxy) AddSNIRoute(ipPort, sni string, dest Target) {
22+
p.addRoute(ipPort, sniMatch(sni), dest)
23+
}
24+
25+
type sniMatch string
26+
27+
func (sni sniMatch) match(br *bufio.Reader) bool {
28+
return clientHelloServerName(br) == string(sni)
29+
}
30+
31+
// clientHelloServerName returns the SNI server name inside the TLS ClientHello,
32+
// without consuming any bytes from br.
33+
// On any error, the empty string is returned.
34+
func clientHelloServerName(br *bufio.Reader) (sni string) {
35+
const recordHeaderLen = 5
36+
hdr, err := br.Peek(recordHeaderLen)
37+
if err != nil {
38+
return ""
39+
}
40+
const recordTypeHandshake = 0x16
41+
if hdr[0] != recordTypeHandshake {
42+
return "" // Not TLS.
43+
}
44+
recLen := int(hdr[3])<<8 | int(hdr[4]) // ignoring version in hdr[1:3]
45+
helloBytes, err := br.Peek(recordHeaderLen + recLen)
46+
if err != nil {
47+
return ""
48+
}
49+
tls.Server(sniSniffConn{r: bytes.NewReader(helloBytes)}, &tls.Config{
50+
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
51+
sni = hello.ServerName
52+
return nil, nil
53+
},
54+
}).Handshake()
55+
return
56+
}
57+
58+
// sniSniffConn is a net.Conn that reads from r, fails on Writes,
59+
// and crashes otherwise.
60+
type sniSniffConn struct {
61+
r io.Reader
62+
net.Conn // nil; crash on any unexpected use
63+
}
64+
65+
func (c sniSniffConn) Read(p []byte) (int, error) { return c.r.Read(p) }
66+
func (sniSniffConn) Write(p []byte) (int, error) { return 0, io.EOF }

0 commit comments

Comments
 (0)