github.com/tsuna/docker@v1.7.0-rc3/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 }