fix(pcap): add pcap filters
This commit is contained in:
@@ -3,7 +3,9 @@ package capture
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
@@ -19,12 +21,91 @@ type PacketCapture struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewPacketCapture(iface, outputPath string) (*PacketCapture, 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) {
|
||||
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()
|
||||
|
||||
@@ -94,7 +94,10 @@ func (r *MeasurementRunner) runMeasurement(upstream string, domains []string, qT
|
||||
fmt.Printf(">>> Measuring %s (dnssec=%v, auth=%v%s) → %s\n", upstream, r.config.DNSSEC, r.config.AuthoritativeDNSSEC, keepAliveStr, relPath)
|
||||
|
||||
// Setup packet capture
|
||||
packetCapture, err := capture.NewPacketCapture(r.config.Interface, pcapPath)
|
||||
proto := DetectProtocol(upstream)
|
||||
|
||||
// Setup packet capture with protocol-aware filtering
|
||||
packetCapture, err := capture.NewPacketCapture(r.config.Interface, pcapPath, proto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,6 +110,7 @@ func (r *MeasurementRunner) runMeasurement(upstream string, domains []string, qT
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
// Run measurements
|
||||
return r.runQueries(dnsClient, upstream, domains, qType, writer, packetCapture)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user