Deploying to gh-pages from @ alessandrod/aya@1153a51202 🚀

gh-pages
alessandrod 3 years ago
parent 76e8e72fe1
commit 026a987afa

@ -76,7 +76,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>

@ -75,7 +75,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html" class="active"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html" class="active"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="../start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>

@ -75,7 +75,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>

@ -75,7 +75,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html" class="active"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html" class="active"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="../start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>

@ -76,7 +76,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
@ -222,7 +222,7 @@ rustup toolchain install nightly --component rust-src
</code></pre>
<p>Once you have the Rust tool-chains installed, you must also install the <code>bpf-linker</code> - for linking our eBPF program - and <code>cargo-generate</code> - for generating the project skeleton.</p>
<pre><code class="language-console">cargo +nightly install bpf-linker
cargo install cargo-generate
cargo install --git https://github.com/cargo-generate/cargo-generate
</code></pre>
<h2 id="starting-a-new-project"><a class="header" href="#starting-a-new-project">Starting A New Project</a></h2>
<p>To start a new project, you can use <code>cargo-generate</code>:</p>
@ -335,7 +335,7 @@ fn try_main() -&gt; Result&lt;(), anyhow::Error&gt; {
}
</code></pre>
<p>Let's adapt it to load our program.</p>
<p>We'll need the following imports at the top of the file:</p>
<p>We will add a dependency on <code>ctrlc = &quot;3.2&quot;</code> to <code>myapp/Cargo.toml</code>, then add the following imports at the top of the <code>myapp/src/main.rs</code>:</p>
<pre><code class="language-rust ignore">use aya::Bpf;
use aya::programs::{Xdp, XdpFlags};
use std::{
@ -343,6 +343,8 @@ use std::{
env,
thread,
time::Duration,
sync::Arc,
sync::atomic::{AtomicBool, Ordering},
};
</code></pre>
<p>Then we'll adapt the <code>try_main</code> function to load our program:</p>
@ -359,9 +361,18 @@ use std::{
let probe: &amp;mut Xdp = bpf.program_mut(&quot;xdp&quot;)?.try_into()?;
probe.load()?;
probe.attach(&amp;iface, XdpFlags::default())?;
for _i in 1..10 {
thread::sleep(Duration::from_secs(1));
};
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
}).expect(&quot;Error setting Ctrl-C handler&quot;);
println!(&quot;Waiting for Ctrl-C...&quot;);
while running.load(Ordering::SeqCst) {}
println!(&quot;Exiting...&quot;);
Ok(())
}
</code></pre>
@ -380,23 +391,277 @@ use std::{
<p>Once our file is loaded, we can extract the XDP probe with <code>let probe: &amp;mut Xdp = bpf.program_mut(&quot;xdp&quot;)?.try_into()?;</code> and then load it in to the kernel with <code>probe.load()</code>.</p>
<p>Finally, we can attach it to an interface with <code>probe.attach(&amp;iface, XdpFlags::default())?;</code></p>
<p>Let's try it out!</p>
<pre><code class="language-console">cargo build
sudo ./target/debug/myapp ./target/bpfel-unknown-none/debug/myapp wlp2s0
<pre><code class="language-console">$ cargo build
$ sudo ./target/debug/myapp ./target/bpfel-unknown-none/debug/myapp wlp2s0
Waiting for Ctrl-C...
Exiting...
</code></pre>
<p>That was uneventful. Did it work?</p>
<blockquote>
<p>💡 <strong>HINT: Error Loading Program?</strong></p>
<p>If you get an error loading the program, try changing <code>XdpFlags::default()</code> to <code>XdpFlags::SKB_MODE</code></p>
</blockquote>
<h3 id="the-lifecycle-of-an-ebpf-program"><a class="header" href="#the-lifecycle-of-an-ebpf-program">The Lifecycle of an eBPF Program</a></h3>
<p>You'll notice that our program ends by sleeping for 10 seconds and you may even have wondered what this is for...</p>
<p>When you load an eBPF program or map in to the kernel, the kernel maintains a reference count.
So, when our eBPF application is loaded, the kernel returns a file descriptor and the reference count is incremented.
When our program terminates, the file descriptor is closed, the reference count is decremented and the memory (eventually) freed.</p>
<p>You can see this when by issuing the <code>sudo bpftool prog list</code> command when <code>myapp</code> is running:</p>
<p>The program runs until CTRL+C is pressed and then exits.
On exit, Aya takes care of detaching the program for us.</p>
<p>If you issue the <code>sudo bpftool prog list</code> command when <code>myapp</code> is running you can verify that it is loaded:</p>
<pre><code class="language-console">84: xdp tag 3b185187f1855c4c gpl
loaded_at 2021-08-05T13:35:06+0100 uid 0
xlated 16B jited 18B memlock 4096B
pids myapp(69184)
</code></pre>
<p>For our firewall to work once the user-space program has exited, we'll need to pin it to the BPF FS.</p>
<p>Running the command again once <code>myapp</code> has exited will show that the program is no longer running.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="logging-packets"><a class="header" href="#logging-packets">Logging Packets</a></h1>
<p>In the previous chapter, our XDP application ran for 10 seconds and permitted some traffic.
There was however no output on the console, so you just have to trust that it was working correctly. Let's expand this program to log the traffic that is being permitted</p>
<h2 id="getting-data-to-user-space"><a class="header" href="#getting-data-to-user-space">Getting Data to User-Space</a></h2>
<h3 id="sharing-data"><a class="header" href="#sharing-data">Sharing Data</a></h3>
<p>To get data from kernel-space to user-space we use an eBPF map. There are numerous types of maps to chose from, but in this example we'll be using a PerfEventArray.</p>
<p>While we could go all out and extract data all the way up to L7, we'll constrain our firewall to L3, and to make things easier, IPv4 only.
The data structure that we'll need to send information to user-space will need to hold an IPv4 address and an action for Permit/Deny, we'll encode both as a <code>u32</code>.</p>
<p>Let's go ahead and add that to <code>myapp-common/src/lib.rs</code></p>
<pre><code class="language-rust ignore">#[repr(C)]
pub struct PacketLog {
pub ipv4_address: u32,
pub action: u32,
}
#[cfg(feature = &quot;user&quot;)]
unsafe impl aya::Pod for PacketLog {}
</code></pre>
<blockquote>
<p>💡 <strong>HINT: Struct Alignment</strong></p>
<p>Structs must be aligned to 8 byte boundaries. You can do this manually, or alternatively you may use <code>#[repr(packed)]</code>. If you do not do this, the eBPF verifier will get upset and emit an <code>invalid indirect read from stack</code> error.</p>
</blockquote>
<p>We implement the <code>aya::Pod</code> trait for our struct since it is Plain Old Data as can be safely converted to a byte-slice and back.</p>
<h3 id="ebpf-map-creation"><a class="header" href="#ebpf-map-creation">eBPF: Map Creation</a></h3>
<p>Let's create a map called <code>EVENTS</code> in <code>myapp-ebpf/src/main.rs</code></p>
<pre><code class="language-rust ignore">use aya_bpf::macros::map;
use aya_bpf::maps::PerfMap;
use myapp_common::PacketLog;
#[map(name = &quot;EVENTS&quot;)]
static mut EVENTS: PerfMap&lt;PacketLog&gt; = PerfMap::&lt;PacketLog&gt;::with_max_entries(1024, 0);
</code></pre>
<p>When the eBPF program is loaded by Aya, the map will be created for us.</p>
<h3 id="userspace-map-creation"><a class="header" href="#userspace-map-creation">Userspace: Map Creation</a></h3>
<p>After our call to <code>probe.attach()</code> we'll add the following code.</p>
<pre><code class="language-rust ignore">use aya::maps::AsyncPerfEventArray;
let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut(&quot;EVENTS&quot;)?)?;
</code></pre>
<p>Our <code>perf_array</code> is a mutable reference to the map that was created after the XDP program was loaded by Aya.</p>
<h2 id="writing-data"><a class="header" href="#writing-data">Writing Data</a></h2>
<p>Now we've got our maps set up, let's add some data!</p>
<h3 id="generating-bindings-to-vmlinuxh"><a class="header" href="#generating-bindings-to-vmlinuxh">Generating Bindings To vmlinux.h</a></h3>
<p>To get useful data to add to our maps, we first need some useful data structures to populate with data from the <code>XdpContext</code>.
We want to log the Source IP Address of incoming traffic, so we'll need to:</p>
<ol>
<li>Read the Ethernet Header to determine if this is an IPv4 Packet</li>
<li>Read the Source IP Address from the IPv4 Header</li>
</ol>
<p>The two structs in the kernel for this are <code>ethhdr</code> from <code>uapi/linux/if_ether.h</code> and <code>iphdr</code> from <code>uapi/linux/ip.h</code>.
If I were to use bindgen to generate Rust bindings for those headers, I'd be tied to the kernel version of the system that I'm developing on.
This is where <code>aya-gen</code> comes in to play. It can easily generate bindings for using the BTF information in <code>/sys/kernel/btf/vmlinux</code>.</p>
<p>Once the bindings are generated and checked in to our repository they shouldn't need to be regenerated again unless we need to add a new struct.</p>
<p>Lets use <code>xtask</code> to automate this so we can easily reproduce this file in future.</p>
<p>We'll add the following content to <code>xtask/src/codegen.rs</code></p>
<pre><code class="language-rust ignore">use aya_gen::btf_types;
use std::{
fs::File,
io::Write,
path::{Path, PathBuf},
};
pub fn generate() -&gt; Result&lt;(), anyhow::Error&gt; {
let dir = PathBuf::from(&quot;myapp-ebpf/src&quot;);
let names: Vec&lt;&amp;str&gt; = vec![&quot;ethhdr&quot;, &quot;iphdr&quot;];
let bindings = btf_types::generate(Path::new(&quot;/sys/kernel/btf/vmlinux&quot;), &amp;names, false)?;
// Write the bindings to the $OUT_DIR/bindings.rs file.
let mut out = File::create(dir.join(&quot;bindings.rs&quot;))?;
write!(out, &quot;{}&quot;, bindings).expect(&quot;unable to write bindings to file&quot;);
Ok(())
}
</code></pre>
<p>This will generate a file called <code>myapp-ebpf/src/bindings.rs</code>. If you've chosen an application name other than <code>myapp</code> you'll need to adjust the path appropriately.</p>
<p>Add a new dependencies to <code>xtask/Cargo.toml</code>:</p>
<pre><code class="language-toml">[dependencies]
aya-gen = { git = &quot;http://github.com/alessandrod/aya&quot;, branch = &quot;main&quot; }
</code></pre>
<p>And finally, we must register the command in <code>xtask/src/main.rs</code>:</p>
<pre><code class="language-rust ignore">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) =&gt; build_ebpf::build(opts),
Codegen =&gt; codegen::generate(),
};
if let Err(e) = ret {
eprintln!(&quot;{:#}&quot;, e);
exit(1);
}
}
</code></pre>
<p>Once we've generated our file using <code>cargo xtask codegen</code> from the root of the project.</p>
<p>These can then be accessed from within <code>myapp-ebpf/src/main.rs</code>:</p>
<pre><code class="language-rust ignore">mod bindings;
use bindings::{ethhdr, iphdr};
</code></pre>
<h3 id="getting-packet-data-from-the-context"><a class="header" href="#getting-packet-data-from-the-context">Getting Packet Data From The Context</a></h3>
<p>The <code>XdpContext</code> contains two fields, <code>data</code> and <code>data_end</code>.
<code>data</code> is a pointer to the start of the data in kernel memory and <code>data_end</code>, a pointer to the end of the data in kernel memory. In order to access this data and ensure that the eBPF verifier is happy, we'll introduce a helper function:</p>
<pre><code class="language-rust ignore">#[inline(always)]
unsafe fn ptr_at&lt;T&gt;(ctx: &amp;XdpContext, offset: usize) -&gt; Result&lt;*const T, ()&gt; {
let start = ctx.data();
let end = ctx.data_end();
let len = mem::size_of::&lt;T&gt;();
if start + offset + len &gt; end {
return Err(());
}
Ok((start + offset) as *const T)
}
</code></pre>
<p>This function will ensure that before we access any data, we check that it's contained between <code>data</code> and <code>data_end</code>.
It is marked as <code>unsafe</code> because when calling the function, you must ensure that there is a valid <code>T</code> at that location or there will be undefined behaviour.</p>
<h3 id="writing-data-to-the-map"><a class="header" href="#writing-data-to-the-map">Writing Data To The Map</a></h3>
<p>With our helper function in place, we can:</p>
<ol>
<li>Read the Ethertype field to check if we have an IPv4 packet.</li>
<li>Read the IPv4 Source Address from the IP header</li>
</ol>
<p>First let's add another dependency on <code>memoffset = &quot;0.6&quot;</code> to <code>myapp-ebpf/Cargo.toml</code>, and then we'll change our <code>try_xdp_firewall</code> function to look like this:</p>
<pre><code class="language-rust ignore">use memoffset::offset_of;
fn try_xdp_firewall(ctx: XdpContext) -&gt; Result&lt;u32, ()&gt; {
let h_proto = u16::from_be(unsafe { *ptr_at(&amp;ctx, offset_of!(ethhdr, h_proto))? });
if h_proto != ETH_P_IP {
return Ok(xdp_action::XDP_PASS)
}
let source = u32::from_be(unsafe { *ptr_at(&amp;ctx, ETH_HDR_LEN + offset_of!(iphdr, saddr))? });
let log_entry = PacketLog{
ipv4_address: source,
action: xdp_action::XDP_PASS,
};
unsafe { EVENTS.output(&amp;ctx, &amp;log_entry, 0); }
Ok(xdp_action::XDP_PASS)
}
</code></pre>
<blockquote>
<p>💡 <strong>HINT: Reading Fields Using <code>offset_of!</code></strong></p>
<p>As there is limited stack space, it's more memory efficient to use the <code>offset_of!</code> macro to read
a single field from a struct, rather than reading the whole struct and accessing the field by name.</p>
</blockquote>
<p>Once we have our IPv4 source address, we can create a <code>PacketLog</code> struct and output this to our PerfEventArray</p>
<h2 id="reading-data"><a class="header" href="#reading-data">Reading Data</a></h2>
<h3 id="going-async"><a class="header" href="#going-async">Going Async</a></h3>
<p>In order to read from the <code>AsyncPerfEventArray</code>, we have to call <code>AsyncPerfEventArray::open()</code> for each online CPU, then we have to poll the file descriptor for events.
While this is do-able using <code>PerfEventArray</code> and <code>mio</code> or <code>epoll</code>, the code is much less easy to follow. Instead, we'll use <code>tokio</code> to make our user-space application async.</p>
<p>Let's add some dependencies to <code>myapp/src/Cargo.toml</code>:</p>
<pre><code class="language-toml">[dependencies]
aya = { git = &quot;https://github.com/alessandrod/aya&quot;, branch=&quot;main&quot;, features=[&quot;async_tokio&quot;] }
myapp-common = { path = &quot;../myapp-common&quot;, features=[&quot;userspace&quot;] }
anyhow = &quot;1.0.42&quot;
bytes = &quot;1&quot;
tokio = { version = &quot;1.9.0&quot;, features = [&quot;full&quot;] }
</code></pre>
<p>And adjust our <code>myapp/src/main.rs</code> to look like this:</p>
<pre><code class="language-rust ignore">use aya::{
maps::perf::AsyncPerfEventArray,
programs::{Xdp, XdpFlags},
util::online_cpus,
Bpf,
};
use bytes::BytesMut;
use std::{
convert::{TryFrom, TryInto},
env, fs, net,
};
use tokio::{signal, task};
use myapp_common::PacketLog;
#[tokio::main]
async fn main() -&gt; Result&lt;(), anyhow::Error&gt; {
let path = match env::args().nth(1) {
Some(iface) =&gt; iface,
None =&gt; panic!(&quot;not path provided&quot;),
};
let iface = match env::args().nth(2) {
Some(iface) =&gt; iface,
None =&gt; &quot;eth0&quot;.to_string(),
};
let data = fs::read(path)?;
let mut bpf = Bpf::load(&amp;data, None)?;
let probe: &amp;mut Xdp = bpf.program_mut(&quot;xdp&quot;)?.try_into()?;
probe.load()?;
probe.attach(&amp;iface, XdpFlags::default())?;
let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut(&quot;EVENTS&quot;)?)?;
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::&lt;Vec&lt;_&gt;&gt;();
loop {
let events = buf.read_events(&amp;mut buffers).await.unwrap();
for i in 0..events.read {
let buf = &amp;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!(&quot;LOG: SRC {}, ACTION {}&quot;, src_addr, data.action);
}
}
});
}
signal::ctrl_c().await.expect(&quot;failed to listen for event&quot;);
Ok::&lt;_, anyhow::Error&gt;(())
}
</code></pre>
<p>This will now spawn a <code>tokio::task</code> to read each of the <code>AsyncPerfEventArrayBuffers</code> contained in out <code>AsyncPerfEventArray</code>.
When we receive an event, we use <code>read_unaligned</code> to read our data into a <code>PacketLog</code>.
We then use <code>println!</code> to log the event to the console.
We no longer need to sleep, as we run until we receive the <code>CTRL+C</code> signal.</p>
<h2 id="running-the-program"><a class="header" href="#running-the-program">Running the program</a></h2>
<pre><code class="language-console">$ cargo build
$ cargo xtask build-ebpf
$ sudo ./target/debug/myapp ./target/bpfel-unknown-none/debug/myapp wlp2s0
LOG: SRC 192.168.1.205, ACTION 2
LOG: SRC 192.168.1.21, ACTION 2
LOG: SRC 192.168.1.21, ACTION 2
LOG: SRC 18.168.253.132, ACTION 2
LOG: SRC 18.168.253.132, ACTION 2
LOG: SRC 18.168.253.132, ACTION 2
LOG: SRC 140.82.121.6, ACTION 2
</code></pre>
</main>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -75,7 +75,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html" class="active"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html" class="active"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="../start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
@ -143,7 +143,7 @@ rustup toolchain install nightly --component rust-src
</code></pre>
<p>Once you have the Rust tool-chains installed, you must also install the <code>bpf-linker</code> - for linking our eBPF program - and <code>cargo-generate</code> - for generating the project skeleton.</p>
<pre><code class="language-console">cargo +nightly install bpf-linker
cargo install cargo-generate
cargo install --git https://github.com/cargo-generate/cargo-generate
</code></pre>
<h2 id="starting-a-new-project"><a class="header" href="#starting-a-new-project">Starting A New Project</a></h2>
<p>To start a new project, you can use <code>cargo-generate</code>:</p>

@ -75,7 +75,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html" class="active"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html" class="active"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="../start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
@ -240,7 +240,7 @@ fn try_main() -&gt; Result&lt;(), anyhow::Error&gt; {
}
</code></pre>
<p>Let's adapt it to load our program.</p>
<p>We'll need the following imports at the top of the file:</p>
<p>We will add a dependency on <code>ctrlc = &quot;3.2&quot;</code> to <code>myapp/Cargo.toml</code>, then add the following imports at the top of the <code>myapp/src/main.rs</code>:</p>
<pre><code class="language-rust ignore">use aya::Bpf;
use aya::programs::{Xdp, XdpFlags};
use std::{
@ -248,6 +248,8 @@ use std::{
env,
thread,
time::Duration,
sync::Arc,
sync::atomic::{AtomicBool, Ordering},
};
</code></pre>
<p>Then we'll adapt the <code>try_main</code> function to load our program:</p>
@ -264,9 +266,18 @@ use std::{
let probe: &amp;mut Xdp = bpf.program_mut(&quot;xdp&quot;)?.try_into()?;
probe.load()?;
probe.attach(&amp;iface, XdpFlags::default())?;
for _i in 1..10 {
thread::sleep(Duration::from_secs(1));
};
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
}).expect(&quot;Error setting Ctrl-C handler&quot;);
println!(&quot;Waiting for Ctrl-C...&quot;);
while running.load(Ordering::SeqCst) {}
println!(&quot;Exiting...&quot;);
Ok(())
}
</code></pre>
@ -285,23 +296,26 @@ use std::{
<p>Once our file is loaded, we can extract the XDP probe with <code>let probe: &amp;mut Xdp = bpf.program_mut(&quot;xdp&quot;)?.try_into()?;</code> and then load it in to the kernel with <code>probe.load()</code>.</p>
<p>Finally, we can attach it to an interface with <code>probe.attach(&amp;iface, XdpFlags::default())?;</code></p>
<p>Let's try it out!</p>
<pre><code class="language-console">cargo build
sudo ./target/debug/myapp ./target/bpfel-unknown-none/debug/myapp wlp2s0
<pre><code class="language-console">$ cargo build
$ sudo ./target/debug/myapp ./target/bpfel-unknown-none/debug/myapp wlp2s0
Waiting for Ctrl-C...
Exiting...
</code></pre>
<p>That was uneventful. Did it work?</p>
<blockquote>
<p>💡 <strong>HINT: Error Loading Program?</strong></p>
<p>If you get an error loading the program, try changing <code>XdpFlags::default()</code> to <code>XdpFlags::SKB_MODE</code></p>
</blockquote>
<h3 id="the-lifecycle-of-an-ebpf-program"><a class="header" href="#the-lifecycle-of-an-ebpf-program">The Lifecycle of an eBPF Program</a></h3>
<p>You'll notice that our program ends by sleeping for 10 seconds and you may even have wondered what this is for...</p>
<p>When you load an eBPF program or map in to the kernel, the kernel maintains a reference count.
So, when our eBPF application is loaded, the kernel returns a file descriptor and the reference count is incremented.
When our program terminates, the file descriptor is closed, the reference count is decremented and the memory (eventually) freed.</p>
<p>You can see this when by issuing the <code>sudo bpftool prog list</code> command when <code>myapp</code> is running:</p>
<p>The program runs until CTRL+C is pressed and then exits.
On exit, Aya takes care of detaching the program for us.</p>
<p>If you issue the <code>sudo bpftool prog list</code> command when <code>myapp</code> is running you can verify that it is loaded:</p>
<pre><code class="language-console">84: xdp tag 3b185187f1855c4c gpl
loaded_at 2021-08-05T13:35:06+0100 uid 0
xlated 16B jited 18B memlock 4096B
pids myapp(69184)
</code></pre>
<p>For our firewall to work once the user-space program has exited, we'll need to pin it to the BPF FS.</p>
<p>Running the command again once <code>myapp</code> has exited will show that the program is no longer running.</p>
</main>
@ -310,6 +324,9 @@ When our program terminates, the file descriptor is closed, the reference count
<a rel="prev" href="../start/development.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../start/logging-packets.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
@ -319,6 +336,9 @@ When our program terminates, the file descriptor is closed, the reference count
<a rel="prev" href="../start/development.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../start/logging-packets.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>

@ -75,7 +75,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html" class="active"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li></ol></li></ol>
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html" class="active"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="../start/logging-packets.html"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>

@ -0,0 +1,421 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Logging Packets - Building eBPF Programs With Aya</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="../ebpf/index.html"><strong aria-hidden="true">2.</strong> eBPF Program Limitiations</a></li><li class="chapter-item expanded "><a href="../start/index.html"><strong aria-hidden="true">3.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../start/development.html"><strong aria-hidden="true">3.1.</strong> Development Environment</a></li><li class="chapter-item expanded "><a href="../start/hello-xdp.html"><strong aria-hidden="true">3.2.</strong> Hello XDP!</a></li><li class="chapter-item expanded "><a href="../start/logging-packets.html" class="active"><strong aria-hidden="true">3.3.</strong> Logging Packets</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Building eBPF Programs With Aya</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="logging-packets"><a class="header" href="#logging-packets">Logging Packets</a></h1>
<p>In the previous chapter, our XDP application ran for 10 seconds and permitted some traffic.
There was however no output on the console, so you just have to trust that it was working correctly. Let's expand this program to log the traffic that is being permitted</p>
<h2 id="getting-data-to-user-space"><a class="header" href="#getting-data-to-user-space">Getting Data to User-Space</a></h2>
<h3 id="sharing-data"><a class="header" href="#sharing-data">Sharing Data</a></h3>
<p>To get data from kernel-space to user-space we use an eBPF map. There are numerous types of maps to chose from, but in this example we'll be using a PerfEventArray.</p>
<p>While we could go all out and extract data all the way up to L7, we'll constrain our firewall to L3, and to make things easier, IPv4 only.
The data structure that we'll need to send information to user-space will need to hold an IPv4 address and an action for Permit/Deny, we'll encode both as a <code>u32</code>.</p>
<p>Let's go ahead and add that to <code>myapp-common/src/lib.rs</code></p>
<pre><code class="language-rust ignore">#[repr(C)]
pub struct PacketLog {
pub ipv4_address: u32,
pub action: u32,
}
#[cfg(feature = &quot;user&quot;)]
unsafe impl aya::Pod for PacketLog {}
</code></pre>
<blockquote>
<p>💡 <strong>HINT: Struct Alignment</strong></p>
<p>Structs must be aligned to 8 byte boundaries. You can do this manually, or alternatively you may use <code>#[repr(packed)]</code>. If you do not do this, the eBPF verifier will get upset and emit an <code>invalid indirect read from stack</code> error.</p>
</blockquote>
<p>We implement the <code>aya::Pod</code> trait for our struct since it is Plain Old Data as can be safely converted to a byte-slice and back.</p>
<h3 id="ebpf-map-creation"><a class="header" href="#ebpf-map-creation">eBPF: Map Creation</a></h3>
<p>Let's create a map called <code>EVENTS</code> in <code>myapp-ebpf/src/main.rs</code></p>
<pre><code class="language-rust ignore">use aya_bpf::macros::map;
use aya_bpf::maps::PerfMap;
use myapp_common::PacketLog;
#[map(name = &quot;EVENTS&quot;)]
static mut EVENTS: PerfMap&lt;PacketLog&gt; = PerfMap::&lt;PacketLog&gt;::with_max_entries(1024, 0);
</code></pre>
<p>When the eBPF program is loaded by Aya, the map will be created for us.</p>
<h3 id="userspace-map-creation"><a class="header" href="#userspace-map-creation">Userspace: Map Creation</a></h3>
<p>After our call to <code>probe.attach()</code> we'll add the following code.</p>
<pre><code class="language-rust ignore">use aya::maps::AsyncPerfEventArray;
let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut(&quot;EVENTS&quot;)?)?;
</code></pre>
<p>Our <code>perf_array</code> is a mutable reference to the map that was created after the XDP program was loaded by Aya.</p>
<h2 id="writing-data"><a class="header" href="#writing-data">Writing Data</a></h2>
<p>Now we've got our maps set up, let's add some data!</p>
<h3 id="generating-bindings-to-vmlinuxh"><a class="header" href="#generating-bindings-to-vmlinuxh">Generating Bindings To vmlinux.h</a></h3>
<p>To get useful data to add to our maps, we first need some useful data structures to populate with data from the <code>XdpContext</code>.
We want to log the Source IP Address of incoming traffic, so we'll need to:</p>
<ol>
<li>Read the Ethernet Header to determine if this is an IPv4 Packet</li>
<li>Read the Source IP Address from the IPv4 Header</li>
</ol>
<p>The two structs in the kernel for this are <code>ethhdr</code> from <code>uapi/linux/if_ether.h</code> and <code>iphdr</code> from <code>uapi/linux/ip.h</code>.
If I were to use bindgen to generate Rust bindings for those headers, I'd be tied to the kernel version of the system that I'm developing on.
This is where <code>aya-gen</code> comes in to play. It can easily generate bindings for using the BTF information in <code>/sys/kernel/btf/vmlinux</code>.</p>
<p>Once the bindings are generated and checked in to our repository they shouldn't need to be regenerated again unless we need to add a new struct.</p>
<p>Lets use <code>xtask</code> to automate this so we can easily reproduce this file in future.</p>
<p>We'll add the following content to <code>xtask/src/codegen.rs</code></p>
<pre><code class="language-rust ignore">use aya_gen::btf_types;
use std::{
fs::File,
io::Write,
path::{Path, PathBuf},
};
pub fn generate() -&gt; Result&lt;(), anyhow::Error&gt; {
let dir = PathBuf::from(&quot;myapp-ebpf/src&quot;);
let names: Vec&lt;&amp;str&gt; = vec![&quot;ethhdr&quot;, &quot;iphdr&quot;];
let bindings = btf_types::generate(Path::new(&quot;/sys/kernel/btf/vmlinux&quot;), &amp;names, false)?;
// Write the bindings to the $OUT_DIR/bindings.rs file.
let mut out = File::create(dir.join(&quot;bindings.rs&quot;))?;
write!(out, &quot;{}&quot;, bindings).expect(&quot;unable to write bindings to file&quot;);
Ok(())
}
</code></pre>
<p>This will generate a file called <code>myapp-ebpf/src/bindings.rs</code>. If you've chosen an application name other than <code>myapp</code> you'll need to adjust the path appropriately.</p>
<p>Add a new dependencies to <code>xtask/Cargo.toml</code>:</p>
<pre><code class="language-toml">[dependencies]
aya-gen = { git = &quot;http://github.com/alessandrod/aya&quot;, branch = &quot;main&quot; }
</code></pre>
<p>And finally, we must register the command in <code>xtask/src/main.rs</code>:</p>
<pre><code class="language-rust ignore">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) =&gt; build_ebpf::build(opts),
Codegen =&gt; codegen::generate(),
};
if let Err(e) = ret {
eprintln!(&quot;{:#}&quot;, e);
exit(1);
}
}
</code></pre>
<p>Once we've generated our file using <code>cargo xtask codegen</code> from the root of the project.</p>
<p>These can then be accessed from within <code>myapp-ebpf/src/main.rs</code>:</p>
<pre><code class="language-rust ignore">mod bindings;
use bindings::{ethhdr, iphdr};
</code></pre>
<h3 id="getting-packet-data-from-the-context"><a class="header" href="#getting-packet-data-from-the-context">Getting Packet Data From The Context</a></h3>
<p>The <code>XdpContext</code> contains two fields, <code>data</code> and <code>data_end</code>.
<code>data</code> is a pointer to the start of the data in kernel memory and <code>data_end</code>, a pointer to the end of the data in kernel memory. In order to access this data and ensure that the eBPF verifier is happy, we'll introduce a helper function:</p>
<pre><code class="language-rust ignore">#[inline(always)]
unsafe fn ptr_at&lt;T&gt;(ctx: &amp;XdpContext, offset: usize) -&gt; Result&lt;*const T, ()&gt; {
let start = ctx.data();
let end = ctx.data_end();
let len = mem::size_of::&lt;T&gt;();
if start + offset + len &gt; end {
return Err(());
}
Ok((start + offset) as *const T)
}
</code></pre>
<p>This function will ensure that before we access any data, we check that it's contained between <code>data</code> and <code>data_end</code>.
It is marked as <code>unsafe</code> because when calling the function, you must ensure that there is a valid <code>T</code> at that location or there will be undefined behaviour.</p>
<h3 id="writing-data-to-the-map"><a class="header" href="#writing-data-to-the-map">Writing Data To The Map</a></h3>
<p>With our helper function in place, we can:</p>
<ol>
<li>Read the Ethertype field to check if we have an IPv4 packet.</li>
<li>Read the IPv4 Source Address from the IP header</li>
</ol>
<p>First let's add another dependency on <code>memoffset = &quot;0.6&quot;</code> to <code>myapp-ebpf/Cargo.toml</code>, and then we'll change our <code>try_xdp_firewall</code> function to look like this:</p>
<pre><code class="language-rust ignore">use memoffset::offset_of;
fn try_xdp_firewall(ctx: XdpContext) -&gt; Result&lt;u32, ()&gt; {
let h_proto = u16::from_be(unsafe { *ptr_at(&amp;ctx, offset_of!(ethhdr, h_proto))? });
if h_proto != ETH_P_IP {
return Ok(xdp_action::XDP_PASS)
}
let source = u32::from_be(unsafe { *ptr_at(&amp;ctx, ETH_HDR_LEN + offset_of!(iphdr, saddr))? });
let log_entry = PacketLog{
ipv4_address: source,
action: xdp_action::XDP_PASS,
};
unsafe { EVENTS.output(&amp;ctx, &amp;log_entry, 0); }
Ok(xdp_action::XDP_PASS)
}
</code></pre>
<blockquote>
<p>💡 <strong>HINT: Reading Fields Using <code>offset_of!</code></strong></p>
<p>As there is limited stack space, it's more memory efficient to use the <code>offset_of!</code> macro to read
a single field from a struct, rather than reading the whole struct and accessing the field by name.</p>
</blockquote>
<p>Once we have our IPv4 source address, we can create a <code>PacketLog</code> struct and output this to our PerfEventArray</p>
<h2 id="reading-data"><a class="header" href="#reading-data">Reading Data</a></h2>
<h3 id="going-async"><a class="header" href="#going-async">Going Async</a></h3>
<p>In order to read from the <code>AsyncPerfEventArray</code>, we have to call <code>AsyncPerfEventArray::open()</code> for each online CPU, then we have to poll the file descriptor for events.
While this is do-able using <code>PerfEventArray</code> and <code>mio</code> or <code>epoll</code>, the code is much less easy to follow. Instead, we'll use <code>tokio</code> to make our user-space application async.</p>
<p>Let's add some dependencies to <code>myapp/src/Cargo.toml</code>:</p>
<pre><code class="language-toml">[dependencies]
aya = { git = &quot;https://github.com/alessandrod/aya&quot;, branch=&quot;main&quot;, features=[&quot;async_tokio&quot;] }
myapp-common = { path = &quot;../myapp-common&quot;, features=[&quot;userspace&quot;] }
anyhow = &quot;1.0.42&quot;
bytes = &quot;1&quot;
tokio = { version = &quot;1.9.0&quot;, features = [&quot;full&quot;] }
</code></pre>
<p>And adjust our <code>myapp/src/main.rs</code> to look like this:</p>
<pre><code class="language-rust ignore">use aya::{
maps::perf::AsyncPerfEventArray,
programs::{Xdp, XdpFlags},
util::online_cpus,
Bpf,
};
use bytes::BytesMut;
use std::{
convert::{TryFrom, TryInto},
env, fs, net,
};
use tokio::{signal, task};
use myapp_common::PacketLog;
#[tokio::main]
async fn main() -&gt; Result&lt;(), anyhow::Error&gt; {
let path = match env::args().nth(1) {
Some(iface) =&gt; iface,
None =&gt; panic!(&quot;not path provided&quot;),
};
let iface = match env::args().nth(2) {
Some(iface) =&gt; iface,
None =&gt; &quot;eth0&quot;.to_string(),
};
let data = fs::read(path)?;
let mut bpf = Bpf::load(&amp;data, None)?;
let probe: &amp;mut Xdp = bpf.program_mut(&quot;xdp&quot;)?.try_into()?;
probe.load()?;
probe.attach(&amp;iface, XdpFlags::default())?;
let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut(&quot;EVENTS&quot;)?)?;
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::&lt;Vec&lt;_&gt;&gt;();
loop {
let events = buf.read_events(&amp;mut buffers).await.unwrap();
for i in 0..events.read {
let buf = &amp;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!(&quot;LOG: SRC {}, ACTION {}&quot;, src_addr, data.action);
}
}
});
}
signal::ctrl_c().await.expect(&quot;failed to listen for event&quot;);
Ok::&lt;_, anyhow::Error&gt;(())
}
</code></pre>
<p>This will now spawn a <code>tokio::task</code> to read each of the <code>AsyncPerfEventArrayBuffers</code> contained in out <code>AsyncPerfEventArray</code>.
When we receive an event, we use <code>read_unaligned</code> to read our data into a <code>PacketLog</code>.
We then use <code>println!</code> to log the event to the console.
We no longer need to sleep, as we run until we receive the <code>CTRL+C</code> signal.</p>
<h2 id="running-the-program"><a class="header" href="#running-the-program">Running the program</a></h2>
<pre><code class="language-console">$ cargo build
$ cargo xtask build-ebpf
$ sudo ./target/debug/myapp ./target/bpfel-unknown-none/debug/myapp wlp2s0
LOG: SRC 192.168.1.205, ACTION 2
LOG: SRC 192.168.1.21, ACTION 2
LOG: SRC 192.168.1.21, ACTION 2
LOG: SRC 18.168.253.132, ACTION 2
LOG: SRC 18.168.253.132, ACTION 2
LOG: SRC 18.168.253.132, ACTION 2
LOG: SRC 140.82.121.6, ACTION 2
</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../start/hello-xdp.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../start/hello-xdp.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>
Loading…
Cancel
Save