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