diff --git a/{{project-name}}-go/go.mod b/{{project-name}}-go/go.mod new file mode 100644 index 0000000..ac23d8d --- /dev/null +++ b/{{project-name}}-go/go.mod @@ -0,0 +1,7 @@ +module gaya.com/m + +go 1.24.4 + +require github.com/cilium/ebpf v0.20.0 + +require golang.org/x/sys v0.37.0 // indirect diff --git a/{{project-name}}-go/go.sum b/{{project-name}}-go/go.sum new file mode 100644 index 0000000..584bb05 --- /dev/null +++ b/{{project-name}}-go/go.sum @@ -0,0 +1,26 @@ +github.com/cilium/ebpf v0.20.0 h1:atwWj9d3NffHyPZzVlx3hmw1on5CLe9eljR8VuHTwhM= +github.com/cilium/ebpf v0.20.0/go.mod h1:pzLjFymM+uZPLk/IXZUL63xdx5VXEo+enTzxkZXdycw= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= +github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff --git a/{{project-name}}-go/main.go b/{{project-name}}-go/main.go new file mode 100644 index 0000000..9a3a6e7 --- /dev/null +++ b/{{project-name}}-go/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "context" + "fmt" + "log" + "net" + "os" + "os/signal" + "syscall" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/ringbuf" +) + +const progName = "{{crate_name}}" + +func main() { + if len(os.Args) < 2 { + fmt.Printf("Usage: %s \n", os.Args[0]) + os.Exit(1) + } + ifaceName := os.Args[1] + + spec, err := ebpf.LoadCollectionSpec("/tmp/{{project-name}}") + if err != nil { + log.Fatalf("LoadCollectionSpec failed: %v", err) + } + + coll, err := ebpf.NewCollection(spec) + if err != nil { + log.Fatalf("NewCollection failed: %v", err) + } + defer coll.Close() + + prog := coll.Programs[progName] + if prog == nil { + log.Fatalf("Program %s not found", progName) + } + + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + log.Fatalf("Interface not found: %v", err) + } + + l, err := link.AttachXDP(link.XDPOptions{ + Program: prog, + Interface: iface.Index, + }) + if err != nil { + log.Fatalf("AttachXDP failed: %v", err) + } + defer l.Close() + fmt.Printf("✅ Program '%s' attached to %s\n", progName, ifaceName) + + logMap, ok := coll.Maps["AYA_LOGS"] + if !ok { + log.Fatal("AYA_LOGS map not found") + } + + reader, err := ringbuf.NewReader(logMap) + if err != nil { + log.Fatalf("failed to create ringbuf reader: %v", err) + } + defer reader.Close() + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + go func() { + fmt.Println("Listening to Aya logs...") + for { + select { + case <-ctx.Done(): + return + default: + record, err := reader.Read() + if err != nil { + continue + } + fmt.Printf("Aya log: %s\n", string(record.RawSample)) + } + } + }() + + <-ctx.Done() + fmt.Println("Shutting down...") +} diff --git a/{{project-name}}/src/main.rs b/{{project-name}}/src/main.rs index 2d7eccd..8c2fe02 100644 --- a/{{project-name}}/src/main.rs +++ b/{{project-name}}/src/main.rs @@ -1,82 +1,8 @@ -{%- case program_type -%} -{%- when "kprobe", "kretprobe" -%} -use aya::programs::KProbe; -{%- when "fentry" -%} -use anyhow::Context as _; -use aya::{Btf, programs::FEntry}; -{%- when "fexit" -%} -use anyhow::Context as _; -use aya::{Btf, programs::FExit}; -{%- when "uprobe", "uretprobe" -%} -use aya::programs::UProbe; -{%- when "sock_ops" -%} -use anyhow::Context as _; -use aya::programs::{SockOps, links::CgroupAttachMode}; -{%- when "sk_msg" -%} -use aya::{maps::SockHash, programs::SkMsg}; -use {{crate_name}}_common::SockKey; -{%- when "xdp" -%} -use anyhow::Context as _; -use aya::programs::{Xdp, XdpFlags}; -{%- when "classifier" -%} -use aya::programs::{SchedClassifier, TcAttachType, tc}; -{%- when "cgroup_skb" -%} -use anyhow::Context as _; -use aya::programs::{CgroupSkb, CgroupSkbAttachType, links::CgroupAttachMode}; -{%- when "cgroup_sysctl" -%} -use anyhow::Context as _; -use aya::programs::{CgroupSysctl, links::CgroupAttachMode}; -{%- when "cgroup_sockopt" -%} -use anyhow::Context as _; -use aya::programs::{CgroupSockopt, links::CgroupAttachMode}; -{%- when "tracepoint" -%} -use aya::programs::TracePoint; -{%- when "lsm" -%} -use aya::{Btf, programs::Lsm}; -{%- when "perf_event" -%} -use aya::{ - programs::{PerfEvent, perf_event}, - util::online_cpus, -}; -{%- when "tp_btf" -%} -use aya::{Btf, programs::BtfTracePoint}; -{%- when "socket_filter" -%} -use aya::programs::SocketFilter; -{%- when "raw_tracepoint" -%} -use aya::programs::RawTracePoint; -{%- endcase %} -{% if program_types_with_opts contains program_type -%} -use clap::Parser; -{% endif -%} - #[rustfmt::skip] -use log::{debug, warn}; -use tokio::signal; - -{% if program_types_with_opts contains program_type -%} -#[derive(Debug, Parser)] -struct Opt { -{%- case program_type -%} -{%- when "xdp", "classifier" %} - #[clap(short, long, default_value = "{{default_iface}}")] - iface: String, -{%- when "sock_ops", "cgroup_skb", "cgroup_sysctl", "cgroup_sockopt" %} - #[clap(short, long, default_value = "/sys/fs/cgroup")] - cgroup_path: std::path::PathBuf, -{%- when "uprobe", "uretprobe" %} - #[clap(short, long)] - pid: Option, -{%- endcase %} -} - -{% endif -%} -#[tokio::main] -async fn main() -> anyhow::Result<()> { -{%- if program_types_with_opts contains program_type %} - let opt = Opt::parse(); -{% endif %} - env_logger::init(); +use log::debug; +use std::fs; +fn main() -> anyhow::Result<()> { // Bump the memlock rlimit. This is needed for older kernels that don't use the // new memcg based accounting, see https://lwn.net/Articles/837122/ let rlim = libc::rlimit { @@ -88,139 +14,9 @@ async fn main() -> anyhow::Result<()> { debug!("remove limit on locked memory failed, ret is: {ret}"); } - // This will include your eBPF object file as raw bytes at compile-time and load it at - // runtime. This approach is recommended for most real-world use cases. If you would - // like to specify the eBPF program at runtime rather than at compile-time, you can - // reach for `Bpf::load_file` instead. - let mut ebpf = aya::Ebpf::load(aya::include_bytes_aligned!(concat!( - env!("OUT_DIR"), - "/{{project-name}}" - )))?; - match aya_log::EbpfLogger::init(&mut ebpf) { - Err(e) => { - // This can happen if you remove all log statements from your eBPF program. - warn!("failed to initialize eBPF logger: {e}"); - } - Ok(logger) => { - let mut logger = - tokio::io::unix::AsyncFd::with_interest(logger, tokio::io::Interest::READABLE)?; - tokio::task::spawn(async move { - loop { - let mut guard = logger.readable_mut().await.unwrap(); - guard.get_inner_mut().flush(); - guard.clear_ready(); - } - }); - } - } - {%- case program_type -%} - {%- when "kprobe", "kretprobe" %} - let program: &mut KProbe = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach("{{kprobe}}", 0)?; - {%- when "fentry" %} - let btf = Btf::from_sys_fs().context("BTF from sysfs")?; - let program: &mut FEntry = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load("{{fn_name}}", &btf)?; - program.attach()?; - {%- when "fexit" %} - let btf = Btf::from_sys_fs().context("BTF from sysfs")?; - let program: &mut FExit = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load("{{fn_name}}", &btf)?; - program.attach()?; - {%- when "uprobe", "uretprobe" %} - let Opt { pid } = opt; - let program: &mut UProbe = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach("{{uprobe_fn_name}}", "{{uprobe_target}}", pid, None /* cookie */)?; - {%- when "sock_ops", "cgroup_skb", "cgroup_sysctl", "cgroup_sockopt" %} - let Opt { cgroup_path } = opt; - let cgroup = - std::fs::File::open(&cgroup_path).with_context(|| format!("{}", cgroup_path.display()))?; - {%- if program_type == "sock_ops" %} - let program: &mut SockOps = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach(cgroup, CgroupAttachMode::default())?; - {%- elsif program_type == "cgroup_skb" %} - let program: &mut CgroupSkb = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach( - cgroup, - CgroupSkbAttachType::{{direction}}, - CgroupAttachMode::default(), - )?; - {%- elsif program_type == "cgroup_sysctl" %} - let program: &mut CgroupSysctl = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach(cgroup, CgroupAttachMode::default())?; - {%- elsif program_type == "cgroup_sockopt" %} - let program: &mut CgroupSockopt = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach(cgroup, CgroupAttachMode::default())?; - {%- endif -%} - {%- when "sk_msg" %} - let sock_map: SockHash<_, SockKey> = ebpf.map("{{sock_map}}").unwrap().try_into()?; - let map_fd = sock_map.fd().try_clone()?; - let prog: &mut SkMsg = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - prog.load()?; - prog.attach(&map_fd)?; - // insert sockets to the map using sock_map.insert here, or from a sock_ops program - {%- when "xdp" %} - let Opt { iface } = opt; - let program: &mut Xdp = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach(&iface, XdpFlags::default()) - .context("failed to attach the XDP program with default flags - try changing XdpFlags::default() to XdpFlags::SKB_MODE")?; - {%- when "classifier" %} - let Opt { iface } = opt; - // error adding clsact to the interface if it is already added is harmless - // the full cleanup can be done with 'sudo tc qdisc del dev eth0 clsact'. - let _ = tc::qdisc_add_clsact(&iface); - let program: &mut SchedClassifier = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach(&iface, TcAttachType::{{direction}})?; - {%- when "tracepoint" %} - let program: &mut TracePoint = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach("{{tracepoint_category}}", "{{tracepoint_name}}")?; - {%- when "lsm" %} - let btf = Btf::from_sys_fs()?; - let program: &mut Lsm = ebpf.program_mut("{{lsm_hook}}").unwrap().try_into()?; - program.load("{{lsm_hook}}", &btf)?; - program.attach()?; - {%- when "tp_btf" %} - let btf = Btf::from_sys_fs()?; - let program: &mut BtfTracePoint = ebpf.program_mut("{{tracepoint_name}}").unwrap().try_into()?; - program.load("{{tracepoint_name}}", &btf)?; - program.attach()?; - {%- when "socket_filter" %} - let listener = std::net::TcpListener::bind("localhost:0")?; - let prog: &mut SocketFilter = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - prog.load()?; - prog.attach(&listener)?; - {%- when "perf_event" %} - // This will raise scheduled events on each CPU at 1 HZ, triggered by the kernel based - // on clock ticks. - let program: &mut PerfEvent = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - for cpu in online_cpus().map_err(|(_, error)| error)? { - program.attach( - perf_event::PerfEventConfig::Software(perf_event::SoftwareEvent::CpuClock), - perf_event::PerfEventScope::AllProcessesOneCpu { cpu }, - perf_event::SamplePolicy::Frequency(1), - true, - )?; - } - {%- when "raw_tracepoint" %} - let program: &mut RawTracePoint = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; - program.load()?; - program.attach("{{tracepoint_name}}")?; - {%- endcase %} - - let ctrl_c = signal::ctrl_c(); - println!("Waiting for Ctrl-C..."); - ctrl_c.await?; - println!("Exiting..."); + let src = concat!(env!("OUT_DIR"), "/{{project-name}}"); + let dst = "/tmp/{{project-name}}"; + fs::copy(src, dst)?; Ok(()) }