Repository: opencontainers/runc
Pull request #3600 (2022-09-11 00:49:43)

Implement to set a domainname
https://github.com/opencontainers/runtime-spec/pull/1156

AkihiroSuda: Can we have an integration test too?
utam0k: @AkihiroSuda @kolyshkin Thanks for your great review 🙇 I have updated it. Please retake a look at it.
utam0k: @kolyshkin Sorry for the late. May I ask you to review again?
utam0k: hmm... It seems that the integration test on cent-os7 failed.
```
# stat: cannot read file system information for ‘/sys/fs/cgroup/unified’: No such file or directory
# /home/runc/tests/integration/helpers.bash: line 76: jq: command not found
# /home/runc/tests/integration/helpers.bash: line 76: jq: command not found
```
kolyshkin: > hmm... It seems that the integration test on cent-os7 failed.

This is fixed by https://github.com/opencontainers/runc/pull/3614, please rebase.

Also PTAL https://github.com/containers/crun/pull/1017
utam0k: > Also PTAL https://github.com/containers/crun/pull/1017

Do I have to address it in this PR?
kolyshkin: > Do I have to address it in this PR?

Ideally, yes. At the very least please open an issue.
utam0k: > > Do I have to address it in this PR?
> 
> Ideally, yes. At the very least please open an issue.

I think this PR has been going on too long, so I'll respond with another PR. I will set up an issue later.
utam0k: @kolyshkin Sorry for the poor setup on my first contribution. Thanks for the kind review.
AkihiroSuda: LGTM, but could you squash commits?
AkihiroSuda: @kolyshkin LGTY?
AkihiroSuda: Please squash commits 
utam0k: > Please squash commits

✅
utam0k: > Please squash commits

@AkihiroSuda  ✅
AkihiroSuda: Sorry, seems to need rebase to pass the CI

utam0k: > Sorry, seems to need rebase to pass the CI

@AkihiroSuda Thanks for letting me know. How's this?
AkihiroSuda: @kolyshkin LGTY?

Patch:
diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go
index 18955cf9..d43ea786 100644
--- a/libcontainer/configs/config.go
+++ b/libcontainer/configs/config.go
@@ -119,6 +119,9 @@ type Config struct {
 	// Hostname optionally sets the container's hostname if provided
 	Hostname string `json:"hostname"`
 
+	// Domainname optionally sets the container's domainname if provided
+	Domainname string `json:"domainname"`
+
 	// Namespaces specifies the container's namespaces that it should setup when cloning the init process
 	// If a namespace is not provided that namespace is shared from the container's parent process
 	Namespaces Namespaces `json:"namespaces"`
diff --git a/libcontainer/configs/validate/validator.go b/libcontainer/configs/validate/validator.go
index 2027a372..483e7a2f 100644
--- a/libcontainer/configs/validate/validator.go
+++ b/libcontainer/configs/validate/validator.go
@@ -23,7 +23,7 @@ func Validate(config *configs.Config) error {
 		cgroupsCheck,
 		rootfs,
 		network,
-		hostname,
+		uts,
 		security,
 		namespaces,
 		sysctl,
@@ -75,10 +75,13 @@ func network(config *configs.Config) error {
 	return nil
 }
 
-func hostname(config *configs.Config) error {
+func uts(config *configs.Config) error {
 	if config.Hostname != "" && !config.Namespaces.Contains(configs.NEWUTS) {
 		return errors.New("unable to set hostname without a private UTS namespace")
 	}
+	if config.Domainname != "" && !config.Namespaces.Contains(configs.NEWUTS) {
+		return errors.New("unable to set domainname without a private UTS namespace")
+	}
 	return nil
 }
 
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index 345c467d..809424a9 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -353,6 +353,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
 		NoPivotRoot:     opts.NoPivotRoot,
 		Readonlyfs:      spec.Root.Readonly,
 		Hostname:        spec.Hostname,
+		Domainname:      spec.Domainname,
 		Labels:          append(labels, "bundle="+cwd),
 		NoNewKeyring:    opts.NoNewKeyring,
 		RootlessEUID:    opts.RootlessEUID,
diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go
index 32de78eb..a4c01953 100644
--- a/libcontainer/standard_init_linux.go
+++ b/libcontainer/standard_init_linux.go
@@ -126,6 +126,11 @@ func (l *linuxStandardInit) Init() error {
 			return &os.SyscallError{Syscall: "sethostname", Err: err}
 		}
 	}
+	if domainname := l.config.Config.Domainname; domainname != "" {
+		if err := unix.Setdomainname([]byte(domainname)); err != nil {
+			return &os.SyscallError{Syscall: "setdomainname", Err: err}
+		}
+	}
 	if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil {
 		return fmt.Errorf("unable to apply apparmor profile: %w", err)
 	}


Test patch:
diff --git a/libcontainer/configs/validate/validator_test.go b/libcontainer/configs/validate/validator_test.go
index 59a40338..f59d0f20 100644
--- a/libcontainer/configs/validate/validator_test.go
+++ b/libcontainer/configs/validate/validator_test.go
@@ -82,7 +82,25 @@ func TestValidateHostname(t *testing.T) {
 	}
 }
 
-func TestValidateHostnameWithoutUTSNamespace(t *testing.T) {
+func TestValidateUTS(t *testing.T) {
+	config := &configs.Config{
+		Rootfs:     "/var",
+		Domainname: "runc",
+		Hostname:   "runc",
+		Namespaces: configs.Namespaces(
+			[]configs.Namespace{
+				{Type: configs.NEWUTS},
+			},
+		),
+	}
+
+	err := Validate(config)
+	if err != nil {
+		t.Errorf("Expected error to not occur: %+v", err)
+	}
+}
+
+func TestValidateUTSWithoutUTSNamespace(t *testing.T) {
 	config := &configs.Config{
 		Rootfs:   "/var",
 		Hostname: "runc",
@@ -92,6 +110,16 @@ func TestValidateHostnameWithoutUTSNamespace(t *testing.T) {
 	if err == nil {
 		t.Error("Expected error to occur but it was nil")
 	}
+
+	config = &configs.Config{
+		Rootfs:     "/var",
+		Domainname: "runc",
+	}
+
+	err = Validate(config)
+	if err == nil {
+		t.Error("Expected error to occur but it was nil")
+	}
 }
 
 func TestValidateSecurityWithMaskPaths(t *testing.T) {
diff --git a/libcontainer/integration/template_test.go b/libcontainer/integration/template_test.go
index 0e054b55..3de1d3e3 100644
--- a/libcontainer/integration/template_test.go
+++ b/libcontainer/integration/template_test.go
@@ -129,8 +129,9 @@ func newTemplateConfig(t *testing.T, p *tParam) *configs.Config {
 		ReadonlyPaths: []string{
 			"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
 		},
-		Devices:  specconv.AllowedDevices,
-		Hostname: "integration",
+		Devices:    specconv.AllowedDevices,
+		Hostname:   "integration",
+		Domainname: "integration",
 		Mounts: []*configs.Mount{
 			{
 				Source:      "proc",
diff --git a/tests/integration/run.bats b/tests/integration/run.bats
index 2cef4fdb..ae4e0fa5 100644
--- a/tests/integration/run.bats
+++ b/tests/integration/run.bats
@@ -57,3 +57,22 @@ function teardown() {
 	runc state test_run_keep
 	[ "$status" -ne 0 ]
 }
+
+@test "runc run [hostname domainname]" {
+	update_config ' .process.args |= ["sh"]
+			| .hostname = "myhostname"
+			| .domainname= "mydomainname"'
+
+	runc run -d --console-socket "$CONSOLE_SOCKET" test_utc
+	[ "$status" -eq 0 ]
+
+	# test hostname
+	runc exec test_utc hostname
+	[ "$status" -eq 0 ]
+	[[ "${lines[0]}" == *'myhostname'* ]]
+
+	# test domainname
+	runc exec test_utc cat /proc/sys/kernel/domainname
+	[ "$status" -eq 0 ]
+	[[ "${lines[0]}" == *'mydomainname'* ]]
+}