From c818c733abf0b9b4ede020cdb5cc94c8243588c5 Mon Sep 17 00:00:00 2001 From: Ziwen Ning Date: Mon, 6 Feb 2023 20:33:49 -0800 Subject: [PATCH] feat: resolve special ip host-gateway Signed-off-by: Ziwen Ning --- cmd/nerdctl/container_run.go | 23 +++++++++++++--- cmd/nerdctl/container_run_linux_test.go | 36 ++++++++++++++++++++++++- cmd/nerdctl/flagutil.go | 5 ++++ cmd/nerdctl/main.go | 3 ++- cmd/nerdctl/main_unix.go | 4 +-- docs/command-reference.md | 4 ++- docs/config.md | 29 ++++++++++---------- pkg/config/config.go | 2 ++ pkg/defaults/defaults_freebsd.go | 4 +++ pkg/defaults/defaults_linux.go | 16 +++++++++++ pkg/defaults/defaults_windows.go | 4 +++ pkg/rootlessutil/parent.go | 3 ++- pkg/testutil/testutil_linux.go | 1 + 13 files changed, 110 insertions(+), 24 deletions(-) diff --git a/cmd/nerdctl/container_run.go b/cmd/nerdctl/container_run.go index 6475f0e453f..e810a32dd70 100644 --- a/cmd/nerdctl/container_run.go +++ b/cmd/nerdctl/container_run.go @@ -57,7 +57,8 @@ import ( "github.com/containerd/nerdctl/pkg/referenceutil" "github.com/containerd/nerdctl/pkg/strutil" "github.com/containerd/nerdctl/pkg/taskutil" - dopts "github.com/docker/cli/opts" + dockercliopts "github.com/docker/cli/opts" + dockerops "github.com/docker/docker/opts" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -707,10 +708,24 @@ func createContainer(ctx context.Context, cmd *cobra.Command, client *containerd return nil, nil, err } extraHosts = strutil.DedupeStrSlice(extraHosts) - for _, host := range extraHosts { - if _, err := dopts.ValidateExtraHost(host); err != nil { + for i, host := range extraHosts { + if _, err := dockercliopts.ValidateExtraHost(host); err != nil { return nil, nil, err } + parts := strings.SplitN(host, ":", 2) + // If the IP Address is a string called "host-gateway", replace this value with the IP address stored + // in the daemon level HostGateway IP config variable. + if parts[1] == dockerops.HostGatewayName { + gateway, err := cmd.Flags().GetString("host-gateway-ip") + if err != nil { + return nil, nil, err + } + if gateway == "" { + return nil, nil, fmt.Errorf("unable to derive the IP value for host-gateway") + } + parts[1] = gateway + extraHosts[i] = fmt.Sprintf(`%s:%s`, parts[0], parts[1]) + } } internalLabels.extraHosts = extraHosts @@ -995,7 +1010,7 @@ func readKVStringsMapfFromLabel(cmd *cobra.Command) (map[string]string, error) { return nil, err } labelsFilePath = strutil.DedupeStrSlice(labelsFilePath) - labels, err := dopts.ReadKVStrings(labelsFilePath, labelsMap) + labels, err := dockercliopts.ReadKVStrings(labelsFilePath, labelsMap) if err != nil { return nil, err } diff --git a/cmd/nerdctl/container_run_linux_test.go b/cmd/nerdctl/container_run_linux_test.go index d28b460b0ef..7bdfdef2f1b 100644 --- a/cmd/nerdctl/container_run_linux_test.go +++ b/cmd/nerdctl/container_run_linux_test.go @@ -19,8 +19,11 @@ package main import ( "bufio" "bytes" + "context" "errors" "fmt" + "io" + "net/http" "os" "path/filepath" "runtime" @@ -32,7 +35,6 @@ import ( "github.com/containerd/nerdctl/pkg/rootlessutil" "github.com/containerd/nerdctl/pkg/strutil" "github.com/containerd/nerdctl/pkg/testutil" - "gotest.tools/v3/assert" ) @@ -150,6 +152,38 @@ func TestRunAddHost(t *testing.T) { return nil }) base.Cmd("run", "--rm", "--add-host", "10.0.0.1:testing.example.com", testutil.AlpineImage, "cat", "/etc/hosts").AssertFail() + + response := "This is the expected response for --add-host special IP test." + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, response) + }) + const hostPort = 8081 + s := http.Server{Addr: fmt.Sprintf(":%d", hostPort), Handler: nil, ReadTimeout: 30 * time.Second} + go s.ListenAndServe() + defer s.Shutdown(context.Background()) + base.Cmd("run", "--rm", "--add-host", "test:host-gateway", testutil.AmazonLinux2Image, "curl", fmt.Sprintf("test:%d", hostPort)).AssertOutExactly(response) +} + +func TestRunAddHostWithCustomHostGatewayIP(t *testing.T) { + // Not parallelizable (https://github.com/containerd/nerdctl/issues/1127) + base := testutil.NewBase(t) + testutil.DockerIncompatible(t) + base.Cmd("run", "--rm", "--host-gateway-ip", "192.168.5.2", "--add-host", "test:host-gateway", testutil.AlpineImage, "cat", "/etc/hosts").AssertOutWithFunc(func(stdout string) error { + var found bool + sc := bufio.NewScanner(bytes.NewBufferString(stdout)) + for sc.Scan() { + //removing spaces and tabs separating items + line := strings.ReplaceAll(sc.Text(), " ", "") + line = strings.ReplaceAll(line, "\t", "") + if strings.Contains(line, "192.168.5.2test") { + found = true + } + } + if !found { + return errors.New("host was not added") + } + return nil + }) } func TestRunUlimit(t *testing.T) { diff --git a/cmd/nerdctl/flagutil.go b/cmd/nerdctl/flagutil.go index 54a035201bc..e64b4980033 100644 --- a/cmd/nerdctl/flagutil.go +++ b/cmd/nerdctl/flagutil.go @@ -70,6 +70,10 @@ func processRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error) if err != nil { return types.GlobalCommandOptions{}, err } + hostGatewayIP, err := cmd.Flags().GetString("host-gateway-ip") + if err != nil { + return types.GlobalCommandOptions{}, err + } return types.GlobalCommandOptions{ Debug: debug, DebugFull: debugFull, @@ -83,5 +87,6 @@ func processRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error) InsecureRegistry: insecureRegistry, HostsDir: hostsDir, Experimental: experimental, + HostGatewayIP: hostGatewayIP, }, nil } diff --git a/cmd/nerdctl/main.go b/cmd/nerdctl/main.go index 1bfe1c697a9..32549ec488d 100644 --- a/cmd/nerdctl/main.go +++ b/cmd/nerdctl/main.go @@ -171,6 +171,7 @@ func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) (*pflag.FlagSet, rootCmd.PersistentFlags().StringSlice("hosts-dir", cfg.HostsDir, "A directory that contains /hosts.toml (containerd style) or /{ca.cert, cert.pem, key.pem} (docker style)") // Experimental enable experimental feature, see in https://github.com/containerd/nerdctl/blob/main/docs/experimental.md AddPersistentBoolFlag(rootCmd, "experimental", nil, nil, cfg.Experimental, "NERDCTL_EXPERIMENTAL", "Control experimental: https://github.com/containerd/nerdctl/blob/main/docs/experimental.md") + AddPersistentStringFlag(rootCmd, "host-gateway-ip", nil, nil, nil, aliasToBeInherited, cfg.HostGatewayIP, "HOST_GATEWAY_IP", "IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the default bridge") return aliasToBeInherited, nil } @@ -228,7 +229,7 @@ Config file ($NERDCTL_TOML): %s } if appNeedsRootlessParentMain(cmd, args) { // reexec /proc/self/exe with `nsenter` into RootlessKit namespaces - return rootlessutil.ParentMain() + return rootlessutil.ParentMain(globalOptions.HostGatewayIP) } return nil } diff --git a/cmd/nerdctl/main_unix.go b/cmd/nerdctl/main_unix.go index f0b6ca25830..ce8e8f9656a 100644 --- a/cmd/nerdctl/main_unix.go +++ b/cmd/nerdctl/main_unix.go @@ -32,7 +32,7 @@ func shellCompleteNamespaceNames(cmd *cobra.Command, args []string, toComplete s return nil, cobra.ShellCompDirectiveError } if rootlessutil.IsRootlessParent() { - _ = rootlessutil.ParentMain() + _ = rootlessutil.ParentMain(globalOptions.HostGatewayIP) return nil, cobra.ShellCompDirectiveNoFileComp } if err != nil { @@ -60,7 +60,7 @@ func shellCompleteSnapshotterNames(cmd *cobra.Command, args []string, toComplete return nil, cobra.ShellCompDirectiveError } if rootlessutil.IsRootlessParent() { - _ = rootlessutil.ParentMain() + _ = rootlessutil.ParentMain(globalOptions.HostGatewayIP) return nil, cobra.ShellCompDirectiveNoFileComp } client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address) diff --git a/docs/command-reference.md b/docs/command-reference.md index 2ed189b9f34..43427163143 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -177,7 +177,8 @@ Network flags: - :whale: `--dns-search`: Set custom DNS search domains - :whale: `--dns-opt, --dns-option`: Set DNS options - :whale: `-h, --hostname`: Container host name -- :whale: `--add-host`: Add a custom host-to-IP mapping (host:ip) +- :whale: `--add-host`: Add a custom host-to-IP mapping (host:ip). `ip` could be a special string `host-gateway`, +- which will be resolved to the `host-gateway-ip` in nerdctl.toml or global flag. - :whale: `--ip`: Specific static IP address(es) to use - :whale: `--mac-address`: Specific MAC address to use. Be aware that it does not check if manually specified MAC addresses are unique. Supports network @@ -1599,6 +1600,7 @@ Flags: - :nerd_face: `--cgroup-manager=(cgroupfs|systemd|none)`: cgroup manager - Default: "systemd" on cgroup v2 (rootful & rootless), "cgroupfs" on v1 rootful, "none" on v1 rootless - :nerd_face: `--insecure-registry`: skips verifying HTTPS certs, and allows falling back to plain HTTP +- :nerd_face: `--host-gateway-ip`: IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host The global flags can be also specified in `/etc/nerdctl/nerdctl.toml` (rootful) and `~/.config/nerdctl/nerdctl.toml` (rootless). See [`./config.md`](./config.md). diff --git a/docs/config.md b/docs/config.md index c01f4ce1912..ae76ea807a5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -30,20 +30,21 @@ experimental = true ## Properties -| TOML property | CLI flag | Env var | Description | Availability \*1 | -|-------------------|--------------------------|---------------------------|-----------------------------|---------------| -| `debug` | `--debug` | | Debug mode | Since 0.16.0 | -| `debug_full` | `--debug-full` | | Debug mode (with full output) | Since 0.16.0 | -| `address` | `--address`,`--host`,`-a`,`-H` | `$CONTAINERD_ADDRESS` | containerd address | Since 0.16.0 | -| `namespace` | `--namespace`,`-n` | `$CONTAINERD_NAMESPACE` | containerd namespace | Since 0.16.0 | -| `snapshotter` | `--snapshotter`,`--storage-driver` | `$CONTAINERD_SNAPSHOTTER` | containerd snapshotter | Since 0.16.0 | -| `cni_path` | `--cni-path` | `$CNI_PATH` | CNI binary directory | Since 0.16.0 | -| `cni_netconfpath` | `--cni-netconfpath` | `$NETCONFPATH` | CNI config directory | Since 0.16.0 | -| `data_root` | `--data-root` | | Persistent state directory | Since 0.16.0 | -| `cgroup_manager` | `--cgroup-manager` | | cgroup manager | Since 0.16.0 | -| `insecure_registry` | `--insecure-registry` | | Allow insecure registry | Since 0.16.0 | -| `hosts_dir` | `--hosts-dir` | | `certs.d` directory | Since 0.16.0 | -| `experimental` | `--experimental` | `NERDCTL_EXPERIMENTAL` | Enable [experimental features](experimental.md) | Since 0.22.3 | +| TOML property | CLI flag | Env var | Description | Availability \*1 | +|---------------------|------------------------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------|------------------| +| `debug` | `--debug` | | Debug mode | Since 0.16.0 | +| `debug_full` | `--debug-full` | | Debug mode (with full output) | Since 0.16.0 | +| `address` | `--address`,`--host`,`-a`,`-H` | `$CONTAINERD_ADDRESS` | containerd address | Since 0.16.0 | +| `namespace` | `--namespace`,`-n` | `$CONTAINERD_NAMESPACE` | containerd namespace | Since 0.16.0 | +| `snapshotter` | `--snapshotter`,`--storage-driver` | `$CONTAINERD_SNAPSHOTTER` | containerd snapshotter | Since 0.16.0 | +| `cni_path` | `--cni-path` | `$CNI_PATH` | CNI binary directory | Since 0.16.0 | +| `cni_netconfpath` | `--cni-netconfpath` | `$NETCONFPATH` | CNI config directory | Since 0.16.0 | +| `data_root` | `--data-root` | | Persistent state directory | Since 0.16.0 | +| `cgroup_manager` | `--cgroup-manager` | | cgroup manager | Since 0.16.0 | +| `insecure_registry` | `--insecure-registry` | | Allow insecure registry | Since 0.16.0 | +| `hosts_dir` | `--hosts-dir` | | `certs.d` directory | Since 0.16.0 | +| `experimental` | `--experimental` | `NERDCTL_EXPERIMENTAL` | Enable [experimental features](experimental.md) | Since 0.22.3 | +| `host_gateway_ip` | `--host-gateway-ip` | | IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host | Since 1.3.0 | The properties are parsed in the following precedence: 1. CLI flag diff --git a/pkg/config/config.go b/pkg/config/config.go index 4e84cb26059..e393933779d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -38,6 +38,7 @@ type Config struct { InsecureRegistry bool `toml:"insecure_registry"` HostsDir []string `toml:"hosts_dir"` Experimental bool `toml:"experimental"` + HostGatewayIP string `toml:"host_gateway_ip"` } // New creates a default Config object statically, @@ -56,5 +57,6 @@ func New() *Config { InsecureRegistry: false, HostsDir: ncdefaults.HostsDirs(), Experimental: true, + HostGatewayIP: ncdefaults.HostGatewayIP(), } } diff --git a/pkg/defaults/defaults_freebsd.go b/pkg/defaults/defaults_freebsd.go index f0925d8bf68..620006a5e9a 100644 --- a/pkg/defaults/defaults_freebsd.go +++ b/pkg/defaults/defaults_freebsd.go @@ -59,3 +59,7 @@ func NerdctlTOML() string { func HostsDirs() []string { return []string{"/etc/containerd/certs.d", "/etc/docker/certs.d"} } + +func HostGatewayIP() string { + return "" +} diff --git a/pkg/defaults/defaults_linux.go b/pkg/defaults/defaults_linux.go index b36db7e14e7..b5f5212004d 100644 --- a/pkg/defaults/defaults_linux.go +++ b/pkg/defaults/defaults_linux.go @@ -18,6 +18,7 @@ package defaults import ( "fmt" + "net" "os" "path/filepath" @@ -130,3 +131,18 @@ func HostsDirs() []string { filepath.Join(xch, "docker/certs.d"), } } + +func HostGatewayIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { + return ipnet.IP.String() + } + } + } + return "" +} diff --git a/pkg/defaults/defaults_windows.go b/pkg/defaults/defaults_windows.go index 37dcd3f01bb..7c4a7929dfe 100644 --- a/pkg/defaults/defaults_windows.go +++ b/pkg/defaults/defaults_windows.go @@ -75,3 +75,7 @@ func HostsDirs() []string { filepath.Join(programData, "docker\\certs.d"), } } + +func HostGatewayIP() string { + return "" +} diff --git a/pkg/rootlessutil/parent.go b/pkg/rootlessutil/parent.go index cc608197e24..754fefaad46 100644 --- a/pkg/rootlessutil/parent.go +++ b/pkg/rootlessutil/parent.go @@ -63,7 +63,7 @@ func RootlessKitChildPid(stateDir string) (int, error) { return strconv.Atoi(pidStr) } -func ParentMain() error { +func ParentMain(hostGatewayIP string) error { if !IsRootlessParent() { return errors.New("should not be called when !IsRootlessParent()") } @@ -104,5 +104,6 @@ func ParentMain() error { os.Setenv("ROOTLESSKIT_STATE_DIR", stateDir) os.Setenv("ROOTLESSKIT_PARENT_EUID", strconv.Itoa(os.Geteuid())) os.Setenv("ROOTLESSKIT_PARENT_EGID", strconv.Itoa(os.Getegid())) + os.Setenv("HOST_GATEWAY_IP", hostGatewayIP) return syscall.Exec(arg0, args, os.Environ()) } diff --git a/pkg/testutil/testutil_linux.go b/pkg/testutil/testutil_linux.go index 96632b43e0e..70f24c40e8d 100644 --- a/pkg/testutil/testutil_linux.go +++ b/pkg/testutil/testutil_linux.go @@ -25,6 +25,7 @@ func mirrorOf(s string) string { var ( AlpineImage = mirrorOf("alpine:3.13") + AmazonLinux2Image = "public.ecr.aws/amazonlinux/amazonlinux:2" NginxAlpineImage = mirrorOf("nginx:1.19-alpine") NginxAlpineIndexHTMLSnippet = "Welcome to nginx!" RegistryImage = mirrorOf("registry:2")