Projects#

Rust projects that fit the language’s sweet spot, parsers that touch hostile input, CLI tools for the operator’s daily kit, and agents that must not crash.

CLI tool#

A subdomain scanner built on clap, tokio, and trust-dns. Cross-compiles to a static binary drop anywhere.

// src/main.rs
use clap::Parser;
use std::path::PathBuf;
use tokio::fs;
use trust_dns_resolver::TokioAsyncResolver;
use futures::stream::{StreamExt, FuturesUnordered};

#[derive(Parser)]
#[command(name = "op-resolve")]
struct Args {
    /// File with one hostname per line
    file: PathBuf,
    /// Parallel queries
    #[arg(short, long, default_value_t = 50)]
    concurrency: usize,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Args::parse();
    let resolver = TokioAsyncResolver::tokio_from_system_conf()?;
    let hosts: Vec<String> = fs::read_to_string(&args.file).await?
        .lines().map(|s| s.to_string()).collect();

    let mut tasks = FuturesUnordered::new();
    for host in hosts {
        let r = resolver.clone();
        tasks.push(tokio::spawn(async move {
            let addrs = r.lookup_ip(&host).await
                .map(|r| r.iter().collect::<Vec<_>>()).unwrap_or_default();
            (host, addrs)
        }));
        if tasks.len() >= args.concurrency {
            if let Some(Ok((h, a))) = tasks.next().await {
                println!("{} {:?}", h, a);
            }
        }
    }
    while let Some(Ok((h, a))) = tasks.next().await {
        println!("{} {:?}", h, a);
    }
    Ok(())
}

Protocol parser#

A binary protocol parser with nom. The operator chooses Rust for parsers that touch hostile input because the borrow checker prevents whole classes of bugs.

use nom::{
    bytes::complete::take,
    number::complete::{be_u8, be_u16},
    IResult,
};

#[derive(Debug)]
pub struct OpPacket<'a> {
    pub version: u8,
    pub length: u16,
    pub flags: u16,
    pub payload: &'a [u8],
}

pub fn parse_op_packet(input: &[u8]) -> IResult<&[u8], OpPacket> {
    let (input, version) = be_u8(input)?;
    let (input, length) = be_u16(input)?;
    let (input, flags) = be_u16(input)?;
    let (input, payload) = take(length as usize)(input)?;
    Ok((input, OpPacket { version, length, flags, payload }))
}

HTTP probe#

An async HTTP banner-grabber using tokio and reqwest.

use anyhow::Result;
use futures::stream::{FuturesUnordered, StreamExt};
use reqwest::Client;

#[tokio::main]
async fn main() -> Result<()> {
    let urls = std::env::args().skip(1).collect::<Vec<_>>();
    let client = Client::builder()
        .timeout(std::time::Duration::from_secs(5))
        .build()?;

    let mut tasks = FuturesUnordered::new();
    for u in urls {
        let c = client.clone();
        tasks.push(async move {
            let r = c.get(&u).send().await;
            let info = match r {
                Ok(resp) => format!("{} {}", resp.status(),
                    resp.headers().get("server").map(|h| h.to_str().unwrap_or(""))
                        .unwrap_or("")),
                Err(e) => format!("ERR: {e}"),
            };
            (u, info)
        });
    }
    while let Some((u, info)) = tasks.next().await {
        println!("{u} {info}");
    }
    Ok(())
}

WASM agent#

A Rust crate compiled to WebAssembly the operator embeds in a browser-side or edge-runtime tool.

$ rustup target add wasm32-unknown-unknown
$ cargo install wasm-pack
$ wasm-pack build --target web
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn classify(text: &str) -> String {
    if text.contains("ERROR") { "error".into() }
    else if text.contains("WARN") { "warn".into() }
    else { "info".into() }
}

References#