client/hijack.go
7c36a1af
 package client
 
 import (
ec7b6238
 	"bufio"
7c36a1af
 	"crypto/tls"
 	"fmt"
 	"net"
d32ffb72
 	"net/http"
7c36a1af
 	"net/http/httputil"
 	"net/url"
 	"strings"
 	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/go-connections/sockets"
ec7b6238
 	"github.com/pkg/errors"
7c36a1af
 	"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
 	}
 
d32ffb72
 	apiPath := cli.getAPIPath(path, query)
 	req, err := http.NewRequest("POST", apiPath, bodyEncoded)
7c36a1af
 	if err != nil {
 		return types.HijackedResponse{}, err
 	}
d32ffb72
 	req = cli.addHeaders(req, headers)
7c36a1af
 
ec7b6238
 	conn, err := cli.setupHijackConn(req, "tcp")
7c36a1af
 	if err != nil {
 		return types.HijackedResponse{}, err
 	}
 
ec7b6238
 	return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
7c36a1af
 }
 
 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() {
f7f101d5
 		deadlineTimeout := time.Until(dialer.Deadline)
7c36a1af
 		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.
6916c215
 		config = tlsConfigClone(config)
7c36a1af
 		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)
 }
ec7b6238
 
 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)
7a53991b
 	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)
 		}
ec7b6238
 	}
 
 	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)
 }