Browse Source

daemon/logger/fluentd: fix missing host, remove urlutil.IsTransportURL()

pkg/urlutil (despite its poorly chosen name) is not really intended as a generic
utility to handle URLs, and should only be used by the builder to handle (remote)
build contexts.

This patch:

- fix some cases where the host was ignored for valid addresses.
- removes a redundant use of urlutil.IsTransportURL(); instead adding code to
  check if the given scheme (protocol) is supported.
- improve port validation for out of range ports.
- fix some missing validation: the driver was silently ignoring path elements,
  but expected a host (not an URL)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Sebastiaan van Stijn 3 years ago
parent
commit
12424cfa6f
2 changed files with 79 additions and 76 deletions
  1. 30 41
      daemon/logger/fluentd/fluentd.go
  2. 49 35
      daemon/logger/fluentd/fluentd_test.go

+ 30 - 41
daemon/logger/fluentd/fluentd.go

@@ -4,7 +4,6 @@ package fluentd // import "github.com/docker/docker/daemon/logger/fluentd"
 
 
 import (
 import (
 	"math"
 	"math"
-	"net"
 	"net/url"
 	"net/url"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -13,7 +12,6 @@ import (
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/daemon/logger/loggerutils"
 	"github.com/docker/docker/daemon/logger/loggerutils"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
-	"github.com/docker/docker/pkg/urlutil"
 	units "github.com/docker/go-units"
 	units "github.com/docker/go-units"
 	"github.com/fluent/fluent-logger-golang/fluent"
 	"github.com/fluent/fluent-logger-golang/fluent"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
@@ -281,54 +279,45 @@ func parseAddress(address string) (*location, error) {
 		address = defaultProtocol + "://" + address
 		address = defaultProtocol + "://" + address
 	}
 	}
 
 
-	protocol := defaultProtocol
-	if urlutil.IsTransportURL(address) {
-		addr, err := url.Parse(address)
-		if err != nil {
-			return nil, err
-		}
-		// unix and unixgram socket
-		if addr.Scheme == "unix" || addr.Scheme == "unixgram" {
-			if strings.TrimLeft(addr.Path, "/") == "" {
-				return nil, errors.New("path is empty")
-			}
-			return &location{
-				protocol: addr.Scheme,
-				host:     "",
-				port:     0,
-				path:     addr.Path,
-			}, nil
-		}
-		// tcp|udp
-		protocol = addr.Scheme
-		address = addr.Host
+	addr, err := url.Parse(address)
+	if err != nil {
+		return nil, err
+	}
 
 
-		if addr.Path != "" {
-			return nil, errors.New("should not contain a path element")
+	switch addr.Scheme {
+	case "unix", "unixgram":
+		if strings.TrimLeft(addr.Path, "/") == "" {
+			return nil, errors.New("path is empty")
 		}
 		}
+		return &location{protocol: addr.Scheme, path: addr.Path}, nil
+	case "tcp", "tcp+tls", "udp":
+		// continue processing below
+	default:
+		return nil, errors.Errorf("unsupported scheme: '%s'", addr.Scheme)
 	}
 	}
 
 
-	host, port, err := net.SplitHostPort(address)
-	if err != nil {
-		if !strings.Contains(err.Error(), "missing port in address") {
-			return nil, err
-		}
-		return &location{
-			protocol: protocol,
-			host:     host,
-			port:     defaultPort,
-			path:     "",
-		}, nil
+	if addr.Path != "" {
+		return nil, errors.New("should not contain a path element")
 	}
 	}
 
 
-	portnum, err := strconv.Atoi(port)
-	if err != nil {
-		return nil, err
+	host := defaultHost
+	port := defaultPort
+
+	if h := addr.Hostname(); h != "" {
+		host = h
+	}
+	if p := addr.Port(); p != "" {
+		// Port numbers are 16 bit: https://www.ietf.org/rfc/rfc793.html#section-3.1
+		portNum, err := strconv.ParseUint(p, 10, 16)
+		if err != nil {
+			return nil, errors.Wrap(err, "invalid port")
+		}
+		port = int(portNum)
 	}
 	}
 	return &location{
 	return &location{
-		protocol: protocol,
+		protocol: addr.Scheme,
 		host:     host,
 		host:     host,
-		port:     portnum,
+		port:     port,
 		path:     "",
 		path:     "",
 	}, nil
 	}, nil
 }
 }

+ 49 - 35
daemon/logger/fluentd/fluentd_test.go

@@ -25,13 +25,20 @@ func TestValidateLogOptReconnectInterval(t *testing.T) {
 }
 }
 
 
 func TestValidateLogOptAddress(t *testing.T) {
 func TestValidateLogOptAddress(t *testing.T) {
-
-	// paths to try
+	// ports to try, and their results
+	validPorts := map[string]int{
+		"":       defaultPort,
+		":":      defaultPort,
+		":123":   123,
+		":65535": 65535,
+	}
+	// paths to try, which should result in an error
 	paths := []string{"/", "/some-path"}
 	paths := []string{"/", "/some-path"}
 
 
 	tests := []struct {
 	tests := []struct {
 		addr        string
 		addr        string
-		paths       []string // paths to append to addr, should be an error for tcp/udp
+		ports       map[string]int // combinations of addr + port -> expected port
+		paths       []string       // paths to append to addr, should be an error for tcp/udp
 		expected    location
 		expected    location
 		expectedErr string
 		expectedErr string
 	}{
 	}{
@@ -45,26 +52,29 @@ func TestValidateLogOptAddress(t *testing.T) {
 		},
 		},
 		{
 		{
 			addr:  "192.168.1.1",
 			addr:  "192.168.1.1",
+			ports: validPorts,
 			paths: paths,
 			paths: paths,
 			expected: location{
 			expected: location{
-				port:     defaultPort,
 				protocol: defaultProtocol,
 				protocol: defaultProtocol,
+				host:     "192.168.1.1",
 			},
 			},
 		},
 		},
 		{
 		{
 			addr:  "[::1]",
 			addr:  "[::1]",
+			ports: validPorts,
 			paths: paths,
 			paths: paths,
 			expected: location{
 			expected: location{
-				port:     defaultPort,
 				protocol: defaultProtocol,
 				protocol: defaultProtocol,
+				host:     "::1",
 			},
 			},
 		},
 		},
 		{
 		{
 			addr:  "example.com",
 			addr:  "example.com",
+			ports: validPorts,
 			paths: paths,
 			paths: paths,
 			expected: location{
 			expected: location{
-				port:     defaultPort,
 				protocol: defaultProtocol,
 				protocol: defaultProtocol,
+				host:     "example.com",
 			},
 			},
 		},
 		},
 		{
 		{
@@ -72,33 +82,26 @@ func TestValidateLogOptAddress(t *testing.T) {
 			paths: paths,
 			paths: paths,
 			expected: location{
 			expected: location{
 				protocol: "tcp",
 				protocol: "tcp",
+				host:     defaultHost,
 				port:     defaultPort,
 				port:     defaultPort,
 			},
 			},
 		},
 		},
 		{
 		{
 			addr:  "tcp://example.com",
 			addr:  "tcp://example.com",
-			paths: paths,
-			expected: location{
-				protocol: "tcp",
-				port:     defaultPort,
-			},
-		},
-		{
-			addr:  "tcp://example.com:65535",
+			ports: validPorts,
 			paths: paths,
 			paths: paths,
 			expected: location{
 			expected: location{
 				protocol: "tcp",
 				protocol: "tcp",
 				host:     "example.com",
 				host:     "example.com",
-				port:     65535,
 			},
 			},
 		},
 		},
 		{
 		{
 			addr:        "://",
 			addr:        "://",
-			expectedErr: "invalid syntax",
+			expectedErr: "missing protocol scheme",
 		},
 		},
 		{
 		{
 			addr:        "something://",
 			addr:        "something://",
-			expectedErr: "invalid syntax",
+			expectedErr: "unsupported scheme: 'something'",
 		},
 		},
 		{
 		{
 			addr:        "corrupted:c",
 			addr:        "corrupted:c",
@@ -112,6 +115,10 @@ func TestValidateLogOptAddress(t *testing.T) {
 			addr:        "tcp://example.com:-1",
 			addr:        "tcp://example.com:-1",
 			expectedErr: "invalid port",
 			expectedErr: "invalid port",
 		},
 		},
+		{
+			addr:        "tcp://example.com:65536",
+			expectedErr: "invalid port",
+		},
 		{
 		{
 			addr:        "unix://",
 			addr:        "unix://",
 			expectedErr: "path is empty",
 			expectedErr: "path is empty",
@@ -133,26 +140,33 @@ func TestValidateLogOptAddress(t *testing.T) {
 	}
 	}
 	for _, tc := range tests {
 	for _, tc := range tests {
 		tc := tc
 		tc := tc
-		if len(tc.paths) == 0 {
-			tc.paths = []string{""}
+		if len(tc.ports) == 0 {
+			tc.ports = map[string]int{"": tc.expected.port}
 		}
 		}
-		for _, path := range tc.paths {
-			address := tc.addr + path
-			t.Run(address, func(t *testing.T) {
-				err := ValidateLogOpt(map[string]string{addressKey: address})
-				if path != "" {
-					assert.ErrorContains(t, err, "should not contain a path element")
-					return
-				}
-				if tc.expectedErr != "" {
-					assert.ErrorContains(t, err, tc.expectedErr)
-					return
-				}
 
 
-				assert.NilError(t, err)
-				addr, _ := parseAddress(address)
-				assert.DeepEqual(t, tc.expected, *addr, cmp.AllowUnexported(location{}))
-			})
+		// always try empty paths; add paths to try if the test specifies it
+		tc.paths = append([]string{""}, tc.paths...)
+		for port, expectedPort := range tc.ports {
+			for _, path := range tc.paths {
+				address := tc.addr + port + path
+				expected := tc.expected
+				expected.port = expectedPort
+				t.Run(address, func(t *testing.T) {
+					err := ValidateLogOpt(map[string]string{addressKey: address})
+					if path != "" {
+						assert.ErrorContains(t, err, "should not contain a path element")
+						return
+					}
+					if tc.expectedErr != "" {
+						assert.ErrorContains(t, err, tc.expectedErr)
+						return
+					}
+
+					assert.NilError(t, err)
+					addr, _ := parseAddress(address)
+					assert.DeepEqual(t, expected, *addr, cmp.AllowUnexported(location{}))
+				})
+			}
 		}
 		}
 	}
 	}
 }
 }