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>
| ... | ... |
@@ -228,10 +228,11 @@ func (cli *DaemonCli) start() (err error) {
|
| 228 | 228 |
if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) {
|
| 229 | 229 |
logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]")
|
| 230 | 230 |
} |
| 231 |
- l, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig) |
|
| 231 |
+ ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig) |
|
| 232 | 232 |
if err != nil {
|
| 233 | 233 |
return err |
| 234 | 234 |
} |
| 235 |
+ ls = wrapListeners(proto, ls) |
|
| 235 | 236 |
// If we're binding to a TCP port, make sure that a container doesn't try to use it. |
| 236 | 237 |
if proto == "tcp" {
|
| 237 | 238 |
if err := allocateDaemonPort(addr); err != nil {
|
| ... | ... |
@@ -239,7 +240,7 @@ func (cli *DaemonCli) start() (err error) {
|
| 239 | 239 |
} |
| 240 | 240 |
} |
| 241 | 241 |
logrus.Debugf("Listener created for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
| 242 |
- api.Accept(protoAddrParts[1], l...) |
|
| 242 |
+ api.Accept(protoAddrParts[1], ls...) |
|
| 243 | 243 |
} |
| 244 | 244 |
|
| 245 | 245 |
if err := migrateKey(); err != nil {
|
| ... | ... |
@@ -11,6 +11,7 @@ import ( |
| 11 | 11 |
"strconv" |
| 12 | 12 |
"syscall" |
| 13 | 13 |
|
| 14 |
+ "github.com/docker/docker/cmd/dockerd/hack" |
|
| 14 | 15 |
"github.com/docker/docker/daemon" |
| 15 | 16 |
"github.com/docker/docker/libcontainerd" |
| 16 | 17 |
"github.com/docker/docker/pkg/system" |
| ... | ... |
@@ -111,3 +112,17 @@ func allocateDaemonPort(addr string) error {
|
| 111 | 111 |
// notifyShutdown is called after the daemon shuts down but before the process exits. |
| 112 | 112 |
func notifyShutdown(err error) {
|
| 113 | 113 |
} |
| 114 |
+ |
|
| 115 |
+func wrapListeners(proto string, ls []net.Listener) []net.Listener {
|
|
| 116 |
+ if os.Getenv("DOCKER_HTTP_HOST_COMPAT") != "" {
|
|
| 117 |
+ switch proto {
|
|
| 118 |
+ case "unix": |
|
| 119 |
+ ls[0] = &hack.MalformedHostHeaderOverride{ls[0]}
|
|
| 120 |
+ case "fd": |
|
| 121 |
+ for i := range ls {
|
|
| 122 |
+ ls[i] = &hack.MalformedHostHeaderOverride{ls[i]}
|
|
| 123 |
+ } |
|
| 124 |
+ } |
|
| 125 |
+ } |
|
| 126 |
+ return ls |
|
| 127 |
+} |
| ... | ... |
@@ -2,6 +2,7 @@ package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
+ "net" |
|
| 5 | 6 |
"os" |
| 6 | 7 |
"syscall" |
| 7 | 8 |
|
| ... | ... |
@@ -75,3 +76,7 @@ func (cli *DaemonCli) getLibcontainerdRoot() string {
|
| 75 | 75 |
func allocateDaemonPort(addr string) error {
|
| 76 | 76 |
return nil |
| 77 | 77 |
} |
| 78 |
+ |
|
| 79 |
+func wrapListeners(proto string, ls []net.Listener) []net.Listener {
|
|
| 80 |
+ return ls |
|
| 81 |
+} |
| 78 | 82 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,116 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package hack |
|
| 3 |
+ |
|
| 4 |
+import "net" |
|
| 5 |
+ |
|
| 6 |
+// MalformedHostHeaderOverride is a wrapper to be able |
|
| 7 |
+// to overcome the 400 Bad request coming from old docker |
|
| 8 |
+// clients that send an invalid Host header. |
|
| 9 |
+type MalformedHostHeaderOverride struct {
|
|
| 10 |
+ net.Listener |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+// MalformedHostHeaderOverrideConn wraps the underlying unix |
|
| 14 |
+// connection and keeps track of the first read from http.Server |
|
| 15 |
+// which just reads the headers. |
|
| 16 |
+type MalformedHostHeaderOverrideConn struct {
|
|
| 17 |
+ net.Conn |
|
| 18 |
+ first bool |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+var closeConnHeader = []byte("\r\nConnection: close\r")
|
|
| 22 |
+ |
|
| 23 |
+// Read reads the first *read* request from http.Server to inspect |
|
| 24 |
+// the Host header. If the Host starts with / then we're talking to |
|
| 25 |
+// an old docker client which send an invalid Host header. To not |
|
| 26 |
+// error out in http.Server we rewrite the first bytes of the request |
|
| 27 |
+// to sanitize the Host header itself. |
|
| 28 |
+// In case we're not dealing with old docker clients the data is just passed |
|
| 29 |
+// to the server w/o modification. |
|
| 30 |
+func (l *MalformedHostHeaderOverrideConn) Read(b []byte) (n int, err error) {
|
|
| 31 |
+ // http.Server uses a 4k buffer |
|
| 32 |
+ if l.first && len(b) == 4096 {
|
|
| 33 |
+ // This keeps track of the first read from http.Server which just reads |
|
| 34 |
+ // the headers |
|
| 35 |
+ l.first = false |
|
| 36 |
+ // The first read of the connection by http.Server is done limited to |
|
| 37 |
+ // DefaultMaxHeaderBytes (usually 1 << 20) + 4096. |
|
| 38 |
+ // Here we do the first read which gets us all the http headers to |
|
| 39 |
+ // be inspected and modified below. |
|
| 40 |
+ c, err := l.Conn.Read(b) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return c, err |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ var ( |
|
| 46 |
+ start, end int |
|
| 47 |
+ firstLineFeed = -1 |
|
| 48 |
+ buf []byte |
|
| 49 |
+ ) |
|
| 50 |
+ for i, bb := range b[:c] {
|
|
| 51 |
+ if bb == '\n' && firstLineFeed == -1 {
|
|
| 52 |
+ firstLineFeed = i |
|
| 53 |
+ } |
|
| 54 |
+ if bb != '\n' {
|
|
| 55 |
+ continue |
|
| 56 |
+ } |
|
| 57 |
+ if b[i+1] != 'H' {
|
|
| 58 |
+ continue |
|
| 59 |
+ } |
|
| 60 |
+ if b[i+2] != 'o' {
|
|
| 61 |
+ continue |
|
| 62 |
+ } |
|
| 63 |
+ if b[i+3] != 's' {
|
|
| 64 |
+ continue |
|
| 65 |
+ } |
|
| 66 |
+ if b[i+4] != 't' {
|
|
| 67 |
+ continue |
|
| 68 |
+ } |
|
| 69 |
+ if b[i+5] != ':' {
|
|
| 70 |
+ continue |
|
| 71 |
+ } |
|
| 72 |
+ if b[i+6] != ' ' {
|
|
| 73 |
+ continue |
|
| 74 |
+ } |
|
| 75 |
+ if b[i+7] != '/' {
|
|
| 76 |
+ continue |
|
| 77 |
+ } |
|
| 78 |
+ // ensure clients other than the docker clients do not get this hack |
|
| 79 |
+ if i != firstLineFeed {
|
|
| 80 |
+ return c, nil |
|
| 81 |
+ } |
|
| 82 |
+ start = i + 7 |
|
| 83 |
+ // now find where the value ends |
|
| 84 |
+ for ii, bbb := range b[start:c] {
|
|
| 85 |
+ if bbb == '\n' {
|
|
| 86 |
+ end = start + ii |
|
| 87 |
+ break |
|
| 88 |
+ } |
|
| 89 |
+ } |
|
| 90 |
+ buf = make([]byte, 0, c+len(closeConnHeader)-(end-start)) |
|
| 91 |
+ // strip the value of the host header and |
|
| 92 |
+ // inject `Connection: close` to ensure we don't reuse this connection |
|
| 93 |
+ buf = append(buf, b[:start]...) |
|
| 94 |
+ buf = append(buf, closeConnHeader...) |
|
| 95 |
+ buf = append(buf, b[end:c]...) |
|
| 96 |
+ copy(b, buf) |
|
| 97 |
+ break |
|
| 98 |
+ } |
|
| 99 |
+ if len(buf) == 0 {
|
|
| 100 |
+ return c, nil |
|
| 101 |
+ } |
|
| 102 |
+ return len(buf), nil |
|
| 103 |
+ } |
|
| 104 |
+ return l.Conn.Read(b) |
|
| 105 |
+} |
|
| 106 |
+ |
|
| 107 |
+// Accept makes the listener accepts connections and wraps the connection |
|
| 108 |
+// in a MalformedHostHeaderOverrideConn initilizing first to true. |
|
| 109 |
+func (l *MalformedHostHeaderOverride) Accept() (net.Conn, error) {
|
|
| 110 |
+ c, err := l.Listener.Accept() |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ return c, err |
|
| 113 |
+ } |
|
| 114 |
+ return &MalformedHostHeaderOverrideConn{c, true}, nil
|
|
| 115 |
+} |
| 0 | 116 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,115 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package hack |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "bytes" |
|
| 6 |
+ "io" |
|
| 7 |
+ "net" |
|
| 8 |
+ "strings" |
|
| 9 |
+ "testing" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func TestHeaderOverrideHack(t *testing.T) {
|
|
| 13 |
+ client, srv := net.Pipe() |
|
| 14 |
+ tests := [][2][]byte{
|
|
| 15 |
+ {
|
|
| 16 |
+ []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
|
|
| 17 |
+ []byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\n"),
|
|
| 18 |
+ }, |
|
| 19 |
+ {
|
|
| 20 |
+ []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\nFoo: Bar\r\n"),
|
|
| 21 |
+ []byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\nFoo: Bar\r\n"),
|
|
| 22 |
+ }, |
|
| 23 |
+ {
|
|
| 24 |
+ []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\ntest something!"),
|
|
| 25 |
+ []byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\ntest something!"),
|
|
| 26 |
+ }, |
|
| 27 |
+ {
|
|
| 28 |
+ []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\ntest something! " + strings.Repeat("test", 15000)),
|
|
| 29 |
+ []byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\ntest something! " + strings.Repeat("test", 15000)),
|
|
| 30 |
+ }, |
|
| 31 |
+ {
|
|
| 32 |
+ []byte("GET /foo\nFoo: Bar\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
|
|
| 33 |
+ []byte("GET /foo\nFoo: Bar\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
|
|
| 34 |
+ }, |
|
| 35 |
+ } |
|
| 36 |
+ l := MalformedHostHeaderOverrideConn{client, true}
|
|
| 37 |
+ read := make([]byte, 4096) |
|
| 38 |
+ |
|
| 39 |
+ for _, pair := range tests {
|
|
| 40 |
+ go func() {
|
|
| 41 |
+ srv.Write(pair[0]) |
|
| 42 |
+ }() |
|
| 43 |
+ n, err := l.Read(read) |
|
| 44 |
+ if err != nil && err != io.EOF {
|
|
| 45 |
+ t.Fatalf("read: %d - %d, err: %v\n%s", n, len(pair[0]), err, string(read[:n]))
|
|
| 46 |
+ } |
|
| 47 |
+ if !bytes.Equal(read[:n], pair[1][:n]) {
|
|
| 48 |
+ t.Fatalf("\n%s\n%s\n", read[:n], pair[1][:n])
|
|
| 49 |
+ } |
|
| 50 |
+ l.first = true |
|
| 51 |
+ // clean out the slice |
|
| 52 |
+ read = read[:0] |
|
| 53 |
+ } |
|
| 54 |
+ srv.Close() |
|
| 55 |
+ l.Close() |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func BenchmarkWithHack(b *testing.B) {
|
|
| 59 |
+ client, srv := net.Pipe() |
|
| 60 |
+ done := make(chan struct{})
|
|
| 61 |
+ req := []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\n")
|
|
| 62 |
+ read := make([]byte, 4096) |
|
| 63 |
+ b.SetBytes(int64(len(req) * 30)) |
|
| 64 |
+ |
|
| 65 |
+ l := MalformedHostHeaderOverrideConn{client, true}
|
|
| 66 |
+ go func() {
|
|
| 67 |
+ for {
|
|
| 68 |
+ if _, err := srv.Write(req); err != nil {
|
|
| 69 |
+ srv.Close() |
|
| 70 |
+ break |
|
| 71 |
+ } |
|
| 72 |
+ l.first = true // make sure each subsequent run uses the hack parsing |
|
| 73 |
+ } |
|
| 74 |
+ close(done) |
|
| 75 |
+ }() |
|
| 76 |
+ |
|
| 77 |
+ for i := 0; i < b.N; i++ {
|
|
| 78 |
+ for i := 0; i < 30; i++ {
|
|
| 79 |
+ if n, err := l.Read(read); err != nil && err != io.EOF {
|
|
| 80 |
+ b.Fatalf("read: %d - %d, err: %v\n%s", n, len(req), err, string(read[:n]))
|
|
| 81 |
+ } |
|
| 82 |
+ } |
|
| 83 |
+ } |
|
| 84 |
+ l.Close() |
|
| 85 |
+ <-done |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func BenchmarkNoHack(b *testing.B) {
|
|
| 89 |
+ client, srv := net.Pipe() |
|
| 90 |
+ done := make(chan struct{})
|
|
| 91 |
+ req := []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\n")
|
|
| 92 |
+ read := make([]byte, 4096) |
|
| 93 |
+ b.SetBytes(int64(len(req) * 30)) |
|
| 94 |
+ |
|
| 95 |
+ go func() {
|
|
| 96 |
+ for {
|
|
| 97 |
+ if _, err := srv.Write(req); err != nil {
|
|
| 98 |
+ srv.Close() |
|
| 99 |
+ break |
|
| 100 |
+ } |
|
| 101 |
+ } |
|
| 102 |
+ close(done) |
|
| 103 |
+ }() |
|
| 104 |
+ |
|
| 105 |
+ for i := 0; i < b.N; i++ {
|
|
| 106 |
+ for i := 0; i < 30; i++ {
|
|
| 107 |
+ if _, err := client.Read(read); err != nil && err != io.EOF {
|
|
| 108 |
+ b.Fatal(err) |
|
| 109 |
+ } |
|
| 110 |
+ } |
|
| 111 |
+ } |
|
| 112 |
+ client.Close() |
|
| 113 |
+ <-done |
|
| 114 |
+} |
| ... | ... |
@@ -22,6 +22,13 @@ Unfortunately, Docker is a fast moving project, and newly introduced features |
| 22 | 22 |
may sometime introduce breaking changes and/or incompatibilities. This page |
| 23 | 23 |
documents these by Engine version. |
| 24 | 24 |
|
| 25 |
+# Engine 1.12 |
|
| 26 |
+ |
|
| 27 |
+Docker clients <= 1.9.2 used an invalid Host header when making request to the |
|
| 28 |
+daemon. Docker 1.12 is built using golang 1.6 which is now checking the validity |
|
| 29 |
+of the Host header and as such clients <= 1.9.2 can't talk anymore to the daemon. |
|
| 30 |
+[An environment variable was added to overcome this issue.](reference/commandline/dockerd.md#miscellaneous-options) |
|
| 31 |
+ |
|
| 25 | 32 |
# Engine 1.10 |
| 26 | 33 |
|
| 27 | 34 |
There were two breaking changes in the 1.10 release. |
| ... | ... |
@@ -849,6 +849,19 @@ set like this: |
| 849 | 849 |
export DOCKER_TMPDIR=/mnt/disk2/tmp |
| 850 | 850 |
/usr/local/bin/dockerd -D -g /var/lib/docker -H unix:// > /var/lib/docker-machine/docker.log 2>&1 |
| 851 | 851 |
|
| 852 |
+Docker clients <= 1.9.2 used an invalid Host header when making request to the |
|
| 853 |
+daemon. Docker 1.12 is built using golang 1.6 which is now checking the validity |
|
| 854 |
+of the Host header and as such clients <= 1.9.2 can't talk anymore to the daemon. |
|
| 855 |
+Docker supports overcoming this issue via a Docker daemon |
|
| 856 |
+environment variable. In case you are seeing this error when contacting the |
|
| 857 |
+daemon: |
|
| 858 |
+ |
|
| 859 |
+ Error response from daemon: 400 Bad Request: malformed Host header |
|
| 860 |
+ |
|
| 861 |
+The `DOCKER_HTTP_HOST_COMPAT` can be set like this: |
|
| 862 |
+ |
|
| 863 |
+ DOCKER_HTTP_HOST_COMPAT=1 /usr/local/bin/dockerd ... |
|
| 864 |
+ |
|
| 852 | 865 |
|
| 853 | 866 |
## Default cgroup parent |
| 854 | 867 |
|