diff --git a/handler.go b/handler.go index 278866b..b215140 100644 --- a/handler.go +++ b/handler.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "net/url" "strings" "time" ) @@ -15,20 +16,28 @@ type ProxyHandler struct { logger *CondLogger dialer ContextDialer httptransport http.RoundTripper + auth AuthProvider } -func NewProxyHandler(dialer ContextDialer, resolver *Resolver, logger *CondLogger) *ProxyHandler { +func NewProxyHandler(dialer, requestDialer ContextDialer, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler { dialer = NewRetryDialer(dialer, resolver, logger) httptransport := &http.Transport{ + Proxy: func(_ *http.Request) (*url.URL, error) { + return &url.URL{ + Scheme: "http", + Host: "void", + }, nil + }, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - DialContext: dialer.DialContext, + DialContext: requestDialer.DialContext, } return &ProxyHandler{ logger: logger, dialer: dialer, + auth: auth, httptransport: httptransport, } } @@ -74,6 +83,8 @@ func (s *ProxyHandler) HandleRequest(wr http.ResponseWriter, req *http.Request) req.URL.Scheme = "http" // We can't access :scheme pseudo-header, so assume http req.URL.Host = req.Host } + delHopHeaders(req.Header) + req.Header.Add("Proxy-Authorization", s.auth()) resp, err := s.httptransport.RoundTrip(req) if err != nil { s.logger.Error("HTTP fetch error: %v", err) diff --git a/main.go b/main.go index 15ad4bc..4e5d83f 100644 --- a/main.go +++ b/main.go @@ -71,8 +71,8 @@ func parse_args() CLIArgs { "See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format.") flag.BoolVar(&args.use_trial, "dont-use-trial", false, "use regular ports instead of trial ports") // would be nice to not show in help page flag.BoolVar(&args.showVersion, "version", false, "show program version and exit") - flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. " + - "Format: ://[login:password@]host[:port] " + + flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+ + "Format: ://[login:password@]host[:port] "+ "Examples: http://user:password@192.168.1.1:3128, socks5://10.0.0.1:1080") flag.Parse() if args.country == "" { @@ -164,9 +164,10 @@ func run() int { return 5 } handlerDialer := NewProxyDialer(endpoint.NetAddr(), endpoint.TLSName, auth, dialer) + requestDialer := NewPlaintextDialer(endpoint.NetAddr(), endpoint.TLSName, dialer) mainLogger.Info("Endpoint: %s", endpoint.URL().String()) mainLogger.Info("Starting proxy server...") - handler := NewProxyHandler(handlerDialer, resolver, proxyLogger) + handler := NewProxyHandler(handlerDialer, requestDialer, auth, resolver, proxyLogger) mainLogger.Info("Init complete.") err = http.ListenAndServe(args.bind_address, handler) mainLogger.Critical("Server terminated with a reason: %v", err) diff --git a/plaintext.go b/plaintext.go new file mode 100644 index 0000000..8bc3a54 --- /dev/null +++ b/plaintext.go @@ -0,0 +1,62 @@ +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "net" +) + +type PlaintextDialer struct { + fixedAddress string + tlsServerName string + next ContextDialer +} + +func NewPlaintextDialer(address, tlsServerName string, next ContextDialer) *PlaintextDialer { + return &PlaintextDialer{ + fixedAddress: address, + tlsServerName: tlsServerName, + next: next, + } +} + +func (d *PlaintextDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + switch network { + case "tcp", "tcp4", "tcp6": + default: + return nil, errors.New("bad network specified for DialContext: only tcp is supported") + } + + conn, err := d.next.DialContext(ctx, "tcp", d.fixedAddress) + if err != nil { + return nil, err + } + + if d.tlsServerName != "" { + // Custom cert verification logic: + // DO NOT send SNI extension of TLS ClientHello + // DO peer certificate verification against specified servername + conn = tls.Client(conn, &tls.Config{ + ServerName: "", + InsecureSkipVerify: true, + VerifyConnection: func(cs tls.ConnectionState) error { + opts := x509.VerifyOptions{ + DNSName: d.tlsServerName, + Intermediates: x509.NewCertPool(), + } + for _, cert := range cs.PeerCertificates[1:] { + opts.Intermediates.AddCert(cert) + } + _, err := cs.PeerCertificates[0].Verify(opts) + return err + }, + }) + } + return conn, nil +} + +func (d *PlaintextDialer) Dial(network, address string) (net.Conn, error) { + return d.DialContext(context.Background(), network, address) +}