diff options
author | Matthew Heon <matthew.heon@pm.me> | 2020-08-24 11:35:01 -0400 |
---|---|---|
committer | Matthew Heon <matthew.heon@pm.me> | 2020-08-27 12:50:22 -0400 |
commit | 2ea9dac5e1d00b2820bd7156e3bea4b9fd98c1e6 (patch) | |
tree | 4251590df91107c2c0dafe12500bae05bc0c8fed /libpod/oci_conmon_exec_linux.go | |
parent | 7ccd821397d03ed545635de2a0b70a68ab4d46db (diff) | |
download | podman-2ea9dac5e1d00b2820bd7156e3bea4b9fd98c1e6.tar.gz podman-2ea9dac5e1d00b2820bd7156e3bea4b9fd98c1e6.tar.bz2 podman-2ea9dac5e1d00b2820bd7156e3bea4b9fd98c1e6.zip |
Send HTTP Hijack headers after successful attach
Our previous flow was to perform a hijack before passing a
connection into Libpod, and then Libpod would attach to the
container's attach socket and begin forwarding traffic.
A problem emerges: we write the attach header as soon as the
attach complete. As soon as we write the header, the client
assumes that all is ready, and sends a Start request. This Start
may be processed *before* we successfully finish attaching,
causing us to lose output.
The solution is to handle hijacking inside Libpod. Unfortunately,
this requires a downright extensive refactor of the Attach and
HTTP Exec StartAndAttach code. I think the result is an
improvement in some places (a lot more errors will be handled
with a proper HTTP error code, before the hijack occurs) but
other parts, like the relocation of printing container logs, are
just *bad*. Still, we need this fixed now to get CI back into
good shape...
Fixes #7195
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
Diffstat (limited to 'libpod/oci_conmon_exec_linux.go')
-rw-r--r-- | libpod/oci_conmon_exec_linux.go | 39 |
1 files changed, 35 insertions, 4 deletions
diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index cfe3745fa..c18da68fe 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -1,9 +1,9 @@ package libpod import ( - "bufio" "fmt" "net" + "net/http" "os" "os/exec" "path/filepath" @@ -80,7 +80,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options // ExecContainerHTTP executes a new command in an existing container and // forwards its standard streams over an attach -func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, httpConn net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, cancel <-chan bool) (int, chan error, error) { +func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool) (int, chan error, error) { if streams != nil { if !streams.Stdin && !streams.Stdout && !streams.Stderr { return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") @@ -129,7 +129,7 @@ func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, o attachChan := make(chan error) go func() { // attachToExec is responsible for closing pipes - attachChan <- attachExecHTTP(ctr, sessionID, httpBuf, streams, pipes, detachKeys, options.Terminal, cancel) + attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen) close(attachChan) }() @@ -496,7 +496,7 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex } // Attach to a container over HTTP -func attachExecHTTP(c *Container, sessionID string, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool) error { +func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool) (deferredErr error) { if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil { return errors.Wrapf(define.ErrInvalidArg, "must provide a start and attach pipe to finish an exec attach") } @@ -549,6 +549,37 @@ func attachExecHTTP(c *Container, sessionID string, httpBuf *bufio.ReadWriter, s attachStdin = streams.Stdin } + // Perform hijack + hijacker, ok := w.(http.Hijacker) + if !ok { + return errors.Errorf("unable to hijack connection") + } + + httpCon, httpBuf, err := hijacker.Hijack() + if err != nil { + return errors.Wrapf(err, "error hijacking connection") + } + + hijackDone <- true + + // Write a header to let the client know what happened + writeHijackHeader(r, httpBuf) + + // Force a flush after the header is written. + if err := httpBuf.Flush(); err != nil { + return errors.Wrapf(err, "error flushing HTTP hijack header") + } + + go func() { + // We need to hold the connection open until the complete exec + // function has finished. This channel will be closed in a defer + // in that function, so we can wait for it here. + // Can't be a defer, because this would block the function from + // returning. + <-holdConnOpen + hijackWriteErrorAndClose(deferredErr, c.ID(), isTerminal, httpCon, httpBuf) + }() + // Next, STDIN. Avoid entirely if attachStdin unset. if attachStdin { go func() { |