{"id":17499599,"url":"https://github.com/pothos/bpf-cgroup-filter","last_synced_at":"2025-04-14T14:28:51.103Z","repository":{"id":142163453,"uuid":"194565201","full_name":"pothos/bpf-cgroup-filter","owner":"pothos","description":"Examples for cgroup socket ingress/egress BPF filters with systemd","archived":false,"fork":false,"pushed_at":"2020-07-24T17:10:41.000Z","size":20,"stargazers_count":13,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-04T22:35:39.419Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://kailueke.gitlab.io/systemd-bpf-firewall-loader/","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pothos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-30T22:00:12.000Z","updated_at":"2025-02-01T12:16:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"9d780e65-7083-4dfa-8d9b-7936e9473218","html_url":"https://github.com/pothos/bpf-cgroup-filter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pothos%2Fbpf-cgroup-filter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pothos%2Fbpf-cgroup-filter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pothos%2Fbpf-cgroup-filter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pothos%2Fbpf-cgroup-filter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pothos","download_url":"https://codeload.github.com/pothos/bpf-cgroup-filter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248896528,"owners_count":21179449,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-19T17:34:44.933Z","updated_at":"2025-04-14T14:28:51.080Z","avatar_url":"https://github.com/pothos.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Examples for cgroup socket ingress/egress BPF filters with systemd\n\nThese are examples for my [blogpost about custom BPF firewalls for systemd services](https://kailueke.gitlab.io/systemd-custom-bpf-firewall/)\nthat I implemented in [this commit](https://github.com/systemd/systemd/commit/fab347489fcfafbc8367c86afc637ce1b81ae59e).\nYou can find more explanations and examples on how to use it when reading the blog post.\n\n## Update: Port-based BPF firewall compiled with clang\n\nThe [port-firewall folder](port-firewall/) contains a\nsmall configurable packet filter that parses IP/IPv6 packets, ICMP, UDP ports,\nand TCP ports.\nThe forward rule is a C expression passed as `FILTER` variable\nto the compiler with `-D`.\n\nThe expression can use the boolean variables `udp`, `tcp`, `icmp`, `ip`, and `ipv6` denoting the packet type and the the integers `dst_port` and `src_port` for the UDP/TCP ports.\nIf the expression evaluates to 0 (false), the packet will be dropped.\nValid filters examples are `FILTER='icmp || (udp \u0026\u0026 dst_port == 53) || (tcp \u0026\u0026 dst_port == 80)'` or `FILTER='!udp || dst_port == 53'`.\n\nThe makefile requires to pass the filter to build the program: `make FILTER='…'`.\nWith `make load` the bytecode is loaded to `/sys/fs/bpf/port-firewall` as pinned BPF program in the special BPF filesystem.\n\nFrom there you can use it with the systemd options `IP(Ingress|Egress)FilterPath=` or attach\nit manually to a cgroup.\n\nThe [folder](port-firewall/) also includes a `bpf-make.service` systemd unit file to configure and load the firewall\nand an example `my-filtered-ping.service` file that uses the loaded firewall.\nIt includes an workaround you can use to not require systemd v243.\n\nInstead of making a custom loader unit for every service you can also use the systemd unit template\n`bpf-firewall@.service` by as done in the `service-with-filter@.service` file.\nThis is also a unit template used through `systemctl start \"service-with-filter@icmp || (udp \u0026\u0026 dst_port == 53).service\"`\nbut it can also be a regular service without the `@`. The disadvantage is that you have to specify the filter more than\nonce it the file but the advantage is that it's you don't use templates unnecessary and don't have the filter as (ugly)\npart of the service name.\nIdeally I would store the filter in a BPF map so that it can be set after BPF program loading, allowing it to be\na simple `ExecStartPre` line in the service file.\n\nThe next section shows how to load and use a simple dropping filter as template for your own filters if you don't want to use this one.\n\n## Simple dropping filter compiled with clang\n\nIn the [bpf-program-only folder](bpf-program-only/) is a\nminimal BPF cgroup filter dropping all packets.\nYou can build it with `make` and then run\n`make load` to `/sys/fs/bpf/cgroup-sock-drop`.\nThis will load it with `bpftool` to\n`/sys/fs/bpf/cgroup-sock-drop`.\n\nYou can use this to specify an `IPIngressFilterPath` or `IPEgressFilterPath`\nfor systemd services (\u003e= 243).\nHere an example with ping running in a temporary systemd system scope (or service)\nwith an ingress filter but no egress filter. You can also use user scopes without `sudo` by passing `--user`.\n\n```\n$ sudo systemd-run -p IPIngressFilterPath=/sys/fs/bpf/cgroup-sock-drop --scope ping 127.0.0.1\nRunning scope as unit: run-re62ba1c….scope\nPING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.\n^C # cancel since it will not get responses\n--- 127.0.0.1 ping statistics ---\n8 packets transmitted, 0 received, 100% packet loss, time 186ms\n```\n\nYou can also find the example service `my-ping.service` configured with a filter\nthat is loaded from its dependency service `cgroup-sock-drop-filter.service` (please note\nthe `LimitMEMLOCK=infinity` entry in the unit that load the filter). When you want to use\nit for your service to start at boot you need to add, e.g.,\n`After=network.target` and an `[Install]` section with `WantedBy=multi-user.target`.\n\nUntil systemd 243 is released you can also try the interactive MTU filter program from the next section below which has an option to attach the filter to a cgroup.\n\n### Workaround when systemd 243 is not available\nUse `systemd-run` to spawn a shell in a new cgroup either as system scope or user scope (a temporary service). `-S` can be replaced with a concrete binary if you don't want to start a shell.\n\n```\n$ sudo systemd-run --scope -S\n$ # or:\n$ systemd-run --user --scope -S\nRunning scope as unit: run-r63de6b74621b4ae3877d4fa86b54be75.scope\n```\n\nThis will print out the unit name which is also the name of the cgroup. The full cgroup path for the system service shell is `/sys/fs/cgroup/unified/system.slice/NAME`. For the user service shell the path is `/sys/fs/cgroup/unified/user.slice/user-1000.slice/user@1000.service/NAME` depening on your UID not being `1000`.\n\nThen attach the BPF program to the cgroup:\n\n```\n$ sudo $(which bpftool) cgroup attach /sys/fs/cgroup/unified/user.slice/user-1000.slice/user@1000.service/run-rfaa93ac79de2482d8ef1870fd6b508cd.scope egress pinned /sys/fs/bpf/cgroup-sock-drop multi\n```\n\nYou can either choose `ingress` or `egress` to filter incoming or outgoing packets. You can load the same filter for both `ingress` and `egrees` and you can load multiple different filters per `ingress`/`egress` (which also true when used through the systemd v243 option above).\nIf you turn on `IPAccounting` in `systemd-run` you need to turn on `Delegate` as well to allow multiple BPF programs.\n\n## Interactive MTU filter\n\nIn the [standalone folder](standalone/) is an interactive program\nthat loads a BPF filter and controls its behavior.\n\n_From the source code comment:_\nLoads a BPF cgroup ingress/egress filter bytecode that filters based on the packet size.\nIt loads the BPF filter to a given location in `/sys/fs/bpf/`.\nThrough the +/- keys the MTU can be changed interactively (changes values in the BPF map).\nOptionally the initial MTU value can be specified on startup.\nThe program can also attach the BPF filter to a cgroup by specifying the cgroup by its path.\nThe BPF filter stays loaded when the program exits and has to be deleted manually.\n\nIt does not use a BPF compiler but uses hardcoded BPF assembly instructions\nto include the BPF code in the final program. Not very accessible for hacking\nbut for me it was interesting to see how BPF instructions work\nand what needs to be done to comply with the verifier.\n\n### With systemd \u003e= 243\nIn one terminal you can run the interactive filter:\n\n```\n$ sudo ./load_and_control_filter -m 100 -t ingress /sys/fs/bpf/ingressfilter\ncgroup dropped 0 packets, forwarded 0 packets, MTU is 100 bytes (Press +/- to change)\n… # keeps running\n```\n\nIt loaded the BPF filter to `/sys/fs/bpf/ingressfilter` which you can use for a\nsystemd service.\n\nIn another terminal you can, for example, again run ping as root in a temporary\nsystemd scope and specify our filter as `IPIngressFilterPath`:\n\n```\n$ sudo systemd-run -p IPIngressFilterPath=/sys/fs/bpf/ingressfilter --scope ping 127.0.0.1\nRunning scope as unit: run-….scope\nPING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.\n64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.086 ms\n64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.069 ms\n…\n```\n\nWhen you switch back to the first terminal and press `-`, the new MTU is 50 bytes\nand you can see the dropped packet count increase.\nIn the ping terminal you will see no new responses because they are all dropped.\n\n### Workaround when systemd 243 is not available\n\nThe `load_and_control_filter` program can be told to attach the filter to a cgroup\nof a systemd service.\n\nSystemd uses a BPF filter for its IP accounting and firewalling based on IP addresses.\nIf such a filter is present but no others, the flag to allow multiple BPF filters for a cgroup is missing.\nAs workaround when, e.g., IP accounting is enabled, you can tell systemd that the cgroup management is done by externally.\nThis means that systemd will use the flag to allow multiple BPF filters instead of loading the\nIP accounting BPF filter without this flag.\n\n```\n$ sudo systemd-run -p IPAccounting=yes -p Delegate=yes --scope ping 127.0.0.1\nRunning scope as unit: run-r9f31b3947f4c4a11a24babf5517fe025.scope\nPING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.\n64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.086 ms\n64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.069 ms\n…\n```\n\nYou can see the scope name in the first output line.\nThis is also the last part of the cgroup path you have to use as argument\nin order to attach the filter to the cgroup.\n\n```\n$ sudo ./load_and_control_filter -m 100 -c /sys/fs/cgroup/unified/system.slice/run-r9f31b3947f4c4a11a24babf5517fe025.scope -t ingress /sys/fs/bpf/myfilter\ncgroup dropped 0 packets, forwarded 4 packets, MTU is 100 bytes (Press +/- to change)\n… # keeps running and increases the forward count\n```\n\nNow hit `-` to reduce the MTU and observe the packet drop count increasing while no ping responses can be seen.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpothos%2Fbpf-cgroup-filter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpothos%2Fbpf-cgroup-filter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpothos%2Fbpf-cgroup-filter/lists"}