From fa756500d3ebc7775b28116626723d88bdc06d69 Mon Sep 17 00:00:00 2001 From: Alexey Novikov Date: Fri, 7 Feb 2025 13:32:34 +0300 Subject: [PATCH] initial --- .gitignore | 3 ++ flake.lock | 60 ++++++++++++++++++++++++++ flake.nix | 64 ++++++++++++++++++++++++++++ gen.go | 3 ++ go.mod | 8 ++++ go.sum | 4 ++ main.go | 56 ++++++++++++++++++++++++ xdp.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 320 insertions(+) create mode 100644 .gitignore create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 gen.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 xdp.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7605c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +pinger_bpf* +cursed-ping +result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..89fa686 --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1738680400, + "narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "799ba5bffed04ced7067a91798353d360788b30d", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..dc668fb --- /dev/null +++ b/flake.nix @@ -0,0 +1,64 @@ +{ + description = "cursed ping"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + ... + }: + { + overlays.default = final: prev: { + inherit (self.packages.${prev.system}) bpf2go; + }; + } // flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [ self.overlays.default ]; + }; + in { + packages = { + bpf2go = pkgs.buildGoModule rec { + pname = "bpf2go"; + version = "0.17.2"; + src = pkgs.fetchFromGitHub { + owner = "cilium"; + repo = "ebpf"; + rev = "v${version}"; + hash = "sha256-lIJsITwdt6PuTMEHXJekBKM2rgCdPJF+i+QHt0jBgp8="; + }; + vendorHash = "sha256-Ygljpp5GVM2EbD51q1ufqA6z5yJnrXVEfVLIPrxfm18="; + subPackages = [ "cmd/bpf2go" ]; + doCheck = false; + }; + pinger = pkgs.buildGoModule rec { + pname = "cursed-ping"; + version = "1"; + src = ./.; + vendorHash = "sha256-RxmMgiMATpO31VP85dlkOXl/nLbVD5W1dfWHhuGKcME="; + deleteVendor = true; + + ldflags = ["-s -w"]; + env.CGO_ENABLED = 0; + nativeBuildInputs = with pkgs; [clang bpftools libllvm linuxHeaders bpf2go glibc_multi libbpf]; + hardeningDisable = [ "all" ]; + buildInputs = nativeBuildInputs; + + postConfigure = '' + go generate ./... + ''; + + meta = with pkgs.lib; { + description = "cursed ping"; + license = licenses.wtfpl; + }; + }; + }; + formatter = pkgs.alejandra; + }); +} diff --git a/gen.go b/gen.go new file mode 100644 index 0000000..cd1a911 --- /dev/null +++ b/gen.go @@ -0,0 +1,3 @@ +package main + +//go:generate bpf2go pinger xdp.c diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8f99509 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/x3lfyn/cursed-ping + +go 1.23.3 + +require ( + github.com/cilium/ebpf v0.17.1 // indirect + golang.org/x/sys v0.26.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7bcd880 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/cilium/ebpf v0.17.1 h1:G8mzU81R2JA1nE5/8SRubzqvBMmAmri2VL8BIZPWvV0= +github.com/cilium/ebpf v0.17.1/go.mod h1:vay2FaYSmIlv3r8dNACd4mW/OCaZLJKJOo+IHBvCIO8= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..fe7452e --- /dev/null +++ b/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "errors" + "fmt" + "log" + "net" + "os" + "os/signal" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" +) + +func main() { + if len(os.Args) < 2 { + log.Fatalf("specify a network interface") + } + + ifaceName := os.Args[1] + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + log.Fatalf("lookup network iface %q: %s", ifaceName, err) + } + + var objs pingerObjects + if err := loadPingerObjects(&objs, nil); err != nil { + var verr *ebpf.VerifierError + if errors.As(err, &verr) { + fmt.Printf("%+v\n", verr) + } + + } + defer objs.Close() + + link, err := link.AttachXDP(link.XDPOptions{ + Program: objs.Pinger, + Interface: iface.Index, + }) + if err != nil { + log.Fatal("attaching XDP:", err) + } + defer link.Close() + + log.Printf("attached to %s", ifaceName) + + stop := make(chan os.Signal, 5) + signal.Notify(stop, os.Interrupt) + for { + select { + case <-stop: + log.Print("received signal, exiting..") + return + } + } +} diff --git a/xdp.c b/xdp.c new file mode 100644 index 0000000..71290ed --- /dev/null +++ b/xdp.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include +#include +#include + +#define bpf_memcpy __builtin_memcpy + +__attribute__((__always_inline__)) static inline __u16 csum_fold_helper( + __u64 csum) { + int i; +#pragma unroll + for (i = 0; i < 4; i++) { + if (csum >> 16) + csum = (csum & 0xffff) + (csum >> 16); + } + return ~csum; +} + +// https://github.com/AirVantage/sbulb/blob/master/sbulb/bpf/checksum.c#L21 +__attribute__((__always_inline__)) +static inline void update_csum(__u64 *csum, __be32 old_addr,__be32 new_addr ) { + // ~HC + *csum = ~*csum; + *csum = *csum & 0xffff; + // + ~m + __u32 tmp; + tmp = ~old_addr; + *csum += tmp; + // + m + *csum += new_addr; + // then fold and complement result ! + *csum = csum_fold_helper(*csum); +} + +__attribute__((__always_inline__)) +static inline void recalc_icmp_csum(struct icmphdr* hdr, __be32 old_value, __be32 new_value) { + __u64 csum = hdr->checksum; + update_csum(&csum, old_value, new_value); + hdr->checksum = csum; +} + +__attribute__((__always_inline__)) +static inline void recalc_ip_csum(struct iphdr* hdr, __be32 old_value, __be32 new_value) { + __u64 csum = hdr->check; + update_csum(&csum, old_value, new_value); + hdr->check = csum; +} + +SEC("xdp") +int pinger(struct xdp_md* ctx) { + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + + struct ethhdr *eth = data; + if ((void *)(eth + 1) > data_end) { + return XDP_PASS; + } + + if (bpf_ntohs(eth->h_proto) != ETH_P_IP) { + return XDP_PASS; + } + + struct iphdr *iph = (void*)(eth + 1); + if ((void*)(iph + 1) > data_end) { + return XDP_PASS; + } + + if (iph->protocol != IPPROTO_ICMP) { + return XDP_PASS; + } + + struct icmphdr* icmphdr = (void*)(iph + 1); + if ((void*)(icmphdr + 1) > data_end) { + return XDP_PASS; + } + + if (icmphdr->type != 8) { + return XDP_PASS; + } + + __u8 tmp_mac[ETH_ALEN]; + bpf_memcpy(tmp_mac, eth->h_dest, ETH_ALEN); + bpf_memcpy(eth->h_dest, eth->h_source, ETH_ALEN); + bpf_memcpy(eth->h_source, tmp_mac, ETH_ALEN); + + __u32 tmp_ip = iph->daddr; + iph->daddr = iph->saddr; + iph->saddr = tmp_ip; + + icmphdr->type = 0; + recalc_icmp_csum(icmphdr, 8, icmphdr->type); + + __u64* ts_secs = (void*)(icmphdr + 1); + __u64* ts_nsecs = (void*)(icmphdr + 1) + sizeof(__u64); + + if ((void*)ts_nsecs + sizeof(__u64) <= data_end) { + __u64 old_secs = *ts_secs; + __u64 old_nsecs = *ts_nsecs; + + *ts_secs -= bpf_get_prandom_u32() % 500; + *ts_nsecs -= bpf_get_prandom_u32(); + + recalc_icmp_csum(icmphdr, old_secs, *ts_secs); + recalc_icmp_csum(icmphdr, old_nsecs, *ts_nsecs); + } + + __u8 old_ttl = iph->ttl; + iph->ttl = bpf_get_prandom_u32() % 200 + 40; + recalc_ip_csum(iph, old_ttl, iph->ttl); + + __be16 old_seq = icmphdr->un.echo.sequence; + icmphdr->un.echo.sequence = bpf_htons(bpf_get_prandom_u32() % 1000); + recalc_icmp_csum(icmphdr, old_seq, icmphdr->un.echo.sequence); + + return XDP_TX; +} + +char LICENSE[] SEC("license") = "GPL"; +