github.com/yandex/pandora@v0.5.32/components/guns/http/connect.go (about) 1 package phttp 2 3 import ( 4 "bufio" 5 "context" 6 "crypto/tls" 7 "net" 8 "net/http" 9 "net/http/httputil" 10 "net/url" 11 12 "github.com/pkg/errors" 13 "github.com/yandex/pandora/lib/netutil" 14 "go.uber.org/zap" 15 ) 16 17 func NewConnectGun(cfg GunConfig, answLog *zap.Logger) *BaseGun { 18 if cfg.TargetResolved == "" { 19 cfg.TargetResolved = cfg.Target 20 } 21 22 return NewBaseGun(newConnectClient, cfg, answLog) 23 } 24 25 func DefaultConnectGunConfig() GunConfig { 26 return GunConfig{ 27 SSL: false, 28 Client: DefaultClientConfig(), 29 AutoTag: AutoTagConfig{ 30 Enabled: false, 31 URIElements: 2, 32 NoTagOnly: true, 33 }, 34 AnswLog: AnswLogConfig{ 35 Enabled: false, 36 Path: "answ.log", 37 Filter: "error", 38 }, 39 HTTPTrace: HTTPTraceConfig{ 40 DumpEnabled: false, 41 TraceEnabled: false, 42 }, 43 } 44 } 45 46 func newConnectClient(conf ClientConfig, target string) Client { 47 transport := NewTransport( 48 conf.Transport, 49 newConnectDialFunc( 50 target, 51 conf.ConnectSSL, 52 NewDialer(conf.Dialer), 53 ), 54 target) 55 return NewRedirectingClient(transport, conf.Redirect) 56 } 57 58 var _ ClientConstructor = newConnectClient 59 60 func newConnectDialFunc(target string, connectSSL bool, dialer netutil.Dialer) netutil.DialerFunc { 61 return func(ctx context.Context, network, address string) (conn net.Conn, err error) { 62 // TODO(skipor): make connect sample. 63 // TODO(skipor): make httptrace callbacks called correctly. 64 if network != "tcp" { 65 panic("unsupported network " + network) 66 } 67 defer func() { 68 if err != nil && conn != nil { 69 _ = conn.Close() 70 conn = nil 71 } 72 }() 73 conn, err = dialer.DialContext(ctx, "tcp", target) 74 if err != nil { 75 err = errors.WithStack(err) 76 return 77 } 78 if connectSSL { 79 conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) 80 } 81 req := &http.Request{ 82 Method: "CONNECT", 83 URL: &url.URL{}, 84 Proto: "HTTP/1.1", 85 ProtoMajor: 1, 86 ProtoMinor: 1, 87 Header: make(http.Header), 88 Body: nil, 89 Host: address, 90 } 91 // NOTE(skipor): any logic for CONNECT request can be easily added via hooks. 92 err = req.Write(conn) 93 if err != nil { 94 err = errors.WithStack(err) 95 return 96 } 97 // NOTE(skipor): according to RFC 2817 we can send origin at that moment and not wait 98 // for request. That requires to wrap conn and do following logic at first read. 99 r := bufio.NewReader(conn) 100 res, err := http.ReadResponse(r, req) 101 if err != nil { 102 err = errors.WithStack(err) 103 return 104 } 105 // RFC 7230 3.3.3.2: Any 2xx (Successful) response to a CONNECT request implies that 106 // the connection will become a tunnel immediately after the empty 107 // line that concludes the header fields. A client MUST ignore any 108 // Content-Length or Transfer-Encoding header fields received in 109 // such a message. 110 if res.StatusCode != http.StatusOK { 111 dump, dumpErr := httputil.DumpResponse(res, false) 112 err = errors.Errorf("Unexpected status code. Dumped response:\n%s\n Dump error: %s", 113 dump, dumpErr) 114 return 115 } 116 // No need to close body. 117 if r.Buffered() != 0 { 118 // Already receive something non HTTP from proxy or dialed server. 119 // Anyway it is incorrect situation. 120 peek, _ := r.Peek(r.Buffered()) 121 err = errors.Errorf("Unexpected extra data after connect: %q", peek) 122 return 123 } 124 return 125 } 126 }