@@ -2,6 +2,7 @@ package main
22
33import (
44 "bufio"
5+ "bytes"
56 "context"
67 "encoding/binary"
78 "errors"
@@ -18,12 +19,11 @@ import (
1819 "strings"
1920 "sync"
2021 "sync/atomic"
21- "syscall"
2222
2323 "golang.org/x/crypto/cryptobyte"
2424)
2525
26- var version = "0.2.2 "
26+ var version = "0.2.4 "
2727
2828// PrereadConn is a wrapper around net.Conn that supports pre-reading from the underlying connection.
2929// Any Read before the EndPreread can be undone and read again by calling the EndPreread function.
@@ -84,27 +84,24 @@ func PrereadSNI(conn *PrereadConn) (_ string, err error) {
8484 err = fmt .Errorf ("failed to preread TLS client hello: %w" , err )
8585 }
8686 }()
87- typeVersionLen := make ([]byte , 5 )
88- n , err := conn .Read (typeVersionLen )
89- if n != 5 {
90- return "" , errors .New ("too short" )
91- }
87+ recordHeader := make ([]byte , 5 )
88+ n , err := io .ReadFull (conn , recordHeader )
9289 if err != nil {
93- return "" , err
90+ return "" , fmt .Errorf ("failed to read TLS record layer header: %w" , err )
91+ }
92+ if n != 5 {
93+ return "" , fmt .Errorf ("failed to read TLS record layer header: too short, less than 5 bytes (%d)" , n )
9494 }
95- if typeVersionLen [0 ] != 22 {
95+ if recordHeader [0 ] != 22 {
9696 return "" , errors .New ("not a TCP handshake" )
9797 }
98- msgLen := binary .BigEndian .Uint16 (typeVersionLen [3 :])
98+ msgLen := binary .BigEndian .Uint16 (recordHeader [3 :])
9999 buf := make ([]byte , msgLen + 5 )
100- n , err = conn . Read ( buf [5 :])
100+ n , err = io . ReadFull ( conn , buf [5 :])
101101 if n != int (msgLen ) {
102- return "" , errors .New ("too short" )
103- }
104- if err != nil {
105- return "" , err
102+ return "" , fmt .Errorf ("client hello too short (%d < %d), err: %w" , n , msgLen , err )
106103 }
107- copy (buf [:5 ], typeVersionLen )
104+ copy (buf [:5 ], recordHeader )
108105 return extractSNI (buf )
109106}
110107
@@ -224,6 +221,7 @@ func DialProxy(proxy string) (net.Conn, error) {
224221}
225222
226223// DialProxyConnect dials the TCP connection and finishes the HTTP CONNECT handshake with the proxy.
224+ // dst: HOST:PORT or IP:PORT
227225func DialProxyConnect (proxy string , dst string ) (net.Conn , error ) {
228226 conn , err := DialProxy (proxy )
229227 if err != nil {
@@ -247,12 +245,12 @@ func DialProxyConnect(proxy string, dst string) (net.Conn, error) {
247245 return nil , fmt .Errorf ("failed to send connect request to http proxy: %w" , err )
248246 }
249247 response , err := http .ReadResponse (bufio .NewReaderSize (conn , 0 ), & request )
250- if response .StatusCode != 200 {
251- return nil , fmt .Errorf ("proxy return %d response for connect request" , response .StatusCode )
252- }
253248 if err != nil {
254249 return nil , fmt .Errorf ("failed to receive http connect response from proxy: %w" , err )
255250 }
251+ if response .StatusCode != 200 {
252+ return nil , fmt .Errorf ("proxy return %d response for connect request" , response .StatusCode )
253+ }
256254 return conn , nil
257255}
258256
@@ -268,11 +266,7 @@ func GetOriginalDst(conn *net.TCPConn) (*net.TCPAddr, error) {
268266 if err != nil {
269267 return nil , fmt .Errorf ("failed to convert connection to file: %w" , err )
270268 }
271- return GetsockoptIPv4OriginalDst (
272- int (file .Fd ()),
273- syscall .SOL_IP ,
274- 80 , // SO_ORIGINAL_DST
275- )
269+ return GetsockoptIPv4OriginalDst (file .Fd ())
276270}
277271
278272// RelayTCP relays data between the incoming TCP connection and the proxy connection.
@@ -304,10 +298,34 @@ func RelayHTTP(conn io.ReadWriter, proxyConn io.ReadWriteCloser, logger *slog.Lo
304298 }
305299 req .URL .Host = req .Host
306300 req .URL .Scheme = "http"
301+ if req .UserAgent () == "" {
302+ req .Header .Set ("User-Agent" , "" )
303+ }
307304 req .Header .Set ("Connection" , "close" )
308- if err := req .WriteProxy (proxyConn ); err != nil {
309- logger .Error ("failed to send HTTP request to proxy" , "error" , err )
310- return
305+ if req .Proto == "HTTP/1.0" {
306+ // no matter what the request protocol is, Go enforces a minimum version of HTTP/1.1
307+ // this causes problems for HTTP/1.0 only clients like GPG (HKP)
308+ // manually modify and send the HTTP/1.0 request to the proxy server
309+ buf := bytes .NewBuffer (nil )
310+ err := req .WriteProxy (buf )
311+ if err != nil {
312+ logger .Error ("failed to serialize HTTP/1.0 request" , "error" , err )
313+ return
314+ }
315+ reqStr := buf .String ()
316+ crlfIndex := strings .Index (reqStr , "\r \n " )
317+ protoSpaceIndex := strings .LastIndex (reqStr [:crlfIndex ], " " )
318+ reqStr = reqStr [:protoSpaceIndex + 1 ] + "HTTP/1.0" + reqStr [crlfIndex :]
319+ _ , err = proxyConn .Write ([]byte (reqStr ))
320+ if err != nil {
321+ logger .Error ("failed to send HTTP request to proxy" , "error" , err )
322+ return
323+ }
324+ } else {
325+ if err := req .WriteProxy (proxyConn ); err != nil {
326+ logger .Error ("failed to send HTTP request to proxy" , "error" , err )
327+ return
328+ }
311329 }
312330 resp , err := http .ReadResponse (bufio .NewReader (proxyConn ), req )
313331 if err != nil {
@@ -349,7 +367,7 @@ func HandleConn(conn net.Conn, proxy string) {
349367 logger .Info ("relay TLS connection to proxy" )
350368 RelayTCP (consigned , proxyConn , logger )
351369 }
352- case 80 :
370+ case 80 , 11371 :
353371 host , err := PrereadHttpHost (consigned )
354372 if err != nil {
355373 logger .Error ("failed to preread HTTP host from connection" , "error" , err )
@@ -367,13 +385,19 @@ func HandleConn(conn net.Conn, proxy string) {
367385 logger .Info ("relay HTTP connection to proxy" )
368386 RelayHTTP (consigned , proxyConn , logger )
369387 default :
370- logger .Error (fmt .Sprintf ("unknown destination port: %d" , dst .Port ))
371- return
388+ logger = logger .With ("host" , fmt .Sprintf ("%s:%d" , dst .IP .String (), dst .Port ))
389+ proxyConn , err := DialProxyConnect (proxy , fmt .Sprintf ("%s:%d" , dst .IP .String (), dst .Port ))
390+ if err != nil {
391+ logger .Error ("failed to connect to tcp proxy" , "error" , err )
392+ return
393+ }
394+ logger .Info ("relay TCP connection to proxy" )
395+ RelayTCP (consigned , proxyConn , logger )
372396 }
373397}
374398
375399func main () {
376- proxyFlag := flag .String ("proxy" , "" , "upstream HTTP proxy address in the 'host:port' format" )
400+ proxyFlag := flag .String ("proxy" , "" , "upstream proxy address in the 'host:port' format" )
377401 listenFlag := flag .String ("listen" , ":8443" , "the address and port on which the server will listen" )
378402 flag .Parse ()
379403 listenAddr := * listenFlag
@@ -387,7 +411,7 @@ func main() {
387411 slog .Info (fmt .Sprintf ("start listening on %s" , listenAddr ))
388412 proxy := * proxyFlag
389413 if proxy == "" {
390- log .Fatalf ("no upstearm proxy specified" )
414+ log .Fatalf ("no upstream proxy specified" )
391415 }
392416 slog .Info (fmt .Sprintf ("start forwarding to proxy %s" , proxy ))
393417 go func () {
0 commit comments