hijack.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package client
  2. import (
  3. "bufio"
  4. "crypto/tls"
  5. "fmt"
  6. "net"
  7. "net/http"
  8. "net/http/httputil"
  9. "net/url"
  10. "strings"
  11. "time"
  12. "github.com/docker/docker/api/types"
  13. "github.com/docker/go-connections/sockets"
  14. "github.com/pkg/errors"
  15. "golang.org/x/net/context"
  16. )
  17. // tlsClientCon holds tls information and a dialed connection.
  18. type tlsClientCon struct {
  19. *tls.Conn
  20. rawConn net.Conn
  21. }
  22. func (c *tlsClientCon) CloseWrite() error {
  23. // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
  24. // on its underlying connection.
  25. if conn, ok := c.rawConn.(types.CloseWriter); ok {
  26. return conn.CloseWrite()
  27. }
  28. return nil
  29. }
  30. // postHijacked sends a POST request and hijacks the connection.
  31. func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
  32. bodyEncoded, err := encodeData(body)
  33. if err != nil {
  34. return types.HijackedResponse{}, err
  35. }
  36. apiPath := cli.getAPIPath(path, query)
  37. req, err := http.NewRequest("POST", apiPath, bodyEncoded)
  38. if err != nil {
  39. return types.HijackedResponse{}, err
  40. }
  41. req = cli.addHeaders(req, headers)
  42. conn, err := cli.setupHijackConn(req, "tcp")
  43. if err != nil {
  44. return types.HijackedResponse{}, err
  45. }
  46. return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
  47. }
  48. func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
  49. return tlsDialWithDialer(new(net.Dialer), network, addr, config)
  50. }
  51. // We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
  52. // order to return our custom tlsClientCon struct which holds both the tls.Conn
  53. // object _and_ its underlying raw connection. The rationale for this is that
  54. // we need to be able to close the write end of the connection when attaching,
  55. // which tls.Conn does not provide.
  56. func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
  57. // We want the Timeout and Deadline values from dialer to cover the
  58. // whole process: TCP connection and TLS handshake. This means that we
  59. // also need to start our own timers now.
  60. timeout := dialer.Timeout
  61. if !dialer.Deadline.IsZero() {
  62. deadlineTimeout := time.Until(dialer.Deadline)
  63. if timeout == 0 || deadlineTimeout < timeout {
  64. timeout = deadlineTimeout
  65. }
  66. }
  67. var errChannel chan error
  68. if timeout != 0 {
  69. errChannel = make(chan error, 2)
  70. time.AfterFunc(timeout, func() {
  71. errChannel <- errors.New("")
  72. })
  73. }
  74. proxyDialer, err := sockets.DialerFromEnvironment(dialer)
  75. if err != nil {
  76. return nil, err
  77. }
  78. rawConn, err := proxyDialer.Dial(network, addr)
  79. if err != nil {
  80. return nil, err
  81. }
  82. // When we set up a TCP connection for hijack, there could be long periods
  83. // of inactivity (a long running command with no output) that in certain
  84. // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
  85. // state. Setting TCP KeepAlive on the socket connection will prohibit
  86. // ECONNTIMEOUT unless the socket connection truly is broken
  87. if tcpConn, ok := rawConn.(*net.TCPConn); ok {
  88. tcpConn.SetKeepAlive(true)
  89. tcpConn.SetKeepAlivePeriod(30 * time.Second)
  90. }
  91. colonPos := strings.LastIndex(addr, ":")
  92. if colonPos == -1 {
  93. colonPos = len(addr)
  94. }
  95. hostname := addr[:colonPos]
  96. // If no ServerName is set, infer the ServerName
  97. // from the hostname we're connecting to.
  98. if config.ServerName == "" {
  99. // Make a copy to avoid polluting argument or default.
  100. config = tlsConfigClone(config)
  101. config.ServerName = hostname
  102. }
  103. conn := tls.Client(rawConn, config)
  104. if timeout == 0 {
  105. err = conn.Handshake()
  106. } else {
  107. go func() {
  108. errChannel <- conn.Handshake()
  109. }()
  110. err = <-errChannel
  111. }
  112. if err != nil {
  113. rawConn.Close()
  114. return nil, err
  115. }
  116. // This is Docker difference with standard's crypto/tls package: returned a
  117. // wrapper which holds both the TLS and raw connections.
  118. return &tlsClientCon{conn, rawConn}, nil
  119. }
  120. func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
  121. if tlsConfig != nil && proto != "unix" && proto != "npipe" {
  122. // Notice this isn't Go standard's tls.Dial function
  123. return tlsDial(proto, addr, tlsConfig)
  124. }
  125. if proto == "npipe" {
  126. return sockets.DialPipe(addr, 32*time.Second)
  127. }
  128. return net.Dial(proto, addr)
  129. }
  130. func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) {
  131. req.Host = cli.addr
  132. req.Header.Set("Connection", "Upgrade")
  133. req.Header.Set("Upgrade", proto)
  134. conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
  135. if err != nil {
  136. return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
  137. }
  138. // When we set up a TCP connection for hijack, there could be long periods
  139. // of inactivity (a long running command with no output) that in certain
  140. // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
  141. // state. Setting TCP KeepAlive on the socket connection will prohibit
  142. // ECONNTIMEOUT unless the socket connection truly is broken
  143. if tcpConn, ok := conn.(*net.TCPConn); ok {
  144. tcpConn.SetKeepAlive(true)
  145. tcpConn.SetKeepAlivePeriod(30 * time.Second)
  146. }
  147. clientconn := httputil.NewClientConn(conn, nil)
  148. defer clientconn.Close()
  149. // Server hijacks the connection, error 'connection closed' expected
  150. resp, err := clientconn.Do(req)
  151. if err != httputil.ErrPersistEOF {
  152. if err != nil {
  153. return nil, err
  154. }
  155. if resp.StatusCode != http.StatusSwitchingProtocols {
  156. resp.Body.Close()
  157. return nil, fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
  158. }
  159. }
  160. c, br := clientconn.Hijack()
  161. if br.Buffered() > 0 {
  162. // If there is buffered content, wrap the connection
  163. c = &hijackedConn{c, br}
  164. } else {
  165. br.Reset(nil)
  166. }
  167. return c, nil
  168. }
  169. type hijackedConn struct {
  170. net.Conn
  171. r *bufio.Reader
  172. }
  173. func (c *hijackedConn) Read(b []byte) (int, error) {
  174. return c.r.Read(b)
  175. }