From 993182ccd5199a7ba5ca4642dbbff92dbd804934 Mon Sep 17 00:00:00 2001 From: Nicholas Cioli Date: Mon, 17 Jul 2023 06:13:50 -0400 Subject: [PATCH] Use Go net ns switcher instead of nsenter This commit swaps out the previous implementation of using the command `nsenter` to switch networking namespaces. By using the Go impl of network namespace switching, read-only filesystems and OSs without the `nsenter` command (such as Talos) should still work. Signed-off-by: Nicholas Cioli --- go.mod | 1 + go.sum | 6 ++-- internal/iptables/iptables.go | 58 +++++++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 1215bd12..b90c4187 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/containernetworking/cni v1.1.2 + github.com/containernetworking/plugins v1.3.0 github.com/mitchellh/mapstructure v1.5.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index 5bbbe1b7..a08e6156 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q6mVDp5H1HnjM= +github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,8 +52,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk= +github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= diff --git a/internal/iptables/iptables.go b/internal/iptables/iptables.go index 4356c04b..b2e9a091 100644 --- a/internal/iptables/iptables.go +++ b/internal/iptables/iptables.go @@ -8,6 +8,7 @@ import ( "strings" "time" + ns "github.com/containernetworking/plugins/pkg/ns" log "github.com/sirupsen/logrus" util "github.com/linkerd/linkerd2-proxy-init/internal/util" @@ -229,28 +230,59 @@ func makeMultiportDestinations(portsToIgnore []string) [][]string { } func executeCommand(firewallConfiguration FirewallConfiguration, cmd *exec.Cmd) ([]byte, error) { - if firewallConfiguration.NetNs != "" { - // BusyBox's `nsenter` needs `--` to separate nsenter arguments from the - // command. - // - // See https://github.com/rancher/k3s/issues/1434#issuecomment-629315909 - nsArgs := fmt.Sprintf("--net=%s", firewallConfiguration.NetNs) - args := append([]string{nsArgs, "--"}, cmd.Args...) - cmd = exec.Command("nsenter", args...) - } + // Always log the command to apply for tracing purposes log.Info(cmd.String()) + // Short out early if we are just simulating the network if firewallConfiguration.SimulateOnly { return nil, nil } - out, err := cmd.CombinedOutput() + // Helper for reusing code when actually calling out to the command + doCommand := func() ([]byte, error) { + out, err := cmd.CombinedOutput() - if len(out) > 0 { - log.Infof("%s", out) + // Log out the output, if any + if len(out) > 0 { + log.Infof("%s", out) + } + + return out, err } - return out, err + // If we need to run within a target network namespace, then wrap the command + // in that namespace. + // + // Note: Network namespace switching is very volatile in Go. Care should be taken + // to ensure that all namespaced commands be wrapped in `targetNamespace.Do`, as explained in + // the link below. + // + // See: https://pkg.go.dev/github.com/containernetworking/plugins/pkg/ns#readme-do-the-recommended-thing + if firewallConfiguration.NetNs != "" { + // Fetch the target net namespace, ensuring that it exists + netNs, err := ns.GetNS(firewallConfiguration.NetNs) + if err != nil { + log.Errorf("could not switch to target network namespace \"%s\": %s", firewallConfiguration.NetNs, err.Error()) + return nil, err + } + + // Actually run the command in the namespace + // Note: Try to keep this code short! Goroutine switches might cause the + // namespace to change... + // + // Note: Result needs to be defined here since `netNs.Do` only returns + // an error. + result := make([]byte, 0) + err = netNs.Do(func(_ ns.NetNS) error { + result, err = doCommand() + + return err + }) + + return result, err + } else { + return doCommand() + } } func (fc FirewallConfiguration) makeIgnoreUserID(chainName string, uid int, comment string) *exec.Cmd {