Selaa lähdekoodia

Add implementation of wildcard DNS lookup for container aliases

This allows use of wildcard aliases for container names via
--network-alias command line.

This is useful for running multi-tenant web applications which expect to
expose tenant interfaces on sub-domains.

Fixes #43442

Signed-off-by: Martin Ashby <martin@ashbysoft.com>
Martin Ashby 3 vuotta sitten
vanhempi
commit
465ec963c8
2 muutettua tiedostoa jossa 90 lisäystä ja 2 poistoa
  1. 74 0
      libnetwork/libnetwork_internal_test.go
  2. 16 2
      libnetwork/network.go

+ 74 - 0
libnetwork/libnetwork_internal_test.go

@@ -559,6 +559,80 @@ func TestServiceVIPReuse(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestWildcardLookup(t *testing.T) {
+	skip.If(t, runtime.GOOS == "windows", "test only works on linux")
+
+	c, err := New()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer c.Stop()
+
+	n, err := c.NewNetwork("bridge", "net1", "", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := n.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep, err := n.CreateEndpoint("testep")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	sb, err := c.NewSandbox("c1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sb.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep.Join(sb)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Add aliases including a wildcard for one service
+	n.(*network).addSvcRecords("ep1", "foo.local", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
+	n.(*network).addSvcRecords("ep1", "*.bar.local", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, false, "test")
+
+	// exact match
+	ipList, _ := n.(*network).ResolveName("foo.local", types.IPv4)
+	if len(ipList) == 0 {
+		t.Fatal("There must be the VIP")
+	}
+	if len(ipList) != 1 {
+		t.Fatal("It must return only 1 VIP")
+	}
+	if ipList[0].String() != "192.168.0.1" {
+		t.Fatal("The service VIP is 192.168.0.1")
+	}
+
+	// wildcard match
+	ipList, _ = n.(*network).ResolveName("my.bar.local", types.IPv4)
+	if len(ipList) == 0 {
+		t.Fatal("There must be the VIP")
+	}
+	if len(ipList) != 1 {
+		t.Fatal("It must return only 1 VIP")
+	}
+	if ipList[0].String() != "192.168.0.1" {
+		t.Fatal("The service VIP is 192.168.0.1")
+	}
+
+	// no match
+	ipList, _ = n.(*network).ResolveName("baz.local", types.IPv4)
+	if len(ipList) != 0 {
+		t.Fatal("Invalid hostname must not resolve")
+	}
+}
+
 func TestIpamReleaseOnNetDriverFailures(t *testing.T) {
 func TestIpamReleaseOnNetDriverFailures(t *testing.T) {
 	skip.If(t, runtime.GOOS == "windows", "test only works on linux")
 	skip.If(t, runtime.GOOS == "windows", "test only works on linux")
 
 

+ 16 - 2
libnetwork/network.go

@@ -1990,7 +1990,21 @@ func (n *network) ResolveName(req string, ipType int) ([]net.IP, bool) {
 
 
 	req = strings.TrimSuffix(req, ".")
 	req = strings.TrimSuffix(req, ".")
 	req = strings.ToLower(req)
 	req = strings.ToLower(req)
-	ipSet, ok := sr.svcMap.Get(req)
+
+	// Support wildcard matching, naïve implementation uses a loop
+	selectedKey := req
+	ok = false
+	var ipSet []interface{}
+	for _, key := range sr.svcMap.Keys() {
+		if key == selectedKey ||
+			(strings.HasPrefix(key, "*.") &&
+				strings.HasSuffix(req, strings.TrimPrefix(key, "*"))) {
+			selectedKey = key
+			ok = true
+			ipSet, _ = sr.svcMap.Get(selectedKey)
+			break
+		}
+	}
 
 
 	if ipType == types.IPv6 {
 	if ipType == types.IPv6 {
 		// If the name resolved to v4 address then its a valid name in
 		// If the name resolved to v4 address then its a valid name in
@@ -2000,7 +2014,7 @@ func (n *network) ResolveName(req string, ipType int) ([]net.IP, bool) {
 		if ok && !n.enableIPv6 {
 		if ok && !n.enableIPv6 {
 			ipv6Miss = true
 			ipv6Miss = true
 		}
 		}
-		ipSet, ok = sr.svcIPv6Map.Get(req)
+		ipSet, ok = sr.svcIPv6Map.Get(selectedKey)
 	}
 	}
 
 
 	if ok && len(ipSet) > 0 {
 	if ok && len(ipSet) > 0 {