mirror of https://github.com/aya-rs/aya
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.
359 lines
18 KiB
HTML
359 lines
18 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js light">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Hello XDP! - 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" 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>
|
|
|
|
<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="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: &core::panic::PanicInfo) -> ! {
|
|
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) -> u32 {
|
|
match unsafe { try_xdp_firewall(ctx) } {
|
|
Ok(ret) => ret,
|
|
Err(_) => xdp_action::XDP_ABORTED,
|
|
}
|
|
}
|
|
|
|
unsafe fn try_xdp_firewall(_ctx: XdpContext) -> Result<u32, c_long> {
|
|
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 <xdp_firewall>:
|
|
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: &core::panic::PanicInfo) -> ! {
|
|
unreachable!()
|
|
}
|
|
|
|
#[xdp]
|
|
pub fn xdp_firewall(ctx: XdpContext) -> u32 {
|
|
match unsafe { try_xdp_firewall(ctx) } {
|
|
Ok(ret) => ret,
|
|
Err(_) => xdp_action::XDP_ABORTED,
|
|
}
|
|
}
|
|
|
|
unsafe fn try_xdp_firewall(_ctx: XdpContext) -> Result<u32, c_long> {
|
|
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!("error: {:#}", e);
|
|
}
|
|
}
|
|
|
|
fn try_main() -> Result<(), anyhow::Error> {
|
|
Ok(())
|
|
}
|
|
</code></pre>
|
|
<p>Let's adapt it to load our program.</p>
|
|
<p>We will add a dependency on <code>ctrlc = "3.2"</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::{
|
|
convert::TryInto,
|
|
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>
|
|
<pre><code class="language-rust ignore">fn try_main() -> Result<(), anyhow::Error> {
|
|
let path = match env::args().nth(1) {
|
|
Some(iface) => iface,
|
|
None => panic!("not path provided"),
|
|
};
|
|
let iface = match env::args().nth(2) {
|
|
Some(iface) => iface,
|
|
None => "eth0".to_string(),
|
|
};
|
|
let mut bpf = Bpf::load_file(&path)?;
|
|
let probe: &mut Xdp = bpf.program_mut("xdp")?.try_into()?;
|
|
probe.load()?;
|
|
probe.attach(&iface, XdpFlags::default())?;
|
|
|
|
let running = Arc::new(AtomicBool::new(true));
|
|
let r = running.clone();
|
|
|
|
ctrlc::set_handler(move || {
|
|
r.store(false, Ordering::SeqCst);
|
|
}).expect("Error setting Ctrl-C handler");
|
|
|
|
println!("Waiting for Ctrl-C...");
|
|
while running.load(Ordering::SeqCst) {}
|
|
println!("Exiting...");
|
|
|
|
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(&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: &mut Xdp = bpf.program_mut("xdp")?.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(&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
|
|
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>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>Running the command again once <code>myapp</code> has exited will show that the program is no longer running.</p>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
<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>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
<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>
|
|
|
|
<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>
|