github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/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/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 76 colonPos := strings.LastIndex(addr, ":") 77 if colonPos == -1 { 78 colonPos = len(addr) 79 } 80 hostname := addr[:colonPos] 81 82 // If no ServerName is set, infer the ServerName 83 // from the hostname we're connecting to. 84 if config.ServerName == "" { 85 // Make a copy to avoid polluting argument or default. 86 c := *config 87 c.ServerName = hostname 88 config = &c 89 } 90 91 conn := tls.Client(rawConn, config) 92 93 if timeout == 0 { 94 err = conn.Handshake() 95 } else { 96 go func() { 97 errChannel <- conn.Handshake() 98 }() 99 100 err = <-errChannel 101 } 102 103 if err != nil { 104 rawConn.Close() 105 return nil, err 106 } 107 108 // This is Docker difference with standard's crypto/tls package: returned a 109 // wrapper which holds both the TLS and raw connections. 110 return &tlsClientCon{conn, rawConn}, nil 111 } 112 113 func (cli *DockerCli) dial() (net.Conn, error) { 114 if cli.tlsConfig != nil && cli.proto != "unix" { 115 // Notice this isn't Go standard's tls.Dial function 116 return tlsDial(cli.proto, cli.addr, cli.tlsConfig) 117 } 118 return net.Dial(cli.proto, cli.addr) 119 } 120 121 func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error { 122 defer func() { 123 if started != nil { 124 close(started) 125 } 126 }() 127 128 params, err := cli.encodeData(data) 129 if err != nil { 130 return err 131 } 132 req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) 133 if err != nil { 134 return err 135 } 136 req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) 137 req.Header.Set("Content-Type", "plain/text") 138 req.Header.Set("Connection", "Upgrade") 139 req.Header.Set("Upgrade", "tcp") 140 req.Host = cli.addr 141 142 dial, err := cli.dial() 143 if err != nil { 144 if strings.Contains(err.Error(), "connection refused") { 145 return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") 146 } 147 return err 148 } 149 clientconn := httputil.NewClientConn(dial, nil) 150 defer clientconn.Close() 151 152 // Server hijacks the connection, error 'connection closed' expected 153 clientconn.Do(req) 154 155 rwc, br := clientconn.Hijack() 156 defer rwc.Close() 157 158 if started != nil { 159 started <- rwc 160 } 161 162 var receiveStdout chan error 163 164 var oldState *term.State 165 166 if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { 167 oldState, err = term.SetRawTerminal(cli.inFd) 168 if err != nil { 169 return err 170 } 171 defer term.RestoreTerminal(cli.inFd, oldState) 172 } 173 174 if stdout != nil || stderr != nil { 175 receiveStdout = promise.Go(func() (err error) { 176 defer func() { 177 if in != nil { 178 if setRawTerminal && cli.isTerminalIn { 179 term.RestoreTerminal(cli.inFd, oldState) 180 } 181 // For some reason this Close call blocks on darwin.. 182 // As the client exists right after, simply discard the close 183 // until we find a better solution. 184 if runtime.GOOS != "darwin" { 185 in.Close() 186 } 187 } 188 }() 189 190 // When TTY is ON, use regular copy 191 if setRawTerminal && stdout != nil { 192 _, err = io.Copy(stdout, br) 193 } else { 194 _, err = stdcopy.StdCopy(stdout, stderr, br) 195 } 196 log.Debugf("[hijack] End of stdout") 197 return err 198 }) 199 } 200 201 sendStdin := promise.Go(func() error { 202 if in != nil { 203 io.Copy(rwc, in) 204 log.Debugf("[hijack] End of stdin") 205 } 206 207 if conn, ok := rwc.(interface { 208 CloseWrite() error 209 }); ok { 210 if err := conn.CloseWrite(); err != nil { 211 log.Debugf("Couldn't send EOF: %s", err) 212 } 213 } 214 // Discard errors due to pipe interruption 215 return nil 216 }) 217 218 if stdout != nil || stderr != nil { 219 if err := <-receiveStdout; err != nil { 220 log.Debugf("Error receiveStdout: %s", err) 221 return err 222 } 223 } 224 225 if !cli.isTerminalIn { 226 if err := <-sendStdin; err != nil { 227 log.Debugf("Error sendStdin: %s", err) 228 return err 229 } 230 } 231 return nil 232 }