proxy.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. package grpc
  19. import (
  20. "bufio"
  21. "context"
  22. "encoding/base64"
  23. "errors"
  24. "fmt"
  25. "io"
  26. "net"
  27. "net/http"
  28. "net/http/httputil"
  29. "net/url"
  30. )
  31. const proxyAuthHeaderKey = "Proxy-Authorization"
  32. var (
  33. // errDisabled indicates that proxy is disabled for the address.
  34. errDisabled = errors.New("proxy is disabled for the address")
  35. // The following variable will be overwritten in the tests.
  36. httpProxyFromEnvironment = http.ProxyFromEnvironment
  37. )
  38. func mapAddress(ctx context.Context, address string) (*url.URL, error) {
  39. req := &http.Request{
  40. URL: &url.URL{
  41. Scheme: "https",
  42. Host: address,
  43. },
  44. }
  45. url, err := httpProxyFromEnvironment(req)
  46. if err != nil {
  47. return nil, err
  48. }
  49. if url == nil {
  50. return nil, errDisabled
  51. }
  52. return url, nil
  53. }
  54. // To read a response from a net.Conn, http.ReadResponse() takes a bufio.Reader.
  55. // It's possible that this reader reads more than what's need for the response and stores
  56. // those bytes in the buffer.
  57. // bufConn wraps the original net.Conn and the bufio.Reader to make sure we don't lose the
  58. // bytes in the buffer.
  59. type bufConn struct {
  60. net.Conn
  61. r io.Reader
  62. }
  63. func (c *bufConn) Read(b []byte) (int, error) {
  64. return c.r.Read(b)
  65. }
  66. func basicAuth(username, password string) string {
  67. auth := username + ":" + password
  68. return base64.StdEncoding.EncodeToString([]byte(auth))
  69. }
  70. func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr string, proxyURL *url.URL) (_ net.Conn, err error) {
  71. defer func() {
  72. if err != nil {
  73. conn.Close()
  74. }
  75. }()
  76. req := &http.Request{
  77. Method: http.MethodConnect,
  78. URL: &url.URL{Host: backendAddr},
  79. Header: map[string][]string{"User-Agent": {grpcUA}},
  80. }
  81. if t := proxyURL.User; t != nil {
  82. u := t.Username()
  83. p, _ := t.Password()
  84. req.Header.Add(proxyAuthHeaderKey, "Basic "+basicAuth(u, p))
  85. }
  86. if err := sendHTTPRequest(ctx, req, conn); err != nil {
  87. return nil, fmt.Errorf("failed to write the HTTP request: %v", err)
  88. }
  89. r := bufio.NewReader(conn)
  90. resp, err := http.ReadResponse(r, req)
  91. if err != nil {
  92. return nil, fmt.Errorf("reading server HTTP response: %v", err)
  93. }
  94. defer resp.Body.Close()
  95. if resp.StatusCode != http.StatusOK {
  96. dump, err := httputil.DumpResponse(resp, true)
  97. if err != nil {
  98. return nil, fmt.Errorf("failed to do connect handshake, status code: %s", resp.Status)
  99. }
  100. return nil, fmt.Errorf("failed to do connect handshake, response: %q", dump)
  101. }
  102. return &bufConn{Conn: conn, r: r}, nil
  103. }
  104. // newProxyDialer returns a dialer that connects to proxy first if necessary.
  105. // The returned dialer checks if a proxy is necessary, dial to the proxy with the
  106. // provided dialer, does HTTP CONNECT handshake and returns the connection.
  107. func newProxyDialer(dialer func(context.Context, string) (net.Conn, error)) func(context.Context, string) (net.Conn, error) {
  108. return func(ctx context.Context, addr string) (conn net.Conn, err error) {
  109. var newAddr string
  110. proxyURL, err := mapAddress(ctx, addr)
  111. if err != nil {
  112. if err != errDisabled {
  113. return nil, err
  114. }
  115. newAddr = addr
  116. } else {
  117. newAddr = proxyURL.Host
  118. }
  119. conn, err = dialer(ctx, newAddr)
  120. if err != nil {
  121. return
  122. }
  123. if proxyURL != nil {
  124. // proxy is disabled if proxyURL is nil.
  125. conn, err = doHTTPConnectHandshake(ctx, conn, addr, proxyURL)
  126. }
  127. return
  128. }
  129. }
  130. func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error {
  131. req = req.WithContext(ctx)
  132. if err := req.Write(conn); err != nil {
  133. return fmt.Errorf("failed to write the HTTP request: %v", err)
  134. }
  135. return nil
  136. }