P2PD 3: peer-to-peer direct connections

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": []
    }
})
namelocal ip:portdest ip:portmappings reusable by
open internetanyanynot applicable
full conesame ip:portanyanyone
restrictsame ip:portsame ipdest ip for con
restrict portsame ip:portsame ip:portdest ip:ports for con
symmetricper dest ip:portper unique ip:portnot reusable
filteredanyall are blockedblocked
namelocal port eq tomapping eq todelta val
equalanylocal_portNA
preservingfirst_local + nfirst_mapped + nNA
independentanylast_mapped + deltae.g = +1
dependentlast_local + 1last_mapped + deltae.g. = +1
randomanyrandomNA

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

Recording

Leave a Reply

Your email address will not be published. Required fields are marked *