hijack.go 4.7 KB

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