summaryrefslogtreecommitdiff
path: root/libpod/oci_conmon_exec_linux.go
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@pm.me>2020-08-24 11:35:01 -0400
committerMatthew Heon <matthew.heon@pm.me>2020-08-27 12:50:22 -0400
commit2ea9dac5e1d00b2820bd7156e3bea4b9fd98c1e6 (patch)
tree4251590df91107c2c0dafe12500bae05bc0c8fed /libpod/oci_conmon_exec_linux.go
parent7ccd821397d03ed545635de2a0b70a68ab4d46db (diff)
downloadpodman-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.go39
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() {