From fd9bb5b0b172788b16b036708fa8abe9b68fc6ef Mon Sep 17 00:00:00 2001 From: bel Date: Sat, 15 Apr 2023 14:46:59 -0600 Subject: [PATCH] merge feature/ssl to support https mode --- internal/proxy/proxy.go | 145 ++++++++++++++++++++++++++++++++++++++++ main.go | 43 +++++++++++- option.go | 3 + 3 files changed, 189 insertions(+), 2 deletions(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index acd8148..6dfbf9e 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -1,10 +1,17 @@ package proxy import ( + "bufio" "context" + "crypto/tls" "encoding/json" + "errors" + "fmt" + "io" + "log" "net" "net/http" + "strings" "github.com/zhsj/wghttp/internal/resolver" "github.com/zhsj/wghttp/internal/third_party/tailscale/httpproxy" @@ -92,3 +99,141 @@ func (p Proxy) Serve(ln net.Listener) { }() <-errc } + +func (p Proxy) ServeTLS(ln net.Listener, certFile, keyFile string) { + d := dialWithDNS(p.Dial, p.DNS) + + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + panic(err) + } + cfg := &tls.Config{Certificates: []tls.Certificate{cert}} + ln = tls.NewListener(ln, cfg) + defer ln.Close() + + for { + if err := acceptAndHandle(ln, httpproxy.Handler(d), d); err != nil { + panic(err) + } + } +} + +type singleListener struct { + single net.Conn + addr net.Addr +} + +func (listener *singleListener) Accept() (net.Conn, error) { + if listener.single == nil { + return nil, errors.New("single listener closed") + } + defer listener.Close() + return listener.single, nil +} +func (listener *singleListener) Close() error { + listener.single = nil + return nil +} +func (listener *singleListener) Addr() net.Addr { return listener.addr } + +func acceptAndHandle(ln net.Listener, h http.Handler, d dialer) error { + conn, err := ln.Accept() + if err != nil { + return err + } + go func() { + conn, ok := isConnect(conn) + if ok { + handleConnectConn(conn, d) + } else { + handleHTTPConn(conn, h) + } + }() + return nil +} + +func handleConnectConn(conn peekingConn, d dialer) { + if err := _handleConnectConn(conn, d); err != nil { + log.Println("connect err:", err) + } +} + +func _handleConnectConn(conn peekingConn, d dialer) error { + defer conn.Close() + + var host string + for { + line, _ := conn.buff.ReadString('\n') + if strings.HasPrefix(line, "Host: ") { + host = strings.TrimSpace(line[6:]) + } + if strings.TrimSpace(line) == "" { + break + } + } + if host == "" { + return errors.New("no Host:") + } + + conn2, err := d(conn.ctx, "tcp", host) + if err != nil { + return fmt.Errorf("failed to dial tcp:%q: %w", host, err) + } + defer conn2.Close() + + io.WriteString(conn, "HTTP/1.1 200 OK\r\n\r\n") + + c := make(chan error, 1) + go func() { + _, err := io.Copy(conn2, conn) + select { + case c <- err: + default: + close(c) + } + }() + go func() { + io.Copy(conn, conn2) + select { + case c <- err: + default: + close(c) + } + }() + <-c + return nil +} + +func handleHTTPConn(conn net.Conn, h http.Handler) { + l := &singleListener{ + single: conn, + addr: conn.LocalAddr(), + } + s := &http.Server{ + Handler: h, + } + s.Serve(l) +} + +type peekingConn struct { + net.Conn + buff *bufio.Reader + ctx context.Context + can context.CancelFunc +} + +func (conn peekingConn) Close() error { + conn.can() + return conn.Conn.Close() +} + +func (conn peekingConn) Read(b []byte) (int, error) { + return conn.buff.Read(b) +} + +func isConnect(c net.Conn) (peekingConn, bool) { + ctx, can := context.WithCancel(context.Background()) + conn := peekingConn{Conn: c, buff: bufio.NewReader(c), ctx: ctx, can: can} + b, _ := conn.buff.Peek(3) + return conn, string(b) == "CON" +} diff --git a/main.go b/main.go index 79369e2..a632735 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,11 @@ package main import ( "context" + "crypto/tls" _ "embed" "errors" "fmt" + "log" "net" "net/netip" "os" @@ -57,9 +59,15 @@ func main() { } proxier := proxy.Proxy{ - Dial: proxyDialer(tnet), DNS: opts.DNS, Stats: stats(dev), + Dial: proxyDialer(tnet), + DNS: opts.DNS, + Stats: stats(dev), + } + if opts.TLSKey != "" { + proxier.ServeTLS(listener, opts.TLSCert, opts.TLSKey) + } else { + proxier.Serve(listener) } - proxier.Serve(listener) os.Exit(1) } @@ -99,6 +107,37 @@ func proxyListener(tnet *netstack.Net) (net.Listener, error) { return tcpListener, nil } +type peekingNetListener struct { + net.Listener +} + +type peekingNetConn struct { + net.Conn +} + +func (peek peekingNetListener) Accept() (net.Conn, error) { + con, err := peek.Listener.Accept() + return peekingNetConn{Conn: con}, err +} + +func (peek peekingNetConn) Read(b []byte) (int, error) { + n, err := peek.Conn.Read(b) + log.Printf("net.Conn.Read: (%d) %q", n, b[:n]) + return n, err +} + +func listenerToTLS(ln net.Listener, certFile, pemFile string) (net.Listener, error) { + ln = peekingNetListener{Listener: ln} + + cert, err := tls.LoadX509KeyPair(certFile, pemFile) + if err != nil { + return nil, err + } + config := &tls.Config{Certificates: []tls.Certificate{cert}} + + return peekingNetListener{tls.NewListener(ln, config)}, nil +} + func setupNet() (*device.Device, *netstack.Net, error) { clientIPs := []netip.Addr{} for _, ip := range opts.ClientIPs { diff --git a/option.go b/option.go index 26295df..6a67466 100644 --- a/option.go +++ b/option.go @@ -77,4 +77,7 @@ type options struct { Verbose bool `short:"v" long:"verbose" description:"Show verbose debug information"` ClientID string `long:"client-id" env:"CLIENT_ID" hidden:"true"` + + TLSCert string `long:"tls-cert" env:"TLS_CERT"` + TLSKey string `long:"tls-key" env:"TLS_KEY"` }