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) {
|
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 [!]")
|
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 {
|
if err != nil {
|
||||||
return err
|
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 we're binding to a TCP port, make sure that a container doesn't try to use it.
|
||||||
if proto == "tcp" {
|
if proto == "tcp" {
|
||||||
if err := allocateDaemonPort(addr); err != nil {
|
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])
|
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 {
|
if err := migrateKey(); err != nil {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cmd/dockerd/hack"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/libcontainerd"
|
"github.com/docker/docker/libcontainerd"
|
||||||
"github.com/docker/docker/pkg/system"
|
"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.
|
// notifyShutdown is called after the daemon shuts down but before the process exits.
|
||||||
func notifyShutdown(err error) {
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
@ -75,3 +76,7 @@ func (cli *DaemonCli) getLibcontainerdRoot() string {
|
||||||
func allocateDaemonPort(addr string) error {
|
func allocateDaemonPort(addr string) error {
|
||||||
return nil
|
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
|
may sometime introduce breaking changes and/or incompatibilities. This page
|
||||||
documents these by Engine version.
|
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
|
# Engine 1.10
|
||||||
|
|
||||||
There were two breaking changes in the 1.10 release.
|
There were two breaking changes in the 1.10 release.
|
||||||
|
|
|
@ -849,6 +849,19 @@ set like this:
|
||||||
export DOCKER_TMPDIR=/mnt/disk2/tmp
|
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
|
/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
|
## Default cgroup parent
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue