Simple P2P File Sharing CLI Tool
  • C++ 57.9%
  • Python 26%
  • Nix 12.2%
  • CMake 3.3%
  • PowerShell 0.6%
Find a file
Max Fritz 6e6cead0ca
All checks were successful
Release / test (push) Successful in 8m36s
Release / build (push) Successful in 2m54s
Release / package (push) Successful in 5s
Release / publish (push) Successful in 4s
Release / release (push) Successful in 5s
feat: add stdin/stdout streaming and streaming tar extraction
Support piping data through p2pshare using `-` as the path argument:
- Sender: `cat file | p2pshare --batch - --link-file link.txt --peer-addr-file peer.txt`
- Receiver: `p2pshare --batch 'link' -o - --link-file addr.txt > output`

Replace the buffering TarExtractor (which held the entire archive in a
std::vector) with a streaming pipe + background thread implementation.
libarchive reads from the pipe's read end while the caller pushes data
into the write end — memory is bounded by the OS pipe buffer (~64 KB).
This allows receiving directories larger than available RAM.

Other changes:
- Progress bar: show spinner + bytes + speed when total is unknown
- Early IPv6 availability check at startup
- Pre-allocate output files with fallocate to catch disk space errors

Testing:
- Integration: add large directory transfer test (~2 GB via streaming
  tar on 1 GB RAM VMs with ext4-formatted virtual disks)
- Integration: add stdin/stdout streaming test (2 GB raw block device
  piped through p2pshare, verified with SHA256)
- O_DIRECT pseudo-random data generator to avoid page cache pressure
  in memory-constrained VMs
- Unit: flush KCP after send_eof in large transfer test
2026-02-17 00:06:35 +01:00
.forgejo/workflows ci: add Windows integration test to release pipeline 2026-02-14 22:59:03 +01:00
src feat: add stdin/stdout streaming and streaming tar extraction 2026-02-17 00:06:35 +01:00
tests feat: add stdin/stdout streaming and streaming tar extraction 2026-02-17 00:06:35 +01:00
third_party/kcp Replace TCP data channel with KCP (reliable UDP) 2026-02-06 14:27:56 +01:00
.clangd adds clangd config pointing to CompilationDatabase 2026-02-04 13:29:40 +01:00
.gitignore test: Windows integration tests with VM image build infrastructure 2026-02-14 16:49:33 +01:00
CLAUDE.md Replace TCP data channel with KCP (reliable UDP) 2026-02-06 14:27:56 +01:00
CMakeLists.txt fix: portable terminal colors (no ANSI on non-TTY/Windows SSH) 2026-02-14 16:34:33 +01:00
flake.lock Add release pipeline and aarch64 cross-compilation support 2026-02-04 13:29:40 +01:00
flake.nix test: reduce peak memory by phasing VM lifecycle in Windows tests 2026-02-14 22:56:52 +01:00
nfpm.yml Add release pipeline and aarch64 cross-compilation support 2026-02-04 13:29:40 +01:00
README.md Replace TCP data channel with KCP (reliable UDP) 2026-02-06 14:27:56 +01:00

p2pshare

A minimal, secure P2P file sharing CLI tool.

Features

  • End-to-end encryption: AES-256-GCM (hardware-accelerated) with XSalsa20-Poly1305 fallback
  • Compression: zstd streaming compression for efficient transfers
  • NAT traversal: UDP hole punching with KCP reliable transport over the same socket
  • IPv6 only: Modern IPv6 networking
  • Directory sharing: Automatically creates tar archives for directories
  • Reproducible builds: Nix flake support

Usage

Send a file

p2pshare myfile.txt

Send a directory

p2pshare -r ./mydir

Receive a file

p2pshare 'p2pshare://[fe80::1]:54321/<token>'

With custom output directory:

p2pshare -o ./downloads 'p2pshare://[fe80::1]:54321/<token>'

The mode is auto-detected: if the argument matches p2pshare://[ipv6]:port/token it receives, otherwise it sends.

Building

nix build

For a static build:

nix build .#static

With CMake

cmake -B build
cmake --build build

The executable will be at build/p2pshare.

How it works

  1. Sender generates a random 32-byte encryption key and 4-byte session ID
  2. Sender creates a share link containing the key and session ID (base62 encoded)
  3. Sender binds a UDP socket and displays its p2pshare://[ipv6]:port/token
  4. Receiver parses the share link and binds its own UDP socket
  5. Both peers exchange addresses out-of-band, then UDP hole punch to establish the connection
  6. Data transfer runs over KCP (reliable UDP) on the same hole-punched socket -- no new ports or TCP connections needed
  7. Sender streams the file: compress with zstd -> encrypt in 128KB chunks -> send via KCP
  8. Receiver receives: decrypt -> decompress -> write to disk

Protocol

Hole punch signaling uses an 8-byte header: [type:1][flags:1][seq:4][length:2]

Connection establishment uses a HELLO/PUNCH handshake with version and session ID verification. Data transfer runs over KCP (reliable UDP) with fast retransmission, selective ACKs, and no congestion control (optimized for direct peer-to-peer links).

Each data chunk is encrypted with AES-256-GCM (or XSalsa20-Poly1305 on CPUs without AES-NI). Nonces use counter mode to prevent reuse.

Security

  • Encryption: AES-256-GCM (hardware-accelerated) with XSalsa20-Poly1305 fallback
  • Key derivation: Random 256-bit key per transfer
  • Nonce handling: Counter-mode nonces (no reuse)
  • Authentication: Session ID verification prevents unauthorized connections
  • Integrity: GCM/Poly1305 MAC on each chunk

Dependencies

  • libsodium - Encryption
  • zstd - Compression
  • libarchive - Tar archive creation/extraction
  • KCP (vendored) - Reliable UDP transport
  • CLI11 - Command-line parsing

License

MIT