github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/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("%s/v%s%s", cli.basePath, api.Version, 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+" ("+runtime.GOOS+")") 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 if err != nil { 160 if strings.Contains(err.Error(), "connection refused") { 161 return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") 162 } 163 return err 164 } 165 166 // When we set up a TCP connection for hijack, there could be long periods 167 // of inactivity (a long running command with no output) that in certain 168 // network setups may cause ECONNTIMEOUT, leaving the client in an unknown 169 // state. Setting TCP KeepAlive on the socket connection will prohibit 170 // ECONNTIMEOUT unless the socket connection truly is broken 171 if tcpConn, ok := dial.(*net.TCPConn); ok { 172 tcpConn.SetKeepAlive(true) 173 tcpConn.SetKeepAlivePeriod(30 * time.Second) 174 } 175 176 clientconn := httputil.NewClientConn(dial, nil) 177 defer clientconn.Close() 178 179 // Server hijacks the connection, error 'connection closed' expected 180 clientconn.Do(req) 181 182 rwc, br := clientconn.Hijack() 183 defer rwc.Close() 184 185 if started != nil { 186 started <- rwc 187 } 188 189 var receiveStdout chan error 190 191 var oldState *term.State 192 193 if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { 194 oldState, err = term.SetRawTerminal(cli.inFd) 195 if err != nil { 196 return err 197 } 198 defer term.RestoreTerminal(cli.inFd, oldState) 199 } 200 201 if stdout != nil || stderr != nil { 202 receiveStdout = promise.Go(func() (err error) { 203 defer func() { 204 if in != nil { 205 if setRawTerminal && cli.isTerminalIn { 206 term.RestoreTerminal(cli.inFd, oldState) 207 } 208 // For some reason this Close call blocks on darwin.. 209 // As the client exists right after, simply discard the close 210 // until we find a better solution. 211 if runtime.GOOS != "darwin" { 212 in.Close() 213 } 214 } 215 }() 216 217 // When TTY is ON, use regular copy 218 if setRawTerminal && stdout != nil { 219 _, err = io.Copy(stdout, br) 220 } else { 221 _, err = stdcopy.StdCopy(stdout, stderr, br) 222 } 223 logrus.Debugf("[hijack] End of stdout") 224 return err 225 }) 226 } 227 228 sendStdin := promise.Go(func() error { 229 if in != nil { 230 io.Copy(rwc, in) 231 logrus.Debugf("[hijack] End of stdin") 232 } 233 234 if conn, ok := rwc.(interface { 235 CloseWrite() error 236 }); ok { 237 if err := conn.CloseWrite(); err != nil { 238 logrus.Debugf("Couldn't send EOF: %s", err) 239 } 240 } 241 // Discard errors due to pipe interruption 242 return nil 243 }) 244 245 if stdout != nil || stderr != nil { 246 if err := <-receiveStdout; err != nil { 247 logrus.Debugf("Error receiveStdout: %s", err) 248 return err 249 } 250 } 251 252 if !cli.isTerminalIn { 253 if err := <-sendStdin; err != nil { 254 logrus.Debugf("Error sendStdin: %s", err) 255 return err 256 } 257 } 258 return nil 259 }