Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TCP Echo Server Example #28

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ compile_commands.json
*.ll
*.o
*.skel.h

# Rust
**/debug/
**/target/
**/Cargo.lock
**/*.rs.bk
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ This git repository contains a diverse set of **practical BPF examples** that
solve (or demonstrate) a specific use-case using BPF.

It is meant to ease doing **rapid prototyping and development**, writing C-code
BPF programs using libbpf. The goal is to make it **easier for developers** to
get started coding.
BPF programs using libbpf, or Rust BPF programs using Aya.
The goal is to make it **easier for developers** to get started coding.

Many developers struggle to get a working BPF build environment. The repo
enviroment makes it easy to build/compile BPF programs by doing the necessary
Expand Down
2 changes: 2 additions & 0 deletions echo/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"
3 changes: 3 additions & 0 deletions echo/.vim/coc-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rust-analyzer.linkedProjects": ["Cargo.toml", "echo-ebpf/Cargo.toml"]
}
3 changes: 3 additions & 0 deletions echo/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rust-analyzer.linkedProjects": ["Cargo.toml", "echo-ebpf/Cargo.toml"]
}
2 changes: 2 additions & 0 deletions echo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[workspace]
members = ["echo", "echo-tokio", "xtask"]
32 changes: 32 additions & 0 deletions echo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# echo

Echo is an example that creates a TCP Echo Server using socket redirection.
For comparison, a simple (naive) TCP Echo Server is also provided.


## Prerequisites

1. Install a rust stable toolchain: `rustup install stable`
1. Install a rust nightly toolchain: `rustup install nightly`
1. Install bpf-linker: `cargo install bpf-linker`

## Running The eBPF Example

```bash
cargo xtask build-ebpf
cargo xtask run
```

## Running the Native Example

```bash
cargo run --bin echo-tokio
```

## Interacting with the Example

```bash
nc localhost 41234
```

Any data sent will be echoed back to the client
6 changes: 6 additions & 0 deletions echo/echo-ebpf/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build]
target-dir = "../target"
target = "bpfel-unknown-none"

[unstable]
build-std = ["core"]
4 changes: 4 additions & 0 deletions echo/echo-ebpf/.vim/coc-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"rust-analyzer.cargo.target": "bpfel-unknown-none",
"rust-analyzer.checkOnSave.allTargets": false
}
4 changes: 4 additions & 0 deletions echo/echo-ebpf/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"rust-analyzer.cargo.target": "bpfel-unknown-none",
"rust-analyzer.checkOnSave.allTargets": false,
}
23 changes: 23 additions & 0 deletions echo/echo-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "echo-ebpf"
version = "0.1.0"
edition = "2018"

[dependencies]
aya-bpf = { git = "https://github.com/aya-rs/aya", branch="main" }

[[bin]]
name = "echo"
path = "src/main.rs"

[profile.dev]
panic = "abort"
debug = 1
opt-level = 2
overflow-checks = false

[profile.release]
panic = "abort"

[workspace]
members = []
2 changes: 2 additions & 0 deletions echo/echo-ebpf/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel="nightly"
45 changes: 45 additions & 0 deletions echo/echo-ebpf/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#![no_std]
#![no_main]

use aya_bpf::{
bindings::sk_action,
macros::{map, stream_parser, stream_verdict},
maps::SockMap,
programs::SkBuffContext,
};

#[map(name = "sockmap")]
static mut SOCKMAP: SockMap = SockMap::with_max_entries(1, 0);

#[stream_parser]
fn stream_parser(ctx: SkBuffContext) -> u32 {
match { try_stream_parser(ctx) } {
Ok(ret) => ret,
Err(ret) => ret,
}
}

fn try_stream_parser(ctx: SkBuffContext) -> Result<u32, u32> {
Ok(ctx.len())
}

#[stream_verdict]
fn stream_verdict(ctx: SkBuffContext) -> u32 {
match unsafe { try_stream_verdict(ctx) } {
Ok(_) => sk_action::SK_PASS,
Err(_) => sk_action::SK_DROP,
}
}

unsafe fn try_stream_verdict(ctx: SkBuffContext) -> Result<u32, u32> {
match SOCKMAP.redirect_skb(&ctx, 0, 0) as u32 {
sk_action::SK_PASS => Ok(sk_action::SK_PASS),
sk_action::SK_DROP => Err(sk_action::SK_DROP),
_ => Err(sk_action::SK_DROP),
}
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}
7 changes: 7 additions & 0 deletions echo/echo-tokio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "echo-tokio"
version = "0.1.0"
edition = "2018"

[dependencies]
tokio = { version = "1.9.0", features = ["full"] }
34 changes: 34 additions & 0 deletions echo/echo-tokio/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:41234").await?;

loop {
let (mut socket, _) = listener.accept().await?;

tokio::spawn(async move {
let mut buf = [0; 65535];

// In a loop, read data from the socket and write the data back.
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(n) if n == 0 => return,
Ok(n) => n,
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
return;
}
};

// Write the data back
if let Err(e) = socket.write_all(&buf[0..n]).await {
eprintln!("failed to write to socket; err = {:?}", e);
return;
}
}
});
}
}
16 changes: 16 additions & 0 deletions echo/echo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "echo"
version = "0.1.0"
edition = "2018"
publish = false

[dependencies]
aya = { git = "https://github.com/aya-rs/aya", branch="main" }
anyhow = "1.0.42"
ctrlc = "3.2"
structopt = { version = "0.3"}
tokio = { version = "1.9.0", features = ["full"] }

[[bin]]
name = "echo"
path = "src/main.rs"
46 changes: 46 additions & 0 deletions echo/echo/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use aya::maps::{MapRefMut, SockMap};
use aya::programs::SkSkb;
use aya::{include_bytes_aligned, Bpf};
use tokio::io::AsyncReadExt;
use tokio::signal;

use std::convert::{TryFrom, TryInto};

use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
#[cfg(debug_assertions)]
let mut bpf = Bpf::load(include_bytes_aligned!(
"../../target/bpfel-unknown-none/debug/echo"
))?;
#[cfg(not(debug_assertions))]
let mut bpf = Bpf::load(include_bytes_aligned!(
"../../target/bpfel-unknown-none/release/echo"
))?;
let mut sock_map = SockMap::<MapRefMut>::try_from(bpf.map_mut("sockmap")?)?;

let parser: &mut SkSkb = bpf.program_mut("stream_parser")?.try_into()?;
parser.load()?;
parser.attach(&sock_map)?;

let verdict: &mut SkSkb = bpf.program_mut("stream_verdict")?.try_into()?;
verdict.load()?;
verdict.attach(&sock_map)?;

let listener = TcpListener::bind("127.0.0.1:41234").await?;

println!("Server Listening on {}", listener.local_addr().unwrap());
// TODO: currently this will only accept one connection at a time. add up to map max_entries handlers
tokio::spawn(async move {
loop {
let (mut socket, _) = listener.accept().await.unwrap();
sock_map.set(0, &socket, 0).unwrap();
let mut buf = [0; 0];
socket.read(&mut buf[..]).await.unwrap();
sock_map.clear_index(&0).unwrap();
}
});
signal::ctrl_c().await?;
Ok(())
}
10 changes: 10 additions & 0 deletions echo/xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
structopt = {version = "0.3", default-features = false }
anyhow = "1"
64 changes: 64 additions & 0 deletions echo/xtask/src/build_ebpf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::path::PathBuf;
use std::process::Command;

use structopt::StructOpt;

#[derive(Debug, Copy, Clone)]
pub enum Architecture {
BpfEl,
BpfEb,
}

impl std::str::FromStr for Architecture {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"bpfel-unknown-none" => Architecture::BpfEl,
"bpfeb-unknown-none" => Architecture::BpfEb,
_ => return Err("invalid target".to_owned()),
})
}
}

impl std::fmt::Display for Architecture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Architecture::BpfEl => "bpfel-unknown-none",
Architecture::BpfEb => "bpfeb-unknown-none",
})
}
}

#[derive(StructOpt)]
pub struct Options {
/// Set the endianness of the BPF target
#[structopt(default_value = "bpfel-unknown-none", long)]
pub target: Architecture,
/// Build the release target
#[structopt(long)]
pub release: bool,
}

pub fn build_ebpf(opts: Options) -> Result<(), anyhow::Error> {
let dir = PathBuf::from("echo-ebpf");
let target = format!("--target={}", opts.target);
let mut args = vec![
"+nightly",
"build",
"--verbose",
target.as_str(),
"-Z",
"build-std=core",
];
if opts.release {
args.push("--release")
}
let status = Command::new("cargo")
.current_dir(&dir)
.args(&args)
.status()
.expect("failed to build bpf program");
assert!(status.success());
Ok(())
}
32 changes: 32 additions & 0 deletions echo/xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
mod build_ebpf;
mod run;

use std::process::exit;

use structopt::StructOpt;
#[derive(StructOpt)]
pub struct Options {
#[structopt(subcommand)]
command: Command,
}

#[derive(StructOpt)]
enum Command {
BuildEbpf(build_ebpf::Options),
Run(run::Options),
}

fn main() {
let opts = Options::from_args();

use Command::*;
let ret = match opts.command {
BuildEbpf(opts) => build_ebpf::build_ebpf(opts),
Run(opts) => run::run(opts),
};

if let Err(e) = ret {
eprintln!("{:#}", e);
exit(1);
}
}
Loading