hijack.go 5.0 KB

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