Rust library
The minipaw crate is a blocking Rust library you can embed. Listen, dial or serve, give it any reader and writer, and get events and progress back.
The networking lives in the
minipaw
crate, the library behind the minipaw command. It’s blocking, with no async
runtime: you build a Session with minipaw::listen or minipaw::dial, and
Session::run drives it on the calling thread until it ends.
cargo add minipaw
use minipaw::{Config, Event, Io};
fn main() -> Result<(), minipaw::Error> {
let outcome = minipaw::listen(Config::default(), Io::stdio())
.on_event(|event| {
if let Event::Listening { ticket } = event {
eprintln!("connect with: {ticket}");
}
})
.run()?;
eprintln!("finished: {outcome:?}");
Ok(())
}
on_eventregisters a callback for status changes (Event). It runs synchronously on the session’s thread, so it must not block.handle()returns aHandleyou can clone and send to other threads:stop()ends the session (the peer is told), andprogress()returns a cheap snapshot of the byte counters, meant for polling.run()returns anOutcomewhen both directions finished, or anErrorsaying why not.
Dial
Ticket parses from and displays as the mp… string:
use minipaw::{Config, Event, Io, Ticket};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let ticket: Ticket = std::env::args().nth(1).ok_or("usage: dial <ticket>")?.parse()?;
minipaw::dial(ticket, Config::default(), Io::stdio())
.on_event(|event| {
if let Event::Upgraded = event {
eprintln!("now direct");
}
})
.run()?;
Ok(())
}
Serve
minipaw::serve keeps listening and serves every dialer with the ticket, up
to Config::max_sessions (16) at once. Its accept callback makes each
session’s local end, such as a fresh connection to a port being forwarded:
use std::net::{SocketAddr, TcpStream};
use std::time::Duration;
use minipaw::{Config, Event, Io, ServeEvent};
fn main() -> Result<(), minipaw::Error> {
let target: SocketAddr = "127.0.0.1:22".parse().expect("address");
minipaw::serve(Config::default(), move |_peer| {
let stream = TcpStream::connect_timeout(&target, Duration::from_secs(5))?;
stream.set_nodelay(true)?;
Io::tcp(stream)
})
.on_event(|event| {
if let ServeEvent::Server(Event::Listening { ticket }) = event {
eprintln!("connect with: {ticket}");
}
})
.run()
}
Input and output
Io::stdio() uses the process’s stdin and stdout, and Io::tcp a
TcpStream. For anything else, pass any Read + Send and Write + Send to
Io::new. Each runs on its own helper thread, so both may block.
Identity and relay
The key and token are fresh every run unless Config::identity holds an
Identity: Identity::load_or_create(path) keeps one in a private file, and
Identity::ticket(relay) gives the ticket a listener with it prints.
Config::default() uses the hosted relay DEFAULT_RELAY. Set Config::relay
(see parse_relay) to use your own, or Config::force_relay to
skip hole punching.
The crate’s README covers stopping, cleanup of reader and writer threads, events and logging in full.