diff --git a/libpod/container.go b/libpod/container.go index f54b8f35fd..fb9ca11800 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -666,8 +666,12 @@ func (c *Container) RuntimeName() string { // Runtime spec accessors // Unlocked -// Hostname gets the container's hostname -func (c *Container) Hostname() string { +// hostname determines the container's hostname. +// If 'network' is true and the container isn't running in a +// private UTS namespoace, an empty string will be returned +// instead of the host's hostname because we never want to +// send the host's hostname to a DHCP or DNS server. +func (c *Container) hostname(network bool) string { if c.config.UTSNsCtr != "" { utsNsCtr, err := c.runtime.GetContainer(c.config.UTSNsCtr) if err != nil { @@ -677,25 +681,66 @@ func (c *Container) Hostname() string { } return utsNsCtr.Hostname() } + if c.config.Spec.Hostname != "" { return c.config.Spec.Hostname } - // if the container is not running in a private UTS namespace, - // return the host's hostname. + // If the container is not running in a private UTS namespace, + // return the host's hostname unless 'network' is true in which + // case we return an empty string. privateUTS := c.hasPrivateUTS() if !privateUTS { hostname, err := os.Hostname() if err == nil { + if network { + return "" + } return hostname } + logrus.Errorf("unable to get host's hostname for container %s: %v", c.ID(), err) + return "" + } + + // If container_name_as_hostname is set in the CONTAINERS table in + // containers.conf, use a sanitized version of the container's name + // as the hostname. Since the container name must already match + // the set '[a-zA-Z0-9][a-zA-Z0-9_.-]*', we can just remove any + // underscores and limit it to 253 characters to make it a valid + // hostname. + if c.runtime.config.Containers.ContainerNameAsHostName { + sanitizedHostname := strings.ReplaceAll(c.Name(), "_", "") + if len(sanitizedHostname) <= 253 { + return sanitizedHostname + } + return sanitizedHostname[:253] } + + // Otherwise use the container's short ID as the hostname. if len(c.ID()) < 11 { return c.ID() } return c.ID()[:12] } +// Hostname gets the container's hostname +func (c *Container) Hostname() string { + return c.hostname(false) +} + +// If the container isn't running in a private UTS namespace, Hostname() +// will return the host's hostname as the container's hostname. If netavark +// were to try and obtain a DHCP lease with the host's hostname in an environment +// where DDNS was active, bad things could happen. NetworkHostname() on the +// other hand, will return an empty string if the container isn't running +// in a private UTS namespace. +// +// This function should only be used to populate the ContainerHostname member +// of the common.libnetwork.types.NetworkOptions struct. +func (c *Container) NetworkHostname() string { + return c.hostname(true) +} + // WorkingDir returns the containers working dir func (c *Container) WorkingDir() string { if c.config.Spec.Process != nil { diff --git a/libpod/networking_common.go b/libpod/networking_common.go index bac0d48622..b23308fa7c 100644 --- a/libpod/networking_common.go +++ b/libpod/networking_common.go @@ -55,9 +55,10 @@ func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOpt nameservers = append(nameservers, ip.String()) } opts := types.NetworkOptions{ - ContainerID: c.config.ID, - ContainerName: getNetworkPodName(c), - DNSServers: nameservers, + ContainerID: c.config.ID, + ContainerName: getNetworkPodName(c), + DNSServers: nameservers, + ContainerHostname: c.NetworkHostname(), } opts.PortMappings = c.convertPortMappings() diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index e86db89622..87826c2088 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -763,4 +763,106 @@ var _ = Describe("Verify podman containers.conf usage", func() { Expect(inspect.OutputToString()).Should(Equal(mode)) } }) + + startContainer := func(params ...string) string { + args := []string{"create"} + for _, param := range params { + if param == "--name" { + args = append(args, "--replace") + break + } + } + args = append(args, params...) + args = append(args, ALPINE, "true") + + result := podmanTest.Podman(args) + result.WaitWithDefaultTimeout() + Expect(result).Should(ExitCleanly()) + containerID := result.OutputToString() + + return containerID + } + + getContainerConfig := func(containerID string, formatParam string) string { + inspect := podmanTest.Podman([]string{"inspect", "--format", formatParam, containerID}) + inspect.WaitWithDefaultTimeout() + value := inspect.OutputToString() + return value + } + + It("podman containers.conf container_name_as_hostname", func() { + + // With default containers.conf + + // Start container with no options + containerID := startContainer() + hostname := getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should be the first 12 characters of the containerID + Expect(hostname).To(Equal(containerID[:12])) + + // Start container with name + containerID = startContainer("--name", "cname1") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should still be the first 12 characters of the containerID + Expect(hostname).To(Equal(containerID[:12])) + + // Start container with just hostname + containerID = startContainer("--hostname", "cname1.dev") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should now be "cname1.dev" + Expect(hostname).To(Equal("cname1.dev")) + + // Start container with name and hostname + containerID = startContainer("--name", "cname1", "--hostname", "cname1.dev") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should now be "cname1.dev" + Expect(hostname).To(Equal("cname1.dev")) + + // Create containers.conf override with container_name_as_hostname=true + conffile := filepath.Join(podmanTest.TempDir, "container.conf") + err := os.WriteFile(conffile, []byte("[containers]\ncontainer_name_as_hostname=true\n"), 0755) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF_OVERRIDE", conffile) + + // Start container with no options + containerID = startContainer() + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + name := getContainerConfig(containerID, "{{ .Name }}") + // Hostname should be the auto generated container name with '_' removed + Expect(hostname).To(Equal(strings.ReplaceAll(name, "_", ""))) + + // Start container with name + containerID = startContainer("--name", "cname1") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should be the container name + Expect(hostname).To(Equal("cname1")) + + // Start container with name containing '_' + containerID = startContainer("--name", "cname1_2_3") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should be the set container name with all '_' removed + Expect(hostname).To(Equal("cname123")) + + // Start container with just hostname + containerID = startContainer("--hostname", "cname1.dev") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should now be "cname1.dev" + Expect(hostname).To(Equal("cname1.dev")) + + // Start container with name and hostname + containerID = startContainer("--name", "cname1", "--hostname", "cname1.dev") + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + // Hostname should still be "cname1.dev" + Expect(hostname).To(Equal("cname1.dev")) + + // Start container with name = 260 characters + longHostname := "cnabcdefghijklmnopqrstuvwxyz1234567890.abcdefghijklmnopqrstuvwxyz1234567890.abcdefghijklmnopqrstuvwxyz1234567890.abcdefghijklmnopqrstuvwxyz1234567890.abcdefghijklmnopqrstuvwxyz1234567890.abcdefghijklmnopqrstuvwxyz1234567890.abcdefghijklmnopqrstuvwxyz1234567890" + containerID = startContainer("--name", longHostname) + hostname = getContainerConfig(containerID, "{{ .Config.Hostname }}") + name = getContainerConfig(containerID, "{{ .Name }}") + // Double check that name actually got set correctly + Expect(name).To(Equal(longHostname)) + // Hostname should be the container name truncated to 253 characters + Expect(hostname).To(Equal(name[:253])) + }) })