From 2ea9dac5e1d00b2820bd7156e3bea4b9fd98c1e6 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 24 Aug 2020 11:35:01 -0400 Subject: 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 --- libpod/util.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'libpod/util.go') diff --git a/libpod/util.go b/libpod/util.go index c93ba7919..585b07aca 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "io" + "net/http" "os" "os/exec" "path/filepath" @@ -257,6 +258,27 @@ func makeHTTPAttachHeader(stream byte, length uint32) []byte { return header } +// writeHijackHeader writes a header appropriate for the type of HTTP Hijack +// that occurred in a hijacked HTTP connection used for attach. +func writeHijackHeader(r *http.Request, conn io.Writer) { + // AttachHeader is the literal header sent for upgraded/hijacked connections for + // attach, sourced from Docker at: + // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go + // Using literally to ensure compatibility with existing clients. + c := r.Header.Get("Connection") + proto := r.Header.Get("Upgrade") + if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") { + // OK - can't upgrade if not requested or protocol is not specified + fmt.Fprintf(conn, + "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + } else { + // Upraded + fmt.Fprintf(conn, + "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n", + proto) + } +} + // Convert OCICNI port bindings into Inspect-formatted port bindings. func makeInspectPortBindings(bindings []ocicni.PortMapping) map[string][]define.InspectHostPort { portBindings := make(map[string][]define.InspectHostPort) -- cgit v1.2.3-54-g00ecf