diff --git a/internal/qol/capture/pcap.go b/internal/qol/capture/pcap.go index 6c638a8..e342098 100644 --- a/internal/qol/capture/pcap.go +++ b/internal/qol/capture/pcap.go @@ -3,9 +3,7 @@ package capture import ( "context" "fmt" - "net" "os" - "strings" "sync" "github.com/google/gopacket" @@ -21,91 +19,12 @@ type PacketCapture struct { err error } -func getLocalIPs() ([]string, error) { - var localIPs []string - - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, fmt.Errorf("failed to get network interfaces: %w", err) - } - - for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - - // Skip loopback - if ip == nil || ip.IsLoopback() { - continue - } - - localIPs = append(localIPs, ip.String()) - } - - if len(localIPs) == 0 { - return nil, fmt.Errorf("no non-loopback IPs found") - } - - return localIPs, nil -} - -func buildBPFFilter(protocol string, localIPs []string) string { - // Build filter for this machine's IPs - var hostFilters []string - for _, ip := range localIPs { - hostFilters = append(hostFilters, fmt.Sprintf("host %s", ip)) - } - testMachineFilter := "(" + strings.Join(hostFilters, " or ") + ")" - - // Protocol-specific ports - var portFilter string - switch strings.ToLower(protocol) { - case "udp": - portFilter = "(port 53)" - case "tls", "dot": - portFilter = "(port 53 or port 853)" - case "https", "doh": - portFilter = "(port 53 or port 443)" - case "doq": - portFilter = "(port 53 or port 853)" - case "doh3": - portFilter = "(port 53 or port 443)" - default: - portFilter = "(port 53 or port 443 or port 853)" - } - - // Exclude private-to-private traffic (LAN-to-LAN, includes Docker ranges) - privateExclude := "not (src net (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and dst net (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16))" - - // Combine: test machine AND protocol ports AND NOT (private to private) - return testMachineFilter + " and " + portFilter + " and " + privateExclude -} - -func NewPacketCapture(iface, outputPath, protocol string) (*PacketCapture, error) { +func NewPacketCapture(iface, outputPath string) (*PacketCapture, error) { handle, err := pcap.OpenLive(iface, 65535, true, pcap.BlockForever) if err != nil { return nil, fmt.Errorf("pcap open (try running as root): %w", err) } - // Get local IPs dynamically - localIPs, err := getLocalIPs() - if err != nil { - handle.Close() - return nil, fmt.Errorf("failed to get local IPs: %w", err) - } - - // Build and apply BPF filter - bpfFilter := buildBPFFilter(protocol, localIPs) - - if err := handle.SetBPFFilter(bpfFilter); err != nil { - handle.Close() - return nil, fmt.Errorf("failed to set BPF filter '%s': %w", bpfFilter, err) - } - file, err := os.Create(outputPath) if err != nil { handle.Close() diff --git a/internal/qol/measurement.go b/internal/qol/measurement.go index da43fea..da776db 100644 --- a/internal/qol/measurement.go +++ b/internal/qol/measurement.go @@ -93,11 +93,8 @@ func (r *MeasurementRunner) runMeasurement(upstream string, domains []string, qT relPath, _ := filepath.Rel(r.config.OutputDir, csvPath) fmt.Printf(">>> Measuring %s (dnssec=%v, auth=%v%s) → %s\n", upstream, r.config.DNSSEC, r.config.AuthoritativeDNSSEC, keepAliveStr, relPath) - // Setup packet capture - proto := DetectProtocol(upstream) - // Setup packet capture with protocol-aware filtering - packetCapture, err := capture.NewPacketCapture(r.config.Interface, pcapPath, proto) + packetCapture, err := capture.NewPacketCapture(r.config.Interface, pcapPath) if err != nil { return err } diff --git a/run.sh b/run.sh index 617c703..e781597 100755 --- a/run.sh +++ b/run.sh @@ -35,7 +35,7 @@ CONNLESS_SERVERS=( # Common args COMMON_ARGS=( "$DOMAINS_FILE" - --interface eth0 + --interface veth0 --timeout 5s ) diff --git a/setup-netns.sh b/setup-netns.sh new file mode 100755 index 0000000..0f0b218 --- /dev/null +++ b/setup-netns.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Exit on error +set -e + +NETNS_NAME="sdns" +VETH_HOST="veth0" +VETH_NS="veth1" +HOST_IP="192.168.100.1" +NS_IP="192.168.100.2" +SUBNET="192.168.100.0/24" +PHYSICAL_IF="en0" + +echo "Creating network namespace: $NETNS_NAME" +sudo ip netns add $NETNS_NAME + +echo "Creating veth pair: $VETH_HOST <-> $VETH_NS" +sudo ip link add $VETH_HOST type veth peer name $VETH_NS + +echo "Moving $VETH_NS into namespace" +sudo ip link set $VETH_NS netns $NETNS_NAME + +echo "Configuring host side ($VETH_HOST)" +sudo ip addr add $HOST_IP/24 dev $VETH_HOST +sudo ip link set $VETH_HOST up + +echo "Configuring namespace side ($VETH_NS)" +sudo ip netns exec $NETNS_NAME ip addr add $NS_IP/24 dev $VETH_NS +sudo ip netns exec $NETNS_NAME ip link set $VETH_NS up +sudo ip netns exec $NETNS_NAME ip link set lo up +sudo ip netns exec $NETNS_NAME ip route add default via $HOST_IP + +echo "Enabling IP forwarding" +sudo sysctl -w net.ipv4.ip_forward=1 + +echo "Setting up NAT" +sudo iptables -t nat -A POSTROUTING -s $SUBNET -o $PHYSICAL_IF -j MASQUERADE + +echo "Done! Network namespace '$NETNS_NAME' is ready." +echo "" +echo "To run your app in the namespace:" +echo " sudo ip netns exec $NETNS_NAME ./your_app" +echo "" +echo "To capture traffic:" +echo " sudo tshark -i $VETH_HOST -w app.pcap"