diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b6be953..a789425 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -37,7 +37,5 @@ jobs: - name: Build run: go build ./... - - # Uncomment for tests if desired: - # - name: Test - # run: go test ./... -v + - name: Test + run: go test ./... -v diff --git a/pkg/capture/capture_test.go b/pkg/capture/capture_test.go new file mode 100644 index 0000000..a211005 --- /dev/null +++ b/pkg/capture/capture_test.go @@ -0,0 +1,62 @@ +package capture_test + +import ( + "os" + "testing" + + "osi-replay/pkg/capture" + "osi-replay/pkg/common" +) + +// Test invalid interface name +func TestCapturePackets_InvalidInterface(t *testing.T) { + logger := common.NewLogger("test-capture") + cfg := &common.CaptureConfig{ + InterfaceName: "does-not-exist", + Promiscuous: true, + SnapLen: 65535, + PcapFile: "test_capture.pcap", + } + + err := capture.CapturePackets(cfg, logger) + if err == nil { + t.Fatalf("Expected an error for a non-existent interface, got nil") + } + // Clean up if the file got created + _ = os.Remove("test_capture.pcap") +} + +// Test file creation error by providing an invalid path +func TestCapturePackets_FileCreationError(t *testing.T) { + logger := common.NewLogger("test-capture") + cfg := &common.CaptureConfig{ + InterfaceName: "lo", // or "eth0" depending on your system + Promiscuous: true, + SnapLen: 65535, + PcapFile: "/invalid-dir/test_capture.pcap", + } + + err := capture.CapturePackets(cfg, logger) + if err == nil { + t.Fatal("Expected an error due to invalid file path, got nil") + } +} + +// Optional real capture test (skipped by default) +func TestCapturePackets_Simple(t *testing.T) { + t.Skip("Skipping real capture test in CI or restricted environment") + + logger := common.NewLogger("test-capture") + cfg := &common.CaptureConfig{ + InterfaceName: "lo", // or another valid interface + Promiscuous: true, + SnapLen: 65535, + PcapFile: "test_capture.pcap", + } + + err := capture.CapturePackets(cfg, logger) + if err != nil { + t.Errorf("Unexpected error capturing on interface 'lo': %v", err) + } + _ = os.Remove("test_capture.pcap") +} diff --git a/pkg/common/common_test.go b/pkg/common/common_test.go new file mode 100644 index 0000000..1ff9741 --- /dev/null +++ b/pkg/common/common_test.go @@ -0,0 +1,35 @@ +package common_test + +import ( + "os" + "path/filepath" + "testing" + + "osi-replay/pkg/common" +) + +func TestEnsureFileWritable_NewFile(t *testing.T) { + testFile := filepath.Join(os.TempDir(), "ensure_writable_test.file") + os.Remove(testFile) // ensure a clean state + + err := common.EnsureFileWritable(testFile) + if err != nil { + t.Fatalf("Expected no error creating test file, got: %v", err) + } + // Clean up + os.Remove(testFile) +} + +func TestEnsureFileWritable_ExistingFile(t *testing.T) { + testFile := filepath.Join(os.TempDir(), "ensure_writable_test.file") + // Pre-create the file + f, _ := os.Create(testFile) + f.Close() + + err := common.EnsureFileWritable(testFile) + if err != nil { + t.Errorf("Expected no error for existing file, got: %v", err) + } + // Clean up + os.Remove(testFile) +} diff --git a/pkg/replay/replay_test.go b/pkg/replay/replay_test.go new file mode 100644 index 0000000..0fcb011 --- /dev/null +++ b/pkg/replay/replay_test.go @@ -0,0 +1,50 @@ +package replay_test + +import ( + "os" + "path/filepath" + "testing" + + "osi-replay/pkg/common" + "osi-replay/pkg/replay" +) + +// Test missing PCAP file +func TestReplayPackets_NoSuchFile(t *testing.T) { + logger := common.NewLogger("test-replay") + cfg := &common.CaptureConfig{ + InterfaceName: "eth999", // likely non-existent + SnapLen: 65535, + Promiscuous: false, + PcapFile: "no_such_file.pcap", + } + + err := replay.ReplayPackets(cfg, logger) + if err == nil { + t.Errorf("Expected error when PCAP file does not exist") + } +} + +// Optional real replay test (skipped by default) +func TestReplayPackets_Basic(t *testing.T) { + t.Skip("Skipping real replay test in CI or restricted environment") + + // Provide a small real PCAP in a testdata/ folder: + pcapFile := filepath.Join("testdata", "small_test.pcap") + if _, err := os.Stat(pcapFile); os.IsNotExist(err) { + t.Skipf("Skipping: testdata file %v not found", pcapFile) + } + + logger := common.NewLogger("test-replay") + cfg := &common.CaptureConfig{ + InterfaceName: "lo", // or eth0, if valid + SnapLen: 65535, + Promiscuous: false, + PcapFile: pcapFile, + } + + err := replay.ReplayPackets(cfg, logger) + if err != nil { + t.Errorf("Unexpected error replaying pcap: %v", err) + } +} diff --git a/pkg/rewriter/rewriter_test.go b/pkg/rewriter/rewriter_test.go new file mode 100644 index 0000000..d3352c4 --- /dev/null +++ b/pkg/rewriter/rewriter_test.go @@ -0,0 +1,81 @@ +package rewriter_test + +import ( + "net" + "testing" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "osi-replay/pkg/rewriter" +) + +func TestRewritePacket_Basic(t *testing.T) { + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + + eth := &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, + DstMAC: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + EthernetType: layers.EthernetTypeIPv4, + } + ip4 := &layers.IPv4{ + SrcIP: net.ParseIP("192.168.1.100"), + DstIP: net.ParseIP("192.168.1.200"), + Version: 4, + IHL: 5, + Protocol: layers.IPProtocolTCP, + } + + if err := gopacket.SerializeLayers(buf, opts, eth, ip4); err != nil { + t.Fatalf("Error serializing layers: %v", err) + } + + original := buf.Bytes() + + cfg := &rewriter.RewriteConfig{ + IPMapSrc: map[string]string{"192.168.1.100": "10.0.0.5"}, + IPMapDst: map[string]string{"192.168.1.200": "10.0.0.10"}, + MACMapSrc: map[string]string{ + "00:11:22:33:44:55": "66:77:88:99:aa:bb", + }, + MACMapDst: map[string]string{ + "aa:bb:cc:dd:ee:ff": "11:22:33:44:55:66", + }, + } + + newData, err := rewriter.RewritePacket(original, cfg) + if err != nil { + t.Fatalf("RewritePacket returned error: %v", err) + } + + newPacket := gopacket.NewPacket(newData, layers.LayerTypeEthernet, gopacket.Default) + ethLayer := newPacket.Layer(layers.LayerTypeEthernet) + ipLayer := newPacket.Layer(layers.LayerTypeIPv4) + + if ethLayer == nil || ipLayer == nil { + t.Fatalf("Missing expected layers in rewritten packet") + } + + ethOut, _ := ethLayer.(*layers.Ethernet) + ip4Out, _ := ipLayer.(*layers.IPv4) + + // Verify MAC addresses + if ethOut.SrcMAC.String() != "66:77:88:99:aa:bb" { + t.Errorf("Expected MAC src 66:77:88:99:aa:bb, got %s", ethOut.SrcMAC) + } + if ethOut.DstMAC.String() != "11:22:33:44:55:66" { + t.Errorf("Expected MAC dst 11:22:33:44:55:66, got %s", ethOut.DstMAC) + } + + // Verify IP addresses + if ip4Out.SrcIP.String() != "10.0.0.5" { + t.Errorf("Expected IP src 10.0.0.5, got %s", ip4Out.SrcIP) + } + if ip4Out.DstIP.String() != "10.0.0.10" { + t.Errorf("Expected IP dst 10.0.0.10, got %s", ip4Out.DstIP) + } +} diff --git a/pkg/sanitizer/sanitizer_test.go b/pkg/sanitizer/sanitizer_test.go new file mode 100644 index 0000000..3ed6972 --- /dev/null +++ b/pkg/sanitizer/sanitizer_test.go @@ -0,0 +1,63 @@ +package sanitizer_test + +import ( + "net" + "testing" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "osi-replay/pkg/sanitizer" +) + +func TestSanitizePacket_BlockedIP(t *testing.T) { + // Synthetic IPv4 packet with src=10.0.0.1 + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + + ip := &layers.IPv4{ + Version: 4, + IHL: 5, + SrcIP: net.ParseIP("10.0.0.1"), + DstIP: net.ParseIP("192.168.1.10"), + } + + if err := gopacket.SerializeLayers(buf, opts, ip); err != nil { + t.Fatalf("Error serializing IP packet: %v", err) + } + + packet := gopacket.NewPacket(buf.Bytes(), layers.LayerTypeIPv4, gopacket.Default) + newPacket, keep := sanitizer.SanitizePacket(packet) + if keep || newPacket != nil { + t.Errorf("Expected packet to be dropped for blocked IP 10.0.0.1") + } +} + +func TestSanitizePacket_AllowedIP(t *testing.T) { + // Synthetic IPv4 packet with src=192.168.100.5, not in blockedIPs + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + + ip := &layers.IPv4{ + Version: 4, + IHL: 5, + SrcIP: net.ParseIP("192.168.100.5"), + DstIP: net.ParseIP("192.168.100.10"), + } + + if err := gopacket.SerializeLayers(buf, opts, ip); err != nil { + t.Fatalf("Error serializing IP packet: %v", err) + } + + packet := gopacket.NewPacket(buf.Bytes(), layers.LayerTypeIPv4, gopacket.Default) + newPacket, keep := sanitizer.SanitizePacket(packet) + if !keep || newPacket == nil { + t.Errorf("Expected packet to pass sanitizer, but was dropped") + } +} diff --git a/pkg/transform/transform_test.go b/pkg/transform/transform_test.go new file mode 100644 index 0000000..c57c32d --- /dev/null +++ b/pkg/transform/transform_test.go @@ -0,0 +1,46 @@ +package transform_test + +import ( + "os" + "path/filepath" + "testing" + + "osi-replay/pkg/common" + "osi-replay/pkg/transform" +) + +// Test error on nonexistent input file +func TestRun_NoSuchFile(t *testing.T) { + logger := common.NewLogger("test-transform") + err := transform.Run("no_such_file.pcap", "out.pcap", logger) + if err == nil { + t.Errorf("Expected error with nonexistent input file, got nil") + } + _ = os.Remove("out.pcap") +} + +// Optional real transform test +func TestRun_Basic(t *testing.T) { + t.Skip("Skipping real transform test if no testdata pcap available") + + pcapIn := filepath.Join("testdata", "example_in.pcap") + pcapOut := filepath.Join("testdata", "example_out.pcap") + + if _, err := os.Stat(pcapIn); os.IsNotExist(err) { + t.Skipf("Skipping test, no input pcap at %s", pcapIn) + } + + logger := common.NewLogger("test-transform") + _ = os.Remove(pcapOut) + + err := transform.Run(pcapIn, pcapOut, logger) + if err != nil { + t.Errorf("Unexpected error transforming pcap: %v", err) + } + + // Check if output got created + if _, err := os.Stat(pcapOut); os.IsNotExist(err) { + t.Errorf("Output file %s not created", pcapOut) + } + _ = os.Remove(pcapOut) +} diff --git a/snap.sh b/snap.sh new file mode 100644 index 0000000..79a7dbe --- /dev/null +++ b/snap.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Output file for the snapshot +output_file="filesystem_snapshot_$(date +%Y%m%d_%H%M%S).txt" + +# Function to process files +process_file() { + local file="$1" + + # Skip files ending with .sum + if [[ "$file" == *.sum ]]; then + return + fi + + # Add file separator and path to output + echo -e "\n=== File: $file ===" >> "$output_file" + + # Check if file is binary + if [[ -f "$file" ]] && ! [[ -x "$file" ]] && file "$file" | grep -q "text"; then + # For text files, add content + echo -e "\n--- Content ---" >> "$output_file" + cat "$file" >> "$output_file" + else + # For binary files, just note that it's binary + echo -e "\n[Binary file]" >> "$output_file" + fi +} + +# Main function to traverse directory +traverse_directory() { + local dir="$1" + + # Find all files and directories, excluding .git + find "$dir" -type f -o -type d | while read -r item; do + # Skip .git directories and their contents + if [[ "$item" == */.git/* ]] || [[ "$item" == */.git ]]; then + continue + fi + + if [[ -f "$item" ]]; then + process_file "$item" + elif [[ -d "$item" ]]; then + echo -e "\n=== Directory: $item ===" >> "$output_file" + fi + done +} + +# Initialize output file with header +echo "File System Snapshot - Generated on $(date)" > "$output_file" +echo "Current working directory: $(pwd)" >> "$output_file" +echo -e "\n----------------------------------------" >> "$output_file" + +# Start traversal from current directory +traverse_directory "." + +echo "Snapshot has been generated in: $output_file" \ No newline at end of file