From 8bd8233c9641559f4a6e9b78e3e5a0dee709b6df Mon Sep 17 00:00:00 2001
From: Nitish Malhotra <nitish.malhotra@gmail.com>
Date: Sat, 30 Oct 2021 19:24:42 -0700
Subject: [PATCH] Add xdp example from book to aya repo

Adding the source for the xdp example from the bookfor easier reference.

Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com>
---
 examples/Cargo.toml                        |   2 +
 examples/README.md                         |  28 +++
 examples/myapp-common/Cargo.toml           |  14 ++
 examples/myapp-common/src/lib.rs           |  10 ++
 examples/myapp-ebpf/.cargo/config.toml     |   6 +
 examples/myapp-ebpf/.vim/coc-settings.json |   4 +
 examples/myapp-ebpf/Cargo.toml             |  25 +++
 examples/myapp-ebpf/rust-toolchain.toml    |   2 +
 examples/myapp-ebpf/src/bindings.rs        | 189 +++++++++++++++++++++
 examples/myapp-ebpf/src/main.rs            |  95 +++++++++++
 examples/myapp/Cargo.toml                  |  19 +++
 examples/myapp/src/main.rs                 |  68 ++++++++
 examples/xtask/Cargo.toml                  |  11 ++
 examples/xtask/src/build_ebpf.rs           |  62 +++++++
 examples/xtask/src/codegen.rs              |  16 ++
 examples/xtask/src/main.rs                 |  32 ++++
 16 files changed, 583 insertions(+)
 create mode 100644 examples/Cargo.toml
 create mode 100644 examples/README.md
 create mode 100644 examples/myapp-common/Cargo.toml
 create mode 100644 examples/myapp-common/src/lib.rs
 create mode 100644 examples/myapp-ebpf/.cargo/config.toml
 create mode 100644 examples/myapp-ebpf/.vim/coc-settings.json
 create mode 100644 examples/myapp-ebpf/Cargo.toml
 create mode 100644 examples/myapp-ebpf/rust-toolchain.toml
 create mode 100644 examples/myapp-ebpf/src/bindings.rs
 create mode 100644 examples/myapp-ebpf/src/main.rs
 create mode 100644 examples/myapp/Cargo.toml
 create mode 100644 examples/myapp/src/main.rs
 create mode 100644 examples/xtask/Cargo.toml
 create mode 100644 examples/xtask/src/build_ebpf.rs
 create mode 100644 examples/xtask/src/codegen.rs
 create mode 100644 examples/xtask/src/main.rs

diff --git a/examples/Cargo.toml b/examples/Cargo.toml
new file mode 100644
index 00000000..6770788c
--- /dev/null
+++ b/examples/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["myapp", "myapp-common", "xtask"]
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..fa7de6a7
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,28 @@
+# myapp
+
+## Prerequisites
+
+1. Install a rust stable toolchain: `rustup install stable`
+1. Install a rust nightly toolchain: `rustup install nightly`
+1. Install bpf-linker: `cargo install bpf-linker`
+
+## Build eBPF
+
+```bash
+cargo xtask build-ebpf
+```
+
+To perform a release build you can use the `--release` flag.
+You may also change the target architecture with the `--target` flag
+
+## Build Userspace
+
+```bash
+cargo build
+```
+
+## Run
+
+```bash
+sudo target/debug/myapp --path target/bpfel-unknown-none/debug/myapp
+```
diff --git a/examples/myapp-common/Cargo.toml b/examples/myapp-common/Cargo.toml
new file mode 100644
index 00000000..b8e2b041
--- /dev/null
+++ b/examples/myapp-common/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "myapp-common"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+default = []
+userspace = [ "aya" ]
+
+[dependencies]
+aya = { git = "https://github.com/aya-rs/aya", branch="main", optional=true }
+
+[lib]
+path = "src/lib.rs"
\ No newline at end of file
diff --git a/examples/myapp-common/src/lib.rs b/examples/myapp-common/src/lib.rs
new file mode 100644
index 00000000..7594c354
--- /dev/null
+++ b/examples/myapp-common/src/lib.rs
@@ -0,0 +1,10 @@
+#![no_std]
+
+#[repr(C)]
+pub struct PacketLog {
+    pub ipv4_address: u32,
+    pub action: u32,
+}
+
+#[cfg(feature = "user")]
+unsafe impl aya::Pod for PacketLog {}
diff --git a/examples/myapp-ebpf/.cargo/config.toml b/examples/myapp-ebpf/.cargo/config.toml
new file mode 100644
index 00000000..5d7e5915
--- /dev/null
+++ b/examples/myapp-ebpf/.cargo/config.toml
@@ -0,0 +1,6 @@
+[build]
+target-dir = "../target"
+target = "bpfel-unknown-none"
+
+[unstable]
+build-std = ["core"]
\ No newline at end of file
diff --git a/examples/myapp-ebpf/.vim/coc-settings.json b/examples/myapp-ebpf/.vim/coc-settings.json
new file mode 100644
index 00000000..e2211a64
--- /dev/null
+++ b/examples/myapp-ebpf/.vim/coc-settings.json
@@ -0,0 +1,4 @@
+{
+    "rust-analyzer.cargo.target": "bpfel-unknown-none",
+    "rust-analyzer.checkOnSave.allTargets": false
+}
diff --git a/examples/myapp-ebpf/Cargo.toml b/examples/myapp-ebpf/Cargo.toml
new file mode 100644
index 00000000..c9cf25b9
--- /dev/null
+++ b/examples/myapp-ebpf/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "myapp-ebpf"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+aya-bpf = { git = "http://github.com/aya-rs/aya", branch = "main" }
+myapp-common = { path = "../myapp-common" }
+memoffset = "0.6.4"
+
+[[bin]]
+name = "myapp"
+path = "src/main.rs"
+
+[profile.dev]
+panic = "abort"
+debug = 1
+opt-level = 2
+overflow-checks = false
+
+[profile.release]
+panic = "abort"
+
+[workspace]
+members = []
diff --git a/examples/myapp-ebpf/rust-toolchain.toml b/examples/myapp-ebpf/rust-toolchain.toml
new file mode 100644
index 00000000..c046a094
--- /dev/null
+++ b/examples/myapp-ebpf/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel="nightly"
diff --git a/examples/myapp-ebpf/src/bindings.rs b/examples/myapp-ebpf/src/bindings.rs
new file mode 100644
index 00000000..efa1387b
--- /dev/null
+++ b/examples/myapp-ebpf/src/bindings.rs
@@ -0,0 +1,189 @@
+/* automatically generated by rust-bindgen 0.59.1 */
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct __BindgenBitfieldUnit<Storage> {
+    storage: Storage,
+}
+impl<Storage> __BindgenBitfieldUnit<Storage> {
+    #[inline]
+    pub const fn new(storage: Storage) -> Self {
+        Self { storage }
+    }
+}
+impl<Storage> __BindgenBitfieldUnit<Storage>
+where
+    Storage: AsRef<[u8]> + AsMut<[u8]>,
+{
+    #[inline]
+    pub fn get_bit(&self, index: usize) -> bool {
+        debug_assert!(index / 8 < self.storage.as_ref().len());
+        let byte_index = index / 8;
+        let byte = self.storage.as_ref()[byte_index];
+        let bit_index = if cfg!(target_endian = "big") {
+            7 - (index % 8)
+        } else {
+            index % 8
+        };
+        let mask = 1 << bit_index;
+        byte & mask == mask
+    }
+    #[inline]
+    pub fn set_bit(&mut self, index: usize, val: bool) {
+        debug_assert!(index / 8 < self.storage.as_ref().len());
+        let byte_index = index / 8;
+        let byte = &mut self.storage.as_mut()[byte_index];
+        let bit_index = if cfg!(target_endian = "big") {
+            7 - (index % 8)
+        } else {
+            index % 8
+        };
+        let mask = 1 << bit_index;
+        if val {
+            *byte |= mask;
+        } else {
+            *byte &= !mask;
+        }
+    }
+    #[inline]
+    pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 {
+        debug_assert!(bit_width <= 64);
+        debug_assert!(bit_offset / 8 < self.storage.as_ref().len());
+        debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len());
+        let mut val = 0;
+        for i in 0..(bit_width as usize) {
+            if self.get_bit(i + bit_offset) {
+                let index = if cfg!(target_endian = "big") {
+                    bit_width as usize - 1 - i
+                } else {
+                    i
+                };
+                val |= 1 << index;
+            }
+        }
+        val
+    }
+    #[inline]
+    pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) {
+        debug_assert!(bit_width <= 64);
+        debug_assert!(bit_offset / 8 < self.storage.as_ref().len());
+        debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len());
+        for i in 0..(bit_width as usize) {
+            let mask = 1 << i;
+            let val_bit_is_set = val & mask == mask;
+            let index = if cfg!(target_endian = "big") {
+                bit_width as usize - 1 - i
+            } else {
+                i
+            };
+            self.set_bit(index + bit_offset, val_bit_is_set);
+        }
+    }
+}
+pub type __u8 = ::aya_bpf::cty::c_uchar;
+pub type __u16 = ::aya_bpf::cty::c_ushort;
+pub type __u32 = ::aya_bpf::cty::c_uint;
+pub type __be16 = __u16;
+pub type __be32 = __u32;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct ethhdr {
+    pub h_dest: [::aya_bpf::cty::c_uchar; 6usize],
+    pub h_source: [::aya_bpf::cty::c_uchar; 6usize],
+    pub h_proto: __be16,
+}
+pub type __sum16 = __u16;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct iphdr {
+    pub _bitfield_align_1: [u8; 0],
+    pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>,
+    pub tos: __u8,
+    pub tot_len: __be16,
+    pub id: __be16,
+    pub frag_off: __be16,
+    pub ttl: __u8,
+    pub protocol: __u8,
+    pub check: __sum16,
+    pub saddr: __be32,
+    pub daddr: __be32,
+}
+impl iphdr {
+    #[inline]
+    pub fn ihl(&self) -> __u8 {
+        unsafe { ::core::mem::transmute(self._bitfield_1.get(0usize, 4u8) as u8) }
+    }
+    #[inline]
+    pub fn set_ihl(&mut self, val: __u8) {
+        unsafe {
+            let val: u8 = ::core::mem::transmute(val);
+            self._bitfield_1.set(0usize, 4u8, val as u64)
+        }
+    }
+    #[inline]
+    pub fn version(&self) -> __u8 {
+        unsafe { ::core::mem::transmute(self._bitfield_1.get(4usize, 4u8) as u8) }
+    }
+    #[inline]
+    pub fn set_version(&mut self, val: __u8) {
+        unsafe {
+            let val: u8 = ::core::mem::transmute(val);
+            self._bitfield_1.set(4usize, 4u8, val as u64)
+        }
+    }
+    #[inline]
+    pub fn new_bitfield_1(ihl: __u8, version: __u8) -> __BindgenBitfieldUnit<[u8; 1usize]> {
+        let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default();
+        __bindgen_bitfield_unit.set(0usize, 4u8, {
+            let ihl: u8 = unsafe { ::core::mem::transmute(ihl) };
+            ihl as u64
+        });
+        __bindgen_bitfield_unit.set(4usize, 4u8, {
+            let version: u8 = unsafe { ::core::mem::transmute(version) };
+            version as u64
+        });
+        __bindgen_bitfield_unit
+    }
+}
+
+impl<Storage> __BindgenBitfieldUnit<Storage> {}
+impl ethhdr {
+    pub fn h_dest(&self) -> Option<[::aya_bpf::cty::c_uchar; 6usize]> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.h_dest) }.ok()
+    }
+    pub fn h_source(&self) -> Option<[::aya_bpf::cty::c_uchar; 6usize]> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.h_source) }.ok()
+    }
+    pub fn h_proto(&self) -> Option<__be16> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.h_proto) }.ok()
+    }
+}
+impl iphdr {
+    pub fn tos(&self) -> Option<__u8> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.tos) }.ok()
+    }
+    pub fn tot_len(&self) -> Option<__be16> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.tot_len) }.ok()
+    }
+    pub fn id(&self) -> Option<__be16> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.id) }.ok()
+    }
+    pub fn frag_off(&self) -> Option<__be16> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.frag_off) }.ok()
+    }
+    pub fn ttl(&self) -> Option<__u8> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.ttl) }.ok()
+    }
+    pub fn protocol(&self) -> Option<__u8> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.protocol) }.ok()
+    }
+    pub fn check(&self) -> Option<__sum16> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.check) }.ok()
+    }
+    pub fn saddr(&self) -> Option<__be32> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.saddr) }.ok()
+    }
+    pub fn daddr(&self) -> Option<__be32> {
+        unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.daddr) }.ok()
+    }
+}
diff --git a/examples/myapp-ebpf/src/main.rs b/examples/myapp-ebpf/src/main.rs
new file mode 100644
index 00000000..670a70a6
--- /dev/null
+++ b/examples/myapp-ebpf/src/main.rs
@@ -0,0 +1,95 @@
+#![no_std]
+#![no_main]
+
+use aya_bpf::{
+    bindings::xdp_action,
+    macros::{map, xdp},
+    maps::{HashMap, PerfEventArray},
+    programs::XdpContext,
+};
+
+use core::mem;
+use memoffset::offset_of;
+use myapp_common::PacketLog;
+
+// ANCHOR: bindings
+mod bindings;
+use bindings::{ethhdr, iphdr};
+// ANCHOR_END: bindings
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    unreachable!()
+}
+
+// ANCHOR: map
+#[map(name = "EVENTS")]
+static mut EVENTS: PerfEventArray<PacketLog> =
+    PerfEventArray::<PacketLog>::with_max_entries(1024, 0);
+// ANCHOR_END: map
+
+// ANCHOR: blocklist
+#[map(name = "BLOCKLIST")]
+static mut BLOCKLIST: HashMap<u32, u32> = HashMap::<u32, u32>::with_max_entries(1024, 0);
+// ANCHOR_END: blocklist
+
+#[xdp(name="myapp")]
+pub fn xdp_myapp(ctx: XdpContext) -> u32 {
+    match try_xdp_myapp(ctx) {
+        Ok(ret) => ret,
+        Err(_) => xdp_action::XDP_ABORTED,
+    }
+}
+
+// ANCHOR: ptr_at
+#[inline(always)]
+unsafe fn ptr_at<T>(ctx: &XdpContext, offset: usize) -> Result<*const T, ()> {
+    let start = ctx.data();
+    let end = ctx.data_end();
+    let len = mem::size_of::<T>();
+
+    if start + offset + len > end {
+        return Err(());
+    }
+
+    Ok((start + offset) as *const T)
+}
+// ANCHOR_END: ptr_at
+
+// ANCHOR: block_ip
+fn block_ip(address: u32) -> bool {
+    unsafe { BLOCKLIST.get(&address).is_some() }
+}
+// ANCHOR_END: block_ip
+
+// ANCHOR: try
+fn try_xdp_myapp(ctx: XdpContext) -> Result<u32, ()> {
+    let h_proto = u16::from_be(unsafe { *ptr_at(&ctx, offset_of!(ethhdr, h_proto))? });
+    if h_proto != ETH_P_IP {
+        return Ok(xdp_action::XDP_PASS);
+    }
+    let dest = u32::from_be(unsafe { *ptr_at(&ctx, ETH_HDR_LEN + offset_of!(iphdr, daddr))? });
+
+    // ANCHOR: action
+    let action = if block_ip(dest) {
+        xdp_action::XDP_DROP
+    } else {
+        xdp_action::XDP_PASS
+    };
+    // ANCHOR_END: action
+
+    let log_entry = PacketLog {
+        ipv4_address: dest,
+        action: action,
+    };
+
+    unsafe {
+        EVENTS.output(&ctx, &log_entry, 0);
+    }
+
+    Ok(action)
+}
+// ANCHOR_END: try
+
+const ETH_P_IP: u16 = 0x0800;
+const ETH_HDR_LEN: usize = mem::size_of::<ethhdr>();
diff --git a/examples/myapp/Cargo.toml b/examples/myapp/Cargo.toml
new file mode 100644
index 00000000..9033008f
--- /dev/null
+++ b/examples/myapp/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "myapp"
+version = "0.1.0"
+edition = "2018"
+publish = false
+
+[dependencies]
+aya = { git = "https://github.com/aya-rs/aya", branch="main", features=["async_tokio"] }
+myapp-common = { path = "../myapp-common", features=["userspace"] }
+anyhow = "1.0.42"
+bytes = "1"
+tokio = { version = "1.9.0", features = ["full"] }
+ctrlc = "3.2"
+
+structopt = { version = "0.3"}
+
+[[bin]]
+name = "myapp"
+path = "src/main.rs"
diff --git a/examples/myapp/src/main.rs b/examples/myapp/src/main.rs
new file mode 100644
index 00000000..cad445c1
--- /dev/null
+++ b/examples/myapp/src/main.rs
@@ -0,0 +1,68 @@
+use aya::{
+    maps::HashMap,
+    maps::perf::AsyncPerfEventArray,
+    programs::{Xdp, XdpFlags},
+    util::online_cpus,
+    Bpf,
+};
+use structopt::StructOpt;
+use bytes::BytesMut;
+use std::{
+    convert::{TryFrom, TryInto}, 
+    net::{self, Ipv4Addr},
+};
+use tokio::{signal, task};
+
+use myapp_common::PacketLog;
+
+#[derive(Debug, StructOpt)]
+struct Opt {
+    #[structopt(short, long)]
+    path: String,
+    #[structopt(short, long, default_value = "eth0")]
+    iface: String,
+}
+
+// ANCHOR: main
+#[tokio::main]
+async fn main() -> Result<(), anyhow::Error> {
+    let opt = Opt::from_args();
+    let mut bpf = Bpf::load_file(&opt.path)?;
+    let program: &mut Xdp = bpf.program_mut("myapp")?.try_into()?;
+    program.load()?;
+    program.attach(&opt.iface, XdpFlags::default())?;
+
+    // ANCHOR: block_address
+    let mut blocklist: HashMap<_, u32, u32> = HashMap::try_from(bpf.map_mut("BLOCKLIST")?)?;
+    let block_addr : u32 = Ipv4Addr::new(192, 168, 0, 10).try_into()?;
+    blocklist.insert(block_addr, 0, 0)?;
+    // ANCHOR_END: block_address
+
+    // ANCHOR: map
+    let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut("EVENTS")?)?;
+    // ANCHOR_END: map
+
+    for cpu_id in online_cpus()? {
+        let mut buf = perf_array.open(cpu_id, None)?;
+
+        task::spawn(async move {
+            let mut buffers = (0..10)
+                .map(|_| BytesMut::with_capacity(1024))
+                .collect::<Vec<_>>();
+
+            loop {
+                let events = buf.read_events(&mut buffers).await.unwrap();
+                for i in 0..events.read {
+                    let buf = &mut buffers[i];
+                    let ptr = buf.as_ptr() as *const PacketLog;
+                    let data = unsafe { ptr.read_unaligned() };
+                    let src_addr = net::Ipv4Addr::from(data.ipv4_address);
+                    println!("LOG: SRC {}, ACTION {}", src_addr, data.action);
+                }
+            }
+        });
+    }
+    signal::ctrl_c().await.expect("failed to listen for event");
+    Ok::<_, anyhow::Error>(())
+}
+// ANCHOR_END: main
diff --git a/examples/xtask/Cargo.toml b/examples/xtask/Cargo.toml
new file mode 100644
index 00000000..fdc05f31
--- /dev/null
+++ b/examples/xtask/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+structopt = {version = "0.3", default-features = false }
+anyhow = "1"
+aya-gen = { git = "http://github.com/aya-rs/aya", branch = "main" }
diff --git a/examples/xtask/src/build_ebpf.rs b/examples/xtask/src/build_ebpf.rs
new file mode 100644
index 00000000..3363a1e9
--- /dev/null
+++ b/examples/xtask/src/build_ebpf.rs
@@ -0,0 +1,62 @@
+use std::path::PathBuf;
+use std::process::Command;
+
+use structopt::StructOpt;
+
+#[derive(Debug, Copy, Clone)]
+pub enum Architecture {
+    BpfEl,
+    BpfEb,
+}
+
+impl std::str::FromStr for Architecture {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s {
+            "bpfel-unknown-none" => Architecture::BpfEl,
+            "bpfeb-unknown-none" => Architecture::BpfEb,
+            _ => return Err("invalid target".to_owned()),
+        })
+    }
+}
+
+impl std::fmt::Display for Architecture {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            Architecture::BpfEl => "bpfel-unknown-none",
+            Architecture::BpfEb => "bpfeb-unknown-none",
+        })
+    }
+}
+
+#[derive(StructOpt)]
+pub struct Options {
+    #[structopt(default_value = "bpfel-unknown-none", long)]
+    target: Architecture,
+    #[structopt(long)]
+    release: bool,
+}
+
+pub fn build(opts: Options) -> Result<(), anyhow::Error> {
+    let dir = PathBuf::from("myapp-ebpf");
+    let target = format!("--target={}", opts.target);
+    let mut args = vec![
+        "+nightly",
+        "build",
+        "--verbose",
+        target.as_str(),
+        "-Z",
+        "build-std=core",
+    ];
+    if opts.release {
+        args.push("--release")
+    }
+    let status = Command::new("cargo")
+        .current_dir(&dir)
+        .args(&args)
+        .status()
+        .expect("failed to build bpf examples");
+    assert!(status.success());
+    Ok(())
+}
diff --git a/examples/xtask/src/codegen.rs b/examples/xtask/src/codegen.rs
new file mode 100644
index 00000000..dec95145
--- /dev/null
+++ b/examples/xtask/src/codegen.rs
@@ -0,0 +1,16 @@
+use aya_gen::btf_types;
+use std::{
+    fs::File,
+    io::Write,
+    path::{Path, PathBuf},
+};
+
+pub fn generate() -> Result<(), anyhow::Error> {
+    let dir = PathBuf::from("myapp-ebpf/src");
+    let names: Vec<&str> = vec!["ethhdr", "iphdr"];
+    let bindings = btf_types::generate(Path::new("/sys/kernel/btf/vmlinux"), &names, true)?;
+    // Write the bindings to the $OUT_DIR/bindings.rs file.
+    let mut out = File::create(dir.join("bindings.rs"))?;
+    write!(out, "{}", bindings)?;
+    Ok(())
+}
diff --git a/examples/xtask/src/main.rs b/examples/xtask/src/main.rs
new file mode 100644
index 00000000..628dafdb
--- /dev/null
+++ b/examples/xtask/src/main.rs
@@ -0,0 +1,32 @@
+mod build_ebpf;
+mod codegen;
+
+use std::process::exit;
+
+use structopt::StructOpt;
+#[derive(StructOpt)]
+pub struct Options {
+    #[structopt(subcommand)]
+    command: Command,
+}
+
+#[derive(StructOpt)]
+enum Command {
+    BuildEbpf(build_ebpf::Options),
+    Codegen,
+}
+
+fn main() {
+    let opts = Options::from_args();
+
+    use Command::*;
+    let ret = match opts.command {
+        BuildEbpf(opts) => build_ebpf::build(opts),
+        Codegen => codegen::generate(),
+    };
+
+    if let Err(e) = ret {
+        eprintln!("{:#}", e);
+        exit(1);
+    }
+}