multiaddr implementation in Python
pip install multiaddr- Python 3.10+
- trio (for async DNS resolution)
py-multiaddr provides a command line interface to inspect multiaddrs and decode them into structured JSON.
$ multiaddr /ip4/1.2.3.4/tcp/80
{
"string": "/ip4/1.2.3.4/tcp/80",
"packed": "0x0401020304060050",
"packedSize": 8,
"components": [
{
"protocol": "ip4",
"code": 4,
"value": "1.2.3.4",
"rawValue": "0x01020304"
},
{
"protocol": "tcp",
"code": 6,
"value": "80",
"rawValue": "0x0050"
}
]
}You can also pass a hex-encoded multiaddr prefixed with 0x, e.g.
multiaddr 0x0401020304060050. Use the -c or --compact flag to
output on a single line.
You can easily classify the IP components of multiaddrs (e.g. check for loopback, private networks, or NAT64):
from multiaddr import Multiaddr, is_private_addr, is_ip_loopback
ma1 = Multiaddr("/ip4/192.168.1.1/tcp/80")
print(is_private_addr(ma1)) # True
print(is_ip_loopback(ma1)) # False
ma2 = Multiaddr("/ip4/127.0.0.1")
print(is_private_addr(ma2)) # True
print(is_ip_loopback(ma2)) # TrueOther available helpers include is_public_addr, is_ip_unspecified,
is_ip6_link_local, is_thin_waist, and is_nat64_ipv4_converted_ipv6_addr.
These operate on Multiaddr objects; is_wildcard / is_link_local_ip
remain available for raw IP strings.
from multiaddr import Multiaddr
# construct from a string
m1 = Multiaddr("/ip4/127.0.0.1/udp/1234")
# construct from bytes
#m2 = Multiaddr(bytes_addr=m1.to_bytes()) # deprecated
m2 = Multiaddr(m1.to_bytes())
assert str(m1) == "/ip4/127.0.0.1/udp/1234"
assert str(m1) == str(m2)
assert m1.to_bytes() == m2.to_bytes()
assert m1 == m2
assert m2 == m1
assert not (m1 != m2)
assert not (m2 != m1)from multiaddr import Multiaddr
m1 = Multiaddr("/ip4/127.0.0.1/udp/1234")
# get the multiaddr protocol description objects
m1.protocols()
# [Protocol(code=4, name='ip4', size=32), Protocol(code=17, name='udp', size=16)]from multiaddr import Multiaddr
m1 = Multiaddr("/ip4/127.0.0.1/udp/1234")
m1.encapsulate(Multiaddr("/sctp/5678"))
# <Multiaddr /ip4/127.0.0.1/udp/1234/sctp/5678>
m1.decapsulate(Multiaddr("/udp"))
# <Multiaddr /ip4/127.0.0.1>
# Decapsulate by protocol code
m2 = Multiaddr("/ip4/192.168.1.1/tcp/8080/udp/1234")
m2.decapsulate_code(6) # TCP protocol code
# <Multiaddr /ip4/192.168.1.1>
# Decapsulate multiple layers
m3 = Multiaddr("/ip4/10.0.0.1/tcp/443/tls/p2p/QmPeer")
m3.decapsulate_code(6) # Remove TCP and everything after
# <Multiaddr /ip4/10.0.0.1>Multiaddr allows expressing tunnels very nicely.
printer = Multiaddr("/ip4/192.168.0.13/tcp/80")
proxy = Multiaddr("/ip4/10.20.30.40/tcp/443")
printerOverProxy = proxy.encapsulate(printer)
print(printerOverProxy)
# /ip4/10.20.30.40/tcp/443/ip4/192.168.0.13/tcp/80
proxyAgain = printerOverProxy.decapsulate(printer)
print(proxyAgain)
# /ip4/10.20.30.40/tcp/443Multiaddr supports DNS-based address resolution using the DNSADDR protocol. This is particularly useful for resolving bootstrap node addresses and maintaining peer IDs during resolution.
from multiaddr import Multiaddr
import trio
# Basic DNS resolution
ma = Multiaddr("/dns/example.com")
resolved = await ma.resolve()
print(resolved)
# [Multiaddr("/ip4/93.184.216.34"), Multiaddr("/ip6/2606:2800:220:1:248:1893:25c8:1946")]
# DNSADDR with peer ID (bootstrap node style)
ma_with_peer = Multiaddr("/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")
resolved_with_peer = await ma_with_peer.resolve()
print(resolved_with_peer)
# [Multiaddr("/ip4/147.75.83.83/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")]
# DNS4 and DNS6 resolution (IPv4/IPv6 specific)
ma_dns4 = Multiaddr("/dns4/example.com/tcp/443")
resolved_dns4 = await ma_dns4.resolve()
print(resolved_dns4)
# [Multiaddr("/ip4/93.184.216.34/tcp/443")]
ma_dns6 = Multiaddr("/dns6/example.com/tcp/443")
resolved_dns6 = await ma_dns6.resolve()
print(resolved_dns6)
# [Multiaddr("/ip6/2606:2800:220:1:248:1893:25c8:1946/tcp/443")]
# Using the DNS resolver directly
from multiaddr.resolvers import DNSResolver
resolver = DNSResolver()
resolved = await resolver.resolve(ma)
print(resolved)
# [Multiaddr("/ip4/93.184.216.34"), Multiaddr("/ip6/2606:2800:220:1:248:1893:25c8:1946")]
# Peer ID preservation test
original_peer_id = ma_with_peer.get_peer_id()
print(f"Original peer ID: {original_peer_id}")
# Original peer ID: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN
for resolved_addr in resolved_with_peer:
preserved_peer_id = resolved_addr.get_peer_id()
print(f"Resolved peer ID: {preserved_peer_id}")
# Resolved peer ID: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJNFor comprehensive examples including bootstrap node resolution, protocol comparison, and py-libp2p integration, see the DNS examples in the examples directory.
Accept/deny IP ranges for multiaddrs, similar to go-multiaddr Filters:
from multiaddr import Action, Filters, Multiaddr
filters = Filters()
filters.add_filter("10.0.0.0/8", Action.DENY)
assert filters.addr_blocked(Multiaddr("/ip4/10.1.2.3/tcp/80"))
assert not filters.addr_blocked(Multiaddr("/ip4/8.8.8.8/tcp/53"))See examples/filters/filters_example.py for a printable demo.
Convert thin-waist and unix multiaddrs to (network, address) pairs suitable
for socket.connect():
from multiaddr import Multiaddr, dial_args
print(dial_args(Multiaddr("/ip4/1.2.3.4/tcp/80")))
# ('tcp4', '1.2.3.4:80')
print(dial_args(Multiaddr("/ip6/::1/tcp/80")))
# ('tcp6', '[::1]:80')
print(dial_args(Multiaddr("/unix/var/run/docker.sock")))
# ('unix', '/var/run/docker.sock')See examples/dial_args/dial_args_example.py for a printable demo.
Convert between Python socket address tuples and multiaddrs:
from multiaddr import from_net_addr, to_net_addr
ma = from_net_addr(("1.2.3.4", 80))
print(ma)
# /ip4/1.2.3.4/tcp/80
print(to_net_addr(ma))
# ('1.2.3.4', 80)
print(from_net_addr(("::1", 53), transport="udp"))
# /ip6/::1/udp/53See examples/net_addr/net_addr_example.py for a printable demo.
from multiaddr import Multiaddr
from multiaddr.utils import get_thin_waist_addresses, get_network_addrs, interface_multiaddrs
# List local interfaces as Multiaddr objects
for ma in interface_multiaddrs():
print(ma)
# Multiaddr('/ip4/192.168.1.10'), ...
# Network interface discovery
ipv4_addrs = get_network_addrs(4)
print(f"Available IPv4 addresses: {ipv4_addrs}")
# Available IPv4 addresses: ['192.168.1.12', '10.152.168.99']
# Specific address (no expansion)
addr = Multiaddr("/ip4/192.168.1.100/tcp/8080")
result = get_thin_waist_addresses(addr)
print(result)
# [<Multiaddr /ip4/192.168.1.100/tcp/8080>]
# IPv4 wildcard expansion
addr = Multiaddr("/ip4/0.0.0.0/tcp/8080")
result = get_thin_waist_addresses(addr)
print(result)
# [<Multiaddr /ip4/192.168.1.12/tcp/8080>, <Multiaddr /ip4/10.152.168.99/tcp/8080>]
# IPv6 wildcard expansion
addr = Multiaddr("/ip6/::/tcp/8080")
result = get_thin_waist_addresses(addr)
print(result)
# [<Multiaddr /ip6/::1/tcp/8080>, <Multiaddr /ip6/fd9b:9eba:8224:1:41a1:8939:231a:b414/tcp/8080>]
# Port override
addr = Multiaddr("/ip4/0.0.0.0/tcp/8080")
result = get_thin_waist_addresses(addr, port=9000)
print(result)
# [<Multiaddr /ip4/192.168.1.12/tcp/9000>, <Multiaddr /ip4/10.152.168.99/tcp/9000>]
# UDP transport support
addr = Multiaddr("/ip4/0.0.0.0/udp/1234")
result = get_thin_waist_addresses(addr)
print(result)
# [<Multiaddr /ip4/192.168.1.12/udp/1234>, <Multiaddr /ip4/10.152.168.99/udp/1234>]
# Server binding scenario
wildcard = Multiaddr("/ip4/0.0.0.0/tcp/8080")
interfaces = get_thin_waist_addresses(wildcard)
print("Available interfaces for server binding:")
for i, interface in enumerate(interfaces, 1):
print(f" {i}. {interface}")
# Available interfaces for server binding:
# 1. /ip4/192.168.1.12/tcp/8080
# 2. /ip4/10.152.168.99/tcp/8080For comprehensive examples including error handling, practical usage scenarios, and detailed network interface information, see the thin waist examples in the examples directory.
- Multiaddr Protocol Support: Full support for the multiaddr specification
- DNS Resolution: Async DNS and DNSADDR resolution with trio
- Thin Waist Validation: Network interface discovery and wildcard expansion
- Protocol Support: IPv4, IPv6, TCP, UDP, DNS, DNS4, DNS6, DNSADDR, p2p, p2p-circuit, onion, onion3, quic, tls, and more
- Type Safety: Full type hints and mypy support
- Modern Python: Python 3.10+ support with modern tooling
Original author: @sbuss.
Current maintainers: @acul71, @pacrob, @manusheel.
Contributions welcome. Please check out the issues.
Check out our contributing document for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS Code of Conduct.
For development setup, see py-multiaddr contributing.
# Clone the repository
git clone https://github.com/multiformats/py-multiaddr.git
cd py-multiaddr
# Install in development mode
pip install -e ".[dev]"
# Run the development workflow
make prDual-licensed: