Browse code

Merge pull request #46853 from akerouanton/libnet-ep-dns-names

libnet: Endpoint: remove isAnonymous & myAliases

Sebastiaan van Stijn authored on 2023/12/21 03:53:16
Showing 18 changed files
... ...
@@ -2530,6 +2530,21 @@ definitions:
2530 2530
         example:
2531 2531
           com.example.some-label: "some-value"
2532 2532
           com.example.some-other-label: "some-other-value"
2533
+      DNSNames:
2534
+        description: |
2535
+          List of all DNS names an endpoint has on a specific network. This
2536
+          list is based on the container name, network aliases, container short
2537
+          ID, and hostname.
2538
+
2539
+          These DNS names are non-fully qualified but can contain several dots.
2540
+          You can get fully qualified DNS names by appending `.<network-name>`.
2541
+          For instance, if container name is `my.ctr` and the network is named
2542
+          `testnet`, `DNSNames` will contain `my.ctr` and the FQDN will be
2543
+          `my.ctr.testnet`.
2544
+        type: array
2545
+        items:
2546
+          type: string
2547
+        example: ["foobar", "server_x", "server_y", "my.ctr"]
2533 2548
 
2534 2549
   EndpointIPAMConfig:
2535 2550
     description: |
... ...
@@ -13,7 +13,7 @@ type EndpointSettings struct {
13 13
 	// Configurations
14 14
 	IPAMConfig *EndpointIPAMConfig
15 15
 	Links      []string
16
-	Aliases    []string
16
+	Aliases    []string // Aliases holds the list of extra, user-specified DNS names for this endpoint.
17 17
 	MacAddress string
18 18
 	// Operational data
19 19
 	NetworkID           string
... ...
@@ -25,6 +25,9 @@ type EndpointSettings struct {
25 25
 	GlobalIPv6Address   string
26 26
 	GlobalIPv6PrefixLen int
27 27
 	DriverOpts          map[string]string
28
+	// DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to
29
+	// generate PTR records.
30
+	DNSNames []string
28 31
 }
29 32
 
30 33
 // Copy makes a deep copy of `EndpointSettings`
... ...
@@ -43,6 +46,12 @@ func (es *EndpointSettings) Copy() *EndpointSettings {
43 43
 		aliases := make([]string, 0, len(es.Aliases))
44 44
 		epCopy.Aliases = append(aliases, es.Aliases...)
45 45
 	}
46
+
47
+	if len(es.DNSNames) > 0 {
48
+		epCopy.DNSNames = make([]string, len(es.DNSNames))
49
+		copy(epCopy.DNSNames, es.DNSNames)
50
+	}
51
+
46 52
 	return &epCopy
47 53
 }
48 54
 
... ...
@@ -122,9 +122,8 @@ func (daemon *Daemon) Register(c *container.Container) error {
122 122
 
123 123
 func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
124 124
 	var (
125
-		id             string
126
-		err            error
127
-		noExplicitName = name == ""
125
+		id  string
126
+		err error
128 127
 	)
129 128
 	id, name, err = daemon.generateIDAndName(name)
130 129
 	if err != nil {
... ...
@@ -151,7 +150,7 @@ func (daemon *Daemon) newContainer(name string, operatingSystem string, config *
151 151
 	base.Config = config
152 152
 	base.HostConfig = &containertypes.HostConfig{}
153 153
 	base.ImageID = imgID
154
-	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
154
+	base.NetworkSettings = &network.Settings{}
155 155
 	base.Name = name
156 156
 	base.Driver = daemon.imageService.StorageDriver()
157 157
 	base.OS = operatingSystem
... ...
@@ -19,6 +19,7 @@ import (
19 19
 	"github.com/docker/docker/daemon/network"
20 20
 	"github.com/docker/docker/errdefs"
21 21
 	"github.com/docker/docker/internal/multierror"
22
+	"github.com/docker/docker/internal/sliceutil"
22 23
 	"github.com/docker/docker/libnetwork"
23 24
 	"github.com/docker/docker/libnetwork/netlabel"
24 25
 	"github.com/docker/docker/libnetwork/options"
... ...
@@ -650,29 +651,7 @@ func cleanOperationalData(es *network.EndpointSettings) {
650 650
 
651 651
 func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error {
652 652
 	if containertypes.NetworkMode(n.Name()).IsUserDefined() {
653
-		addShortID := true
654
-		shortID := stringid.TruncateID(container.ID)
655
-		for _, alias := range endpointConfig.Aliases {
656
-			if alias == shortID {
657
-				addShortID = false
658
-				break
659
-			}
660
-		}
661
-		if addShortID {
662
-			endpointConfig.Aliases = append(endpointConfig.Aliases, shortID)
663
-		}
664
-		if container.Name != container.Config.Hostname {
665
-			addHostname := true
666
-			for _, alias := range endpointConfig.Aliases {
667
-				if alias == container.Config.Hostname {
668
-					addHostname = false
669
-					break
670
-				}
671
-			}
672
-			if addHostname {
673
-				endpointConfig.Aliases = append(endpointConfig.Aliases, container.Config.Hostname)
674
-			}
675
-		}
653
+		endpointConfig.DNSNames = buildEndpointDNSNames(container, endpointConfig.Aliases)
676 654
 	}
677 655
 
678 656
 	if err := validateEndpointSettings(n, n.Name(), endpointConfig); err != nil {
... ...
@@ -687,6 +666,29 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *lib
687 687
 	return nil
688 688
 }
689 689
 
690
+// buildEndpointDNSNames constructs the list of DNSNames that should be assigned to a given endpoint. The order within
691
+// the returned slice is important as the first entry will be used to generate the PTR records (for IPv4 and v6)
692
+// associated to this endpoint.
693
+func buildEndpointDNSNames(ctr *container.Container, aliases []string) []string {
694
+	var dnsNames []string
695
+
696
+	if ctr.Name != "" {
697
+		dnsNames = append(dnsNames, strings.TrimPrefix(ctr.Name, "/"))
698
+	}
699
+
700
+	dnsNames = append(dnsNames, aliases...)
701
+
702
+	if ctr.ID != "" {
703
+		dnsNames = append(dnsNames, stringid.TruncateID(ctr.ID))
704
+	}
705
+
706
+	if ctr.Config.Hostname != "" {
707
+		dnsNames = append(dnsNames, ctr.Config.Hostname)
708
+	}
709
+
710
+	return sliceutil.Dedup(dnsNames)
711
+}
712
+
690 713
 func (daemon *Daemon) connectToNetwork(cfg *config.Config, container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
691 714
 	start := time.Now()
692 715
 	if container.HostConfig.NetworkMode.IsContainer() {
693 716
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+package daemon
1
+
2
+import (
3
+	"encoding/json"
4
+	"testing"
5
+
6
+	containertypes "github.com/docker/docker/api/types/container"
7
+	networktypes "github.com/docker/docker/api/types/network"
8
+	"github.com/docker/docker/container"
9
+	"github.com/docker/docker/libnetwork"
10
+	"gotest.tools/v3/assert"
11
+	is "gotest.tools/v3/assert/cmp"
12
+)
13
+
14
+func TestDNSNamesOrder(t *testing.T) {
15
+	d := &Daemon{}
16
+	ctr := &container.Container{
17
+		ID:   "35de8003b19e27f636fc6ecbf4d7072558b872a8544f287fd69ad8182ad59023",
18
+		Name: "foobar",
19
+		Config: &containertypes.Config{
20
+			Hostname: "baz",
21
+		},
22
+	}
23
+	nw := buildNetwork(t, map[string]any{
24
+		"id":          "1234567890",
25
+		"name":        "testnet",
26
+		"networkType": "bridge",
27
+		"enableIPv6":  false,
28
+	})
29
+	epSettings := &networktypes.EndpointSettings{
30
+		Aliases: []string{"myctr"},
31
+	}
32
+
33
+	if err := d.updateNetworkConfig(ctr, nw, epSettings, false); err != nil {
34
+		t.Fatal(err)
35
+	}
36
+
37
+	assert.Check(t, is.DeepEqual(epSettings.DNSNames, []string{"foobar", "myctr", "35de8003b19e", "baz"}))
38
+}
39
+
40
+func buildNetwork(t *testing.T, config map[string]any) *libnetwork.Network {
41
+	t.Helper()
42
+
43
+	b, err := json.Marshal(config)
44
+	if err != nil {
45
+		t.Fatal(err)
46
+	}
47
+
48
+	nw := &libnetwork.Network{}
49
+	if err := nw.UnmarshalJSON(b); err != nil {
50
+		t.Fatal(err)
51
+	}
52
+
53
+	return nw
54
+}
... ...
@@ -15,6 +15,8 @@ import (
15 15
 	"github.com/docker/docker/daemon/config"
16 16
 	"github.com/docker/docker/daemon/network"
17 17
 	"github.com/docker/docker/errdefs"
18
+	"github.com/docker/docker/internal/sliceutil"
19
+	"github.com/docker/docker/pkg/stringid"
18 20
 	"github.com/docker/go-connections/nat"
19 21
 )
20 22
 
... ...
@@ -27,6 +29,18 @@ func (daemon *Daemon) ContainerInspect(ctx context.Context, name string, size bo
27 27
 		return daemon.containerInspectPre120(ctx, name)
28 28
 	case versions.Equal(version, "1.20"):
29 29
 		return daemon.containerInspect120(name)
30
+	case versions.LessThan(version, "1.45"):
31
+		ctr, err := daemon.ContainerInspectCurrent(ctx, name, size)
32
+		if err != nil {
33
+			return nil, err
34
+		}
35
+
36
+		shortCID := stringid.TruncateID(ctr.ID)
37
+		for _, ep := range ctr.NetworkSettings.Networks {
38
+			ep.Aliases = sliceutil.Dedup(append(ep.Aliases, shortCID, ctr.Config.Hostname))
39
+		}
40
+
41
+		return ctr, nil
30 42
 	default:
31 43
 		return daemon.ContainerInspectCurrent(ctx, name, size)
32 44
 	}
... ...
@@ -793,10 +793,6 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e
793 793
 	var genericOptions = make(options.Generic)
794 794
 
795 795
 	nwName := n.Name()
796
-	defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
797
-	if c.NetworkSettings.IsAnonymousEndpoint || (nwName == defaultNetName && !serviceDiscoveryOnDefaultNetwork()) {
798
-		createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
799
-	}
800 796
 
801 797
 	if epConfig != nil {
802 798
 		if ipam := epConfig.IPAMConfig; ipam != nil {
... ...
@@ -822,9 +818,8 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e
822 822
 			createOptions = append(createOptions, libnetwork.CreateOptionIpam(ip, ip6, ipList, nil))
823 823
 		}
824 824
 
825
-		for _, alias := range epConfig.Aliases {
826
-			createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias))
827
-		}
825
+		createOptions = append(createOptions, libnetwork.CreateOptionDNSNames(epConfig.DNSNames))
826
+
828 827
 		for k, v := range epConfig.DriverOpts {
829 828
 			createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(options.Generic{k: v}))
830 829
 		}
... ...
@@ -24,7 +24,6 @@ type Settings struct {
24 24
 	Ports                  nat.PortMap
25 25
 	SecondaryIPAddresses   []networktypes.Address
26 26
 	SecondaryIPv6Addresses []networktypes.Address
27
-	IsAnonymousEndpoint    bool
28 27
 	HasSwarmEndpoint       bool
29 28
 }
30 29
 
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"github.com/containerd/log"
8 8
 	"github.com/docker/docker/api/types/events"
9 9
 	dockercontainer "github.com/docker/docker/container"
10
+	"github.com/docker/docker/daemon/network"
10 11
 	"github.com/docker/docker/errdefs"
11 12
 	"github.com/docker/docker/libnetwork"
12 13
 	"github.com/pkg/errors"
... ...
@@ -38,7 +39,6 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) {
38 38
 	defer container.Unlock()
39 39
 
40 40
 	oldName = container.Name
41
-	oldIsAnonymousEndpoint := container.NetworkSettings.IsAnonymousEndpoint
42 41
 
43 42
 	if oldName == newName {
44 43
 		return errdefs.InvalidParameter(errors.New("Renaming a container with the same name as its current name"))
... ...
@@ -62,12 +62,10 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) {
62 62
 	}
63 63
 
64 64
 	container.Name = newName
65
-	container.NetworkSettings.IsAnonymousEndpoint = false
66 65
 
67 66
 	defer func() {
68 67
 		if retErr != nil {
69 68
 			container.Name = oldName
70
-			container.NetworkSettings.IsAnonymousEndpoint = oldIsAnonymousEndpoint
71 69
 			daemon.reserveName(container.ID, oldName)
72 70
 			for k, v := range links {
73 71
 				daemon.containersReplica.ReserveName(oldName+k, v.ID)
... ...
@@ -101,7 +99,6 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) {
101 101
 	defer func() {
102 102
 		if retErr != nil {
103 103
 			container.Name = oldName
104
-			container.NetworkSettings.IsAnonymousEndpoint = oldIsAnonymousEndpoint
105 104
 			if err := container.CheckpointTo(daemon.containersReplica); err != nil {
106 105
 				log.G(context.TODO()).WithFields(log.Fields{
107 106
 					"containerID": container.ID,
... ...
@@ -118,10 +115,57 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) {
118 118
 			return err
119 119
 		}
120 120
 
121
-		err = sb.Rename(strings.TrimPrefix(container.Name, "/"))
122
-		if err != nil {
121
+		if err := sb.Rename(newName[1:]); err != nil {
123 122
 			return err
124 123
 		}
124
+		defer func() {
125
+			if retErr != nil {
126
+				if err := sb.Rename(oldName); err != nil {
127
+					log.G(context.TODO()).WithFields(log.Fields{
128
+						"sandboxID": sid,
129
+						"oldName":   oldName,
130
+						"newName":   newName,
131
+						"error":     err,
132
+					}).Errorf("failed to revert sandbox rename")
133
+				}
134
+			}
135
+		}()
136
+
137
+		for nwName, epConfig := range container.NetworkSettings.Networks {
138
+			nw, err := daemon.FindNetwork(nwName)
139
+			if err != nil {
140
+				return err
141
+			}
142
+
143
+			ep, err := nw.EndpointByID(epConfig.EndpointID)
144
+			if err != nil {
145
+				return err
146
+			}
147
+
148
+			oldDNSNames := make([]string, len(epConfig.DNSNames))
149
+			copy(oldDNSNames, epConfig.DNSNames)
150
+
151
+			epConfig.DNSNames = buildEndpointDNSNames(container, epConfig.Aliases)
152
+			if err := ep.UpdateDNSNames(epConfig.DNSNames); err != nil {
153
+				return err
154
+			}
155
+
156
+			defer func(ep *libnetwork.Endpoint, epConfig *network.EndpointSettings, oldDNSNames []string) {
157
+				if retErr == nil {
158
+					return
159
+				}
160
+
161
+				epConfig.DNSNames = oldDNSNames
162
+				if err := ep.UpdateDNSNames(epConfig.DNSNames); err != nil {
163
+					log.G(context.TODO()).WithFields(log.Fields{
164
+						"sandboxID": sid,
165
+						"oldName":   oldName,
166
+						"newName":   newName,
167
+						"error":     err,
168
+					}).Errorf("failed to revert DNSNames update")
169
+				}
170
+			}(ep, epConfig, oldDNSNames)
171
+		}
125 172
 	}
126 173
 
127 174
 	daemon.LogContainerEventWithAttributes(container, events.ActionRename, attributes)
... ...
@@ -68,6 +68,13 @@ keywords: "API, Docker, rcli, REST, documentation"
68 68
 * The `Container` and `ContainerConfig` fields in the `GET /images/{name}/json`
69 69
   response are deprecated and will no longer be included in API v1.45.
70 70
 * `GET /info` now includes `status` properties in `Runtimes`.
71
+* A new field named `DNSNames` and containing all non-fully qualified DNS names
72
+  a container takes on a specific network has been added to `GET /containers/{name:.*}/json`.
73
+* The `Aliases` field returned in calls to `GET /containers/{name:.*}/json` in v1.44  and older
74
+  versions contains the short container ID. This will change in the next API version,  v1.45.
75
+  Starting with that API version, this specific value will  be removed from the `Aliases` field
76
+  such that this field will reflect exactly the values originally submitted to the
77
+  `POST /containers/create` endpoint. The newly introduced `DNSNames` should now be used instead.
71 78
 
72 79
 ## v1.43 API changes
73 80
 
... ...
@@ -13,7 +13,8 @@ source hack/make/.integration-test-helpers
13 13
 # --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_attach_no_stream
14 14
 # TODO re-enable test_attach_no_stream after https://github.com/docker/docker-py/issues/2513 is resolved
15 15
 # TODO re-enable test_run_container_reading_socket_ws. It's reported in https://github.com/docker/docker-py/issues/1478, and we're getting that error in our tests.
16
-: "${PY_TEST_OPTIONS:=--junitxml=${DEST}/junit-report.xml --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_attach_no_stream --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_run_container_reading_socket_ws}"
16
+# TODO re-enable test_run_with_networking_config once this issue is fixed: https://github.com/moby/moby/pull/46853#issuecomment-1864679942.
17
+: "${PY_TEST_OPTIONS:=--junitxml=${DEST}/junit-report.xml --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_attach_no_stream --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_run_container_reading_socket_ws --deselect=tests/integration/models_containers_test.py::ContainerCollectionTest::test_run_with_networking_config}"
17 18
 
18 19
 # build --squash is not supported with containerd integration.
19 20
 if [ -n "$TEST_INTEGRATION_USE_SNAPSHOTTER" ]; then
... ...
@@ -5,6 +5,7 @@ import (
5 5
 
6 6
 	containertypes "github.com/docker/docker/api/types/container"
7 7
 	"github.com/docker/docker/api/types/network"
8
+	"github.com/docker/docker/client"
8 9
 	"github.com/docker/docker/integration/internal/container"
9 10
 	net "github.com/docker/docker/integration/internal/network"
10 11
 	"github.com/docker/docker/integration/internal/swarm"
... ...
@@ -13,13 +14,13 @@ import (
13 13
 	"gotest.tools/v3/skip"
14 14
 )
15 15
 
16
-func TestDockerNetworkConnectAlias(t *testing.T) {
16
+func TestDockerNetworkConnectAliasPreV144(t *testing.T) {
17 17
 	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
18 18
 	ctx := setupTest(t)
19 19
 
20 20
 	d := swarm.NewSwarm(ctx, t, testEnv)
21 21
 	defer d.Stop(t)
22
-	client := d.NewClientT(t)
22
+	client := d.NewClientT(t, client.WithVersion("1.43"))
23 23
 	defer client.Close()
24 24
 
25 25
 	name := t.Name() + "test-alias"
26 26
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package sliceutil
1
+
2
+func Dedup[T comparable](slice []T) []T {
3
+	keys := make(map[T]struct{})
4
+	out := make([]T, 0, len(slice))
5
+	for _, s := range slice {
6
+		if _, ok := keys[s]; !ok {
7
+			out = append(out, s)
8
+			keys[s] = struct{}{}
9
+		}
10
+	}
11
+	return out
12
+}
... ...
@@ -598,7 +598,7 @@ func (ep *Endpoint) deleteDriverInfoFromCluster() error {
598 598
 }
599 599
 
600 600
 func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error {
601
-	if len(ep.myAliases) == 0 && ep.isAnonymous() || ep.Iface() == nil || ep.Iface().Address() == nil {
601
+	if len(ep.dnsNames) == 0 || ep.Iface() == nil || ep.Iface().Address() == nil {
602 602
 		return nil
603 603
 	}
604 604
 
... ...
@@ -628,10 +628,8 @@ func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error {
628 628
 		return nil
629 629
 	}
630 630
 
631
-	name := ep.Name()
632
-	if ep.isAnonymous() {
633
-		name = ep.MyAliases()[0]
634
-	}
631
+	dnsNames := ep.getDNSNames()
632
+	primaryDNSName, dnsAliases := dnsNames[0], dnsNames[1:]
635 633
 
636 634
 	var ingressPorts []*PortConfig
637 635
 	if ep.svcID != "" {
... ...
@@ -640,24 +638,24 @@ func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error {
640 640
 		if n.ingress {
641 641
 			ingressPorts = ep.ingressPorts
642 642
 		}
643
-		if err := n.getController().addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), name, ep.virtualIP, ingressPorts, ep.svcAliases, ep.myAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil {
643
+		if err := n.getController().addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), primaryDNSName, ep.virtualIP, ingressPorts, ep.svcAliases, dnsAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil {
644 644
 			return err
645 645
 		}
646 646
 	} else {
647 647
 		// This is a container simply attached to an attachable network
648
-		if err := n.getController().addContainerNameResolution(n.ID(), ep.ID(), name, ep.myAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil {
648
+		if err := n.getController().addContainerNameResolution(n.ID(), ep.ID(), primaryDNSName, dnsAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil {
649 649
 			return err
650 650
 		}
651 651
 	}
652 652
 
653 653
 	buf, err := proto.Marshal(&EndpointRecord{
654
-		Name:            name,
654
+		Name:            primaryDNSName,
655 655
 		ServiceName:     ep.svcName,
656 656
 		ServiceID:       ep.svcID,
657 657
 		VirtualIP:       ep.virtualIP.String(),
658 658
 		IngressPorts:    ingressPorts,
659 659
 		Aliases:         ep.svcAliases,
660
-		TaskAliases:     ep.myAliases,
660
+		TaskAliases:     dnsAliases,
661 661
 		EndpointIP:      ep.Iface().Address().IP.String(),
662 662
 		ServiceDisabled: false,
663 663
 	})
... ...
@@ -676,7 +674,7 @@ func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error {
676 676
 }
677 677
 
678 678
 func (ep *Endpoint) deleteServiceInfoFromCluster(sb *Sandbox, fullRemove bool, method string) error {
679
-	if len(ep.myAliases) == 0 && ep.isAnonymous() {
679
+	if len(ep.dnsNames) == 0 {
680 680
 		return nil
681 681
 	}
682 682
 
... ...
@@ -699,10 +697,8 @@ func (ep *Endpoint) deleteServiceInfoFromCluster(sb *Sandbox, fullRemove bool, m
699 699
 		return nil
700 700
 	}
701 701
 
702
-	name := ep.Name()
703
-	if ep.isAnonymous() {
704
-		name = ep.MyAliases()[0]
705
-	}
702
+	dnsNames := ep.getDNSNames()
703
+	primaryDNSName, dnsAliases := dnsNames[0], dnsNames[1:]
706 704
 
707 705
 	// First update the networkDB then locally
708 706
 	if fullRemove {
... ...
@@ -720,12 +716,12 @@ func (ep *Endpoint) deleteServiceInfoFromCluster(sb *Sandbox, fullRemove bool, m
720 720
 			if n.ingress {
721 721
 				ingressPorts = ep.ingressPorts
722 722
 			}
723
-			if err := n.getController().rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), name, ep.virtualIP, ingressPorts, ep.svcAliases, ep.myAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster", true, fullRemove); err != nil {
723
+			if err := n.getController().rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), primaryDNSName, ep.virtualIP, ingressPorts, ep.svcAliases, dnsAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster", true, fullRemove); err != nil {
724 724
 				return err
725 725
 			}
726 726
 		} else {
727 727
 			// This is a container simply attached to an attachable network
728
-			if err := n.getController().delContainerNameResolution(n.ID(), ep.ID(), name, ep.myAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster"); err != nil {
728
+			if err := n.getController().delContainerNameResolution(n.ID(), ep.ID(), primaryDNSName, dnsAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster"); err != nil {
729 729
 				return err
730 730
 			}
731 731
 		}
... ...
@@ -47,7 +47,7 @@ func (sb *Sandbox) setupDefaultGW() error {
47 47
 		}
48 48
 	}
49 49
 
50
-	createOptions := []EndpointOption{CreateOptionAnonymous()}
50
+	createOptions := []EndpointOption{}
51 51
 
52 52
 	var gwName string
53 53
 	if len(sb.containerID) <= gwEPlen {
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"sync"
9 9
 
10 10
 	"github.com/containerd/log"
11
+	"github.com/docker/docker/internal/sliceutil"
11 12
 	"github.com/docker/docker/libnetwork/datastore"
12 13
 	"github.com/docker/docker/libnetwork/ipamapi"
13 14
 	"github.com/docker/docker/libnetwork/netlabel"
... ...
@@ -23,21 +24,22 @@ type EndpointOption func(ep *Endpoint)
23 23
 
24 24
 // Endpoint represents a logical connection between a network and a sandbox.
25 25
 type Endpoint struct {
26
-	name              string
27
-	id                string
28
-	network           *Network
29
-	iface             *EndpointInterface
30
-	joinInfo          *endpointJoinInfo
31
-	sandboxID         string
32
-	exposedPorts      []types.TransportPort
33
-	anonymous         bool
26
+	name         string
27
+	id           string
28
+	network      *Network
29
+	iface        *EndpointInterface
30
+	joinInfo     *endpointJoinInfo
31
+	sandboxID    string
32
+	exposedPorts []types.TransportPort
33
+	// dnsNames holds all the non-fully qualified DNS names associated to this endpoint. Order matters: first entry
34
+	// will be used for the PTR records associated to the endpoint's IPv4 and IPv6 addresses.
35
+	dnsNames          []string
34 36
 	disableResolution bool
35 37
 	generic           map[string]interface{}
36 38
 	prefAddress       net.IP
37 39
 	prefAddressV6     net.IP
38 40
 	ipamOptions       map[string]string
39 41
 	aliases           map[string]string
40
-	myAliases         []string
41 42
 	svcID             string
42 43
 	svcName           string
43 44
 	virtualIP         net.IP
... ...
@@ -64,9 +66,8 @@ func (ep *Endpoint) MarshalJSON() ([]byte, error) {
64 64
 		epMap["generic"] = ep.generic
65 65
 	}
66 66
 	epMap["sandbox"] = ep.sandboxID
67
-	epMap["anonymous"] = ep.anonymous
67
+	epMap["dnsNames"] = ep.dnsNames
68 68
 	epMap["disableResolution"] = ep.disableResolution
69
-	epMap["myAliases"] = ep.myAliases
70 69
 	epMap["svcName"] = ep.svcName
71 70
 	epMap["svcID"] = ep.svcID
72 71
 	epMap["virtualIP"] = ep.virtualIP.String()
... ...
@@ -156,8 +157,9 @@ func (ep *Endpoint) UnmarshalJSON(b []byte) (err error) {
156 156
 		}
157 157
 	}
158 158
 
159
+	var anonymous bool
159 160
 	if v, ok := epMap["anonymous"]; ok {
160
-		ep.anonymous = v.(bool)
161
+		anonymous = v.(bool)
161 162
 	}
162 163
 	if v, ok := epMap["disableResolution"]; ok {
163 164
 		ep.disableResolution = v.(bool)
... ...
@@ -192,7 +194,23 @@ func (ep *Endpoint) UnmarshalJSON(b []byte) (err error) {
192 192
 	ma, _ := json.Marshal(epMap["myAliases"])
193 193
 	var myAliases []string
194 194
 	json.Unmarshal(ma, &myAliases) //nolint:errcheck
195
-	ep.myAliases = myAliases
195
+
196
+	_, hasDNSNames := epMap["dnsNames"]
197
+	dn, _ := json.Marshal(epMap["dnsNames"])
198
+	var dnsNames []string
199
+	json.Unmarshal(dn, &dnsNames)
200
+	ep.dnsNames = dnsNames
201
+
202
+	// TODO(aker): remove this migration code in v27
203
+	if !hasDNSNames {
204
+		// The field dnsNames was introduced in v25.0. If we don't have it, the on-disk state was written by an older
205
+		// daemon, thus we need to populate dnsNames based off of myAliases and anonymous values.
206
+		if !anonymous {
207
+			myAliases = append([]string{ep.name}, myAliases...)
208
+		}
209
+		ep.dnsNames = sliceutil.Dedup(myAliases)
210
+	}
211
+
196 212
 	return nil
197 213
 }
198 214
 
... ...
@@ -210,7 +228,6 @@ func (ep *Endpoint) CopyTo(o datastore.KVObject) error {
210 210
 	dstEp.sandboxID = ep.sandboxID
211 211
 	dstEp.dbIndex = ep.dbIndex
212 212
 	dstEp.dbExists = ep.dbExists
213
-	dstEp.anonymous = ep.anonymous
214 213
 	dstEp.disableResolution = ep.disableResolution
215 214
 	dstEp.svcName = ep.svcName
216 215
 	dstEp.svcID = ep.svcID
... ...
@@ -240,8 +257,8 @@ func (ep *Endpoint) CopyTo(o datastore.KVObject) error {
240 240
 	dstEp.exposedPorts = make([]types.TransportPort, len(ep.exposedPorts))
241 241
 	copy(dstEp.exposedPorts, ep.exposedPorts)
242 242
 
243
-	dstEp.myAliases = make([]string, len(ep.myAliases))
244
-	copy(dstEp.myAliases, ep.myAliases)
243
+	dstEp.dnsNames = make([]string, len(ep.dnsNames))
244
+	copy(dstEp.dnsNames, ep.dnsNames)
245 245
 
246 246
 	dstEp.generic = options.Generic{}
247 247
 	for k, v := range ep.generic {
... ...
@@ -267,13 +284,6 @@ func (ep *Endpoint) Name() string {
267 267
 	return ep.name
268 268
 }
269 269
 
270
-func (ep *Endpoint) MyAliases() []string {
271
-	ep.mu.Lock()
272
-	defer ep.mu.Unlock()
273
-
274
-	return ep.myAliases
275
-}
276
-
277 270
 // Network returns the name of the network to which this endpoint is attached.
278 271
 func (ep *Endpoint) Network() string {
279 272
 	if ep.network == nil {
... ...
@@ -283,10 +293,15 @@ func (ep *Endpoint) Network() string {
283 283
 	return ep.network.name
284 284
 }
285 285
 
286
-func (ep *Endpoint) isAnonymous() bool {
286
+// getDNSNames returns a copy of the DNS names associated to this endpoint. The first entry is the one used for PTR
287
+// records.
288
+func (ep *Endpoint) getDNSNames() []string {
287 289
 	ep.mu.Lock()
288 290
 	defer ep.mu.Unlock()
289
-	return ep.anonymous
291
+
292
+	dnsNames := make([]string, len(ep.dnsNames))
293
+	copy(dnsNames, ep.dnsNames)
294
+	return dnsNames
290 295
 }
291 296
 
292 297
 // isServiceEnabled check if service is enabled on the endpoint
... ...
@@ -568,71 +583,52 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) {
568 568
 }
569 569
 
570 570
 func (ep *Endpoint) rename(name string) error {
571
-	var (
572
-		err error
573
-		ok  bool
574
-	)
571
+	ep.mu.Lock()
572
+	ep.name = name
573
+	ep.mu.Unlock()
575 574
 
576
-	n := ep.getNetwork()
577
-	if n == nil {
578
-		return fmt.Errorf("network not connected for ep %q", ep.name)
575
+	// Update the store with the updated name
576
+	if err := ep.getNetwork().getController().updateToStore(ep); err != nil {
577
+		return err
579 578
 	}
580 579
 
581
-	c := n.getController()
580
+	return nil
581
+}
582 582
 
583
+func (ep *Endpoint) UpdateDNSNames(dnsNames []string) error {
584
+	nw := ep.getNetwork()
585
+	c := nw.getController()
583 586
 	sb, ok := ep.getSandbox()
584 587
 	if !ok {
585
-		log.G(context.TODO()).Warnf("rename for %s aborted, sandbox %s is not anymore present", ep.ID(), ep.sandboxID)
588
+		log.G(context.TODO()).WithFields(log.Fields{
589
+			"sandboxID":  ep.sandboxID,
590
+			"endpointID": ep.ID(),
591
+		}).Warn("DNSNames update aborted, sandbox is not present anymore")
586 592
 		return nil
587 593
 	}
588 594
 
589 595
 	if c.isAgent() {
590
-		if err = ep.deleteServiceInfoFromCluster(sb, true, "rename"); err != nil {
591
-			return types.InternalErrorf("Could not delete service state for endpoint %s from cluster on rename: %v", ep.Name(), err)
596
+		if err := ep.deleteServiceInfoFromCluster(sb, true, "UpdateDNSNames"); err != nil {
597
+			return types.InternalErrorf("could not delete service state for endpoint %s from cluster on UpdateDNSNames: %v", ep.Name(), err)
592 598
 		}
593
-	} else {
594
-		n.updateSvcRecord(ep, false)
595
-	}
596
-
597
-	oldName := ep.name
598
-	oldAnonymous := ep.anonymous
599
-	ep.name = name
600
-	ep.anonymous = false
601 599
 
602
-	if c.isAgent() {
603
-		if err = ep.addServiceInfoToCluster(sb); err != nil {
604
-			return types.InternalErrorf("Could not add service state for endpoint %s to cluster on rename: %v", ep.Name(), err)
600
+		ep.dnsNames = dnsNames
601
+		if err := ep.addServiceInfoToCluster(sb); err != nil {
602
+			return types.InternalErrorf("could not add service state for endpoint %s to cluster on UpdateDNSNames: %v", ep.Name(), err)
605 603
 		}
606
-		defer func() {
607
-			if err != nil {
608
-				if err2 := ep.deleteServiceInfoFromCluster(sb, true, "rename"); err2 != nil {
609
-					log.G(context.TODO()).WithField("main error", err).WithError(err2).Debug("Error during cleanup due deleting service info from cluster while cleaning up due to other error")
610
-				}
611
-				ep.name = oldName
612
-				ep.anonymous = oldAnonymous
613
-				if err2 := ep.addServiceInfoToCluster(sb); err2 != nil {
614
-					log.G(context.TODO()).WithField("main error", err).WithError(err2).Debug("Error during cleanup due adding service to from cluster while cleaning up due to other error")
615
-				}
616
-			}
617
-		}()
618 604
 	} else {
619
-		n.updateSvcRecord(ep, true)
620
-		defer func() {
621
-			if err != nil {
622
-				n.updateSvcRecord(ep, false)
623
-				ep.name = oldName
624
-				ep.anonymous = oldAnonymous
625
-				n.updateSvcRecord(ep, true)
626
-			}
627
-		}()
605
+		nw.updateSvcRecord(ep, false)
606
+
607
+		ep.dnsNames = dnsNames
608
+		nw.updateSvcRecord(ep, true)
628 609
 	}
629 610
 
630 611
 	// Update the store with the updated name
631
-	if err = c.updateToStore(ep); err != nil {
612
+	if err := c.updateToStore(ep); err != nil {
632 613
 		return err
633 614
 	}
634 615
 
635
-	return err
616
+	return nil
636 617
 }
637 618
 
638 619
 func (ep *Endpoint) hasInterface(iName string) bool {
... ...
@@ -951,11 +947,11 @@ func CreateOptionDNS(dns []string) EndpointOption {
951 951
 	}
952 952
 }
953 953
 
954
-// CreateOptionAnonymous function returns an option setter for setting
955
-// this endpoint as anonymous
956
-func CreateOptionAnonymous() EndpointOption {
954
+// CreateOptionDNSNames specifies the list of (non fully qualified) DNS names associated to an endpoint. These will be
955
+// used to populate the embedded DNS server. Order matters: first name will be used to generate PTR records.
956
+func CreateOptionDNSNames(names []string) EndpointOption {
957 957
 	return func(ep *Endpoint) {
958
-		ep.anonymous = true
958
+		ep.dnsNames = names
959 959
 	}
960 960
 }
961 961
 
... ...
@@ -988,13 +984,6 @@ func CreateOptionService(name, id string, vip net.IP, ingressPorts []*PortConfig
988 988
 	}
989 989
 }
990 990
 
991
-// CreateOptionMyAlias function returns an option setter for setting endpoint's self alias
992
-func CreateOptionMyAlias(alias string) EndpointOption {
993
-	return func(ep *Endpoint) {
994
-		ep.myAliases = append(ep.myAliases, alias)
995
-	}
996
-}
997
-
998 991
 // CreateOptionLoadBalancer function returns an option setter for denoting the endpoint is a load balancer for a network
999 992
 func CreateOptionLoadBalancer() EndpointOption {
1000 993
 	return func(ep *Endpoint) {
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"encoding/json"
6 6
 	"fmt"
7 7
 	"net"
8
+	"reflect"
8 9
 	"runtime"
9 10
 	"testing"
10 11
 	"time"
... ...
@@ -191,7 +192,6 @@ func TestEndpointMarshalling(t *testing.T) {
191 191
 		name:      "Bau",
192 192
 		id:        "efghijklmno",
193 193
 		sandboxID: "ambarabaciccicocco",
194
-		anonymous: true,
195 194
 		iface: &EndpointInterface{
196 195
 			mac: []byte{11, 12, 13, 14, 15, 16},
197 196
 			addr: &net.IPNet{
... ...
@@ -205,6 +205,7 @@ func TestEndpointMarshalling(t *testing.T) {
205 205
 			v6PoolID:  "poolv6",
206 206
 			llAddrs:   lla,
207 207
 		},
208
+		dnsNames: []string{"test", "foobar", "baz"},
208 209
 	}
209 210
 
210 211
 	b, err := json.Marshal(e)
... ...
@@ -218,7 +219,7 @@ func TestEndpointMarshalling(t *testing.T) {
218 218
 		t.Fatal(err)
219 219
 	}
220 220
 
221
-	if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !compareEndpointInterface(e.iface, ee.iface) || e.anonymous != ee.anonymous {
221
+	if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !reflect.DeepEqual(e.dnsNames, ee.dnsNames) || !compareEndpointInterface(e.iface, ee.iface) {
222 222
 		t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v\nOriginal iface: %#v\nDecodediface:\n%#v", e, ee, e.iface, ee.iface)
223 223
 	}
224 224
 }
... ...
@@ -175,7 +175,7 @@ func (i *IpamInfo) UnmarshalJSON(data []byte) error {
175 175
 type Network struct {
176 176
 	ctrlr            *Controller
177 177
 	name             string
178
-	networkType      string
178
+	networkType      string // networkType is the name of the netdriver used by this network
179 179
 	id               string
180 180
 	created          time.Time
181 181
 	scope            string // network data scope
... ...
@@ -1302,8 +1302,6 @@ func (n *Network) updateSvcRecord(ep *Endpoint, isAdd bool) {
1302 1302
 	}
1303 1303
 
1304 1304
 	var ipv6 net.IP
1305
-	epName := ep.Name()
1306
-	myAliases := ep.MyAliases()
1307 1305
 	if iface.AddressIPv6() != nil {
1308 1306
 		ipv6 = iface.AddressIPv6().IP
1309 1307
 	}
... ...
@@ -1312,30 +1310,17 @@ func (n *Network) updateSvcRecord(ep *Endpoint, isAdd bool) {
1312 1312
 	if serviceID == "" {
1313 1313
 		serviceID = ep.ID()
1314 1314
 	}
1315
+
1316
+	dnsNames := ep.getDNSNames()
1315 1317
 	if isAdd {
1316
-		// If anonymous endpoint has an alias use the first alias
1317
-		// for ip->name mapping. Not having the reverse mapping
1318
-		// breaks some apps
1319
-		if ep.isAnonymous() {
1320
-			if len(myAliases) > 0 {
1321
-				n.addSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
1322
-			}
1323
-		} else {
1324
-			n.addSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
1325
-		}
1326
-		for _, alias := range myAliases {
1327
-			n.addSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord")
1318
+		for i, dnsName := range dnsNames {
1319
+			ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated.
1320
+			n.addSvcRecords(ep.ID(), dnsName, serviceID, iface.Address().IP, ipv6, ipMapUpdate, "updateSvcRecord")
1328 1321
 		}
1329 1322
 	} else {
1330
-		if ep.isAnonymous() {
1331
-			if len(myAliases) > 0 {
1332
-				n.deleteSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
1333
-			}
1334
-		} else {
1335
-			n.deleteSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord")
1336
-		}
1337
-		for _, alias := range myAliases {
1338
-			n.deleteSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord")
1323
+		for i, dnsName := range dnsNames {
1324
+			ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated.
1325
+			n.deleteSvcRecords(ep.ID(), dnsName, serviceID, iface.Address().IP, ipv6, ipMapUpdate, "updateSvcRecord")
1339 1326
 		}
1340 1327
 	}
1341 1328
 }
... ...
@@ -1374,6 +1359,7 @@ func delNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID strin
1374 1374
 	})
1375 1375
 }
1376 1376
 
1377
+// TODO(aker): remove ipMapUpdate param and add a proper method dedicated to update PTR records.
1377 1378
 func (n *Network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP, ipMapUpdate bool, method string) {
1378 1379
 	// Do not add service names for ingress network as this is a
1379 1380
 	// routing only network
... ...
@@ -2176,10 +2162,6 @@ func (n *Network) createLoadBalancerSandbox() (retErr error) {
2176 2176
 		CreateOptionIpam(n.loadBalancerIP, nil, nil, nil),
2177 2177
 		CreateOptionLoadBalancer(),
2178 2178
 	}
2179
-	if n.hasLoadBalancerEndpoint() && !n.ingress {
2180
-		// Mark LB endpoints as anonymous so they don't show up in DNS
2181
-		epOptions = append(epOptions, CreateOptionAnonymous())
2182
-	}
2183 2179
 	ep, err := n.createEndpoint(endpointName, epOptions...)
2184 2180
 	if err != nil {
2185 2181
 		return err