Lightweight UDP P2P networking library
Project description
Setowire - Python
A lightweight P2P networking library built on UDP. No central servers, no brokers — peers find each other and communicate directly.
Built to be simple enough that the protocol fits in your head.
Why
Most P2P libraries are either too heavy or too tied to a specific runtime. Setowire is small, auditable, and designed to be reimplemented in any language. The wire protocol is documented and intentionally minimal.
Requirements
- Python 3.11+
cryptographypackage (X25519 + ChaCha20-Poly1305)
# Standard
pip install cryptography
# Termux (Android)
pkg install python
pip install cryptography
# Debian / Ubuntu
sudo apt install python3 python3-pip
pip install cryptography
# Arch
sudo pacman -S python python-pip
pip install cryptography
# macOS (Homebrew)
brew install python
pip install cryptography
How it works
Peers discover each other through multiple strategies running in parallel — whichever works first wins:
- DHT — decentralized peer discovery by topic
- Piping servers — HTTPS rendezvous for peers behind strict NATs
- LAN multicast — instant discovery on local networks
- HTTP bootstrap nodes — fallback seed servers
- Peer cache — remembers peers from previous sessions
Once connected, all traffic is encrypted end-to-end with X25519 + ChaCha20-Poly1305. Peers that detect they have a full-cone NAT automatically become relays for others.
File structure
constants.py — all tuneable parameters and frame type definitions
crypto.py — X25519 key exchange, ChaCha20-Poly1305 encrypt/decrypt
structs.py — BloomFilter, LRU, RingBuffer, PayloadCache
framing.py — packet fragmentation, jitter buffer, batch UDP sender
dht_lib.py — minimal DHT for decentralized topic-based discovery
peer.py — per-peer state: queues, congestion control, multipath
swarm.py — main class: discovery, mesh, relay, sync, gossip
__init__.py — entry point
chat.py — example terminal chat app
Quick start
import asyncio
import hashlib
from setowire import Swarm
async def main():
swarm = Swarm()
topic = hashlib.sha256(b'my-topic').digest()
await swarm.join(topic, announce=True, lookup=True)
swarm.on('connection', lambda peer: peer.write(b'hello'))
swarm.on('data', lambda data, peer: print('got:', data))
await asyncio.sleep(3600)
asyncio.run(main())
API
Swarm(opts?)
| option | default | description |
|---|---|---|
seed |
random | hex string — deterministic identity |
max_peers |
100 | max simultaneous connections |
relay |
False | force relay mode regardless of NAT |
bootstrap |
[] | ["host:port"] bootstrap nodes |
seeds |
[] | additional hardcoded seed peers |
await swarm.join(topic, announce=True, lookup=True)
Start announcing and/or looking up peers on a topic. topic is a bytes object (usually a hash).
swarm.broadcast(data)
Send data to all connected peers. Returns number of peers reached.
swarm.store(key, value)
Store a value locally and announce it to the mesh.
await swarm.fetch(key, timeout?)
Fetch a value — returns local copy immediately or pulls from the network.
await swarm.destroy()
Graceful shutdown. Notifies peers and closes the socket.
Events
| event | args | description |
|---|---|---|
connection |
peer |
new peer connected |
data |
data, peer |
message received |
disconnect |
peer_id |
peer dropped |
sync |
key, value |
value received from network |
nat |
— | public address discovered |
Protocol
The wire protocol is plain UDP. Each packet starts with a 1-byte frame type:
| byte | type | description |
|---|---|---|
0x01 |
DATA | encrypted application data |
0x03 |
PING | keepalive + RTT measurement |
0x04 |
PONG | keepalive reply |
0x0A |
GOAWAY | graceful disconnect |
0x0B |
FRAG | fragment of a large message |
0x13 |
BATCH | multiple frames in one datagram |
0x20 |
RELAY_ANN | peer announcing itself as relay |
0x21 |
RELAY_REQ | request introduction via relay |
0x22 |
RELAY_FWD | relay forwarding an introduction |
0x30 |
PEX | peer exchange |
Handshake is two frames: 0xA1 (hello) and 0xA2 (hello ack). Each carries the sender's ID and raw X25519 public key. After that, all data is encrypted.
The session key derivation label is p2p-v12-session. The peer with the lexicographically lower ID uses the first 32 bytes as send key; the other peer flips them.
Chat example
python -m chat <nick> [room]
python -m chat alice myroom
Commands: /peers, /nat, /quit
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_setowire-0.1.0.tar.gz.
File metadata
- Download URL: py_setowire-0.1.0.tar.gz
- Upload date:
- Size: 24.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73cbb48c63f93969f692ec776289c39bb5b094ca57be6330ce88dda436aa5bb8
|
|
| MD5 |
bd727a5e8533a183cfe6d7fd4c95f946
|
|
| BLAKE2b-256 |
27d1e80a1fa51ceff9e07e6e214b1d0a1e9978ef9395410b730474a187e3b00f
|
File details
Details for the file py_setowire-0.1.0-py3-none-any.whl.
File metadata
- Download URL: py_setowire-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2e29ca50906c1ac12d1acdd7a43982e7aff46cb3f71cdad86e43807b90d10135
|
|
| MD5 |
70cee77de1f42b376e5c2be99e19b80c
|
|
| BLAKE2b-256 |
3f359f22d9cf5be3a0da708679f69a0a9e5b456fc1cee06a154af41f892d6478
|