github.com/OpenFlowLabs/moby@v17.12.1-ce-rc2+incompatible/client/hijack.go (about)

     1  package client
     2  
     3  import (
     4  	"bufio"
     5  	"crypto/tls"
     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/go-connections/sockets"
    16  	"github.com/pkg/errors"
    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  	conn, err := cli.setupHijackConn(req, "tcp")
    50  	if err != nil {
    51  		return types.HijackedResponse{}, err
    52  	}
    53  
    54  	return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
    55  }
    56  
    57  func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
    58  	return tlsDialWithDialer(new(net.Dialer), network, addr, config)
    59  }
    60  
    61  // We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
    62  // order to return our custom tlsClientCon struct which holds both the tls.Conn
    63  // object _and_ its underlying raw connection. The rationale for this is that
    64  // we need to be able to close the write end of the connection when attaching,
    65  // which tls.Conn does not provide.
    66  func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
    67  	// We want the Timeout and Deadline values from dialer to cover the
    68  	// whole process: TCP connection and TLS handshake. This means that we
    69  	// also need to start our own timers now.
    70  	timeout := dialer.Timeout
    71  
    72  	if !dialer.Deadline.IsZero() {
    73  		deadlineTimeout := time.Until(dialer.Deadline)
    74  		if timeout == 0 || deadlineTimeout < timeout {
    75  			timeout = deadlineTimeout
    76  		}
    77  	}
    78  
    79  	var errChannel chan error
    80  
    81  	if timeout != 0 {
    82  		errChannel = make(chan error, 2)
    83  		time.AfterFunc(timeout, func() {
    84  			errChannel <- errors.New("")
    85  		})
    86  	}
    87  
    88  	proxyDialer, err := sockets.DialerFromEnvironment(dialer)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	rawConn, err := proxyDialer.Dial(network, addr)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	// When we set up a TCP connection for hijack, there could be long periods
    98  	// of inactivity (a long running command with no output) that in certain
    99  	// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
   100  	// state. Setting TCP KeepAlive on the socket connection will prohibit
   101  	// ECONNTIMEOUT unless the socket connection truly is broken
   102  	if tcpConn, ok := rawConn.(*net.TCPConn); ok {
   103  		tcpConn.SetKeepAlive(true)
   104  		tcpConn.SetKeepAlivePeriod(30 * time.Second)
   105  	}
   106  
   107  	colonPos := strings.LastIndex(addr, ":")
   108  	if colonPos == -1 {
   109  		colonPos = len(addr)
   110  	}
   111  	hostname := addr[:colonPos]
   112  
   113  	// If no ServerName is set, infer the ServerName
   114  	// from the hostname we're connecting to.
   115  	if config.ServerName == "" {
   116  		// Make a copy to avoid polluting argument or default.
   117  		config = tlsConfigClone(config)
   118  		config.ServerName = hostname
   119  	}
   120  
   121  	conn := tls.Client(rawConn, config)
   122  
   123  	if timeout == 0 {
   124  		err = conn.Handshake()
   125  	} else {
   126  		go func() {
   127  			errChannel <- conn.Handshake()
   128  		}()
   129  
   130  		err = <-errChannel
   131  	}
   132  
   133  	if err != nil {
   134  		rawConn.Close()
   135  		return nil, err
   136  	}
   137  
   138  	// This is Docker difference with standard's crypto/tls package: returned a
   139  	// wrapper which holds both the TLS and raw connections.
   140  	return &tlsClientCon{conn, rawConn}, nil
   141  }
   142  
   143  func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
   144  	if tlsConfig != nil && proto != "unix" && proto != "npipe" {
   145  		// Notice this isn't Go standard's tls.Dial function
   146  		return tlsDial(proto, addr, tlsConfig)
   147  	}
   148  	if proto == "npipe" {
   149  		return sockets.DialPipe(addr, 32*time.Second)
   150  	}
   151  	return net.Dial(proto, addr)
   152  }
   153  
   154  func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) {
   155  	req.Host = cli.addr
   156  	req.Header.Set("Connection", "Upgrade")
   157  	req.Header.Set("Upgrade", proto)
   158  
   159  	conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
   160  	if err != nil {
   161  		return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
   162  	}
   163  
   164  	// When we set up a TCP connection for hijack, there could be long periods
   165  	// of inactivity (a long running command with no output) that in certain
   166  	// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
   167  	// state. Setting TCP KeepAlive on the socket connection will prohibit
   168  	// ECONNTIMEOUT unless the socket connection truly is broken
   169  	if tcpConn, ok := conn.(*net.TCPConn); ok {
   170  		tcpConn.SetKeepAlive(true)
   171  		tcpConn.SetKeepAlivePeriod(30 * time.Second)
   172  	}
   173  
   174  	clientconn := httputil.NewClientConn(conn, nil)
   175  	defer clientconn.Close()
   176  
   177  	// Server hijacks the connection, error 'connection closed' expected
   178  	resp, err := clientconn.Do(req)
   179  	if err != httputil.ErrPersistEOF {
   180  		if err != nil {
   181  			return nil, err
   182  		}
   183  		if resp.StatusCode != http.StatusSwitchingProtocols {
   184  			resp.Body.Close()
   185  			return nil, fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
   186  		}
   187  	}
   188  
   189  	c, br := clientconn.Hijack()
   190  	if br.Buffered() > 0 {
   191  		// If there is buffered content, wrap the connection
   192  		c = &hijackedConn{c, br}
   193  	} else {
   194  		br.Reset(nil)
   195  	}
   196  
   197  	return c, nil
   198  }
   199  
   200  type hijackedConn struct {
   201  	net.Conn
   202  	r *bufio.Reader
   203  }
   204  
   205  func (c *hijackedConn) Read(b []byte) (int, error) {
   206  	return c.r.Read(b)
   207  }