github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/client/lib/hijack.go (about)

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