Skip to content

feat: add NAT-traversing P2P file sharing example - #1220

Open
GautamBytes wants to merge 9 commits into
libp2p:mainfrom
GautamBytes:feat/p2p-file-share-nat
Open

feat: add NAT-traversing P2P file sharing example#1220
GautamBytes wants to merge 9 commits into
libp2p:mainfrom
GautamBytes:feat/p2p-file-share-nat

Conversation

@GautamBytes

Copy link
Copy Markdown
Contributor

What was wrong?

The repository lacked a practical, runnable example demonstrating serverless, peer-to-peer file sharing across NATs with local peer persistence, as requested in the product-innovation bounty.

Issue #877

How was it fixed?

Added a lightweight, serverless file-sharing application under examples/file-share/ that fulfills the exact requirements of the issue.

Summary of approach.

  • NAT Traversal: Leveraged the library's built-in capabilities by initializing the host with enable_upnp=True. The CLI visually splits Local IPs and Public IPs on startup to prove hole-punching success.

  • Peer Persistence: Implemented a lightweight peerstore alternative using a local friends.json file. This acts as a "Contacts List," allowing the user to persist and instantly reconnect to known peers across restarts without needing to rely on external DHT or bootstrap nodes.

  • Stream Protocol: Implemented a custom, chunk-based binary stream protocol (/file-share/1.0.0) over trio to transfer file metadata (name, size) and safely stream file data directly to disk without bloating RAM.

To-Do

  • Clean up commit history
  • Add or update documentation related to these changes
  • Add entry to the release notes

Cute Animal Picture

ʕ•ᴥ•ʔ

@acul71 acul71 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Thanks for this example — it’s a clear, self-contained demo for #877 (UPnP + friends list + chunked file streaming), and the newsfragment looks good.

Merge readiness: Needs fixes before approval.

Blockers

  1. Path traversal on receive — remote filename is written as received_{filename} without sanitization.
  2. Framing correctnessstream.read(n) may return fewer than n bytes (mplex); header parse needs read_exact.
  3. Resource / integrity bounds — unbounded fname_len / file_size; incomplete transfers still print success.
  4. Zero-byte send crashsent_bytes / filesize raises ZeroDivisionError.
  5. CLI UX bug — message allows raw multiaddr, but empty friends list continues before prompting.

Also please address

  • Document UPnP-only NAT limitations (no STUN/relay fallback yet) vs issue wording.
  • Prefer Fixes #877 in the PR body; expand README with security notes + failure modes.
  • Consider using / pointing at the existing persistent peerstore APIs for the “peerstore” hack-value story.

Local checks on this branch: make lint / make typecheck / make linux-docs passed. make test had 3 unrelated WebRTC caplog failures (not from this PR).

Inline comments cover the main code issues.

print(f"📄 Receiving '{filename}' ({file_size} bytes)")

# Step C: Write to Disk (Prevent overwriting)
save_name = f"received_{filename}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical — path traversal: filename comes from the remote peer and is used directly in received_{filename}.

Please sanitize before writing (at least os.path.basename, reject empty/../separators) and ideally confine writes to a dedicated download directory with a resolve/containment check. Example code is often copy-pasted; automatic inbound writes need to be safe by default.


try:
# Step A: Read Filename Length & Filename
len_bytes = await stream.read(4)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical — partial reads: stream.read(n) is not guaranteed to return exactly n bytes (mplex docs say it may return fewer). Reading the 4-byte length, filename, and 8-byte size this way can desync the protocol.

Please add a read_exact(stream, n) helper that loops until n bytes are collected (or EOF/error), and use it for all header fields.

sys.stdout.write(f"\r⏳ Downloading: {percent}%")
sys.stdout.flush()

print(f"\n✅ File saved as '{save_name}'")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical — incomplete transfer reported as success: if the stream ends early (if not chunk: break), this still prints that the file was saved successfully.

Also please bound fname_len / file_size to avoid memory/disk DoS from a malicious peer. Only treat the transfer as successful when received_bytes == file_size (otherwise error and remove/leave a clear partial).

await stream.write(chunk)
sent_bytes += len(chunk)

percent = int((sent_bytes / filesize) * 100)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major — zero-byte send crash: when filesize == 0, sent_bytes / filesize raises ZeroDivisionError. Special-case empty files for the progress display (or skip percent).

friends = load_friends()
if not friends:
print("⚠️ No friends saved. Add one first or enter raw Multiaddr.")
continue

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major — UX bug: the message says the user can enter a raw multiaddr, but with an empty friends list this continues and never prompts.

Allow raw multiaddr entry even when no friends are saved; only require the friends list when resolving by name.

):
print(f" 🏠 Local: {addr_str}")
else:
print(f" 🌍 Public: {addr_str} (NAT Traversal Success!)")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major — overstated NAT success: any address that isn’t a simple RFC1918/loopback string match is labeled “NAT Traversal Success!”. That can mis-label CGNAT, some IPv6, or non-UPnP interfaces.

Please soften this messaging and document in the README that this demo is UPnP-only (no STUN/relay fallback yet), which only partially covers #877’s “STUN/relay if needed” ask.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants