package client import ( "bufio" "crypto/tls" "fmt" "net" "net/http" "net/http/httputil" "net/url" "strings" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/tlsconfig" "github.com/docker/go-connections/sockets" "github.com/pkg/errors" "golang.org/x/net/context" ) // 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(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) { bodyEncoded, err := encodeData(body) if err != nil { return types.HijackedResponse{}, err } apiPath := cli.getAPIPath(path, query) req, err := http.NewRequest("POST", apiPath, bodyEncoded) if err != nil { return types.HijackedResponse{}, err } req = cli.addHeaders(req, headers) conn, err := cli.setupHijackConn(req, "tcp") if err != nil { return types.HijackedResponse{}, err } return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err } 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("") }) } proxyDialer, err := sockets.DialerFromEnvironment(dialer) if err != nil { return nil, err } rawConn, err := proxyDialer.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. config = tlsconfig.Clone(config) config.ServerName = hostname } 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" && proto != "npipe" { // Notice this isn't Go standard's tls.Dial function return tlsDial(proto, addr, tlsConfig) } if proto == "npipe" { return sockets.DialPipe(addr, 32*time.Second) } return net.Dial(proto, addr) } func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) { req.Host = cli.addr req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", proto) conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) if err != nil { return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } // 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 resp, err := clientconn.Do(req) if err != httputil.ErrPersistEOF { if err != nil { return nil, err } if resp.StatusCode != http.StatusSwitchingProtocols { resp.Body.Close() return nil, fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode) } } c, br := clientconn.Hijack() if br.Buffered() > 0 { // If there is buffered content, wrap the connection c = &hijackedConn{c, br} } else { br.Reset(nil) } return c, nil } type hijackedConn struct { net.Conn r *bufio.Reader } func (c *hijackedConn) Read(b []byte) (int, error) { return c.r.Read(b) }