hijack.go 5.1 KB

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