返回资讯中心
外部精选
软件工程
#开发者实时流

RustDrop – privacy-first P2P file transfer for your LAN (Rust)

Privacy-first, blazing-fast file transfer for your local network.

Hacker News 最新xyzhaku2026/11/1 · 5 天前30 分钟阅读

以下正文同步自 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

  1. 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

  1. 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

  1. 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

  1. 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 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →