diff --git a/cmd/nerdctl/container/container_run_network_linux_test.go b/cmd/nerdctl/container/container_run_network_linux_test.go index 8d020905eb8..0cb2b6f729e 100644 --- a/cmd/nerdctl/container/container_run_network_linux_test.go +++ b/cmd/nerdctl/container/container_run_network_linux_test.go @@ -24,7 +24,6 @@ import ( "os/exec" "path/filepath" "regexp" - "runtime" "strings" "testing" "time" @@ -513,35 +512,279 @@ func TestRunNetworkHost2613(t *testing.T) { base.Cmd("run", "--rm", "--add-host", "foo:1.2.3.4", testutil.CommonImage, "getent", "hosts", "foo").AssertOutExactly("1.2.3.4 foo foo\n") } -func TestSharedNetworkStack(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("--network=container: only supports linux now") - } - base := testutil.NewBase(t) +func TestSharedNetworkSetup(t *testing.T) { + nerdtest.Setup() + testCase := &test.Case{ + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("containerName1", data.Identifier("-container1")) + containerName1 := data.Get("containerName1") + helpers.Ensure("run", "-d", "--name", containerName1, + testutil.NginxAlpineImage) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("-container1")) + }, + SubTests: []*test.Case{ + { + Description: "Test network is shared", + NoParallel: true, // The validation involves starting of the main container: container1 + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, + "--network=container:"+data.Get("containerName1"), + testutil.NginxAlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + containerName2 := data.Identifier() + assert.Assert(t, strings.Contains(helpers.Capture("exec", containerName2, "wget", "-qO-", "http://127.0.0.1:80"), testutil.NginxAlpineIndexHTMLSnippet), info) + helpers.Ensure("restart", data.Get("containerName1")) + helpers.Ensure("stop", "--time=1", containerName2) + helpers.Ensure("start", containerName2) + assert.Assert(t, strings.Contains(helpers.Capture("exec", containerName2, "wget", "-qO-", "http://127.0.0.1:80"), testutil.NginxAlpineIndexHTMLSnippet), info) + }, + } + }, + }, + { + Description: "Test both have same /etc/hosts", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, + "--network=container:"+data.Get("containerName1"), + testutil.NginxAlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + containerName1 := data.Get("containerName1") + containerName2 := data.Identifier() + assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("exec", containerName1, "cat", "/etc/hosts")), + strings.TrimSpace(helpers.Capture("exec", containerName2, "cat", "/etc/hosts"))) == 0, info) + }, + } + }, + }, + { + Description: "Test both have same /etc/resolv.conf", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "--name", containerName2, + "--network=container:"+data.Get("containerName1"), + testutil.AlpineImage, "cat", "/etc/resolv.conf") + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + containerName1 := data.Get("containerName1") + assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("exec", containerName1, "cat", "/etc/resolv.conf")), strings.TrimSpace(stdout)) == 0, info) + }, + } + }, + }, + { + Description: "Test uts is supported in shared network", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, "--uts", "host", + "--network=container:"+data.Get("containerName1"), + testutil.AlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + } + }, + }, + { + Description: "Test dns is not supported", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, "--dns", "0.1.2.3", + "--network=container:"+data.Get("containerName1"), + testutil.AlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + if nerdtest.IsDocker() { + return &test.Expected{ + ExitCode: 125, + } - containerName := testutil.Identifier(t) - defer base.Cmd("rm", "-f", containerName).AssertOK() - base.Cmd("run", "-d", "--name", containerName, - testutil.NginxAlpineImage).AssertOK() - base.EnsureContainerStarted(containerName) + } + return &test.Expected{ + ExitCode: 1, + } + }, + }, + { + Description: "Test dns options is not supported", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "--name", containerName2, "--dns-option", "attempts:5", + "--network=container:"+data.Get("containerName1"), + testutil.AlpineImage, "cat", "/etc/resolv.conf") + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + // The Option doesnt throw an error but is never inserted to the resolv.conf + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + assert.Assert(t, !strings.Contains(stdout, "attempts:5"), info) + }, + } + }, + }, + { + Description: "Test publish is not supported", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, "--publish", "80:8080", + "--network=container:"+data.Get("containerName1"), + testutil.AlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + if nerdtest.IsDocker() { + return &test.Expected{ + ExitCode: 125, + } + + } + return &test.Expected{ + ExitCode: 1, + } + }, + }, + { + Description: "Test hostname is not supported", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, "--hostname", "test", + "--network=container:"+data.Get("containerName1"), + testutil.AlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + if nerdtest.IsDocker() { + return &test.Expected{ + ExitCode: 125, + } - containerNameJoin := testutil.Identifier(t) + "-network" - defer base.Cmd("rm", "-f", containerNameJoin).AssertOK() - base.Cmd("run", - "-d", - "--name", containerNameJoin, - "--network=container:"+containerName, - testutil.CommonImage, - "sleep", nerdtest.Infinity).AssertOK() - - base.Cmd("exec", containerNameJoin, "wget", "-qO-", "http://127.0.0.1:80"). - AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet) - - base.Cmd("restart", containerName).AssertOK() - base.Cmd("stop", "--time=1", containerNameJoin).AssertOK() - base.Cmd("start", containerNameJoin).AssertOK() - base.Cmd("exec", containerNameJoin, "wget", "-qO-", "http://127.0.0.1:80"). - AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet) + } + return &test.Expected{ + ExitCode: 1, + } + }, + }, + }, + } + testCase.Run(t) +} + +func TestSharedNetworkWithNone(t *testing.T) { + nerdtest.Setup() + testCase := &test.Case{ + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("containerName1", data.Identifier("-container1")) + containerName1 := data.Get("containerName1") + helpers.Ensure("run", "-d", "--name", containerName1, "--network", "none", + testutil.NginxAlpineImage) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Get("containerName1")) + }, + SubTests: []*test.Case{ + { + Description: "Test both have same /etc/hosts", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName2 := data.Identifier() + cmd := helpers.Command() + cmd.WithArgs("run", "-d", "--name", containerName2, + "--network=container:"+data.Get("containerName1"), + testutil.NginxAlpineImage) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + containerName1 := data.Get("containerName1") + containerName2 := data.Identifier() + assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("exec", containerName1, "cat", "/etc/hosts")), + strings.TrimSpace(helpers.Capture("exec", containerName2, "cat", "/etc/hosts"))) == 0, info) + }, + } + }, + }, + { + Description: "Test both have same /etc/resolv.conf", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + containerName1 := data.Get("containerName1") + cmd := helpers.Command() + cmd.WithArgs("run", "--name", data.Identifier(), + "--network=container:"+containerName1, + testutil.AlpineImage, "cat", "/etc/resolv.conf") + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + containerName1 := data.Get("containerName1") + fmt.Println(helpers.Capture("exec", containerName1, "cat", "/etc/hosts")) + assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("exec", containerName1, "cat", "/etc/resolv.conf")), strings.TrimSpace(stdout)) == 0, info) + }, + } + }, + }, + }, + } + testCase.Run(t) } func TestRunContainerInExistingNetNS(t *testing.T) { @@ -669,6 +912,8 @@ func TestHostsFileMounts(t *testing.T) { "sh", "-euxc", "echo >> /etc/hosts").AssertOK() base.Cmd("run", "--rm", "-v", "/etc/hosts:/etc/hosts", "--network", "host", testutil.CommonImage, "sh", "-euxc", "head -n -1 /etc/hosts > temp && cat temp > /etc/hosts").AssertOK() + base.Cmd("run", "--rm", "--network", "none", testutil.CommonImage, + "sh", "-euxc", "echo >> /etc/hosts").AssertOK() base.Cmd("run", "--rm", testutil.CommonImage, "sh", "-euxc", "echo >> /etc/resolv.conf").AssertOK() @@ -681,6 +926,8 @@ func TestHostsFileMounts(t *testing.T) { "sh", "-euxc", "echo >> /etc/resolv.conf").AssertOK() base.Cmd("run", "--rm", "-v", "/etc/resolv.conf:/etc/resolv.conf", "--network", "host", testutil.CommonImage, "sh", "-euxc", "head -n -1 /etc/resolv.conf > temp && cat temp > /etc/resolv.conf").AssertOK() + base.Cmd("run", "--rm", "--network", "host", testutil.CommonImage, + "sh", "-euxc", "echo >> /etc/resolv.conf").AssertOK() } func TestRunContainerWithStaticIP6(t *testing.T) { @@ -752,3 +999,112 @@ func TestRunContainerWithStaticIP6(t *testing.T) { }) } } + +func TestNoneNetworkHostName(t *testing.T) { + nerdtest.Setup() + testCase := &test.Case{ + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("containerName1", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "-d", "--name", data.Identifier(), "--network", "none", testutil.NginxAlpineImage) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + hostname := stdout + if len(hostname) > 12 { + hostname = hostname[:12] + } + assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("exec", data.Identifier(), "cat", "/etc/hostname")), hostname) == 0, info) + }, + } + }, + } + testCase.Run(t) +} + +func TestHostNetworkHostName(t *testing.T) { + nerdtest.Setup() + testCase := &test.Case{ + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("containerName1", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Custom("cat", "/etc/hostname") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + hostname := stdout + assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("run", "--name", data.Identifier(), "--network", "host", testutil.AlpineImage, "cat", "/etc/hostname")), strings.TrimSpace(hostname)) == 0, info) + }, + } + }, + } + testCase.Run(t) +} + +func TestNoneNetworkDnsConfigs(t *testing.T) { + nerdtest.Setup() + testCase := &test.Case{ + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("containerName1", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "-d", "--name", data.Identifier(), "--network", "none", "--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5", testutil.NginxAlpineImage) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "0.1.2.3"), info) + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "example.com"), info) + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "attempts:5"), info) + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "timeout:3"), info) + + }, + } + }, + } + testCase.Run(t) +} + +func TestHostNetworkDnsConfigs(t *testing.T) { + nerdtest.Setup() + testCase := &test.Case{ + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("containerName1", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "-d", "--name", data.Identifier(), "--network", "host", "--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5", testutil.NginxAlpineImage) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "0.1.2.3"), info) + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "example.com"), info) + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "attempts:5"), info) + assert.Assert(t, strings.Contains(helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf"), "timeout:3"), info) + + }, + } + }, + } + testCase.Run(t) +} diff --git a/pkg/containerutil/container_network_manager.go b/pkg/containerutil/container_network_manager.go index 23852f56f58..8a33cfeb352 100644 --- a/pkg/containerutil/container_network_manager.go +++ b/pkg/containerutil/container_network_manager.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "os" "path/filepath" "reflect" @@ -42,6 +43,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/mountutil" "github.com/containerd/nerdctl/v2/pkg/netutil" "github.com/containerd/nerdctl/v2/pkg/netutil/nettype" + "github.com/containerd/nerdctl/v2/pkg/resolvconf" "github.com/containerd/nerdctl/v2/pkg/rootlessutil" "github.com/containerd/nerdctl/v2/pkg/strutil" ) @@ -86,6 +88,48 @@ func withCustomHosts(src string) func(context.Context, oci.Client, *containers.C } } +func loadDNSConfig(dns *[]string, dnsSearch *[]string, dnsOptions *[]string) error { + if len(*dns) == 0 || len(*dnsSearch) == 0 || len(*dnsOptions) == 0 { + conf, err := resolvconf.Get() + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + // if resolvConf file does't exist, using default resolvers + conf = &resolvconf.File{} + log.L.WithError(err).Debugf("resolvConf file doesn't exist on host") + } + conf, err = resolvconf.FilterResolvDNS(conf.Content, true) + if err != nil { + return err + } + if len(*dns) == 0 { + *dns = resolvconf.GetNameservers(conf.Content, resolvconf.IPv4) + } + if len(*dnsSearch) == 0 { + *dnsSearch = resolvconf.GetSearchDomains(conf.Content) + } + if len(*dnsOptions) == 0 { + *dnsOptions = resolvconf.GetOptions(conf.Content) + } + } + return nil +} + +func getDNSConfig(netOpts types.NetworkOptions, dns *[]string, dnsSearch *[]string, dnsOptions *[]string) { + + if len(netOpts.DNSServers) > 0 { + *dns = netOpts.DNSServers + } + if len(netOpts.DNSSearchDomains) > 0 { + *dnsSearch = netOpts.DNSSearchDomains + } + if len(netOpts.DNSResolvConfOptions) > 0 { + *dnsOptions = netOpts.DNSResolvConfOptions + } + +} + // NetworkOptionsManager types.NetworkOptionsManager is an interface for reading/setting networking // options for containers based on the provided command flags. type NetworkOptionsManager interface { @@ -157,31 +201,164 @@ func (m *noneNetworkManager) NetworkOptions() types.NetworkOptions { // VerifyNetworkOptions Verifies that the internal network settings are correct. func (m *noneNetworkManager) VerifyNetworkOptions(_ context.Context) error { - // No options to verify if no network settings are provided. + err := validateUtsSettings(m.netOpts) + if err != nil { + return err + } + return nil } // SetupNetworking Performs setup actions required for the container with the given ID. -func (m *noneNetworkManager) SetupNetworking(_ context.Context, _ string) error { +func (m *noneNetworkManager) SetupNetworking(ctx context.Context, containerID string) error { + // Retrieve the container + container, err := m.client.ContainerService().Get(ctx, containerID) + if err != nil { + return err + } + + // Get the dataStore + dataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address) + if err != nil { + return err + } + + // Get the hostsStore + hs, err := hostsstore.New(dataStore, container.Labels[labels.Namespace]) + if err != nil { + return err + } + + // Get extra-hosts + extraHostsJSON := container.Labels[labels.ExtraHosts] + var extraHosts []string + if err = json.Unmarshal([]byte(extraHostsJSON), &extraHosts); err != nil { + return err + } + + hosts := make(map[string]string) + for _, host := range extraHosts { + if v := strings.SplitN(host, ":", 2); len(v) == 2 { + hosts[v[0]] = v[1] + } + } + + // Prep the meta + hsMeta := hostsstore.Meta{ + ID: container.ID, + Hostname: container.Labels[labels.Hostname], + ExtraHosts: hosts, + Name: container.Labels[labels.Name], + } + + // Save the meta information + if err = hs.Acquire(hsMeta); err != nil { + return err + } + return nil } // CleanupNetworking Performs any required cleanup actions for the given container. // Should only be called to revert any setup steps performed in SetupNetworking. -func (m *noneNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error { +func (m *noneNetworkManager) CleanupNetworking(ctx context.Context, container containerd.Container) error { + // Get the dataStore + dataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address) + if err != nil { + return err + } + + // Get labels + lbls, err := container.Labels(ctx) + if err != nil { + return err + } + + // Get the hostsStore + hs, err := hostsstore.New(dataStore, lbls[labels.Namespace]) + if err != nil { + return err + } + + // Release + if err = hs.Release(container.ID()); err != nil { + return err + } return nil } // InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container. func (m *noneNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) { - return m.netOpts, nil + opts := m.netOpts + // Cannot have a MAC address in host networking mode. + opts.MACAddress = "" + return opts, nil } // ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent // the network specs which need to be applied to the container with the given ID. -func (m *noneNetworkManager) ContainerNetworkingOpts(_ context.Context, _ string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) { - // No options to return if no network settings are provided. - return []oci.SpecOpts{}, []containerd.NewContainerOpts{}, nil +func (m *noneNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) { + dataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address) + if err != nil { + return nil, nil, err + } + + stateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID) + if err != nil { + return nil, nil, err + } + + resolvConfPath := filepath.Join(stateDir, "resolv.conf") + dns := []string{} + dnsSearch := []string{} + dnsOptions := []string{} + + getDNSConfig(m.netOpts, &dns, &dnsSearch, &dnsOptions) + err = loadDNSConfig(&dns, &dnsSearch, &dnsOptions) + if err != nil { + return nil, nil, err + } + _, err = resolvconf.Build(resolvConfPath, dns, dnsSearch, dnsOptions) + if err != nil { + return nil, nil, err + } + + hs, err := hostsstore.New(dataStore, m.globalOptions.Namespace) + if err != nil { + return nil, nil, err + } + + etcHostsPath, err := hs.AllocHostsFile(containerID, []byte{}) + if err != nil { + return nil, nil, err + } + + specs := []oci.SpecOpts{ + withDedupMounts("/etc/hosts", withCustomHosts(etcHostsPath)), + withDedupMounts("/etc/resolv.conf", withCustomResolvConf(resolvConfPath)), + } + + // `/etc/hostname` does not exist on FreeBSD + if runtime.GOOS == "linux" { + // If no hostname is set, default to first 12 characters of the container ID. + hostname := m.netOpts.Hostname + if hostname == "" { + hostname = containerID + if len(hostname) > 12 { + hostname = hostname[0:12] + } + } + m.netOpts.Hostname = hostname + + hostnameOpts, err := writeEtcHostnameForContainer(m.globalOptions, m.netOpts.Hostname, containerID) + if err != nil { + return nil, nil, err + } + if hostnameOpts != nil { + specs = append(specs, hostnameOpts...) + } + } + return specs, []containerd.NewContainerOpts{}, nil } // types.NetworkOptionsManager implementation for container networking settings. @@ -203,8 +380,9 @@ func (m *containerNetworkManager) VerifyNetworkOptions(_ context.Context) error return errors.New("container networking mode is currently only supported on Linux") } - if len(m.netOpts.NetworkSlice) > 1 { - return errors.New("conflicting options: only one network specification is allowed when using '--network=container:'") + err := validateUtsSettings(m.netOpts) + if err != nil { + return err } // Note that mac-address is accepted, though it is a no-op @@ -330,6 +508,10 @@ func (m *containerNetworkManager) ContainerNetworkingOpts(ctx context.Context, _ return nil, nil, err } hostname := s.Hostname + // if Utsnamespace is set we should not set the hostname + if m.netOpts.UTSNamespace == UtsNamespaceHost { + hostname = "" + } netNSPath, err := ContainerNetNSPath(ctx, container) if err != nil { @@ -524,7 +706,21 @@ func (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containe } resolvConfPath := filepath.Join(stateDir, "resolv.conf") - copyFileContent("/etc/resolv.conf", resolvConfPath) + + dns := []string{} + dnsSearch := []string{} + dnsOptions := []string{} + + getDNSConfig(m.netOpts, &dns, &dnsSearch, &dnsOptions) + err = loadDNSConfig(&dns, &dnsSearch, &dnsOptions) + if err != nil { + return nil, nil, err + } + + _, err = resolvconf.Build(resolvConfPath, dns, dnsSearch, dnsOptions) + if err != nil { + return nil, nil, err + } hs, err := hostsstore.New(dataStore, m.globalOptions.Namespace) if err != nil { @@ -553,7 +749,7 @@ func (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containe } // `/etc/hostname` does not exist on FreeBSD - if runtime.GOOS == "linux" && m.netOpts.UTSNamespace != UtsNamespaceHost { + if runtime.GOOS == "linux" { hostname := m.netOpts.Hostname if hostname == "" { // Hostname by default should be the host hostname