Skip to content
LogoLogo

@portable/ip

Functions

Ip.canParse

Returns whether text is an IP address that parse accepts. Anything other than a string returns false.

function canParse(
  text: unknown,
  options: canParse.Options = {},
): boolean;
OptionTypeDefaultDescription
version4 | 6undefined, which accepts bothAccept only this IP version.
import * as Ip from '@portable/ip';
 
Ip.canParse('2001:db8::1'); // true
Ip.canParse('2001:db8::1', { version: 4 }); // false
Ip.canParse('010.0.0.1'); // false

Ip.compare

Orders two addresses: IPv4 before IPv6, then by bytes, then by zone. Returns a negative number, zero or a positive number, so it fits Array.prototype.sort. An IPv4 address and its IPv4-mapped IPv6 form count as different addresses. Pass both through unmap to treat them as one.

function compare(a: Address | string, b: Address | string): number;
import * as Ip from '@portable/ip';
 
['10.0.0.2', '::1', '10.0.0.1'].sort(Ip.compare); // ['10.0.0.1', '10.0.0.2', '::1']
Ip.compare('2001:db8::1', '2001:DB8:0::1') === 0; // true

Ip.format

Formats an address as text. IPv6 follows RFC 5952: lowercase hex without leading zeros, and :: for the longest run of two or more zero groups (the first one on a tie). IPv4-mapped addresses end in dotted IPv4, as RFC 5952 section 5 recommends.

function format(address: Address | string): string;
import * as Ip from '@portable/ip';
 
Ip.format('2001:0DB8:0:0:0:0:0:1'); // '2001:db8::1'
Ip.format('::ffff:c000:201'); // '::ffff:192.0.2.1'

Ip.fromBytes

Builds an address from its bytes in network order: 4 bytes for IPv4, 16 for IPv6. The bytes are copied.

function fromBytes(bytes: Uint8Array): Address;
import * as Ip from '@portable/ip';
 
Ip.format(Ip.fromBytes(new Uint8Array([127, 0, 0, 1]))); // '127.0.0.1'

Ip.parse

Parses an IPv4 or IPv6 address.

IPv4 must be four decimal numbers from 0 to 255 without leading zeros. parse throws on octal (0177.0.0.1), hex (0x7f.0.0.1), integer (2130706433) and short (127.1) forms, because programs disagree on what they mean. IPv6 follows RFC 4291: up to four hex digits per group, one ::, an optional dotted IPv4 address in the last 32 bits, and an optional %zone.

function parse(text: string): Address;
import * as Ip from '@portable/ip';
 
Ip.parse('192.0.2.1'); // { version: 4, bytes: Uint8Array [192, 0, 2, 1] }
Ip.parse('fe80::1%eth0'); // { version: 6, bytes: Uint8Array [254, 128, ..., 1], zone: 'eth0' }

Ip.reverseName

Returns the DNS name for a reverse (PTR) lookup of the address: under in-addr.arpa for IPv4 and ip6.arpa for IPv6, one label per byte or per hex digit, last first. An IPv4-mapped address gets an ip6.arpa name, as in Node's dns.reverse. unmap it first to get the IPv4 name.

function reverseName(address: Address | string): string;
import * as Ip from '@portable/ip';
 
Ip.reverseName('192.0.2.1'); // '1.2.0.192.in-addr.arpa'
Ip.reverseName('2001:db8::1'); // '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'

Ip.toIpv6

Returns the IPv4-mapped IPv6 form of an IPv4 address (::ffff:192.0.2.1), and an IPv6 address unchanged.

function toIpv6(address: Address | string): Address;
import * as Ip from '@portable/ip';
 
Ip.format(Ip.toIpv6('192.0.2.1')); // '::ffff:192.0.2.1'

Ip.unmap

Returns the IPv4 address inside an IPv4-mapped IPv6 address (::ffff:192.0.2.1), and any other address unchanged. Call it before checking an address that may come from a dual-stack socket, which reports IPv4 clients in mapped form.

function unmap(address: Address | string): Address;
import * as Ip from '@portable/ip';
 
Ip.format(Ip.unmap('::ffff:127.0.0.1')); // '127.0.0.1'
Ip.format(Ip.unmap('2001:db8::1')); // '2001:db8::1'

Ip.contains

Returns whether the CIDR block contains the address. An IPv4 address and its IPv4-mapped IPv6 form (::ffff:192.0.2.1) count as the same address, as in Node's BlockList, so 127.0.0.0/8 contains ::ffff:127.0.0.1. Zones are ignored. Strings are parsed with parseCidr and parse, so parse a block once when you check many addresses against it.

function contains(
  cidr: Cidr | string,
  address: Address | string,
): boolean;
import * as Ip from '@portable/ip';
 
Ip.contains('10.0.0.0/8', '10.1.2.3'); // true
Ip.contains('127.0.0.0/8', '::ffff:127.0.0.1'); // true
 
const internal = Ip.parseCidr('fc00::/7');
Ip.contains(internal, 'fd12:3456::1'); // true

Ip.formatCidr

Formats a CIDR block as address/prefix, with the address formatted by format.

function formatCidr(cidr: Cidr): string;
import * as Ip from '@portable/ip';
 
Ip.formatCidr({ address: Ip.parse('2001:db8::'), prefix: 32 }); // '2001:db8::/32'

Ip.parseCidr

Parses CIDR notation, such as 10.0.0.0/8 or 2001:db8::/32. The address follows the rules of parse, without a zone, and its host bits are cleared, so 192.168.1.134/26 gives the network 192.168.1.128/26. The prefix is a decimal number without leading zeros, up to 32 for IPv4 and 128 for IPv6.

function parseCidr(text: string): Cidr;
import * as Ip from '@portable/ip';
 
const cidr = Ip.parseCidr('192.168.1.134/26');
Ip.formatCidr(cidr); // '192.168.1.128/26'
cidr.prefix; // 26

Ip.range

Returns the special-purpose block the address is in, or 'unicast' for an ordinary public address. To reject internal destinations before a server-side request (SSRF), allow only 'unicast'. An IPv4-mapped address returns 'ipv4Mapped', so pass it through unmap first to classify the IPv4 address inside.

function range(address: Address | string): Range;
import * as Ip from '@portable/ip';
 
Ip.range('10.1.2.3'); // 'private'
Ip.range('fe80::1'); // 'linkLocal'
Ip.range(Ip.unmap('::ffff:127.0.0.1')); // 'loopback'
Ip.range('8.8.8.8'); // 'unicast'

Errors

Ip.InvalidInputError

Thrown for text that isn't an IP address or CIDR block in the strict notation parse and parseCidr accept, and by fromBytes for a length other than 4 or 16.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as Ip from '@portable/ip';
 
try {
  Ip.parse('0x7f.1');
} catch (error) {
  if (error instanceof Ip.InvalidInputError) {
    console.log('not an IP address');
  }
}

Types

Ip.Address

An IPv4 or IPv6 address. bytes holds the address in network order: 4 bytes for IPv4 and 16 for IPv6. zone is the IPv6 zone index after %, as in fe80::1%eth0.

type Address = {
    version: ;
    bytes: Uint8Array;
} | {
    version: ;
    bytes: Uint8Array;
    zone?: string | undefined;
};

Ip.Cidr

A CIDR block: the network address and the number of leading bits that every address in it shares.

type Cidr = {
  address: Address;
  prefix: number;
};

Ip.Range

The special-purpose block an address belongs to, named after its entry in the IANA IPv4 and IPv6 Special-Purpose Address Registries. 'multicast', 'deprecatedSiteLocal' and 'ipv4Translated' come from RFC 5771, RFC 3879 and RFC 2765. 'unicast' is every other IPv4 address and every other IPv6 address in 2000::/3. 'reserved' is the rest of the IPv6 space and 240.0.0.0/4.

type Range =  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | ;