원문 캡처 · github.com
원문 캡처 · github.com

RustDrop은 같은 로컬 네트워크(LAN)에 있는 기기끼리 파일을 전송하는 Rust 기반 도구다. 클라우드와 계정, 원격 측정 없이 작동하며, 제작자는 파일이 LAN 밖으로 나가지 않는다고 설명한다.

설치와 기본 사용

Homebrew, Cargo, 미리 빌드된 바이너리, 소스 코드 설치를 지원한다. Cargo로 설치하려면 다음 명령을 쓴다.

sh
cargo install rustdrop

미리 빌드된 바이너리는 릴리스 페이지에서 운영체제에 맞는 압축 파일을 받거나, Cargo Binstall을 이용해 설치할 수 있다.

sh
cargo binstall rustdrop

미리 빌드된 바이너리의 대상은 x86_64-unknown-linux-gnu(대부분의 리눅스 배포판), x86_64-unknown-linux-musl(완전 정적 바이너리, libc 불필요), aarch64-unknown-linux-gnu(64비트 라즈베리 파이와 ARM 서버), x86_64-apple-darwin 및 aarch64-apple-darwin(인텔·애플 실리콘 맥), x86_64-pc-windows-msvc(Windows 10 이상), aarch64-linux-android와 armv7-linux-androideabi(Termux를 사용하는 안드로이드)다.

리눅스 정적 바이너리를 내려받는 방법은 다음과 같다.

sh
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/

Termux에서는 안드로이드용 압축 파일을 릴리스 페이지에서 받아 푼다. 대부분의 휴대전화는 aarch64-linux-android, 오래된 32비트 기기는 armv7-linux-androideabi 대상이다.

sh
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

안드로이드에서도 기기 탐색, 신뢰 코드, 자동 전송 재개, --json을 사용할 수 있다. 소스에서 설치하려면 다음 명령을 실행한다.

sh
git clone https://github.com/hakuxyz/rustdrop
cd rustdrop
cargo install --path .

기본 전송은 수신 쪽에서 먼저 receive를 실행하고 송신 쪽에서 send를 실행한다. 수신기는 mDNS로 자신을 알리고, 송신기는 발견된 수신기 목록에서 대상을 고른다. 수신기가 하나뿐이면 바로 전송할 수 있다. 양쪽에 같은 신뢰 코드가 표시되고, 파일은 검증 후 저장된다.

sh
# Terminal A
rustdrop receive -o ~/Downloads

# Terminal B
rustdrop send ~/Documents/report.pdf

수신 명령에 --yes를 붙이면 전송마다 묻는 확인 절차를 건너뛴다. --keep-open은 수신기가 재시작 없이 여러 송신자의 연결을 받도록 한다.

mDNS가 차단된 게스트 Wi-Fi나 VLAN에서는 주소를 직접 지정할 수 있다. 양쪽에서 --port 9000 또는 --listen 0.0.0.0으로 포트를 고정할 수도 있다.

sh
rustdrop send --to 192.168.1.20:9000 ./build/

보안과 전송 방식

RustDrop은 같은 네트워크에서 mDNS 서비스 _rustdrop._tcp를 이용해 수신기를 찾고, TCP로 파일을 보낸다. 세션마다 임시 X25519 키를 만들고, 공유 비밀을 HKDF-SHA256으로 처리해 manifest·데이터·확인 응답용 키와 신뢰 코드를 만든다. 데이터는 ChaCha20-Poly1305로 암호화한다. 선택적으로 암호구문을 키 생성 과정에 더할 수 있다.

8자리 단문 인증 문자열(SAS)이 송신자와 수신자 양쪽에 표시된다. 두 값을 별도의 경로로 비교하고 다르면 연결을 끊어 중간자 공격을 확인한다. --trust로 특정 코드를 지정하면 상대의 코드가 일치하지 않을 때 전송을 중단한다. 인증 기관(CA) 없이 이 확인을 제공하는 방식이다.

암호구문을 양쪽에서 지정하거나 RUSTDROP_PASSPHRASE 환경 변수로 설정할 수 있다.

sh
rustdrop receive -p 's3same-phrase' -o ./vault
rustdrop send -p 's3same-phrase' ./vault-backup/
# or: export RUSTDROP_PASSPHRASE='s3same-phrase' on both sides

암호구문은 HKDF 키 생성 과정에 섞인다. 프로젝트 설명에 따르면 세션 전체를 기록한 공격자도 오프라인에서 이를 복호화할 수 없다. QR 코드로 암호구문을 전달할 수도 있다. rustdrop pair는 새 암호구문과 QR 코드를 화면에 표시하고, --json은 같은 코드를 기계가 읽을 수 있는 형식으로 내보낸다. --show는 암호구문 대신 임의의 문자열을 QR 코드로 만든다.

sh
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

휴대전화로 코드를 스캔한 뒤 같은 문자열을 양쪽에서 -p로 전달하면 인터넷이나 클라우드 없이 암호구문을 공유할 수 있다.

프로토콜에서는 송신자가 파일 트리와 크기, BLAKE3 해시를 포함한 암호화 manifest를 보낸다. 수신자는 정책·사용자 선택·신뢰 코드 불일치에 따라 준비 완료 또는 중단을 알린다. 재개 위치가 전달된 경우에는 건너뛸 각 구간의 BLAKE3 해시를 증명하고 수신자가 검증한다. 데이터는 순번이 붙은 nonce를 사용해 청크 단위로 암호화되고, 수신자는 확인 응답을 보낸다. 전송 뒤 파일 전체의 BLAKE3 해시를 확인하고 원자적으로 이름을 바꾸며 수정 시각과 권한을 복원한다.

키는 세션마다 새로 협상하고, 세션 안에서 nonce가 반복되지 않도록 한다. 부분 다운로드는 *.rustdrop-part 파일에 보관하며 완성 파일로 취급하지 않는다. 송신자와 수신자는 Hello 과정에서 프로토콜 버전을 알린다. 현재 버전과 오래된 버전이 통신하면 전송 도중 멈추는 대신 unsupported protocol version 오류를 표시하고 빠르게 실패한다.

RustDrop이 보호한다고 설명하는 항목은 수동적인 LAN 도청, 신뢰 코드를 비교해 대응하는 능동적 중간자 공격, 캡처된 세션의 오프라인 무차별 대입 공격, 변조된 청크, 전송 도중 중단돼 불완전하게 기록된 파일이다. 청크에는 Poly1305 인증 태그와 파일 전체 BLAKE3 검증을 적용하고, 검증 뒤 파일명을 바꾼다.

범위 밖인 위협도 있다. 양 끝 기기 중 하나가 침해된 경우에는 보호하지 못한다. 전송이 이뤄졌다는 사실과 파일 크기는 상대가 알 수 있어 트래픽 분석도 막지 못한다. 수신 호스트의 파일시스템 접근 제어도 제공하지 않는다. 프로젝트는 분석 정보 수집, 업데이트 확인, DNS 조회, 전송 디렉터리 밖의 영구 저장을 하지 않는다고 설명한다. strace로 확인할 때 열리는 소켓은 mDNS 멀티캐스트 그룹과 전송용 소켓뿐이라고 안내한다.

재개·동기화와 전송 옵션

전송이 중단되면 수신기는 기본적으로 *.rustdrop-part 파일을 이어받는다. 재개할 때 송신자는 건너뛸 각 구간의 BLAKE3 해시를 증명하고, 수신자는 남은 파일을 대조한다. 원본이 바뀌어 오래된 부분 파일이 발견되면 해당 파일만 처음부터 다시 시작한다. 파일 전체 해시는 여전히 검증한다. 재개 파일을 버리고 새로 시작하려면 --no-resume을 지정한다.

sh
# receiver: resumes automatically, no flag needed
rustdrop receive -o downloads

# or throw the leftovers away and start over
rustdrop receive -o downloads --no-resume

수신기는 개별 파일과 전체 세션의 크기 제한을 설정할 수 있다.

sh
rustdrop receive -o inbox --max-file-size 500M --max-total-size 5G

--watch를 사용하면 송신기가 주기적으로 파일을 확인해 새 파일이나 바뀐 파일만 보낸다. 수신기는 --keep-open --yes로 계속 대기할 수 있다. 매번 새 세션을 열며 삭제된 파일은 상대편에 반영하지 않는다. --watch --json은 세션마다 JSON 한 줄을 출력한다.

sh
# receiver stays up; sender polls and pushes only what changed
rustdrop receive -o backup --keep-open --yes
rustdrop send --watch --interval 2 ~/projects/

전송 전 미리 확인하고, 파일을 제외하거나 속도를 제한할 수 있다. 다음 예시는 로그 파일과 node_modules를 제외해 초당 약 24 MiB로 제한하고 전송 대상을 먼저 미리 본다. --compress는 압축으로 크기가 실제 줄어드는 청크만 압축한다.

sh
# 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

스크립트와 CI 작업에서는 --json, --quiet, --yes, --dry-run을 쓸 수 있으며 셸 자동 완성도 제공한다.

sh
rustdrop send --json --quiet ./artifact.tar.zst | jq '.files, .mbps'
rustdrop receive -o /srv/incoming --yes --no-clobber --json --keep-open

Bash와 Zsh는 다음과 같이 자동 완성을 불러오고, Fish는 파일로 저장한다.

sh
source <(rustdrop completions bash) # bash
source <(rustdrop completions zsh) # zsh
rustdrop completions fish > ~/.config/fish/completions/rustdrop.fish

수신기를 목록으로 확인하는 명령은 rustdrop list다. --json으로 스크립트용 출력을 받을 수 있고 --timeout 5로 탐색 시간을 늘릴 수 있다.

성능과 배포 범위

프로젝트가 공개한 측정은 기가비트급 링크의 한 기기에서 단일 500 MiB 파일을 전송한 결과다. 일반 전송과 mDNS 자동 탐색을 함께 사용한 전송 모두 404 MiB/s였으며, 혼합된 46 MiB 디렉터리를 --compress --limit 24M으로 보낸 경우에는 의도한 제한에 따라 55 MiB/s였다. 수치는 디스크와 네트워크 카드에 따라 달라질 수 있다고 안내한다.

전송은 읽기·암호화·쓰기를 파이프라인 처리하고 메모리 사용량을 제한한다. 기본 청크 크기는 64 KiB이며 --chunk-size로 바꿀 수 있다. 최대 청크 크기는 1 MiB다. 제작자는 이 방식이 큰 파일에서도 처리량이 급격히 떨어지지 않도록 한다고 설명한다. 자세한 내용은 성능 안내를 참고할 수 있다.

RustDrop은 런타임 의존성이 없는 단일 정적 바이너리로 제공되며 크기는 3.5 MB다. 리눅스(glibc·musl), 인텔·애플 실리콘 맥, Windows, Termux 기반 안드로이드를 지원한다. 프로젝트는 scp·rsync, 클라우드 드라이브, 채팅 앱과의 비교도 제시한다. 비교 항목은 인터넷 없이 사용, 자동 기기 탐색, 인증 기관 없이 중간자 공격 확인, 계정 불필요, 단일 정적 바이너리, 속도 제한과 압축이다.

명령 참고

rustdrop send는 파일과 디렉터리를 전송한다. <PATH>...에는 보낼 대상을 지정한다. -t, --to <ADDR>는 ip:port로 직접 연결해 mDNS를 건너뛰고, --no-mdns는 --to와 함께 mDNS를 완전히 끈다. -n, --name <PATTERN>은 이름에 패턴이 포함된 수신기만 고른다. -p, --passphrase <PASS>는 공유 암호구문이며 RUSTDROP_PASSPHRASE 환경 변수로도 설정할 수 있다. --trust <CODE>는 상대가 표시하는 코드가 지정값과 다르면 중단한다.

--discovery-timeout <SECS>는 수신기 탐색 시간이며 기본값은 3초다. --retries <N>은 첫 연결 실패 뒤 추가로 시도할 횟수이며 기본값은 2다. --chunk-size <BYTES>는 평문 청크 크기로 기본값은 65536, 최댓값은 1 MiB다. --compress는 크기를 줄이는 경우에만 청크를 Deflate 압축한다. --limit <RATE>는 전송 속도를 제한하며 10M, 512K, 1.5M 같은 값을 받는다. 반복해서 쓸 수 있는 --exclude <GLOB>은 패턴에 맞는 파일을 제외한다. --watch는 입력을 확인해 새 파일과 변경 파일을 다시 보내며 수신기에는 --keep-open이 필요하다. --interval <SECS>는 watch 모드의 확인 간격으로 기본값은 2초다. --dry-run은 전송 예정 항목을 표시하고 종료한다. --json은 기계가 읽을 수 있는 요약을 표준 출력에 내보내며 -q, --quiet와 -v, --verbose는 출력을 줄이거나 늘린다.

rustdrop receive의 -o, --out <DIR>은 저장 디렉터리이며 기본값은 현재 디렉터리다. --listen <IP>는 바인딩할 로컬 주소로 기본값은 모든 인터페이스다. --port <PORT>는 TCP 포트이며 기본값 0은 포트를 자동 선택해 알린다는 뜻이다. -p, --passphrase <PASS>는 송신자와 공유할 암호구문이며 RUSTDROP_PASSPHRASE 환경 변수도 사용할 수 있다. --trust <CODE>는 해당 신뢰 코드를 제시하는 송신자만 받는다. -y, --yes는 모든 전송을 묻지 않고 받으며 --keep-open은 전송 뒤에도 계속 대기한다. --no-clobber는 이미 존재하는 파일을 거부한다. --no-resume은 *.rustdrop-part 파일을 버린다. --max-file-size <BYTES>는 개별 파일의 최대 크기, --max-total-size <BYTES>는 세션 전체 크기의 한도다. --no-preserve-time은 수정 시각을 복원하지 않으며 --no-mdns는 mDNS 알림을 끈다. --timeout <SECS>는 송신자를 기다리는 시간이다. --keep-open을 쓰면 유휴 시간 제한으로 작동하며 0은 무기한, 기본값은 600초다. --json은 JSON 요약을 출력한다.

rustdrop pair는 페어링 코드를 만들어 텍스트와 QR로 표시한다. --show <TEXT>는 암호구문 대신 지정한 텍스트를 QR에 넣고, --json은 페어링 코드를 JSON으로 출력한다. 전역 옵션인 -v/--verbose, -q/--quiet, -h/--help, -V/--version은 모든 명령에서 사용할 수 있다.

개발과 프로젝트 상태

개발용 명령은 다음과 같다. 엔드투엔드 테스트는 127.0.0.1에서 실제 TCP 수신기를 실행해 단위 테스트만으로는 잡기 어려운 핸드셰이크, 순서 처리, 연결 종료 회귀를 확인한다고 프로젝트는 설명한다.

sh
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

기여를 환영하며, 수정 수준보다 큰 변경은 먼저 이슈를 열어 달라고 안내한다. 로드맵에는 mDNS 탐색, 신뢰 코드, 암호구문 강화, 압축, 속도 제한, 제외 규칙, 재개에 안전한 임시 파일, JSON 출력, 셸 자동 완성, 계속 대기 모드, 미리 실행, 세션 간 청크 재개(프로토콜 v2 증명 기능, 기본 활성화), 수신 크기 제한, --watch 디렉터리 동기화, QR 페어링, Termux용 안드로이드 바이너리가 포함돼 있다. iOS 동반 앱은 macOS와 Xcode가 필요하다고 적혀 있다.

라이선스는 MIT이며 LICENSE에서 확인할 수 있다.