feat(netns): add netns

This commit is contained in:
2025-10-12 01:03:20 +01:00
parent 742f8a2a8e
commit a8aa6bb01c
4 changed files with 48 additions and 87 deletions

View File

@@ -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()

View File

@@ -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
}

2
run.sh
View File

@@ -35,7 +35,7 @@ CONNLESS_SERVERS=(
# Common args
COMMON_ARGS=(
"$DOMAINS_FILE"
--interface eth0
--interface veth0
--timeout 5s
)

45
setup-netns.sh Executable file
View File

@@ -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"