mirror of https://github.com/docker/docs.git
				
				
				
			Ignore invalid host header between go1.6 and old docker clients
BenchmarkWithHack-4 50000 37082 ns/op 44.50 MB/s 1920 B/op 30 allocs/op BenchmarkNoHack-4 50000 30829 ns/op 53.52 MB/s 0 B/op 0 allocs/op Signed-off-by: Brian Goff <cpuguy83@gmail.com> Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
		
							parent
							
								
									376c15bbaa
								
							
						
					
					
						commit
						3d6f5984f5
					
				| 
						 | 
				
			
			@ -228,10 +228,11 @@ func (cli *DaemonCli) start() (err error) {
 | 
			
		|||
		if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) {
 | 
			
		||||
			logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]")
 | 
			
		||||
		}
 | 
			
		||||
		l, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
 | 
			
		||||
		ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		ls = wrapListeners(proto, ls)
 | 
			
		||||
		// If we're binding to a TCP port, make sure that a container doesn't try to use it.
 | 
			
		||||
		if proto == "tcp" {
 | 
			
		||||
			if err := allocateDaemonPort(addr); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +240,7 @@ func (cli *DaemonCli) start() (err error) {
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
		logrus.Debugf("Listener created for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
 | 
			
		||||
		api.Accept(protoAddrParts[1], l...)
 | 
			
		||||
		api.Accept(protoAddrParts[1], ls...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := migrateKey(); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ import (
 | 
			
		|||
	"strconv"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/cmd/dockerd/hack"
 | 
			
		||||
	"github.com/docker/docker/daemon"
 | 
			
		||||
	"github.com/docker/docker/libcontainerd"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
| 
						 | 
				
			
			@ -111,3 +112,17 @@ func allocateDaemonPort(addr string) error {
 | 
			
		|||
// notifyShutdown is called after the daemon shuts down but before the process exits.
 | 
			
		||||
func notifyShutdown(err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func wrapListeners(proto string, ls []net.Listener) []net.Listener {
 | 
			
		||||
	if os.Getenv("DOCKER_HTTP_HOST_COMPAT") != "" {
 | 
			
		||||
		switch proto {
 | 
			
		||||
		case "unix":
 | 
			
		||||
			ls[0] = &hack.MalformedHostHeaderOverride{ls[0]}
 | 
			
		||||
		case "fd":
 | 
			
		||||
			for i := range ls {
 | 
			
		||||
				ls[i] = &hack.MalformedHostHeaderOverride{ls[i]}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ls
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ package main
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,3 +76,7 @@ func (cli *DaemonCli) getLibcontainerdRoot() string {
 | 
			
		|||
func allocateDaemonPort(addr string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func wrapListeners(proto string, ls []net.Listener) []net.Listener {
 | 
			
		||||
	return ls
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,116 @@
 | 
			
		|||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package hack
 | 
			
		||||
 | 
			
		||||
import "net"
 | 
			
		||||
 | 
			
		||||
// MalformedHostHeaderOverride is a wrapper to be able
 | 
			
		||||
// to overcome the 400 Bad request coming from old docker
 | 
			
		||||
// clients that send an invalid Host header.
 | 
			
		||||
type MalformedHostHeaderOverride struct {
 | 
			
		||||
	net.Listener
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MalformedHostHeaderOverrideConn wraps the underlying unix
 | 
			
		||||
// connection and keeps track of the first read from http.Server
 | 
			
		||||
// which just reads the headers.
 | 
			
		||||
type MalformedHostHeaderOverrideConn struct {
 | 
			
		||||
	net.Conn
 | 
			
		||||
	first bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var closeConnHeader = []byte("\r\nConnection: close\r")
 | 
			
		||||
 | 
			
		||||
// Read reads the first *read* request from http.Server to inspect
 | 
			
		||||
// the Host header. If the Host starts with / then we're talking to
 | 
			
		||||
// an old docker client which send an invalid Host header. To not
 | 
			
		||||
// error out in http.Server we rewrite the first bytes of the request
 | 
			
		||||
// to sanitize the Host header itself.
 | 
			
		||||
// In case we're not dealing with old docker clients the data is just passed
 | 
			
		||||
// to the server w/o modification.
 | 
			
		||||
func (l *MalformedHostHeaderOverrideConn) Read(b []byte) (n int, err error) {
 | 
			
		||||
	// http.Server uses a 4k buffer
 | 
			
		||||
	if l.first && len(b) == 4096 {
 | 
			
		||||
		// This keeps track of the first read from http.Server which just reads
 | 
			
		||||
		// the headers
 | 
			
		||||
		l.first = false
 | 
			
		||||
		// The first read of the connection by http.Server is done limited to
 | 
			
		||||
		// DefaultMaxHeaderBytes (usually 1 << 20) + 4096.
 | 
			
		||||
		// Here we do the first read which gets us all the http headers to
 | 
			
		||||
		// be inspected and modified below.
 | 
			
		||||
		c, err := l.Conn.Read(b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return c, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var (
 | 
			
		||||
			start, end    int
 | 
			
		||||
			firstLineFeed = -1
 | 
			
		||||
			buf           []byte
 | 
			
		||||
		)
 | 
			
		||||
		for i, bb := range b[:c] {
 | 
			
		||||
			if bb == '\n' && firstLineFeed == -1 {
 | 
			
		||||
				firstLineFeed = i
 | 
			
		||||
			}
 | 
			
		||||
			if bb != '\n' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+1] != 'H' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+2] != 'o' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+3] != 's' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+4] != 't' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+5] != ':' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+6] != ' ' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if b[i+7] != '/' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// ensure clients other than the docker clients do not get this hack
 | 
			
		||||
			if i != firstLineFeed {
 | 
			
		||||
				return c, nil
 | 
			
		||||
			}
 | 
			
		||||
			start = i + 7
 | 
			
		||||
			// now find where the value ends
 | 
			
		||||
			for ii, bbb := range b[start:c] {
 | 
			
		||||
				if bbb == '\n' {
 | 
			
		||||
					end = start + ii
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			buf = make([]byte, 0, c+len(closeConnHeader)-(end-start))
 | 
			
		||||
			// strip the value of the host header and
 | 
			
		||||
			// inject `Connection: close` to ensure we don't reuse this connection
 | 
			
		||||
			buf = append(buf, b[:start]...)
 | 
			
		||||
			buf = append(buf, closeConnHeader...)
 | 
			
		||||
			buf = append(buf, b[end:c]...)
 | 
			
		||||
			copy(b, buf)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(buf) == 0 {
 | 
			
		||||
			return c, nil
 | 
			
		||||
		}
 | 
			
		||||
		return len(buf), nil
 | 
			
		||||
	}
 | 
			
		||||
	return l.Conn.Read(b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Accept makes the listener accepts connections and wraps the connection
 | 
			
		||||
// in a MalformedHostHeaderOverrideConn initilizing first to true.
 | 
			
		||||
func (l *MalformedHostHeaderOverride) Accept() (net.Conn, error) {
 | 
			
		||||
	c, err := l.Listener.Accept()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return c, err
 | 
			
		||||
	}
 | 
			
		||||
	return &MalformedHostHeaderOverrideConn{c, true}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,115 @@
 | 
			
		|||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package hack
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHeaderOverrideHack(t *testing.T) {
 | 
			
		||||
	client, srv := net.Pipe()
 | 
			
		||||
	tests := [][2][]byte{
 | 
			
		||||
		{
 | 
			
		||||
			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
 | 
			
		||||
			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\n"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\nFoo: Bar\r\n"),
 | 
			
		||||
			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\nFoo: Bar\r\n"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\ntest something!"),
 | 
			
		||||
			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\ntest something!"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\ntest something! " + strings.Repeat("test", 15000)),
 | 
			
		||||
			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\ntest something! " + strings.Repeat("test", 15000)),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]byte("GET /foo\nFoo: Bar\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
 | 
			
		||||
			[]byte("GET /foo\nFoo: Bar\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	l := MalformedHostHeaderOverrideConn{client, true}
 | 
			
		||||
	read := make([]byte, 4096)
 | 
			
		||||
 | 
			
		||||
	for _, pair := range tests {
 | 
			
		||||
		go func() {
 | 
			
		||||
			srv.Write(pair[0])
 | 
			
		||||
		}()
 | 
			
		||||
		n, err := l.Read(read)
 | 
			
		||||
		if err != nil && err != io.EOF {
 | 
			
		||||
			t.Fatalf("read: %d - %d, err: %v\n%s", n, len(pair[0]), err, string(read[:n]))
 | 
			
		||||
		}
 | 
			
		||||
		if !bytes.Equal(read[:n], pair[1][:n]) {
 | 
			
		||||
			t.Fatalf("\n%s\n%s\n", read[:n], pair[1][:n])
 | 
			
		||||
		}
 | 
			
		||||
		l.first = true
 | 
			
		||||
		// clean out the slice
 | 
			
		||||
		read = read[:0]
 | 
			
		||||
	}
 | 
			
		||||
	srv.Close()
 | 
			
		||||
	l.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkWithHack(b *testing.B) {
 | 
			
		||||
	client, srv := net.Pipe()
 | 
			
		||||
	done := make(chan struct{})
 | 
			
		||||
	req := []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\n")
 | 
			
		||||
	read := make([]byte, 4096)
 | 
			
		||||
	b.SetBytes(int64(len(req) * 30))
 | 
			
		||||
 | 
			
		||||
	l := MalformedHostHeaderOverrideConn{client, true}
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			if _, err := srv.Write(req); err != nil {
 | 
			
		||||
				srv.Close()
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			l.first = true // make sure each subsequent run uses the hack parsing
 | 
			
		||||
		}
 | 
			
		||||
		close(done)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		for i := 0; i < 30; i++ {
 | 
			
		||||
			if n, err := l.Read(read); err != nil && err != io.EOF {
 | 
			
		||||
				b.Fatalf("read: %d - %d, err: %v\n%s", n, len(req), err, string(read[:n]))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	l.Close()
 | 
			
		||||
	<-done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkNoHack(b *testing.B) {
 | 
			
		||||
	client, srv := net.Pipe()
 | 
			
		||||
	done := make(chan struct{})
 | 
			
		||||
	req := []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\n")
 | 
			
		||||
	read := make([]byte, 4096)
 | 
			
		||||
	b.SetBytes(int64(len(req) * 30))
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			if _, err := srv.Write(req); err != nil {
 | 
			
		||||
				srv.Close()
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		close(done)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		for i := 0; i < 30; i++ {
 | 
			
		||||
			if _, err := client.Read(read); err != nil && err != io.EOF {
 | 
			
		||||
				b.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	client.Close()
 | 
			
		||||
	<-done
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +22,13 @@ Unfortunately, Docker is a fast moving project, and newly introduced features
 | 
			
		|||
may sometime introduce breaking changes and/or incompatibilities. This page
 | 
			
		||||
documents these by Engine version.
 | 
			
		||||
 | 
			
		||||
# Engine 1.12
 | 
			
		||||
 | 
			
		||||
Docker clients <= 1.9.2 used an invalid Host header when making request to the
 | 
			
		||||
daemon. Docker 1.12 is built using golang 1.6 which is now checking the validity
 | 
			
		||||
of the Host header and as such clients <= 1.9.2 can't talk anymore to the daemon. 
 | 
			
		||||
[An environment variable was added to overcome this issue.](reference/commandline/dockerd.md#miscellaneous-options)
 | 
			
		||||
 | 
			
		||||
# Engine 1.10
 | 
			
		||||
 | 
			
		||||
There were two breaking changes in the 1.10 release.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -849,6 +849,19 @@ set like this:
 | 
			
		|||
    export DOCKER_TMPDIR=/mnt/disk2/tmp
 | 
			
		||||
    /usr/local/bin/dockerd -D -g /var/lib/docker -H unix:// > /var/lib/docker-machine/docker.log 2>&1
 | 
			
		||||
 | 
			
		||||
Docker clients <= 1.9.2 used an invalid Host header when making request to the
 | 
			
		||||
daemon. Docker 1.12 is built using golang 1.6 which is now checking the validity
 | 
			
		||||
of the Host header and as such clients <= 1.9.2 can't talk anymore to the daemon.
 | 
			
		||||
Docker supports overcoming this issue via a Docker daemon
 | 
			
		||||
environment variable. In case you are seeing this error when contacting the
 | 
			
		||||
daemon:
 | 
			
		||||
 | 
			
		||||
    Error response from daemon: 400 Bad Request: malformed Host header
 | 
			
		||||
 | 
			
		||||
The `DOCKER_HTTP_HOST_COMPAT` can be set like this:
 | 
			
		||||
 | 
			
		||||
    DOCKER_HTTP_HOST_COMPAT=1 /usr/local/bin/dockerd ...
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Default cgroup parent
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue