|
@@ -0,0 +1,165 @@
|
|
|
+package lib
|
|
|
+
|
|
|
+import (
|
|
|
+ "crypto/tls"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "net"
|
|
|
+ "net/http/httputil"
|
|
|
+ "net/url"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/docker/docker/api/types"
|
|
|
+)
|
|
|
+
|
|
|
+// tlsClientCon holds tls information and a dialed connection.
|
|
|
+type tlsClientCon struct {
|
|
|
+ *tls.Conn
|
|
|
+ rawConn net.Conn
|
|
|
+}
|
|
|
+
|
|
|
+func (c *tlsClientCon) CloseWrite() error {
|
|
|
+ // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
|
|
|
+ // on its underlying connection.
|
|
|
+ if conn, ok := c.rawConn.(types.CloseWriter); ok {
|
|
|
+ return conn.CloseWrite()
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// postHijacked sends a POST request and hijacks the connection.
|
|
|
+func (cli *Client) postHijacked(path string, query url.Values, body io.Reader, headers map[string][]string) (*types.HijackedResponse, error) {
|
|
|
+ bodyEncoded, err := encodeData(body)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ req.Host = cli.Addr
|
|
|
+
|
|
|
+ req.Header.Set("Connection", "Upgrade")
|
|
|
+ req.Header.Set("Upgrade", "tcp")
|
|
|
+
|
|
|
+ conn, err := dial(cli.Proto, cli.Addr, cli.tlsConfig)
|
|
|
+ if err != nil {
|
|
|
+ if strings.Contains(err.Error(), "connection refused") {
|
|
|
+ return nil, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
|
+ }
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // When we set up a TCP connection for hijack, there could be long periods
|
|
|
+ // of inactivity (a long running command with no output) that in certain
|
|
|
+ // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
|
+ // state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
|
+ // ECONNTIMEOUT unless the socket connection truly is broken
|
|
|
+ if tcpConn, ok := conn.(*net.TCPConn); ok {
|
|
|
+ tcpConn.SetKeepAlive(true)
|
|
|
+ tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
|
+ }
|
|
|
+
|
|
|
+ clientconn := httputil.NewClientConn(conn, nil)
|
|
|
+ defer clientconn.Close()
|
|
|
+
|
|
|
+ // Server hijacks the connection, error 'connection closed' expected
|
|
|
+ clientconn.Do(req)
|
|
|
+
|
|
|
+ rwc, br := clientconn.Hijack()
|
|
|
+
|
|
|
+ return &types.HijackedResponse{rwc, br}, nil
|
|
|
+}
|
|
|
+
|
|
|
+func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
|
|
+ return tlsDialWithDialer(new(net.Dialer), network, addr, config)
|
|
|
+}
|
|
|
+
|
|
|
+// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
|
|
|
+// order to return our custom tlsClientCon struct which holds both the tls.Conn
|
|
|
+// object _and_ its underlying raw connection. The rationale for this is that
|
|
|
+// we need to be able to close the write end of the connection when attaching,
|
|
|
+// which tls.Conn does not provide.
|
|
|
+func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
|
|
|
+ // We want the Timeout and Deadline values from dialer to cover the
|
|
|
+ // whole process: TCP connection and TLS handshake. This means that we
|
|
|
+ // also need to start our own timers now.
|
|
|
+ timeout := dialer.Timeout
|
|
|
+
|
|
|
+ if !dialer.Deadline.IsZero() {
|
|
|
+ deadlineTimeout := dialer.Deadline.Sub(time.Now())
|
|
|
+ if timeout == 0 || deadlineTimeout < timeout {
|
|
|
+ timeout = deadlineTimeout
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var errChannel chan error
|
|
|
+
|
|
|
+ if timeout != 0 {
|
|
|
+ errChannel = make(chan error, 2)
|
|
|
+ time.AfterFunc(timeout, func() {
|
|
|
+ errChannel <- errors.New("")
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ rawConn, err := dialer.Dial(network, addr)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ // When we set up a TCP connection for hijack, there could be long periods
|
|
|
+ // of inactivity (a long running command with no output) that in certain
|
|
|
+ // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
|
+ // state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
|
+ // ECONNTIMEOUT unless the socket connection truly is broken
|
|
|
+ if tcpConn, ok := rawConn.(*net.TCPConn); ok {
|
|
|
+ tcpConn.SetKeepAlive(true)
|
|
|
+ tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
|
+ }
|
|
|
+
|
|
|
+ colonPos := strings.LastIndex(addr, ":")
|
|
|
+ if colonPos == -1 {
|
|
|
+ colonPos = len(addr)
|
|
|
+ }
|
|
|
+ hostname := addr[:colonPos]
|
|
|
+
|
|
|
+ // If no ServerName is set, infer the ServerName
|
|
|
+ // from the hostname we're connecting to.
|
|
|
+ if config.ServerName == "" {
|
|
|
+ // Make a copy to avoid polluting argument or default.
|
|
|
+ c := *config
|
|
|
+ c.ServerName = hostname
|
|
|
+ config = &c
|
|
|
+ }
|
|
|
+
|
|
|
+ conn := tls.Client(rawConn, config)
|
|
|
+
|
|
|
+ if timeout == 0 {
|
|
|
+ err = conn.Handshake()
|
|
|
+ } else {
|
|
|
+ go func() {
|
|
|
+ errChannel <- conn.Handshake()
|
|
|
+ }()
|
|
|
+
|
|
|
+ err = <-errChannel
|
|
|
+ }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ rawConn.Close()
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // This is Docker difference with standard's crypto/tls package: returned a
|
|
|
+ // wrapper which holds both the TLS and raw connections.
|
|
|
+ return &tlsClientCon{conn, rawConn}, nil
|
|
|
+}
|
|
|
+
|
|
|
+func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
|
|
+ if tlsConfig != nil && proto != "unix" {
|
|
|
+ // Notice this isn't Go standard's tls.Dial function
|
|
|
+ return tlsDial(proto, addr, tlsConfig)
|
|
|
+ }
|
|
|
+ return net.Dial(proto, addr)
|
|
|
+}
|