hijack.go 4.6 KB

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