mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
105 lines
3.4 KiB
Go
105 lines
3.4 KiB
Go
// NOTE: A lot of this code was copied from this very helpful article:
|
|
// https://www.agwa.name/blog/post/writing_an_sni_proxy_in_go
|
|
|
|
package boringproxy
|
|
|
|
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. This is
|
|
// necessary because by calling peekClientHello, part of the reader is read,
|
|
// so we need to create a new reader with the already read data inserted back
|
|
// in the front.
|
|
// 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, or does it need to be
|
|
// connected to the reader somehow?
|
|
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) }
|