github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/api/client/hijack.go (about)

     1  package client
     2  
     3  import (
     4  	"crypto/tls"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"os"
    12  	"runtime"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/Sirupsen/logrus"
    17  	"github.com/docker/docker/api"
    18  	"github.com/docker/docker/autogen/dockerversion"
    19  	"github.com/docker/docker/pkg/promise"
    20  	"github.com/docker/docker/pkg/stdcopy"
    21  	"github.com/docker/docker/pkg/term"
    22  )
    23  
    24  type tlsClientCon struct {
    25  	*tls.Conn
    26  	rawConn net.Conn
    27  }
    28  
    29  func (c *tlsClientCon) CloseWrite() error {
    30  	// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
    31  	// on its underlying connection.
    32  	if cwc, ok := c.rawConn.(interface {
    33  		CloseWrite() error
    34  	}); ok {
    35  		return cwc.CloseWrite()
    36  	}
    37  	return nil
    38  }
    39  
    40  func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
    41  	return tlsDialWithDialer(new(net.Dialer), network, addr, config)
    42  }
    43  
    44  // We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
    45  // order to return our custom tlsClientCon struct which holds both the tls.Conn
    46  // object _and_ its underlying raw connection. The rationale for this is that
    47  // we need to be able to close the write end of the connection when attaching,
    48  // which tls.Conn does not provide.
    49  func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
    50  	// We want the Timeout and Deadline values from dialer to cover the
    51  	// whole process: TCP connection and TLS handshake. This means that we
    52  	// also need to start our own timers now.
    53  	timeout := dialer.Timeout
    54  
    55  	if !dialer.Deadline.IsZero() {
    56  		deadlineTimeout := dialer.Deadline.Sub(time.Now())
    57  		if timeout == 0 || deadlineTimeout < timeout {
    58  			timeout = deadlineTimeout
    59  		}
    60  	}
    61  
    62  	var errChannel chan error
    63  
    64  	if timeout != 0 {
    65  		errChannel = make(chan error, 2)
    66  		time.AfterFunc(timeout, func() {
    67  			errChannel <- errors.New("")
    68  		})
    69  	}
    70  
    71  	rawConn, err := dialer.Dial(network, addr)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	// When we set up a TCP connection for hijack, there could be long periods
    76  	// of inactivity (a long running command with no output) that in certain
    77  	// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
    78  	// state. Setting TCP KeepAlive on the socket connection will prohibit
    79  	// ECONNTIMEOUT unless the socket connection truly is broken
    80  	if tcpConn, ok := rawConn.(*net.TCPConn); ok {
    81  		tcpConn.SetKeepAlive(true)
    82  		tcpConn.SetKeepAlivePeriod(30 * time.Second)
    83  	}
    84  
    85  	colonPos := strings.LastIndex(addr, ":")
    86  	if colonPos == -1 {
    87  		colonPos = len(addr)
    88  	}
    89  	hostname := addr[:colonPos]
    90  
    91  	// If no ServerName is set, infer the ServerName
    92  	// from the hostname we're connecting to.
    93  	if config.ServerName == "" {
    94  		// Make a copy to avoid polluting argument or default.
    95  		c := *config
    96  		c.ServerName = hostname
    97  		config = &c
    98  	}
    99  
   100  	conn := tls.Client(rawConn, config)
   101  
   102  	if timeout == 0 {
   103  		err = conn.Handshake()
   104  	} else {
   105  		go func() {
   106  			errChannel <- conn.Handshake()
   107  		}()
   108  
   109  		err = <-errChannel
   110  	}
   111  
   112  	if err != nil {
   113  		rawConn.Close()
   114  		return nil, err
   115  	}
   116  
   117  	// This is Docker difference with standard's crypto/tls package: returned a
   118  	// wrapper which holds both the TLS and raw connections.
   119  	return &tlsClientCon{conn, rawConn}, nil
   120  }
   121  
   122  func (cli *DockerCli) dial() (net.Conn, error) {
   123  	if cli.tlsConfig != nil && cli.proto != "unix" {
   124  		// Notice this isn't Go standard's tls.Dial function
   125  		return tlsDial(cli.proto, cli.addr, cli.tlsConfig)
   126  	}
   127  	return net.Dial(cli.proto, cli.addr)
   128  }
   129  
   130  func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
   131  	defer func() {
   132  		if started != nil {
   133  			close(started)
   134  		}
   135  	}()
   136  
   137  	params, err := cli.encodeData(data)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
   147  	// then the user can't change OUR headers
   148  	for k, v := range cli.configFile.HttpHeaders {
   149  		req.Header.Set(k, v)
   150  	}
   151  
   152  	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
   153  	req.Header.Set("Content-Type", "text/plain")
   154  	req.Header.Set("Connection", "Upgrade")
   155  	req.Header.Set("Upgrade", "tcp")
   156  	req.Host = cli.addr
   157  
   158  	dial, err := cli.dial()
   159  	// When we set up a TCP connection for hijack, there could be long periods
   160  	// of inactivity (a long running command with no output) that in certain
   161  	// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
   162  	// state. Setting TCP KeepAlive on the socket connection will prohibit
   163  	// ECONNTIMEOUT unless the socket connection truly is broken
   164  	if tcpConn, ok := dial.(*net.TCPConn); ok {
   165  		tcpConn.SetKeepAlive(true)
   166  		tcpConn.SetKeepAlivePeriod(30 * time.Second)
   167  	}
   168  	if err != nil {
   169  		if strings.Contains(err.Error(), "connection refused") {
   170  			return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
   171  		}
   172  		return err
   173  	}
   174  	clientconn := httputil.NewClientConn(dial, nil)
   175  	defer clientconn.Close()
   176  
   177  	// Server hijacks the connection, error 'connection closed' expected
   178  	clientconn.Do(req)
   179  
   180  	rwc, br := clientconn.Hijack()
   181  	defer rwc.Close()
   182  
   183  	if started != nil {
   184  		started <- rwc
   185  	}
   186  
   187  	var receiveStdout chan error
   188  
   189  	var oldState *term.State
   190  
   191  	if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
   192  		oldState, err = term.SetRawTerminal(cli.inFd)
   193  		if err != nil {
   194  			return err
   195  		}
   196  		defer term.RestoreTerminal(cli.inFd, oldState)
   197  	}
   198  
   199  	if stdout != nil || stderr != nil {
   200  		receiveStdout = promise.Go(func() (err error) {
   201  			defer func() {
   202  				if in != nil {
   203  					if setRawTerminal && cli.isTerminalIn {
   204  						term.RestoreTerminal(cli.inFd, oldState)
   205  					}
   206  					// For some reason this Close call blocks on darwin..
   207  					// As the client exists right after, simply discard the close
   208  					// until we find a better solution.
   209  					if runtime.GOOS != "darwin" {
   210  						in.Close()
   211  					}
   212  				}
   213  			}()
   214  
   215  			// When TTY is ON, use regular copy
   216  			if setRawTerminal && stdout != nil {
   217  				_, err = io.Copy(stdout, br)
   218  			} else {
   219  				_, err = stdcopy.StdCopy(stdout, stderr, br)
   220  			}
   221  			logrus.Debugf("[hijack] End of stdout")
   222  			return err
   223  		})
   224  	}
   225  
   226  	sendStdin := promise.Go(func() error {
   227  		if in != nil {
   228  			io.Copy(rwc, in)
   229  			logrus.Debugf("[hijack] End of stdin")
   230  		}
   231  
   232  		if conn, ok := rwc.(interface {
   233  			CloseWrite() error
   234  		}); ok {
   235  			if err := conn.CloseWrite(); err != nil {
   236  				logrus.Debugf("Couldn't send EOF: %s", err)
   237  			}
   238  		}
   239  		// Discard errors due to pipe interruption
   240  		return nil
   241  	})
   242  
   243  	if stdout != nil || stderr != nil {
   244  		if err := <-receiveStdout; err != nil {
   245  			logrus.Debugf("Error receiveStdout: %s", err)
   246  			return err
   247  		}
   248  	}
   249  
   250  	if !cli.isTerminalIn {
   251  		if err := <-sendStdin; err != nil {
   252  			logrus.Debugf("Error sendStdin: %s", err)
   253  			return err
   254  		}
   255  	}
   256  	return nil
   257  }