You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aya/print.html

433 lines
23 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Building eBPF Programs With Aya</title>
<meta name="robots" content="noindex" />
<!-- 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></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="introduction"><a class="header" href="#introduction">Introduction</a></h1>
<p>Welcome to Building eBPF Programs with Aya: An introductory book about using the Rust
Programming Language and Aya library to build extended Berkley Packet Filter (eBPF)
programs.</p>
<h2 id="who-aya-is-for"><a class="header" href="#who-aya-is-for">Who Aya Is For</a></h2>
<p>Rust is proving to be a popular systems programming language because of its
safety features and excellent C interoperability. The safety features are less
important in the context of eBPF as programs often need to read kernel memory, which
is considered unsafe. However, what Rust combined with Aya does offer is a fast and
efficient development experience:</p>
<ul>
<li>Cargo for project scaffolding, build, test and debugging</li>
<li>Generation of Rust bindings to Kernel Headers with Compile-Once, Run-Everywhere (CO-RE) support</li>
<li>Easy code sharing between user-space and eBPF programs</li>
<li>Fast compile times</li>
<li>No runtime dependency on LLVM or BCC</li>
</ul>
<h2 id="scope"><a class="header" href="#scope">Scope</a></h2>
<p>The goals of this book are:</p>
<ul>
<li>
<p>Get developers up to speed with eBPF Rust development. i.e. How to set
up a development environment.</p>
</li>
<li>
<p>Share <em>current</em> best practices about using Rust for eBPF</p>
</li>
</ul>
<h2 id="who-this-book-is-for"><a class="header" href="#who-this-book-is-for">Who This Book is For</a></h2>
<p>This book caters towards people with either some eBPF or some Rust background. For those without any prior knowledge we suggest you read the &quot;Assumptions and Prerequisites&quot; section first. You can check out the &quot;Other Resources&quot; section to find resources on topics you might want to read up on.</p>
<h3 id="assumptions-and-prerequisites"><a class="header" href="#assumptions-and-prerequisites">Assumptions and Prerequisites</a></h3>
<ul>
<li>You are comfortable using the Rust Programming Language, and have written,
run, and debugged Rust applications on a desktop environment. You should also
be familiar with the idioms of the <a href="https://doc.rust-lang.org/edition-guide/">2018 edition</a> as this book targets
Rust 2018.</li>
</ul>
<ul>
<li>You are familiar with the core concepts of eBPF</li>
</ul>
<h3 id="other-resources"><a class="header" href="#other-resources">Other Resources</a></h3>
<p>If you are unfamiliar with anything mentioned above or if you want more information about a specific topic mentioned in this book you might find some of these resources helpful.</p>
<table><thead><tr><th>Topic</th><th>Resource</th><th>Description</th></tr></thead><tbody>
<tr><td>Rust</td><td><a href="https://doc.rust-lang.org/book/">Rust Book</a></td><td>If you are not yet comfortable with Rust, we highly suggest reading this book.</td></tr>
<tr><td>eBPF</td><td><a href="https://docs.cilium.io/en/stable/bpf/">Cilium BPF and XDP Reference Guide</a></td><td>If you are not yet comfortable with eBPF, this guide is excellent.</td></tr>
</tbody></table>
<h2 id="how-to-use-this-book"><a class="header" href="#how-to-use-this-book">How to Use This Book</a></h2>
<p>This book generally assumes that youre reading it front-to-back. Later
chapters build on concepts in earlier chapters, and earlier chapters may
not dig into details on a topic, revisiting the topic in a later chapter.</p>
<h2 id="source-code"><a class="header" href="#source-code">Source Code</a></h2>
<p>The source files from which this book is generated can be found on <a href="https://github.com/alessandrod/aya">GitHub</a>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="ebpf-program-constraints"><a class="header" href="#ebpf-program-constraints">eBPF Program Constraints</a></h1>
<p>The eBPF Virtual Machine, where our eBPF programs will be run, is a constrained runtime environment:</p>
<ul>
<li>There is only 512 bytes of stack (or 256 bytes if we are using tail calls).</li>
<li>There is no access to heap space and data must instead be written to maps.</li>
</ul>
<p>Even applications written in C are restricted to a subset of language features:</p>
<ul>
<li>no loops</li>
<li>no global variables</li>
<li>no variadic functions</li>
<li>no floating-point numbers</li>
<li>no passing structures as function arguments</li>
</ul>
<p>While these limitations do not map 1:1 with Rust, we are still constrained:</p>
<ul>
<li>We may not use the standard library. We use <code>core</code> instead.</li>
<li><code>core::fmt</code> may not be used and neither can traits that rely on it, for example <code>Display</code> and <code>Debug</code></li>
<li>As there is no heap, we cannot use <code>alloc</code> or <code>collections</code>.</li>
<li>We must not <code>panic</code> as the eBPF VM does not support stack unwinding, or the <code>abort</code> instruction.</li>
<li>There is no <code>main</code> function</li>
</ul>
<p>Alongside this, a lot of the code that we write is <code>unsafe</code>, as we are reading directly from kernel memory.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="getting-started"><a class="header" href="#getting-started">Getting Started</a></h1>
<p>In this section we'll walk you through the process of writing, building
and running a simple eBPF program and userspace application.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="development-environment"><a class="header" href="#development-environment">Development Environment</a></h1>
<h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
<p>Before getting started you will need the Rust stable and nightly tool-chains installed on your system.
This is easily achieved with [<code>rustup</code>]:</p>
<pre><code class="language-console">rustup install stable
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
</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>
<pre><code class="language-console">cargo generate https://github.com/dave-tucker/aya-template
</code></pre>
<p>This will prompt you for a project name. We'll be using <code>myapp</code> in this example</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="hello-xdp"><a class="header" href="#hello-xdp">Hello XDP!</a></h1>
<h2 id="example-project"><a class="header" href="#example-project">Example Project</a></h2>
<p>While there are myriad trace points to attach to and program types to write we should start somewhere simple.</p>
<p>XDP (eXpress Data Path) programs permit our eBPF program to make decisions about packets that have been received on the interface to which our program is attached. To keep things simple, we'll build a very simplistic firewall to permit or deny traffic.</p>
<h2 id="ebpf-component"><a class="header" href="#ebpf-component">eBPF Component</a></h2>
<h3 id="permit-all"><a class="header" href="#permit-all">Permit All</a></h3>
<p>We must first write the eBPF component of our program.
The logic for this program is located in <code>myapp-ebpf/src/main.rs</code> and currently looks like this:</p>
<pre><code class="language-rust ignore">#![no_std]
#![no_main]
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
unreachable!()
}
</code></pre>
<ul>
<li><code>#![no_std]</code> is required since we cannot use the standard library.</li>
<li><code>#![no_main]</code> is required as we have no main function.</li>
<li>The <code>#[panic_handler]</code> is required to keep the compiler happy, although it is never used since we cannot panic.</li>
</ul>
<p>Let's expand this by adding an XDP program that permits all traffic.</p>
<p>First we'll add some imports:</p>
<pre><code class="language-rust ignore">use aya_bpf::bindings::xdp_action;
use aya_bpf::cty::c_long;
use aya_bpf::macros::xdp;
use aya_bpf::programs::XdpContext;
</code></pre>
<p>Then our application logic:</p>
<pre><code class="language-rust ignore">#[xdp]
pub fn xdp_firewall(ctx: XdpContext) -&gt; u32 {
match unsafe { try_xdp_firewall(ctx) } {
Ok(ret) =&gt; ret,
Err(_) =&gt; xdp_action::XDP_ABORTED,
}
}
unsafe fn try_xdp_firewall(_ctx: XdpContext) -&gt; Result&lt;u32, c_long&gt; {
Ok(xdp_action::XDP_PASS)
}
</code></pre>
<ul>
<li><code>#[xdp]</code> indicates that this function is an XDP program</li>
<li>The <code>try_xdp_firewall</code> function returns a Result that permits all traffic</li>
<li>The <code>xdp_firewall</code> program calls <code>try_xdp_firewall</code> and handles any errors by returning <code>XDP_ABORTED</code>, which will drop the packet and raise a tracepoint exception.</li>
</ul>
<p>Now we can compile this using <code>cargo xtask build-ebpf</code></p>
<h3 id="verifying-the-program"><a class="header" href="#verifying-the-program">Verifying The Program</a></h3>
<p>Let's take a look at the compiled eBPF program:</p>
<pre><code class="language-console">$ llvm-objdump -S target/bpfel-unknown-none/debug/myapp
target/bpfel-unknown-none/debug/myapp: file format elf64-bpf
Disassembly of section xdp:
0000000000000000 &lt;xdp_firewall&gt;:
0: b7 00 00 00 02 00 00 00 r0 = 2
1: 95 00 00 00 00 00 00 00 exit
</code></pre>
<p>We can see an <code>xdp_firewall</code> section here.
<code>r0 = 2</code> sets register <code>0</code> to <code>2</code>, which is the value of the <code>XDP_PASS</code> action.
<code>exit</code> ends the program.</p>
<p>Simple!</p>
<h3 id="completed-program"><a class="header" href="#completed-program">Completed Program</a></h3>
<pre><code class="language-rust ignore">#![no_std]
#![no_main]
use aya_bpf::bindings::xdp_action;
use aya_bpf::cty::c_long;
use aya_bpf::macros::xdp;
use aya_bpf::programs::XdpContext;
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
unreachable!()
}
#[xdp]
pub fn xdp_firewall(ctx: XdpContext) -&gt; u32 {
match unsafe { try_xdp_firewall(ctx) } {
Ok(ret) =&gt; ret,
Err(_) =&gt; xdp_action::XDP_ABORTED,
}
}
unsafe fn try_xdp_firewall(_ctx: XdpContext) -&gt; Result&lt;u32, c_long&gt; {
Ok(xdp_action::XDP_PASS)
}
</code></pre>
<h2 id="user-space-component"><a class="header" href="#user-space-component">User-space Component</a></h2>
<p>Now our eBPF program is complete and compiled, we need a user-space program to load it and attach it to a trace point.
Fortunately, we have a program ready in <code>myapp/src/main.rs</code> which is going to do that for us.</p>
<h3 id="starting-out"><a class="header" href="#starting-out">Starting Out</a></h3>
<p>The generated application has the following content:</p>
<pre><code class="language-rust ignore">fn main() {
if let Err(e) = try_main() {
eprintln!(&quot;error: {:#}&quot;, e);
}
}
fn try_main() -&gt; Result&lt;(), anyhow::Error&gt; {
Ok(())
}
</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>
<pre><code class="language-rust ignore">use aya::Bpf;
use aya::programs::{Xdp, XdpFlags};
use std::{
convert::TryInto,
env,
thread,
time::Duration,
};
</code></pre>
<p>Then we'll adapt the <code>try_main</code> function to load our program:</p>
<pre><code class="language-rust ignore">fn try_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 mut bpf = Bpf::load_file(&amp;path)?;
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));
};
Ok(())
}
</code></pre>
<p>The program takes two positional arguments</p>
<ul>
<li>The path to our eBPF application</li>
<li>The interface we wish to attach it to (defaults to <code>eth0</code>)</li>
</ul>
<p>The line <code>let mut bpf = Bpf::load_file(&amp;path)?;</code>:</p>
<ul>
<li>Opens the file</li>
<li>Reads the ELF contents</li>
<li>Creates any maps</li>
<li>If your system supports BPF Type Format (BTF), it will read the current BTF description and performs any necessary relocations</li>
</ul>
<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
</code></pre>
<p>That was uneventful. Did it work?</p>
<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>
<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>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</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 -->
<script type="text/javascript">
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</body>
</html>