denoland / denoland/std

proposal(net): classify IP addresses against the IANA special-purpose registries

Open
#7,315 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
3.6k
Forks
681
PR merge metrics
No merged PRs in 30d

Description

I'd like to add IP classification to `@std/net/unstable-ip`, backed by the IANA special-purpose registries. Fix the parsers first.

The current validators accept these inputs, reproduced on this checkout:

| Validator | Invalid inputs returning `true` |
| --- | --- |
| `isIPv4` | `"0x7f.0.0.1"`, `" 1.2.3.4"`, `"1..2.3"`, `"01.002.3.4"` |
| `isIPv6` | `":1"`, `"2001:db8:::1"`, `"::1 "` |

In #6765, 0f-0b [requested URL-standard syntax and parsers that return bytes](https://github.com/denoland/std/pull/6765#pullrequestreview-3031081186). This would follow through on that proposal.

There's demand beyond validation. Better Auth's shared host classifier serves eleven call sites across seven packages, including [SSRF fixes](https://github.com/better-auth/better-auth/pull/9226) and [client metadata fetches](https://github.com/better-auth/better-auth/pull/10126). Go `netip`, Rust `std::net`, and Python `ipaddress` already offer address classification. Potential std consumers include client-IP handling in #7237 and unspecified-address handling in `http/file_server.ts`.

Proposed scope, in separate PRs:

1. Add `parseIPv4` / `parseIPv6`, returning 4 / 16 bytes as `Uint8Array`, or `undefined` for invalid input. Share parsing with the validators and subnet matchers.
2. Add `classifyIP(): IPAddressKind | undefined`, plus `isLoopback`, `isPrivate`, `isLinkLocal`, `isMulticast`, and `isUnspecified`. Kinds: loopback, unspecified, private, link-local, shared, documentation, benchmarking, multicast, broadcast, reserved, global.
3. Add `embeddedIPv4` for 6to4, NAT64's well-known prefix, Teredo, and deprecated IPv4-compatible addresses. Add `isGloballyReachable` once the handling of embedded addresses and IANA's N/A entries is defined and tested.
4. Use the helpers in std where they simplify existing code, starting with the file server or rate limiter.

Keep this in `net/unstable_ip.ts`, under the existing entry point. Use longest-prefix matching against the [IPv4](https://www.iana.org/assignments/iana-ipv4-special-registry/) and [IPv6](https://www.iana.org/assignments/iana-ipv6-special-registry/) registries, with RFC-defined multicast ranges. Exceptions matter: `192.0.0.9` and `.10` are globally reachable within a non-global `/24`.

Unmap IPv4-mapped IPv6 before classification, so `::ffff:127.0.0.1` classifies as loopback. Keep tunnel extraction separate from classification of the outer address.

Inputs are bare IP literals: no ports, brackets, whitespace trimming, zone IDs, or hostname classification. URL callers normalize through `URL` and remove IPv6 hostname brackets before passing the address. Global reachability describes address ranges. It does not resolve DNS, prevent rebinding, or check redirects.

For IPv4, I propose the URL standard's [strict dotted-quad syntax](https://url.spec.whatwg.org/#host-writing): four decimal octets, no leading zeros. Should we use that grammar, or the URL parser's permissive grammar accepting hex, octal, and shorthand?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.