Addr

NAME

MCP::Server::Tool::Web::Addr - IP literal parsing, unwrapping and classification

SYNOPSIS


use MCP::Server::Tool::Web::Addr;

# Parse, then ask what kind of address it is.
my $a = MCP::Server::Tool::Web::Addr.parse('10.0.0.5');
say $a.classify;                     # private
say $a.reference;                    # RFC 1918

# Anything that is not a public address is refused by the guard, and the
# class name is what the refusal teaches with.
say classify-address('8.8.8.8');            # public
say classify-address('169.254.169.254');    # metadata
say classify-address('::1');                # loopback
say classify-address('not an address');     # (Str) — unparseable

# IPv6 forms that carry an IPv4 address inside them are unwrapped first, so
# there is no way to smuggle 127.0.0.1 past the table by spelling it in v6.
say classify-address('::ffff:127.0.0.1');   # loopback   (v4-mapped)
say classify-address('::ffff:8.8.8.8');     # public     (v4-mapped)
say classify-address('64:ff9b::a00:1');     # private    (NAT64, 10.0.0.1)
say classify-address('2002:7f00:1::');      # loopback   (6to4, 127.0.0.1)

# The allow-list helper: does this address fall inside this block?
say cidr-contains('10.0.0.0/8', '10.4.5.6');            # True
say cidr-contains('10.0.0.0/8', '11.0.0.1');            # False
say cidr-contains('10.0.0.0/8', '::ffff:10.4.5.6');     # True — mapped form counts
say valid-cidr('10.0.0.0/33');                          # False

DESCRIPTION

Pure address arithmetic: no DNS, no sockets, no clock, nothing that can fail in a way a test cannot reproduce. Everything here answers one question — given a literal address, is it somewhere on the public internet, or somewhere on the machine (or the network) that is running the tool?

The answer is a class name, not a boolean, because the whole point of the SSRF floor is to teach: a model that is told "refused: loopback (RFC 1122)" can decide to stop asking for localhost, while one told "refused" tries seventeen spellings of the same thing.

The classification

classify returns exactly one of these strings. Everything not covered by the table is 'public':

class what it covers reference
public anything not listed below —
unspecified :: RFC 4291 §2.5.2
this-network 0.0.0.0/8 RFC 1122 §3.2.1.3
loopback 127.0.0.0/8, ::1 RFC 1122, RFC 4291 §2.5.3
private 10/8, 172.16/12, 192.168/16 RFC 1918
unique-local fc00::/7 RFC 4193
cgnat 100.64.0.0/10 RFC 6598
link-local 169.254.0.0/16, fe80::/10 RFC 3927, RFC 4291 §2.5.6
metadata well-known cloud metadata endpoints (inside the ranges above)
ietf-protocol 192.0.0.0/24 RFC 6890 §2.1
documentation 192.0.2/24, 198.51.100/24, 203.0.113/24, 2001:db8::/32 RFC 5737, RFC 3849
6to4-relay 192.88.99.0/24 RFC 7526
benchmark 198.18.0.0/15 RFC 2544
multicast 224.0.0.0/4, ff00::/8 RFC 5771, RFC 4291 §2.7
reserved 240.0.0.0/4 (incl. 255.255.255.255) RFC 1112 §4
discard 100::/64 RFC 6666
teredo 2001::/32 RFC 4380
segment-routing 5f00::/16 RFC 9602
nat64-local 64:ff9b:1::/48 that carries no RFC 6052 address RFC 8215

metadata is mostly a refinement rather than an extra denial: nearly every address it names sits inside a range that is denied anyway (169.254.169.254 is link-local, 100.100.100.200 is CGNAT, 192.0.0.192 is IETF-protocol, fd00:ec2::254 is unique-local). It exists because "cloud instance metadata endpoint" is a far more useful thing to tell a model — or an operator reading a log — than "link-local". The one genuine extra denial is Azure's wireserver, 168.63.129.16: it sits in public address space but is routed only inside an Azure VNet, so nothing on the public web legitimately serves from it and denying the single address closes a well-known SSRF target.

Unwrapping

IPv6 has five ways to write an IPv4 address, and every one of them is a way to spell 127.0.0.1 that a naive table misses. unwrap maps them all back to the address they carry, and classify unwraps before it looks anything up:

  • ::ffff:0:0/96 — IPv4-mapped (RFC 4291 §2.5.5.2). What a dual-stack socket reports for a v4 peer on some platforms, so this is the common case, not the exotic one.

  • ::/96 — IPv4-compatible (deprecated, RFC 4291 §2.5.5.1), except :: and ::1 themselves, which keep their own meanings.

  • 64:ff9b::/96 — the well-known NAT64 prefix (RFC 6052 §2.1).

  • 64:ff9b:1::/48 — local-use NAT64 (RFC 8215) with the RFC 6052 §2.2 /48 embedding (two octets, the zero u-octet, two more octets). An address in that prefix whose u-octet is not zero carries no address we can read, so it fails closed as nat64-local rather than being waved through as public.

  • 2002::/16 — 6to4 (RFC 3056), the embedded address being the next 32 bits.

Unwrapping is one level deep by construction: every wrapper yields an IPv4 address, and IPv4 has no wrappers.

Strictness

parse takes canonical literals only. 0177.0.0.1, 127.1, 0x7f000001 and 2130706433 are not addresses here — they are refused, and MCP::Server::Tool::Web::Url refuses URLs that carry them, so neither an operator nor a model ever has to reason about which radix a host name is in. Zone identifiers (fe80::1%eth0) are refused for the same reason: the guard would have nothing meaningful to say about the scope.

MCP::Server::Tool::Web v0.1.1

web search, fetch, crawl and grep for MCP::Server

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

MCP::Server:auth<zef:apogee>:ver<0.6.0+>Cro::HTTP:auth<zef:cro>:ver<0.8.11+>Cro::Core:auth<zef:cro>:ver<0.8.10+>IO::Socket::Async::SSL:auth<zef:raku-community-modules>:ver<0.8.2+>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>

Test Dependencies

Provides

  • MCP::Server::Tool::Web
  • MCP::Server::Tool::Web::Addr
  • MCP::Server::Tool::Web::Budget
  • MCP::Server::Tool::Web::Crawl
  • MCP::Server::Tool::Web::Extract
  • MCP::Server::Tool::Web::Fetcher
  • MCP::Server::Tool::Web::Guard
  • MCP::Server::Tool::Web::Provider::Brave
  • MCP::Server::Tool::Web::Robots
  • MCP::Server::Tool::Web::SearchProvider
  • MCP::Server::Tool::Web::Transport
  • MCP::Server::Tool::Web::Url
  • MCP::Server::Tool::Web::X

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.