diff --git a/go.mod b/go.mod index f58d153..f0c3764 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/coreos/go-iptables v0.6.1-0.20220901214115-d2b8608923d1 github.com/fsnotify/fsnotify v1.6.0 github.com/spf13/cobra v1.5.0 + golang.org/x/sync v0.8.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -19,7 +20,6 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect - golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/tools v0.1.12 // indirect diff --git a/tests/strace.txtar b/tests/strace.txtar new file mode 100644 index 0000000..0f962d7 --- /dev/null +++ b/tests/strace.txtar @@ -0,0 +1,7 @@ +# this testscript test the 'create' command + +# if go is not installed, then skip +[!exec:go] skip + +strace fwdctl list -h +stdout 'Usage:' \ No newline at end of file diff --git a/utils.go b/utils.go index a9ad005..ebfa69e 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,14 @@ package main import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + "github.com/alegrey91/fwdctl/pkg/iptables" goiptables "github.com/coreos/go-iptables/iptables" "github.com/rogpeppe/go-internal/testscript" @@ -39,11 +47,82 @@ func fwdExists(ts *testscript.TestScript, neg bool, args []string) { } } +func strace(ts *testscript.TestScript, neg bool, args []string) { + if len(args) < 1 { + ts.Fatalf("syntax: strace needs at least one argument") + } + + outputDir := "syscalls" + if err := os.MkdirAll(outputDir, 0755); err != nil { + ts.Fatalf("failed to create output directory: %v", err) + } + + // Prepare the strace output file + timestamp := time.Now().Format("20060102-150405") + outputFile := filepath.Join(outputDir, fmt.Sprintf("strace-%s.log", timestamp)) + + // Prepare the command to execute with strace, filtering only system calls + straceArgs := []string{"-f", "-e", "trace=all"} + straceArgs = append(straceArgs, args...) + strace := exec.Command("strace", straceArgs...) + + var stdoutBuf bytes.Buffer + strace.Stdout = &stdoutBuf + var stderrBuf bytes.Buffer + strace.Stderr = &stderrBuf + + // Run the command + if err := strace.Run(); err != nil { + if !neg { + ts.Fatalf("command failed: %v", err) + } + } else { + if neg { + ts.Fatalf("command succeeded when it should have failed") + } + } + + fmt.Fprintf(ts.Stdout(), "%s", stdoutBuf.String()) + + syscalls := processStraceOutput(stderrBuf.String()) + if err := os.WriteFile(outputFile, []byte(syscalls), 0644); err != nil { + ts.Logf("strace output saved to %s", outputFile) + } +} + +func processStraceOutput(output string) string { + lines := strings.Split(output, "\n") + + // Use a map to store unique system call names + syscalls := make(map[string]struct{}) + + // Iterate over each line and extract the system call name + for _, line := range lines { + // Extract the system call name before the first '(' + if idx := strings.Index(line, "("); idx != -1 { + syscall := strings.TrimSpace(line[:idx]) + if syscall != "" { + syscalls[syscall] = struct{}{} + } + } + } + + // Convert the map keys to a slice to get unique system call names + var syscallList []string + for syscall := range syscalls { + syscallList = append(syscallList, syscall) + } + + // Join the unique system call names into a single string, one per line + return strings.Join(syscallList, "\n") +} + func customCommands() map[string]func(ts *testscript.TestScript, neg bool, args []string) { return map[string]func(ts *testscript.TestScript, neg bool, args []string){ // fwd_exists check that the given forward exists // invoke as "fwd_exists iface proto dest_port src_addr src_port" "fwd_exists": fwdExists, + "strace": strace, } }