
RustDrop – privacy-first P2P file transfer for your LAN (Rust)
Privacy-first, blazing-fast file transfer for your local network.
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
Privacy-first, blazing-fast file transfer for your local network.
No cloud. No accounts. No telemetry. Your bytes never leave the LAN.
Above: real transfer between two terminals - mDNS discovery, matching trust codes, per-chunk progress, BLAKE3 verification.
Regenerate with vhs demo/demo.tape or python3 demo/render.py.
Terminal A - receive into ./downloads
rustdrop receive -o downloads
Terminal B - auto-discover the receiver and send
rustdrop send ~/Photos/2026-holiday
No IPs, no pairing, no setup. The receiver announces itself over mDNS, the sender picks it from a list (or sends straight to the only one), both sides print the same trust code, and the file lands verified and encrypted.
LAN-only by design TCP sockets stay on your subnet. There is no server, no relay, no phone-home.
Encrypted end-to-end Ephemeral X25519 → HKDF-SHA256 → ChaCha20-Poly1305, with optional passphrase hardening.
Trust codes An 8-character short-authentication-string appears on both sides - compare it, kill the connection if it differs. MITM detection without a CA.
Zero configuration mDNS discovery (_rustdrop._tcp). Works the moment both machines are on the same network.
Actually fast ~400 MiB/s measured on real hardware (see Performance). Fully pipelined, tokio-powered, zero-copy chunking.
Pretty Multi-progress bars per file + overall, per-chunk rate, ETA, trust-code prompts.
Scriptable --json summaries, --quiet, --yes, --dry-run, shell completions.
One static binary Single static file, no runtime deps, 3.5 MB, ships on Linux (glibc/musl), macOS (Intel/ARM), Windows and Android (Termux).
Install
Homebrew
brew tap hakuxyz/tap # or just: brew install hakuxyz/tap/rustdrop
Cargo
cargo install rustdrop
cargo-binstall (prebuilt, no compilation)
cargo binstall rustdrop
Prebuilt binaries
Grab the archive for your OS from the releases page, or:
static Linux binary (works on any distro, incl. Alpine & WSL)
VER=$(curl -fsSL https://api.github.com/repos/hakuxyz/rustdrop/releases/latest
| grep -o '"tag_name": "[^"]"' | cut -d'"' -f4)
curl -fsSL -o rustdrop.tar.gz
"https://github.com/hakuxyz/rustdrop/releases/download/${VER}/rustdrop-${VER}-x86_64-unknown-linux-musl.tar.gz"
tar -xzf rustdrop.tar.gz && sudo install rustdrop-${VER}-x86_64-unknown-linux-musl/rustdrop /usr/local/bin/
Target
x86_64-unknown-linux-gnu most distros
x86_64-unknown-linux-musl fully static, no libc needed
aarch64-unknown-linux-gnu Raspberry Pi 64-bit, ARM servers
x86_64-apple-darwin, aarch64-apple-darwin macOS Intel / Apple Silicon
x86_64-pc-windows-msvc Windows 10+
aarch64-linux-android, armv7-linux-androideabi Android, via Termux
Android (Termux)
Grab the Android archive from the releases page (aarch64-linux-android for most phones, armv7-linux-androideabi for old 32-bit devices) and unpack it inside Termux:
tar -xzf rustdrop--aarch64-linux-android.tar.gz install -D rustdrop-/rustdrop ~/.local/bin/rustdrop
phone -> PC in one line
rustdrop send --to 192.168.1.10:9000 ~/storage/downloads/photo.jpg
Everything else works the same as on the desktop - discovery, trust codes, automatic resume, --json.
git clone https://github.com/hakuxyz/rustdrop cd rustdrop cargo install --path .
Usage
- Receive, then send (zero config)
Terminal A
rustdrop receive -o ~/Downloads
Terminal B
rustdrop send ~/Documents/report.pdf
Add --yes to the receiver to skip the per-transfer confirmation prompt, and --keep-open to accept several senders in a row without restarting it.
rustdrop send --to 192.168.1.20:9000 ./build/
Handy when mDNS is blocked (some guest Wi-Fi, VLANs). Both sides can pin a port with --port 9000 / --listen 0.0.0.0.
Terminal A
rustdrop receive --trust 7GKM-2XQ4 -o ./secrets
Terminal B - fails loudly if the codes don't match
rustdrop send -p 'correct horse battery staple' --trust 7GKM-2XQ4 secrets.tar
The code is derived from the ephemeral session keys, so a relay in the middle would show a different one. Compare it out of band (read it across the room, ping it on Signal) and you have MITM detection with no PKI.
rustdrop receive -p 's3same-phrase' -o ./vault rustdrop send -p 's3same-phrase' ./vault-backup/
or: export RUSTDROP_PASSPHRASE='s3same-phrase' on both sides
The passphrase is mixed into the HKDF key schedule - an attacker who recorded the whole session still cannot decrypt it offline.
cap at ~24 MiB/s, skip logs & node_modules, preview first
rustdrop send --dry-run --exclude '/*.log' --exclude '/node_modules/' --limit 24M ./project rustdrop send --compress --limit 24M --exclude '/*.log' ./project
- Resume an interrupted transfer
receiver: resumes automatically, no flag needed
rustdrop receive -o downloads
or throw the leftovers away and start over
rustdrop receive -o downloads --no-resume
Half-finished *.rustdrop-part files are continued by default. Before a single byte is skipped, the sender proves each would-be-skipped prefix with a BLAKE3 digest and the receiver verifies its own leftover against it. A stale leftover (the source changed since) restarts that one file instead of poisoning the transfer, while the file-level BLAKE3 still covers the whole file. Optional size guards on the receiver side:
rustdrop receive -o inbox --max-file-size 500M --max-total-size 5G
- Keep a directory in sync
receiver stays up; sender polls and pushes only what changed
rustdrop receive -o backup --keep-open --yes rustdrop send --watch --interval 2 ~/projects/
Every poll is a fresh session containing only new or modified files (deletions are never mirrored). Add --watch --json to get one JSON line per session.
rustdrop pair # fresh passphrase + QR on screen rustdrop pair --json | jq -r .passphrase # same code, machine-readable rustdrop pair --show 'rustdrop://192.168.1.10:9000' # any text as a QR
Scan the code with a phone and pass the same string via -p on both sides - offline pairing with no typing errors and no cloud in between.
rustdrop send --json --quiet ./artifact.tar.zst | jq '.files, .mbps' rustdrop receive -o /srv/incoming --yes --no-clobber --json --keep-open
- Shell completions
source <(rustdrop completions bash) # bash source <(rustdrop completions zsh) # zsh rustdrop completions fish > ~/.config/fish/completions/rustdrop.fish
How it works
sequenceDiagram autonumber participant S as sender participant R as receiver
Note over S,R: 1. Discovery - mDNS announce / browse (_rustdrop._tcp) S->>R: Hello (name, ephemeral X25519 pubkey) R-->>S: HelloAck (name, ephemeral X25519 pubkey) Note over S,R: 2. Trust - both sides print the same SAS code (e.g. 7GKM-2XQ4) S->>R: Manifest (encrypted: file tree, sizes, BLAKE3 hashes) R-->>S: Ready | Abort (rejected by policy / user / trust mismatch) opt Resume (protocol v2) - only when a resume offset is offered S->>R: ResumeProof (BLAKE3 per would-be-skipped prefix) R-->>S: ResumeAccepted (verified offsets, stale leftovers zeroed) end loop 3. Transfer - per-chunk AEAD, pipelined S->>R: DataFrame (ChaCha20-Poly1305, seq-numbered nonce) R-->>S: Ack end Note over R: 4. Verify BLAKE3 → atomic rename → restore mtime & permissions
Loading
Both sides announce their protocol version during the hello: a current build talking to an older one fails fast with a clear unsupported protocol version error instead of hanging mid-transfer.
flowchart LR A["ephemeral<br/>X25519 keypair"] -->|ECDH shared secret| B["HKDF-SHA256<br/>+ optional passphrase"] B --> C["manifest key"] B --> D["data key"] B --> E["ack key"] B --> F["SAS / trust code"] C & D & E --> G["ChaCha20-Poly1305<br/>prefix MANI / SD01 / RD01<br/>+ 64-bit sequence nonce"] G --> H["streamed over TCP<br/>length-prefixed frames"]
Loading
Everything is negotiated per session: keys are ephemeral, nonces never repeat inside a session, and each file is verified against the BLAKE3 hash shipped in the (encrypted) manifest before it is renamed into place. Partial downloads live in *.rustdrop-part files and are never mistaken for finished data.
No analytics, no update checks, no DNS lookups, no persistence outside the transfer directory. strace it - the only sockets it opens are the mDNS multicast group and your transfer.
Measured on this machine over a gigabit-class link (loopback/LAN, single file, 500 MiB):
Numbers vary with disk speed and NIC. RustDrop pipelines read → encrypt → write with bounded memory (--chunk-size, default 64 KiB), so throughput does not collapse on huge files.
rustdrop send
Flag Description
<PATH>... Files and/or directories to send
-t, --to <ADDR> Connect straight to ip:port (skips mDNS)
--no-mdns Ignore mDNS entirely (requires --to)
-n, --name <PATTERN> Only consider receivers whose name contains PATTERN
-p, --passphrase <PASS> Shared passphrase (env: RUSTDROP_PASSPHRASE)
--trust <CODE> Abort unless the peer displays this trust code
--discovery-timeout <SECS> Seconds to keep browsing for receivers (default: 3)
--retries <N> Extra connection attempts after the first failure (default: 2)
--chunk-size <BYTES> Plaintext chunk size (default: 65536, max 1 MiB)
--compress Deflate chunks when that actually shrinks them
--limit <RATE> Cap the send rate, e.g. 10M, 512K, 1.5M
--exclude <GLOB> Skip files matching GLOB (repeatable)
--watch Poll the inputs and re-send new/changed files (receiver: --keep-open)
--interval <SECS> Seconds between watch-mode polls (default: 2, requires --watch)
--dry-run Show what would be sent, then exit
--json Machine-readable summary on stdout
-q, --quiet / -v, --verbose Less / more output
rustdrop receive
Flag Description
-o, --out <DIR> Destination directory (default: .)
--listen <IP> Local address to bind (default: all interfaces)
--port <PORT> TCP port to listen on (default: 0 = pick one and announce)
-p, --passphrase <PASS> Shared passphrase (env: RUSTDROP_PASSPHRASE)
--trust <CODE> Only accept senders presenting this trust code
-y, --yes Accept every transfer without asking
--keep-open Keep listening for more senders after each transfer
--no-clobber Refuse files that already exist
--no-resume Discard *.rustdrop-part leftovers (resume is the default)
--max-file-size <BYTES> Refuse single files larger than this
--max-total-size <BYTES> Refuse sessions whose combined size exceeds this
--no-preserve-time Do not restore modification times
--no-mdns Skip mDNS announcement
--timeout <SECS> Wait for a sender (idle timeout with --keep-open, 0 = forever, default: 600)
--json Machine-readable summary on stdout
rustdrop pair
rustdrop pair # generate a pairing code and show it as text + QR rustdrop pair --show <TEXT> # encode arbitrary text (a URL, a command) instead rustdrop pair --json # {"ok":true,"role":"pair","passphrase":"..."}
Flag Description
--show <TEXT> Encode this text into the QR instead of generating a passphrase
--json Emit the pairing code as JSON on stdout
rustdrop list
rustdrop list # table of receivers on this network rustdrop list --json # for scripts rustdrop list --timeout 5 # keep browsing longer
Global flags
-v/--verbose, -q/--quiet, -h/--help, -V/--version work everywhere.
RustDrop scp / rsync cloud drive chat app
Works with no internet ✅ ✅ ❌ ❌
Zero-config peer discovery ✅ mDNS ❌ manual
❌
MITM check without a CA ✅ trust code host keys
❌
No account ✅ ✅ ❌ ❌
Single static binary ✅ ❌
❌
Rate limiting + compression ✅ partial
❌
Development
cargo build --all-targets cargo test --all-targets # 57 unit + 27 end-to-end tests cargo clippy --all-targets -- -D warnings cargo fmt --all
regenerate the demo recording
python3 demo/render.py demo-rec assets/demo.gif
The end-to-end suite starts real TCP listeners on 127.0.0.1, so it catches handshake, ordering, and teardown regressions that unit tests miss. Contributions welcome - open an issue first for anything larger than a fix.
mDNS discovery, trust codes, passphrase hardening
Compression, rate limiting, excludes, resume-sa
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。