Summary
go-multiaddr's manet package provides a full set of IP classification helpers that operate directly on Multiaddr objects (loopback, unspecified, link-local, public, private, NAT64). py-multiaddr only has is_wildcard() and is_link_local_ip() in utils.py, and both take raw IP strings rather than Multiaddr objects — plus several classifications (loopback, public, private, thin-waist, NAT64) don't exist at all.
Motivation
- Address filtering: P2P nodes need to filter loopback/link-local/unspecified addresses out of address books, peer discovery results, and NAT traversal candidate lists.
- Public vs. private routing decisions: Deciding whether to advertise an address to the public DHT vs. only on a local network requires
is_public_addr / is_private_addr.
- Interop with go-libp2p / go-multiaddr: Ports of Go networking code (relay logic, hole punching, autonat) assume these predicates exist on the multiaddr object itself, not on a parsed string — so contributors porting that logic currently have to write ad hoc helpers per-PR.
- API consistency: Working directly on
Multiaddr (rather than extracting the IP string first) matches how the rest of the library's transform/predicate functions are structured.
Current Behavior
| Function |
Takes |
Description |
is_wildcard(ip: str) |
raw string |
only checks "0.0.0.0" or "::" |
is_link_local_ip(ip: str) |
raw string |
checks fe80: or 169.254 prefix |
get_multiaddr_options(ma) |
Multiaddr |
implicit thin-waist check, not exposed as a named predicate |
Missing entirely: is_ip_loopback, is_ip_unspecified (on Multiaddr), is_ip6_link_local (on Multiaddr), is_public_addr, is_private_addr, is_thin_waist, is_nat64_ipv4_converted_ipv6_addr.
Reference: what Go provides
| Function |
Description |
IsThinWaist(ma) |
Is it /{ip4,ip6}[/{tcp,udp}]? |
IsIPLoopback(ma) |
Is it 127.0.0.0/8 or ::1? |
IsIPUnspecified(ma) |
Is it 0.0.0.0 or ::? |
IsIP6LinkLocal(ma) |
Is it fe80::/10? |
IsPublicAddr(ma) |
Is it publicly routable? |
IsPrivateAddr(ma) |
Is it in a private network range? |
IsNAT64IPv4ConvertedIPv6Addr(ma) |
Is it a NAT64-translated IPv4-in-IPv6 address? |
Proposed Solution
Add to utils.py:
import ipaddress
# Well-known multiaddr constants
IP4_LOOPBACK = Multiaddr("/ip4/127.0.0.1")
IP6_LOOPBACK = Multiaddr("/ip6/::1")
IP4_UNSPECIFIED = Multiaddr("/ip4/0.0.0.0")
IP6_UNSPECIFIED = Multiaddr("/ip6/::")
# Private CIDR ranges
PRIVATE4 = [ipaddress.ip_network(cidr) for cidr in [
"127.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10",
"172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16",
]]
PRIVATE6 = [ipaddress.ip_network(cidr) for cidr in [
"::1/128", "fc00::/7", "fe80::/10",
]]
def is_thin_waist(ma: Multiaddr) -> bool: ...
def is_ip_loopback(ma: Multiaddr) -> bool: ...
def is_ip_unspecified(ma: Multiaddr) -> bool: ...
def is_ip6_link_local(ma: Multiaddr) -> bool: ...
def is_public_addr(ma: Multiaddr) -> bool: ...
def is_private_addr(ma: Multiaddr) -> bool: ...
Each function should extract the IP component from the multiaddr (first /ip4 or /ip6 protocol) and test it against the appropriate range/constant, mirroring the semantics of the Go equivalents linked below.
References
Summary
go-multiaddr'smanetpackage provides a full set of IP classification helpers that operate directly onMultiaddrobjects (loopback, unspecified, link-local, public, private, NAT64).py-multiaddronly hasis_wildcard()andis_link_local_ip()inutils.py, and both take raw IP strings rather thanMultiaddrobjects — plus several classifications (loopback, public, private, thin-waist, NAT64) don't exist at all.Motivation
is_public_addr/is_private_addr.Multiaddr(rather than extracting the IP string first) matches how the rest of the library's transform/predicate functions are structured.Current Behavior
is_wildcard(ip: str)"0.0.0.0"or"::"is_link_local_ip(ip: str)fe80:or169.254prefixget_multiaddr_options(ma)MultiaddrMissing entirely:
is_ip_loopback,is_ip_unspecified(onMultiaddr),is_ip6_link_local(onMultiaddr),is_public_addr,is_private_addr,is_thin_waist,is_nat64_ipv4_converted_ipv6_addr.Reference: what Go provides
IsThinWaist(ma)/{ip4,ip6}[/{tcp,udp}]?IsIPLoopback(ma)127.0.0.0/8or::1?IsIPUnspecified(ma)0.0.0.0or::?IsIP6LinkLocal(ma)fe80::/10?IsPublicAddr(ma)IsPrivateAddr(ma)IsNAT64IPv4ConvertedIPv6Addr(ma)Proposed Solution
Add to
utils.py:Each function should extract the IP component from the multiaddr (first
/ip4or/ip6protocol) and test it against the appropriate range/constant, mirroring the semantics of the Go equivalents linked below.References
go-multiaddr/net/ip.go,go-multiaddr/net/private.go