feat(netns): add netns
This commit is contained in:
@@ -3,9 +3,7 @@ package capture
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
@@ -21,91 +19,12 @@ type PacketCapture struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalIPs() ([]string, error) {
|
func NewPacketCapture(iface, outputPath string) (*PacketCapture, 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)
|
handle, err := pcap.OpenLive(iface, 65535, true, pcap.BlockForever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("pcap open (try running as root): %w", err)
|
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)
|
file, err := os.Create(outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handle.Close()
|
handle.Close()
|
||||||
|
|||||||
@@ -93,11 +93,8 @@ func (r *MeasurementRunner) runMeasurement(upstream string, domains []string, qT
|
|||||||
relPath, _ := filepath.Rel(r.config.OutputDir, csvPath)
|
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)
|
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
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
2
run.sh
2
run.sh
@@ -35,7 +35,7 @@ CONNLESS_SERVERS=(
|
|||||||
# Common args
|
# Common args
|
||||||
COMMON_ARGS=(
|
COMMON_ARGS=(
|
||||||
"$DOMAINS_FILE"
|
"$DOMAINS_FILE"
|
||||||
--interface eth0
|
--interface veth0
|
||||||
--timeout 5s
|
--timeout 5s
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
45
setup-netns.sh
Executable file
45
setup-netns.sh
Executable 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"
|
||||||
Reference in New Issue
Block a user