Python UDP communication library with opporunistic encryption, authenticated encryption, automatic key exchange, and replay attack prevention.
- 🔒 Authenticated Encryption: Uses NaCl Box (X25519 + XSalsa20-Poly1305) for authenticated encryption
- ✍️ Signature Support: Ed25519 signing for key exchange verification
- 🛡️ Replay Attack Prevention: Automatic nonce tracking and message timestamp verification
- 🔑 Automatic Key Exchange: Seamless public key distribution with signature verification
- ⏰ Key Lifecycle Management: Automatic expiration and cleanup of old keys
⚠️ Warning: opportunistic encryption, fallback is unencrypted UDP
- 📦 Message Compression: Zstd compression for reduced bandwidth
- 🚀 Thread-Safe: RLock-based synchronization for concurrent operations
- 📊 Rate Limiting: Configurable per-peer rate limiting to prevent DoS
- 🎯 MTU-Aware: Automatic MTU calculation with proper overhead accounting
- 🔄 Multiple Peers: Support for multiple peers on same IP address
- 🌐 Network Discovery: Automatic interface and MTU detection
- 📝 Comprehensive Logging: Detailed logging at all levels
- 🧹 Resource Management: Automatic cleanup of expired keys and nonces
- 📡 Protocol Versioning: Future-proof design with version negotiation (currently v2)
Version 2 introduces a structured protocol with clear separation of concerns:
- Version Field: Enables future protocol evolution and backward compatibility
- Separate Channels:
- Payload Channel (
p): User data transmission - Control Channel (
c): Key exchange and protocol management
- Payload Channel (
- Structured Format: msgpack dict
{'v': version, 'p': payload, 'c': control, 'g': padding}
pip install -r requirements.txt- Python 3.7+
- msgpack
- pyzstd
- psutil
- PySignal~=1.1.1
- PyNaCl~=1.6.0
from vault_udp_socket import UDPSocketClass
# Create socket
socket = UDPSocketClass(recv_port=11000)
# Add peer
socket.add_peer(("192.168.1.100", 8000))
# Connect callback for received data
def on_data(data, addr):
print(f"Received: {data} from {addr}")
socket.udp_recv_data.connect(on_data)
# Send data
socket.send_data("Hello, World!")
# Send to specific peer
socket.send_data("Direct message", ("192.168.1.100", 8000))
# Cleanup
socket.stop()with UDPSocketClass(recv_port=11000) as socket:
socket.add_peer(("192.168.1.100", 8000))
socket.send_data("Hello!")
# Automatic cleanup on exit# Multiple peers on same IP
socket.add_peer(("127.0.0.1", 8000))
socket.add_peer(("127.0.0.1", 8001))
socket.add_peer(("127.0.0.1", 8002))
# Get peers by IP
peers = socket.get_peers_by_ip("127.0.0.1")
print(f"Peers on localhost: {peers}")
# Broadcast to all peers
socket.send_data("Broadcast message")# Custom rate limit (messages per second per peer)
socket = UDPSocketClass(recv_port=11000, rate_limit=50)stats = socket.get_stats()
print(f"Protocol version: {stats['protocol_version']}")
# Output: Protocol version: 2vault_udp_socket.py # Main UDP socket with encryption (Protocol v2)
├── vault_udp_encryption.py # Encryption manager with replay protection
│ └── vault_udp_socket_helper.py # Crypto primitives (NaCl wrapper)
└── vault_ip.py # Network utilities (MTU, IP detection)
Encrypted Packet (after encryption):
┌─────────────────────────────────────────┐
│ NaCl Box (authenticated encryption) │
│ ┌─────────────────────────────────────┐ │
│ │ Nonce (16 bytes) │ │
│ │ Timestamp (8 bytes, double) │ │
│ │ Msgpack Payload: │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ { │ │ │
│ │ │ 'v': 2, # version │ │ │
│ │ │ 'p': bytes, # payload │ │ │
│ │ │ 'c': bytes, # control │ │ │
│ │ │ 'g': bytes # padding │ │ │
│ │ │ } │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
Payload Channel (p):
- Compressed user data (zstd)
- Empty for control-only packets
- Emits
udp_recv_datasignal when received
Control Channel (c):
- Key exchange messages (JSON)
- Protocol management
- Processed internally, not exposed to user
Version Field (v):
- Current version: 2
- Future versions can add features
- Receivers check version compatibility
Padding Field (g):
- Random padding to reach MTU
- Prevents traffic analysis based on size
- Uses NaCl Box for authenticated encryption between peers
- Each message includes sender authentication
- Prevents tampering and impersonation attacks
- 16-byte random nonce per message
- 8-byte timestamp in each message
- Nonce cache with automatic cleanup
- Configurable message freshness window (default: 60 seconds)
- Generate encryption keypair (X25519) and signing keypair (Ed25519)
- Sign public keys with signing private key
- Exchange signed public keys with peers via control channel
- Verify signatures before accepting keys
- Periodic key refresh with configurable lifetime
# When you call: socket.send_data("Hello")
# Sent packet structure (after all processing):
{
'v': 2, # Protocol version
'p': b'compressed("Hello")', # Compressed payload
'c': b'', # Empty control
'g': b'random...' # Padding
}# Automatically sent during key exchange:
{
'v': 2,
'p': b'', # Empty payload
'c': b'{"enc_key": "...", "sign_key": "...", "signature": "..."}',
'g': b'random...'
}__init__(recv_port: int = 11000, rate_limit: int = 100)
- Initialize UDP socket with protocol v2
recv_port: Port to listen onrate_limit: Maximum messages per second per peer
add_peer(addr: Tuple[str, int])
- Add peer and initiate key exchange
addr: Tuple of (ip, port)
remove_peer(addr: Tuple[str, int])
- Remove peer and cleanup keys
send_data(data: Union[str, bytes], addr: Optional[Tuple[str, int]] = None)
- Send data to peer(s) via payload channel
addr: Specific peer or None for broadcast
get_peers() -> List[Tuple[str, int]]
- Get list of all peers
get_peers_by_ip(ip: str) -> List[Tuple[str, int]]
- Get all peers with specific IP
has_peer(addr: Tuple[str, int]) -> bool
- Check if peer exists
update_recv_port(recv_port: int)
- Change listening port (atomic)
get_stats() -> dict
- Get socket statistics including protocol version
stop(timeout: float = 5.0)
- Stop all threads and close sockets
udp_recv_data
- Emitted when user data is received (payload channel)
- Signature:
(data: str, addr: Tuple[str, int])
udp_send_data
- Connected to
send_datamethod - For external triggering
Lower-level encryption manager (usually not used directly).
generate_keys() -> Tuple[str, str]
- Generate new key pairs
- Returns: (enc_public_key, sign_public_key)
encrypt(data: bytes, addr: Tuple[str, int]) -> bytes
- Encrypt with authentication and replay protection
decrypt(data: bytes, addr: Tuple[str, int]) -> bytes
- Decrypt and verify (with replay detection)
update_peer_keys(addr, enc_key: str, sign_key: str)
- Update peer's public keys
get_min_mtu() -> int
- Get minimum MTU across all interfaces
get_ipv4_addresses() -> List[str]
- Get all IPv4 addresses
get_ipv6_addresses() -> List[str]
- Get all IPv6 addresses
get_network_info() -> dict
- Get comprehensive network information
socket = UDPSocketClass(recv_port=11000)
socket.lifetime = 120 # secondsThe library automatically calculates effective MTU:
Base MTU: 1500 (from interface)
- IP Header: 20 bytes
- UDP Header: 8 bytes
- NaCl Box: 40 bytes (nonce + authenticator)
- Msgpack: ~15 bytes (v2 structured format)
- Replay Protection: 24 bytes (nonce + timestamp)
= Effective MTU: ~1393 bytes
# In vault_udp_encryption.py
MAX_MESSAGE_AGE_SECONDS = 60 # Reject messages older than 60s
NONCE_CACHE_SIZE = 10000 # Max nonces tracked per peerv2 (Current):
- Structured msgpack format with version field
- Separate payload and control channels
- Improved extensibility for future features
The protocol is designed for evolution:
- New versions can add fields to the msgpack dict
- Unknown fields are ignored by older implementations
- Version mismatch is logged but doesn't break connections
- Control channel can negotiate capabilities
✅ Man-in-the-Middle: Authenticated encryption prevents tampering
✅ Replay Attacks: Nonce and timestamp validation
✅ Impersonation: Signature verification on key exchange
✅ DoS: Rate limiting per peer
✅ Eavesdropping: All data encrypted with NaCl
✅ Protocol Downgrade: Version checking prevents downgrade attacks
- Use TLS for initial setup if you need to verify peer identity
- Limit key lifetime to reduce impact of compromised keys
- Monitor rate limits and adjust based on your use case
- Use firewall rules to restrict allowed peers at network level
- Regular updates of PyNaCl and dependencies
- Check protocol version in get_stats() after connecting
- Adjust compression level:
pyzstd.compress(data, level=1)for speed - Increase MTU on local networks (jumbo frames)
- Batch messages when possible
- Use multiple sockets for parallel communication
- Check your MTU:
socket.get_stats()['mtu'] - Reduce message size or split into chunks
- Data is compressed automatically but some data doesn't compress well
- v2 uses ~5 more bytes than v1 for structure
- Check network connectivity
- Verify firewall allows UDP on specified ports
- Wait 2-3 seconds after
add_peer()for initial exchange - Check logs:
logging.basicConfig(level=logging.DEBUG) - Verify protocol version compatibility
- Increase rate limit:
UDPSocketClass(rate_limit=200) - Check for message loops
- Verify peer isn't flooding
- Check system clocks are synchronized
- Adjust
MAX_MESSAGE_AGE_SECONDSif needed - Verify no message duplication in network
- Check logs for version mismatch warnings
- Older v1 clients can still receive from v2 (backward compatible)
- Update all peers to v2 for best compatibility
# Run built-in tests
python vault_udp_socket.py # Tests protocol v2
python vault_udp_encryption.py
python vault_udp_socket_helper.py
python vault_ip.pySee main() functions in each module for working examples.
from vault_udp_socket import UDPSocketClass
def echo_handler(data, addr):
print(f"Echo from {addr}: {data}")
socket.send_data(f"Echo: {data}", addr)
with UDPSocketClass(11000) as socket:
socket.udp_recv_data.connect(echo_handler)
print(f"Running protocol v{socket.get_stats()['protocol_version']}")
# Keep running
import time
while True:
time.sleep(1)from vault_udp_socket import UDPSocketClass
with UDPSocketClass(11000) as socket:
stats = socket.get_stats()
print(f"Protocol: v{stats['protocol_version']}")
print(f"MTU: {stats['mtu']} bytes")
print(f"Peers: {stats['peer_count']}")- Copyright [2025] [ecki]
- SPDX-License-Identifier: Apache-2.0
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
- ✨ Introduced protocol v2 with version field
- ✨ Separated control and payload channels
- ✨ Structured msgpack format for extensibility
- 📝 Updated documentation for protocol v2
- ✨ Added authenticated encryption with NaCl Box
- ✨ Added replay attack prevention
- ✨ Added signature verification for key exchange
- ✨ Added rate limiting per peer
- 🐛 Fixed MTU calculation to include all overheads
- 🐛 Fixed memory leaks in nonce tracking
- 🐛 Fixed race condition in port updates
- 🔥 Removed password hashing (unused feature)
- 📝 Completely rewrote documentation
- Initial release with basic UDP + encryption
For issues and questions:
- Documentation: This README
- NaCl/libsodium for cryptography
- Zstandard for compression
- MessagePack for efficient serialization