github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/client/hijack.go (about)

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