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
}