Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(capture): ignore known copy failure and fix iptables issue #903

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
100 changes: 74 additions & 26 deletions pkg/capture/provider/network_capture_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package provider

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -72,11 +73,7 @@ func (ncp *NetworkCaptureProvider) CaptureNetworkPacket(ctx context.Context, fil
// tcpdump: Couldn't find user 'tcpdump'
// To disable this behavior, we use `--relinquish-privileges=root` same as `-Z root`.
// ref: https://manpages.debian.org/bullseye/tcpdump/tcpdump.8.en.html#Z
captureStartCmd := exec.Command(
"tcpdump",
"-w", captureFilePath,
"--relinquish-privileges=root",
)
captureStartCmd := exec.Command("tcpdump", "-w", captureFilePath, "--relinquish-privileges=root")

if packetSize := os.Getenv(captureConstants.PacketSizeEnvKey); len(packetSize) != 0 {
captureStartCmd.Args = append(
Expand Down Expand Up @@ -195,15 +192,20 @@ func (ncp *NetworkCaptureProvider) CaptureNetworkPacket(ctx context.Context, fil
}

type command struct {
name string
args []string
description string
name string
args []string
description string
ignoreFailure bool
}

func (ncp *NetworkCaptureProvider) CollectMetadata() error {
ncp.l.Info("Start to collect network metadata")

iptablesMode := obtainIptablesMode()
iptablesMode, err := obtainIptablesMode(ncp.l)
if err != nil {
return fmt.Errorf("failed to determine iptables modes. %w", err)
}

ncp.l.Info(fmt.Sprintf("Iptables mode %s is used", iptablesMode))
iptablesSaveCmdName := fmt.Sprintf("iptables-%s-save", iptablesMode)
iptablesCmdName := fmt.Sprintf("iptables-%s", iptablesMode)
Expand Down Expand Up @@ -292,6 +294,9 @@ func (ncp *NetworkCaptureProvider) CollectMetadata() error {
name: "cp",
args: []string{"-r", "/proc/sys/net", filepath.Join(ncp.TmpCaptureDir, "proc-sys-net")},
description: "kernel networking configuration",
// Errors will occur when copying kernel networking configuration for not all files under /proc/sys/net are
// readable, like '/proc/sys/net/ipv4/route/flush', which doesn't implement the read function.
ignoreFailure: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment as to why this is optional? Also, maybe log this as a warn so that user doesn't go looking for this information in the capture.

},
},
},
Expand All @@ -314,7 +319,7 @@ func (ncp *NetworkCaptureProvider) CollectMetadata() error {
// Print headlines for all commands in output file.
cmds := []*exec.Cmd{}
for _, command := range metadata.commands {
cmd := exec.Command(command.name, command.args...)
cmd := exec.Command(command.name, command.args...) // nolint:gosec // no sensitive data
cmds = append(cmds, cmd)
commandSummary := fmt.Sprintf("%s(%s)\n", cmd.String(), command.description)
if _, err := outfile.WriteString(commandSummary); err != nil {
Expand All @@ -327,29 +332,26 @@ func (ncp *NetworkCaptureProvider) CollectMetadata() error {
}

// Write command stdout and stderr to output file
for _, cmd := range cmds {
for _, command := range metadata.commands {
cmd := exec.Command(command.name, command.args...) // nolint:gosec // no sensitive data
if _, err := outfile.WriteString(fmt.Sprintf("%s\n\n", cmd.String())); err != nil {
ncp.l.Error("Failed to write string to file", zap.String("file", outfile.Name()), zap.Error(err))
}

cmd.Stdout = outfile
cmd.Stderr = outfile
if err := cmd.Run(); err != nil {
if err := cmd.Run(); err != nil && !command.ignoreFailure {
// Don't return for error to continue capturing following network metadata.
ncp.l.Error("Failed to execute command", zap.String("command", cmd.String()), zap.Error(err))
// Log the error in output file because this error does not stop capture job pod from finishing,
// and the job can be recycled automatically leaving no info to debug.
if _, err = outfile.WriteString(fmt.Sprintf("Failed to run %q, error: %s)", cmd.String(), err.Error())); err != nil {
ncp.l.Error("Failed to write command run failure", zap.String("command", cmd.String()), zap.Error(err))
}
}
}
} else {
for _, command := range metadata.commands {
cmd := exec.Command(command.name, command.args...)
// Errors will when copying kernel networking configuration for not all files under /proc/sys/net are
// readable, like '/proc/sys/net/ipv4/route/flush', which doesn't implement the read function.
if output, err := cmd.CombinedOutput(); err != nil {
cmd := exec.Command(command.name, command.args...) // nolint:gosec,gomnd // no sensitive data
if output, err := cmd.CombinedOutput(); err != nil && !command.ignoreFailure {
// Don't return for error to continue capturing following network metadata.
ncp.l.Error("Failed to execute command", zap.String("command", cmd.String()), zap.String("output", string(output)), zap.Error(err))
}
Expand All @@ -368,16 +370,62 @@ func (ncp *NetworkCaptureProvider) Cleanup() error {
return nil
}

func obtainIptablesMode() string {
type iptablesMode string

const (
legacyIptablesMode iptablesMode = "legacy"
nftIptablesMode iptablesMode = "nft"
)

var errIptablesUnavilable = errors.New("no iptables command is available")

// obtainIptablesMode return the available iptables mode, and returns empty when no iptables is available.
func obtainIptablesMode(logger *log.ZapLogger) (iptablesMode, error) {
// Since iptables v1.8, nf_tables are introduced as an improvement of legacy iptables, but provides the same user
// interface as legacy iptables through iptables-nft command.
// based on: https://github.com/kubernetes-sigs/iptables-wrappers/blob/97b01f43a8e8db07840fc4b95e833a37c0d36b12/iptables-wrapper-installer.sh
legacySaveOut, _ := exec.Command("iptables-legacy-save").CombinedOutput()
legacySaveLineNum := len(strings.Split(string(legacySaveOut), "\n"))
nftSaveOut, _ := exec.Command("iptables-nft-save").CombinedOutput()
nftSaveLineNum := len(strings.Split(string(nftSaveOut), "\n"))
if legacySaveLineNum > nftSaveLineNum {
return "legacy"

// When both iptables modes available, we choose the one with more rules, because the other one normally outputs empty rules.
nftIptablesModeAvaiable := true
legacyIptablesModeAvaiable := true
legacySaveLineNum := 0
nftSaveLineNum := 0
if _, err := exec.LookPath("iptables-legacy-save"); err != nil {
legacyIptablesModeAvaiable = false
logger.Info("iptables-legacy-save is not available", zap.Error(err))
} else {
legacySaveOut, err := exec.Command("iptables-legacy-save").CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to run iptables-legacy-save, %s", err.Error()) //nolint:goerr113 //no specific handling expected
}
legacySaveLineNum = len(strings.Split(string(legacySaveOut), "\n"))
}
return "nft"

if _, err := exec.LookPath("iptables-nft-save"); err != nil {
nftIptablesModeAvaiable = false
logger.Info("iptables-nft-save is not available", zap.Error(err))
} else {
nftSaveOut, err := exec.Command("iptables-nft-save").CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to run iptables-nft-save, %s", err.Error()) //nolint:goerr113 //no specific handling expected
}
legacySaveLineNum = len(strings.Split(string(nftSaveOut), "\n"))
}

if nftIptablesModeAvaiable && legacyIptablesModeAvaiable {
if legacySaveLineNum > nftSaveLineNum {
return legacyIptablesMode, nil
}
return nftIptablesMode, nil
}

if nftIptablesModeAvaiable {
return nftIptablesMode, nil
}

if legacyIptablesModeAvaiable {
return legacyIptablesMode, nil
}

return "", errIptablesUnavilable
}
Loading