#packet #security #linux #pcap #networking #linux-kernel #per-thread

af_packet

AF_PACKET bindings for Rust, primarily to be used for high-performance network security applications

15 releases

Uses old Rust 2015

0.3.1 Jun 2, 2020
0.3.0 Jun 20, 2018
0.2.7 Jun 12, 2018
0.2.5 May 31, 2018
0.1.1 Dec 29, 2017

#627 in Unix APIs

GPL-3.0 license

25KB
554 lines

High-Performance AF_PACKET bindings for Rust

Crates.io License: GPL v3 af_packet

This library is intended to provide an efficient, safe, and ergonomic way of reading raw packet data on an interface across multiple threads. Its primary intended use is for network security and monitoring applications, in conjunction with crates like nom (https://github.com/Geal/nom) to build protocol decoders and more complex analysis engines.

A multi-threaded raw receiver in ~40 lines of code

The Linux kernel provides flow balancing based on a hashed tuple so threads do not need to communicate with each other to do flow reassembly. This behavior is, however, configurable. A full multithreaded DNS sniffer in ~70 lines of code can be seen at (https://github.com/DominoTree/rs-dns-sniffer/blob/master/src/main.rs). It can decode over 120,000 DNS messages per second on an eight-core machine without dropping any frames, and has been tested beyond 1,500,000 records per second.

extern crate af_packet;
extern crate num_cpus;

use std::env;
use std::thread;

fn main() {
    //get args
    let args: Vec<String> = env::args().collect();
    
    //create a vec for file descriptors to poll for statistics
    let mut fds = Vec::<i32>::new();

    //spawn one thread per CPU
    for _ in 0..num_cpus::get() {
        let interface = args[1].clone();
        
        //open an mmap()ed ring buffer for this thread
        let mut ring = af_packet::Ring::from_if_name(&interface).unwrap();
        
        //store the fd from the ring to get stats later
        fds.push(ring.fd);
        
        thread::spawn(move || {
            //move struct into the thread
            //receive blocks and process them
            loop {
                let mut block = ring.get_block();
                for _packet in block.get_raw_packets() {
                    //process frame data here
                }
                block.mark_as_consumed();
            }
        });
    }

    //Below is to print statistics only
    let mut packets: u64 = 0;
    let mut drops: u64 = 0;

    loop {
        let mut stats: (u64, u64) = (0, 0);
        for fd in &fds {
            let ring_stats = af_packet::get_rx_statistics(*fd).unwrap();
            stats.0 += ring_stats.tp_drops as u64;
            stats.1 += ring_stats.tp_packets as u64;
        }
        drops += stats.0;
        packets += stats.1;
        eprintln!("{} frames received per second, {} dropped. {} total drops of {} total packets ({}%)", stats.1, stats.0, drops, packets, format!("{:.*}", 4, drops as f64 / packets as f64 * 100 as f64));
        thread::sleep(std::time::Duration::from_secs(1));
    }
}

Based on work by Tom Karpiniec (http://thomask.sdf.org/blog/2017/09/01/layer-2-raw-sockets-on-rustlinux.html) and Herman Radtke (http://hermanradtke.com/2016/03/17/unions-rust-ffi.html)

Dependencies

~1MB
~19K SLoC