mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
Begin implementing TLS passthrough
Basically working, but still needs: * UI for selecting TLS passthrough * Client Let's Encrypt integration for automatically getting certs. * More testing. The changes were pretty invasive.
This commit is contained in:
parent
05aeec0e9b
commit
14a666481a
@ -10,9 +10,11 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -29,9 +31,10 @@ type SmtpConfig struct {
|
||||
}
|
||||
|
||||
type BoringProxy struct {
|
||||
db *Database
|
||||
tunMan *TunnelManager
|
||||
httpClient *http.Client
|
||||
db *Database
|
||||
tunMan *TunnelManager
|
||||
httpClient *http.Client
|
||||
httpListener *PassthroughListener
|
||||
}
|
||||
|
||||
func Listen() {
|
||||
@ -98,16 +101,15 @@ func Listen() {
|
||||
},
|
||||
}
|
||||
|
||||
p := &BoringProxy{db, tunMan, httpClient}
|
||||
httpListener := NewPassthroughListener()
|
||||
|
||||
p := &BoringProxy{db, tunMan, httpClient, httpListener}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
GetCertificate: certConfig.GetCertificate,
|
||||
NextProtos: []string{"h2", "acme-tls/1"},
|
||||
}
|
||||
tlsListener, err := tls.Listen("tcp", ":443", tlsConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tlsListener := tls.NewListener(httpListener, tlsConfig)
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
timestamp := time.Now().Format(time.RFC3339)
|
||||
@ -131,7 +133,69 @@ func Listen() {
|
||||
}
|
||||
}()
|
||||
|
||||
http.Serve(tlsListener, nil)
|
||||
go http.Serve(tlsListener, nil)
|
||||
|
||||
listener, err := net.Listen("tcp", ":443")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
|
||||
go p.handleConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BoringProxy) handleConnection(clientConn net.Conn) {
|
||||
|
||||
clientHello, clientReader, err := peekClientHello(clientConn)
|
||||
if err != nil {
|
||||
log.Println("peekClientHello error", err)
|
||||
return
|
||||
}
|
||||
|
||||
passConn := NewProxyConn(clientConn, clientReader)
|
||||
|
||||
tunnel, exists := p.db.GetTunnel(clientHello.ServerName)
|
||||
|
||||
if exists && tunnel.TlsPassthrough {
|
||||
p.passthroughRequest(passConn, tunnel)
|
||||
} else {
|
||||
p.httpListener.PassConn(passConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BoringProxy) passthroughRequest(conn net.Conn, tunnel Tunnel) {
|
||||
|
||||
upstreamAddr := fmt.Sprintf("localhost:%d", tunnel.TunnelPort)
|
||||
upstreamConn, err := net.Dial("tcp", upstreamAddr)
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
defer upstreamConn.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
io.Copy(conn, upstreamConn)
|
||||
conn.(*ProxyConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(upstreamConn, conn)
|
||||
upstreamConn.(*net.TCPConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (p *BoringProxy) proxyRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -50,6 +50,7 @@ type Tunnel struct {
|
||||
AuthUsername string `json:"auth_username"`
|
||||
AuthPassword string `json:"auth_password"`
|
||||
CssId string `json:"css_id"`
|
||||
TlsPassthrough bool `json:"tls_passthrough"`
|
||||
}
|
||||
|
||||
func NewDatabase() (*Database, error) {
|
||||
|
101
sni.go
Normal file
101
sni.go
Normal file
@ -0,0 +1,101 @@
|
||||
// NOTE: The code in this file was mostly copied from this very helpful
|
||||
// article:
|
||||
// https://www.agwa.name/blog/post/writing_an_sni_proxy_in_go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type readOnlyConn struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (conn readOnlyConn) Read(p []byte) (int, error) { return conn.reader.Read(p) }
|
||||
func (conn readOnlyConn) Write(p []byte) (int, error) { return 0, io.ErrClosedPipe }
|
||||
func (conn readOnlyConn) Close() error { return nil }
|
||||
func (conn readOnlyConn) LocalAddr() net.Addr { return nil }
|
||||
func (conn readOnlyConn) RemoteAddr() net.Addr { return nil }
|
||||
func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
func peekClientHello(reader io.Reader) (*tls.ClientHelloInfo, io.Reader, error) {
|
||||
peekedBytes := new(bytes.Buffer)
|
||||
hello, err := readClientHello(io.TeeReader(reader, peekedBytes))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return hello, io.MultiReader(peekedBytes, reader), nil
|
||||
}
|
||||
|
||||
func readClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) {
|
||||
var hello *tls.ClientHelloInfo
|
||||
|
||||
err := tls.Server(readOnlyConn{reader: reader}, &tls.Config{
|
||||
GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
hello = new(tls.ClientHelloInfo)
|
||||
*hello = *argHello
|
||||
return nil, nil
|
||||
},
|
||||
}).Handshake()
|
||||
|
||||
if hello == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hello, nil
|
||||
}
|
||||
|
||||
type PassthroughListener struct {
|
||||
ch chan net.Conn
|
||||
}
|
||||
|
||||
func NewPassthroughListener() *PassthroughListener {
|
||||
return &PassthroughListener{
|
||||
ch: make(chan net.Conn),
|
||||
}
|
||||
}
|
||||
func (f *PassthroughListener) Accept() (net.Conn, error) {
|
||||
return <-f.ch, nil
|
||||
}
|
||||
func (f *PassthroughListener) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (f *PassthroughListener) Addr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
func (f *PassthroughListener) PassConn(conn net.Conn) {
|
||||
f.ch <- conn
|
||||
}
|
||||
|
||||
// This type creates a new net.Conn that's the same as an old one, except a new
|
||||
// reader is provided. So it proxies every method except Read. I'm sure there's
|
||||
// a cleaner way to do this...
|
||||
type ProxyConn struct {
|
||||
conn net.Conn
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func NewProxyConn(conn net.Conn, reader io.Reader) *ProxyConn {
|
||||
return &ProxyConn{
|
||||
conn,
|
||||
reader,
|
||||
}
|
||||
}
|
||||
func (c ProxyConn) CloseWrite() error { return c.conn.(*net.TCPConn).CloseWrite() }
|
||||
func (c ProxyConn) Read(p []byte) (int, error) { return c.reader.Read(p) }
|
||||
func (c ProxyConn) Write(p []byte) (int, error) { return c.conn.Write(p) }
|
||||
|
||||
// TODO: is this safe? Will it actually close properly?
|
||||
func (c ProxyConn) Close() error { return c.conn.Close() }
|
||||
func (c ProxyConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
|
||||
func (c ProxyConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
|
||||
func (c ProxyConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) }
|
||||
func (c ProxyConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) }
|
||||
func (c ProxyConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) }
|
Loading…
Reference in New Issue
Block a user