{"id":23444090,"url":"https://github.com/sevagh/ape","last_synced_at":"2025-04-13T12:34:22.534Z","repository":{"id":53396758,"uuid":"206687533","full_name":"sevagh/ape","owner":"sevagh","description":"XDP-based packet manipulation tool with Prometheus metrics","archived":false,"fork":false,"pushed_at":"2019-09-10T14:07:17.000Z","size":146,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-05T01:01:52.570Z","etag":null,"topics":["bpf","chaos-engineering","linux","linux-kernel","networking","xdp"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sevagh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-06T01:26:15.000Z","updated_at":"2023-09-12T10:29:51.000Z","dependencies_parsed_at":"2022-08-23T16:10:34.270Z","dependency_job_id":null,"html_url":"https://github.com/sevagh/ape","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/sevagh%2Fape","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sevagh%2Fape/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sevagh%2Fape/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sevagh%2Fape/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sevagh","download_url":"https://codeload.github.com/sevagh/ape/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248714628,"owners_count":21149928,"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":["bpf","chaos-engineering","linux","linux-kernel","networking","xdp"],"created_at":"2024-12-23T18:26:37.376Z","updated_at":"2025-04-13T12:34:22.507Z","avatar_url":"https://github.com/sevagh.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ape - an XDP packet manipulation tool\n\nApe is a tool to manipulate UDP (both ipv4 and ipv6) packets.\n\nIt can:\n\n* **drop** a % of UDP packets (pseudorandomly) on an interface, by port or on all ports\n* **scramble** a % of UDP packets on an interface, by redirecting them to userspace with AF_XDP and randomly sleeping before forwarding it to the original destination\n* **reflect** all the packets from one UDP port to another\n\nDesired future functionality:\n\n* **mirror** packets from one port to another. But how? It triggers an XDP feedback loop. XDP redirects port XXX packets to userspace, userspace sends port XXX packets to XXX and YYY, XDP redirects port XXX packets, etc. XDP metadata solution?\n\nThe learning resource I used to create this project is https://github.com/xdp-project/xdp-tutorial  - I highly recommended it if you want to get started with XDP. `headers/` and `common/` are copied from it. You most likely need the [bpf-next](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git) kernel to use AF_XDP. I was able to follow the [Fedora instructions](https://fedoraproject.org/wiki/Building_a_custom_kernel#Building_Vanilla_upstream_kernel) for installing a custom kernel with no problems.\n\n### architecture\n\nApe looks stitched together, because it is. It consists of the following pieces:\n\n1. Userspace XDP loaders, named `xdp_user_*.c` - these must be built **before** using ape, with `make clean all`\n2. Kernel XDP programs, named `xdp_kern_*.c` - these are compiled at runtime by `ape.py` based on command-line arguments which are converted to `-D` compiler switches\n3. `ape.py`, the main Python script and entrypoint of this project, which parses command-line arguments, builds and attaches the XDP kernel objects, and exposes XDP metrics to Prometheus using `bpftool`\n\n`ape.py` must be run with sudo to execute privileged actions e.g. attaching XDP programs to NICs, but it drops privileges to `SUDO_UID` when compiling the kernel modules to not create root-owned files in the repository.\n\nThe maps intended for packet counts and stats are pinned using `bpffs`, and read using `bpftool`.\n\n### drop\n\nDrop UDP4/6 packets from an interface, with an optional port.\n\nape, dropping 50% of UDP packets on port 1337 on lo:\n```\nsevagh:ape $ sudo ./ape.py --udp-drop 50 --udp-port 1337 lo\nb'Success: Loaded BPF-object(xdp_kern_drop.o) and used section(xdp_ape_drop)\\n - XDP prog attached on device:lo(ifindex:1)\\n - Unpinning (remove) prev maps in /sys/fs/bpf/lo/\\n - Pinning maps in /sys/fs/bpf/lo/\\n'\nStarting bpftool listener for map /sys/fs/bpf/lo/drop_count in thread\nStarted prometheus metrics server at http://localhost:8000\n```\n\nsocat sender:\n```\nsevagh:~ $ for x in 1 2 3 4 5 6 7 8 9 10; do echo \"hello world ${x}\" | socat - UDP6-SENDTO:[::1]:1337; done\n```\n\nsocat receiver:\n```\nsevagh:~ $ socat - UDP6-LISTEN:1337,bind=[::1],fork\nhello world 1\nhello world 2\nhello world 4\nhello world 5\nhello world 7\nhello world 9\nhello world 10\n```\n\nWe can see packets `3, 6, 8` missing. The Prometheus metrics show it, at `http://127.0.0.1:8000`:\n\n```\nape_total_udp_packets{action=\"drop\",device=\"lo\",port=\"1337\"} 10.0\n# HELP ape_manipulated_udp_packets UDP packets manipulated by ape\n# TYPE ape_manipulated_udp_packets gauge\nape_manipulated_udp_packets{action=\"drop\",device=\"lo\",port=\"1337\"} 3.0\n```\n\n### scramble\n\nScramble UDP4/6 packets from an interface, with an optional port.\n\nThe implementation of scramble is much more complex than drop. There's no way to copy a packet, or \"sleep\", in XDP.\n\nI need to use [`AF_XDP`](https://www.kernel.org/doc/html/latest/networking/af_xdp.html), which is a way to redirect packets from the XDP kernel program to userspace. Inside `xdp_user_scramble.c`, I set up UDP4 and UDP6 sender sockets. When the XDP kernel scramble program redirects a packet to the XSK map and I receive it in the user scramble program, I sleep randomly between 0-MAX_SLEEP_MS ms before re-sending it on the appropriate UDP4 or UDP6 socket. This results in packets seemingly being received out of order.\n\nape, scrambling 50% of UDP packets on port 1337 on lo:\n```\nsevagh:ape $ sudo ./ape.py --udp-scramble 50 --udp-port 1337 lo\nsleeping 2s to let module load\nStarting bpftool listener for map /sys/fs/bpf/lo/scramble_count in thread\nStarted prometheus metrics server at http://localhost:8000\n```\n\nsocat sender:\n```\nsevagh:~ $ for x in 1 2 3 4 5 6 7 8 9 10; do echo \"hello world ${x}\" | socat - UDP6-SENDTO:[::1]:1337; done\n```\n\nsocat receiver:\n```\nsevagh:~ $ socat - UDP6-LISTEN:1337,bind=[::1],fork\nhello world 2\nhello world 5\nhello world 3\nhello world 6\nhello world 1\nhello world 8\nhello world 9\nhello world 10\nhello world 7\nhello world 4\n```\n\nWe can see the packets arrive out of order. The Prometheus metrics show it, at `http://127.0.0.1:8000`:\n\n```\nape_total_udp_packets{action=\"scramble\",device=\"lo\",port=\"1337\"} 18.0\n# HELP ape_manipulated_udp_packets UDP packets manipulated by ape\n# TYPE ape_manipulated_udp_packets gauge\nape_manipulated_udp_packets{action=\"scramble\",device=\"lo\",port=\"1337\"} 8.0\n```\n\nThere are more than 10 total packets, because `scramble` will feed back to itself (the XDP kernel program will probably re-scramble packets sent by the user scramble program).\n\n### reflect\n\nReflect, similar to scramble, relies on AF_XDP.\n\nape, reflecting UDP packets from port 1337 to 1234 on lo:\n```\nsevagh:ape $ sudo ./ape.py --udp-reflect 1234 --udp-port 1337 lo\nsleeping 2s to let module load\nStarting bpftool listener for map /sys/fs/bpf/lo/reflect_count in thread\nStarted prometheus metrics server at http://localhost:8000\n```\n\nsocat sender:\n```\nsevagh:~ $ for x in 1 2 3 4 5 6 7 8 9 10; do echo \"hello world ${x}\" | socat - UDP6-SENDTO:[::1]:1337; done\n```\n\nsocat receiver, port 1234:\n```\nsevagh:~ $ socat - UDP6-LISTEN:1234,bind=[::1],fork\nhello world 1\nhello world 2\nhello world 3\nhello world 4\nhello world 5\nhello world 6\nhello world 7\nhello world 8\nhello world 9\nhello world 10\n```\n\nsocat receiver, port 1337:\n```\nsevagh:~ $ socat - UDP6-LISTEN:1337,bind=[::1],fork\n```\n\nApe metrics:\n\n```\n# HELP ape_loaded_xdp_progs XDP kernel programs loaded and attached by ape\n# TYPE ape_loaded_xdp_progs gauge\nape_loaded_xdp_progs{interface=\"lo\",name=\"xdp_ape_reflect\"} 1.0\n# HELP ape_total_udp_packets UDP packets intercepted by ape\n# TYPE ape_total_udp_packets gauge\nape_total_udp_packets{action=\"reflect\",device=\"lo\",port=\"1234\"} 10.0\n# HELP ape_manipulated_udp_packets UDP packets manipulated by ape\n# TYPE ape_manipulated_udp_packets gauge\nape_manipulated_udp_packets{action=\"reflect\",device=\"lo\",port=\"1234\"} 10.0\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsevagh%2Fape","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsevagh%2Fape","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsevagh%2Fape/lists"}