Skip to content

Add IP classification functions (loopback, unspecified, link-local, public/private) for Multiaddr #116

Description

@sumanjeet0012

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions