Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sunet-xdpd

This is a daemon that reads config files that describe BPF (tcpdump) filter expressions that should be applied to a network interface via XDP, and when the filter is matched the matching packet is dropped.

The tool relies heavily on the Cloudflare cbpfc library for generating eBPF from classic BPF. On top of this we use gopacket to allow us the write actual BPF filter strings (like you would do when using tcpdump) which it then compiles into the BPF instructions that cbpfc wants.

A given filter can be applied in a "monitor" mode, and when this is done the packets are matched but not dropped, which can be helpful for figuring out the impact of adding a given filter.

The eBPF code is instrumented to call hook maps for xdpcap, so that you can capture pcap of matched packets for further analysis. There is one hook per interface for dropped packets (<ifname>-drop) and one for packets matched by a filter in monitor mode (<ifname>-monitor), e.g. for eth0:

xdpcap /sys/fs/bpf/sunet-xdpd/eth0-drop - "" | tcpdump -nr -
xdpcap /sys/fs/bpf/sunet-xdpd/eth0-drop - "tcp and port 80" | tcpdump -nr -
xdpcap /sys/fs/bpf/sunet-xdpd/eth0-drop dropped.pcap "tcp and port 80"
xdpcap /sys/fs/bpf/sunet-xdpd/eth0-monitor - "" | tcpdump -nr -

Matched packets are counted per filter and are visible in prometheus metrics available at 127.0.0.1:2112/metrics, e.g.:

curl http://127.0.0.1:2112/metrics | grep ^filter

Configuration

The config is read from every *.json file in the directory given by -config-dir (default /etc/sunet-xdpd/conf.d), and the filters for an interface are appended across files. This way separate processes can each own a file, for example:

/etc/sunet-xdpd/conf.d/00-base.json   # from config management
/etc/sunet-xdpd/conf.d/50-ddos.json   # from a DDoS mitigation tool

See conf.d.sample for the file format. The rules are:

  • Files are read in name order, sorted as strings, so 10-x.json comes before 9-x.json. Names starting with ., other suffixes and directories are skipped.
  • To change a file, write a temporary file starting with . in the same directory and rename it into place, so a half written file is never read. Then reload with pkill -HUP sunet-xdpd, once all files of a change are in place.
  • A reload is all or nothing: if any file is broken (invalid JSON, an unknown field or one given twice, a missing or empty expr, an expression that doesn't compile) nothing changes and the error, naming the file, is logged. A broken file from one writer blocks changes from all of them until it is fixed.
  • Two filters on one interface with the same description, expr and monitor setting are an error, also when they are in different files.
  • Removing a file removes its filters on the next reload. A directory without *.json files is an error rather than detaching every filter, so to run without filters use a file containing {}, or sunet-xdpd -unload.
  • The log line after a load or reload lists the files that were read and how many filters each added.

Building

The Dockerfile is not meant for creating a container, rather it is a way to more easily build a static executable even when the google/gopacket dependency requires use of CGO and needs to link with libpcap for BPF expression compilation. This way we do not have to care about installing libpcap on machines running the tool.

Example for building binaries for two different targets with binaries ending up in the "dist" directory can look like this:

docker buildx build --platform linux/amd64,linux/arm64 -o dist .

Formatting and linting

When working with this code at least the following tools are expected to be run at the top level directory prior to commiting:

About

Daemon that maintains XDP filters on interfaces based on BFP filter expressions

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages