hijack.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package client
  2. import (
  3. "crypto/tls"
  4. "errors"
  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/docker/pkg/tlsconfig"
  14. "github.com/docker/go-connections/sockets"
  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. req.Host = cli.addr
  43. req.Header.Set("Connection", "Upgrade")
  44. req.Header.Set("Upgrade", "tcp")
  45. conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
  46. if err != nil {
  47. if strings.Contains(err.Error(), "connection refused") {
  48. return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
  49. }
  50. return types.HijackedResponse{}, err
  51. }
  52. // When we set up a TCP connection for hijack, there could be long periods
  53. // of inactivity (a long running command with no output) that in certain
  54. // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
  55. // state. Setting TCP KeepAlive on the socket connection will prohibit
  56. // ECONNTIMEOUT unless the socket connection truly is broken
  57. if tcpConn, ok := conn.(*net.TCPConn); ok {
  58. tcpConn.SetKeepAlive(true)
  59. tcpConn.SetKeepAlivePeriod(30 * time.Second)
  60. }
  61. clientconn := httputil.NewClientConn(conn, nil)
  62. defer clientconn.Close()
  63. // Server hijacks the connection, error 'connection closed' expected
  64. _, err = clientconn.Do(req)
  65. rwc, br := clientconn.Hijack()
  66. return types.HijackedResponse{Conn: rwc, Reader: br}, err
  67. }
  68. func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
  69. return tlsDialWithDialer(new(net.Dialer), network, addr, config)
  70. }
  71. // We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
  72. // order to return our custom tlsClientCon struct which holds both the tls.Conn
  73. // object _and_ its underlying raw connection. The rationale for this is that
  74. // we need to be able to close the write end of the connection when attaching,
  75. // which tls.Conn does not provide.
  76. func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
  77. // We want the Timeout and Deadline values from dialer to cover the
  78. // whole process: TCP connection and TLS handshake. This means that we
  79. // also need to start our own timers now.
  80. timeout := dialer.Timeout
  81. if !dialer.Deadline.IsZero() {
  82. deadlineTimeout := dialer.Deadline.Sub(time.Now())
  83. if timeout == 0 || deadlineTimeout < timeout {
  84. timeout = deadlineTimeout
  85. }
  86. }
  87. var errChannel chan error
  88. if timeout != 0 {
  89. errChannel = make(chan error, 2)
  90. time.AfterFunc(timeout, func() {
  91. errChannel <- errors.New("")
  92. })
  93. }
  94. proxyDialer, err := sockets.DialerFromEnvironment(dialer)
  95. if err != nil {
  96. return nil, err
  97. }
  98. rawConn, err := proxyDialer.Dial(network, addr)
  99. if err != nil {
  100. return nil, err
  101. }
  102. // When we set up a TCP connection for hijack, there could be long periods
  103. // of inactivity (a long running command with no output) that in certain
  104. // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
  105. // state. Setting TCP KeepAlive on the socket connection will prohibit
  106. // ECONNTIMEOUT unless the socket connection truly is broken
  107. if tcpConn, ok := rawConn.(*net.TCPConn); ok {
  108. tcpConn.SetKeepAlive(true)
  109. tcpConn.SetKeepAlivePeriod(30 * time.Second)
  110. }
  111. colonPos := strings.LastIndex(addr, ":")
  112. if colonPos == -1 {
  113. colonPos = len(addr)
  114. }
  115. hostname := addr[:colonPos]
  116. // If no ServerName is set, infer the ServerName
  117. // from the hostname we're connecting to.
  118. if config.ServerName == "" {
  119. // Make a copy to avoid polluting argument or default.
  120. config = tlsconfig.Clone(config)
  121. config.ServerName = hostname
  122. }
  123. conn := tls.Client(rawConn, config)
  124. if timeout == 0 {
  125. err = conn.Handshake()
  126. } else {
  127. go func() {
  128. errChannel <- conn.Handshake()
  129. }()
  130. err = <-errChannel
  131. }
  132. if err != nil {
  133. rawConn.Close()
  134. return nil, err
  135. }
  136. // This is Docker difference with standard's crypto/tls package: returned a
  137. // wrapper which holds both the TLS and raw connections.
  138. return &tlsClientCon{conn, rawConn}, nil
  139. }
  140. func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
  141. if tlsConfig != nil && proto != "unix" && proto != "npipe" {
  142. // Notice this isn't Go standard's tls.Dial function
  143. return tlsDial(proto, addr, tlsConfig)
  144. }
  145. if proto == "npipe" {
  146. return sockets.DialPipe(addr, 32*time.Second)
  147. }
  148. return net.Dial(proto, addr)
  149. }