1 /*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 19 package transport
20 21 import (
22 "bufio"
23 "context"
24 "encoding/base64"
25 "fmt"
26 "io"
27 "net"
28 "net/http"
29 "net/http/httputil"
30 "net/url"
31 32 "google.golang.org/grpc/internal"
33 "google.golang.org/grpc/internal/proxyattributes"
34 "google.golang.org/grpc/resolver"
35 )
36 37 const proxyAuthHeaderKey = "Proxy-Authorization"
38 39 // To read a response from a net.Conn, http.ReadResponse() takes a bufio.Reader.
40 // It's possible that this reader reads more than what's need for the response
41 // and stores those bytes in the buffer. bufConn wraps the original net.Conn
42 // and the bufio.Reader to make sure we don't lose the bytes in the buffer.
43 type bufConn struct {
44 net.Conn
45 r io.Reader
46 }
47 48 func (c *bufConn) Read(b []byte) (int, error) {
49 return c.r.Read(b)
50 }
51 52 func basicAuth(username, password string) string {
53 auth := username + ":" + password
54 return base64.StdEncoding.EncodeToString([]byte(auth))
55 }
56 57 func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, grpcUA string, opts proxyattributes.Options) (_ net.Conn, err error) {
58 defer func() {
59 if err != nil {
60 conn.Close()
61 }
62 }()
63 64 req := &http.Request{
65 Method: http.MethodConnect,
66 URL: &url.URL{Host: opts.ConnectAddr},
67 Header: map[string][]string{"User-Agent": {grpcUA}},
68 }
69 if user := opts.User; user != nil {
70 u := user.Username()
71 p, _ := user.Password()
72 req.Header.Add(proxyAuthHeaderKey, "Basic "+basicAuth(u, p))
73 }
74 if err := sendHTTPRequest(ctx, req, conn); err != nil {
75 return nil, fmt.Errorf("failed to write the HTTP request: %v", err)
76 }
77 78 r := bufio.NewReader(conn)
79 resp, err := http.ReadResponse(r, req)
80 if err != nil {
81 return nil, fmt.Errorf("reading server HTTP response: %v", err)
82 }
83 defer resp.Body.Close()
84 if resp.StatusCode != http.StatusOK {
85 dump, err := httputil.DumpResponse(resp, true)
86 if err != nil {
87 return nil, fmt.Errorf("failed to do connect handshake, status code: %s", resp.Status)
88 }
89 return nil, fmt.Errorf("failed to do connect handshake, response: %q", dump)
90 }
91 // The buffer could contain extra bytes from the target server, so we can't
92 // discard it. However, in many cases where the server waits for the client
93 // to send the first message (e.g. when TLS is being used), the buffer will
94 // be empty, so we can avoid the overhead of reading through this buffer.
95 if r.Buffered() != 0 {
96 return &bufConn{Conn: conn, r: r}, nil
97 }
98 return conn, nil
99 }
100 101 // proxyDial establishes a TCP connection to the specified address and performs an HTTP CONNECT handshake.
102 func proxyDial(ctx context.Context, addr resolver.Address, grpcUA string, opts proxyattributes.Options) (net.Conn, error) {
103 conn, err := internal.NetDialerWithTCPKeepalive().DialContext(ctx, "tcp", addr.Addr)
104 if err != nil {
105 return nil, err
106 }
107 return doHTTPConnectHandshake(ctx, conn, grpcUA, opts)
108 }
109 110 func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error {
111 req = req.WithContext(ctx)
112 if err := req.Write(conn); err != nil {
113 return fmt.Errorf("failed to write the HTTP request: %v", err)
114 }
115 return nil
116 }
117