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#
Setup for the toolchain.
Frameworks for tokio, axum, leptos, bevy, etc.
Networking for
tokio,reqwest,hyper,trust-dns.