github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/client/hijack.go (about)

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