client/hijack.go
7c36a1af
 package client
 
 import (
 	"crypto/tls"
 	"errors"
 	"fmt"
 	"net"
d32ffb72
 	"net/http"
7c36a1af
 	"net/http/httputil"
 	"net/url"
 	"strings"
 	"time"
 
 	"github.com/docker/docker/api/types"
9d7be9df
 	"github.com/docker/docker/pkg/tlsconfig"
7c36a1af
 	"github.com/docker/go-connections/sockets"
 	"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
 
d32ffb72
 	req.Host = cli.addr
7c36a1af
 	req.Header.Set("Connection", "Upgrade")
 	req.Header.Set("Upgrade", "tcp")
 
dc9f5c2c
 	conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
7c36a1af
 	if err != nil {
 		if strings.Contains(err.Error(), "connection refused") {
 			return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
 		}
 		return types.HijackedResponse{}, 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
 	_, err = clientconn.Do(req)
 
 	rwc, br := clientconn.Hijack()
 
 	return types.HijackedResponse{Conn: rwc, Reader: br}, 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.
9d7be9df
 		config = tlsconfig.Clone(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)
 }