P2PD is my Python networking library aimed at making direct connections easy. Currently it’s not easy because of NATs and firewalls. But with the right design it can look like this:
from p2pd import * async def example(): node = await P2PNode() pipe = await node.connect("unique.peer")
Nicknames
Nodes in P2PD have long addresses. They contain fields like IPs, public keys, and contact details. Long-addresses aren’t user-friendly so there is a feature to give nodes a nickname.
The nickname system assigns names to ECDSA public keys and requires no registration or fee to use. Instead, name creation is limited by IP and expires after a set period if not updated.
from p2pd import * async def example(): node = await P2PNode() nick = await node.nickname("my_name") print(nick, node.addr_bytes)
Protocols
The node class provides a familiar interface for writing protocols. Message handlers can be added and removed. Allowing for custom protocols to be extended as needed.
from p2pd import * # Put your custom protocol code here. async def msg_cb(msg, client_tup, pipe): # E.G. add a ping feature to your protocol. if b"PING" in msg: await pipe.send(b"PONG", client_tup) async def example(): node = await P2PNode() node.add_msg_cb(msg_cb)
Interfaces
P2PD has been designed to make it easy to select specific network interfaces. Most algorithms take into account multiple interfaces to improve connectivity options.
from p2pd import * async def example(): if_names = await list_interfaces() nics = await load_interfaces(if_names) nic = await Interface("name here") async_test(example)
Methodology
A list of network interfaces is queried to generate an address. The address includes information like public and private IPs, details on NAT configurations, and relevant metadata. The address determines what IPs to use for a connecting party.
From there, different connectivity methods try various approaches to establish a connection. A pathway – either external (WAN) or local (LAN or same machine) – can be used, and the interface list is ordered best based on the chosen pathway.
Enumeration
The success of NAT hole punching depends heavily on the types of NATs involved. NATs have unique properties which are referenced in the design of the hole-punching code.
Paradoxically, hole punching requires prior communication (to exchange initial mappings.) Since even this contact can be challenging, the punching code is optimized for success even if no reply message is received.
from p2pd import * async def example(): nic = await Interface() await nic.load_nat() print(nic) async_test(example)
Interface.from_dict({ "name": "Intel(R) Wi-Fi 6 AX200 160MHz", "nat": { "type": 5, "nat_info": "restrict port", "delta": { "type": 6, "value": 0 }, "delta_info": "random delta (local port == rand port)" }, "rp": { "2": [ { "af": 2, "nic_ips": [ { "ip": "192.168.21.21", "cidr": 32, "af": 2 } ], "ext_ips": [ { "ip": "1.3.3.7", "cidr": 32, "af": 2 } ] } ], "23": [] } })
name | local ip:port | dest ip:port | mappings reusable by |
---|---|---|---|
open internet | any | any | not applicable |
full cone | same ip:port | any | anyone |
restrict | same ip:port | same ip | dest ip for con |
restrict port | same ip:port | same ip:port | dest ip:ports for con |
symmetric | per dest ip:port | per unique ip:port | not reusable |
filtered | any | all are blocked | blocked |
name | local port eq to | mapping eq to | delta val |
---|---|---|---|
equal | any | local_port | NA |
preserving | first_local + n | first_mapped + n | NA |
independent | any | last_mapped + delta | e.g = +1 |
dependent | last_local + 1 | last_mapped + delta | e.g. = +1 |
random | any | random | NA |
Architecture
The right protocols ensures the availability of public infrastructure. STUN and TURN is a key part of WebRTC; MQTT is used for IoT devices. And NTP provides accurate time keeping – a necessary condition for TCP punching.
The only custom service is the name system: “Peer Name Protocol”, created due to a lack of a free-to-use, registration-less, key-value store – essential for making the library user-friendly.
Demo
There is an interactive, text-based demo to try the peer-to-peer networking features within the software. It functions by having one device run in ‘accept’ mode (option 1) and another make connections to the device (option 0.)
You can spin up multiple nodes from the same program and try out different options, too. The software gives nodes a short address to help with connecting. Or long address can be used.
python3 -m pip install p2pd python3 -m p2pd.demo
Leave a Reply