google.golang.org/grpc@v1.72.2/internal/transport/proxy.go (about) 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 }