import _isIP from 'is-ip' ; import ipaddr , { IPv4 , IPv6 } from 'ipaddr.js' ; import { parse , stringify } from 'ip-bigint' ; import ip6addr from 'ip6addr' ; import validateCidr from 'is-cidr' ; import { isString } from './strings' ; import { toInteger , isNumberLike , toBigIntOrThrow } from './numbers' ; import { getTypeOf } from './deps' ; export function isIP ( input ) { return isString ( input ) && _isIP ( input ) ; } /** Will throw if input is not a valid CIDR */ export function isIPRangeOrThrow ( input ) { if ( ! isCIDR ( input ) ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be a valid IP range` ) ; } return input ; } /** Will throw if input is not a valid IP */ export function isIPOrThrow ( input ) { if ( ! isString ( input ) || ! isIP ( input ) ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be a valid IP` ) ; } return input ; } export function isIPv6 ( input ) { return isString ( input ) && _isIP . v6 ( input ) ; } export function isIPv4 ( input ) { return isString ( input ) && _isIP . v4 ( input ) ; } export function isMappedIPv4 ( input ) { if ( isIPv6 ( input ) ) { const parsed = ipaddr . parse ( input as string ) as IPv6 ; return parsed . isIPv4MappedAddress ( ) ; } return false ; } export function extractMappedIPv4 ( input ) { if ( isIPv6 ( input ) && isMappedIPv4 ( input ) ) { const parsed = ipaddr . parse ( input as string ) as IPv6 ; const ipv4 = parsed . toIPv4Address ( ) ; return ipv4 . octets . join ( '.' ) ; } throw Error ( 'input must be an IPv4 address mapped to an IPv6 address' ) ; } export function inIPRange ( input , args ) { if ( ! isIP ( input ) ) return false ; if ( args . cidr != null ) { return isCIDR ( args . cidr ) && ip6addr . createCIDR ( args . cidr ) . contains ( input as string ) ; } const ipType = _isIP . version ( input as string ) ; const min = args . min || _assignMin ( ipType as number ) ; const max = args . max || _assignMax ( ipType as number ) ; return _validMinAndMax ( min , max ) && ip6addr . createAddrRange ( min , max ) . contains ( input as string ) ; } function _assignMin ( ipType , min ? ) { if ( min ) return min ; if ( ipType === 4 ) return '0.0.0.0' ; return '::' ; } function _assignMax ( ipType , max ? ) { if ( max ) return max ; if ( ipType === 4 ) return '255.255.255.255' ; return 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ; } function _validMinAndMax ( min , max ) { return isIP ( min ) && isIP ( max ) && _isIP . version ( min ) === _isIP . version ( max ) && ip6addr . compare ( min , max ) === - 1 ; } export function isRoutableIP ( input ) { if ( ! isIP ( input ) ) return false ; return ! _isPrivateIP ( input as string ) ; } export function isNonRoutableIP ( input ) { if ( ! isIP ( input ) ) return false ; return _isPrivateIP ( input as string ) ; } function _isPrivateIP ( input ) { const parsedIp = _parseIpAddress ( input ) ; return _inPrivateIPRange ( parsedIp . range ( ) ) || _inRestrictedIPRange ( parsedIp ) ; } function _parseIpAddress ( input ) { if ( isMappedIPv4 ( input ) ) { return ipaddr . parse ( extractMappedIPv4 ( input ) ) ; } return ipaddr . parse ( input ) ; } function _inPrivateIPRange ( ipRange ) { return [ 'private' , 'uniqueLocal' , 'loopback' , 'unspecified' , 'carrierGradeNat' , 'linkLocal' , 'reserved' , 'rfc6052' , 'teredo' , '6to4' , 'broadcast' ] . includes ( ipRange ) ; } const ipv4RestrictedRanges = [ ipaddr . parseCIDR ( '192.31.196.0/24' ) , ipaddr . parseCIDR ( '192.52.193.0/24' ) , ipaddr . parseCIDR ( '192.175.48.0/24' ) , ipaddr . parseCIDR ( '198.18.0.0/15' ) , ipaddr . parseCIDR ( '224.0.0.0/8' ) ] ; const ipv6RestrictedRanges = [ ipaddr . parseCIDR ( '64:ff9b:1::/48' ) , ipaddr . parseCIDR ( '100::/64' ) , ipaddr . parseCIDR ( '2001::/23' ) , ipaddr . parseCIDR ( '2620:4f:8000::/48' ) ] ; function _inRestrictedIPRange ( parsedIp ) { const rangesToCheck = parsedIp . kind ( ) === 'ipv4' ? ipv4RestrictedRanges : ipv6RestrictedRanges ; return rangesToCheck . some ( ( ipRange ) => parsedIp . match ( ipRange ) ) ; } export function isCIDR ( input ) { return isString ( input ) && validateCidr ( input ) > 0 ; } export function getCIDRMin ( input ) { if ( isCIDR ( input ) ) { return createCIDR ( input as string ) . first ( ) . toString ( ) ; } throw Error ( 'input must be a valid IP address in CIDR notation' ) ; } export function getCIDRMax ( input ) { if ( isCIDR ( input ) ) { return createCIDR ( input as string ) . last ( ) . toString ( ) ; } throw Error ( 'input must be a valid IP address in CIDR notation' ) ; } export function getCIDRBroadcast ( input ) { if ( isCIDR ( input ) ) { const asCIDR = createCIDR ( input as string ) ; if ( isIPv4 ( asCIDR . address ( ) . toString ( ) ) ) { return asCIDR . broadcast ( ) . toString ( ) ; } } throw Error ( 'input must be a valid IPv4 address in CIDR notation' ) ; } export function getCIDRNetwork ( input ) { if ( isCIDR ( input ) ) { const asCIDR = createCIDR ( input as string ) ; const address = asCIDR . address ( ) . toString ( ) ; if ( isIPv4 ( address ) ) { return ipaddr . IPv4 . networkAddressFromCIDR ( input as string ) . octets . join ( '.' ) ; } } throw Error ( 'input must be a valid IPv4 address in CIDR notation' ) ; } export function toCIDR ( input , suffix ) { if ( isIP ( input ) && _validSuffix ( _isIP . version ( input as string ) , suffix ) ) { return createCIDR ( input as string , toInteger ( suffix ) as number ) . toString ( ) ; } throw Error ( 'input must be a valid IP address and suffix must be a value <= 32 for IPv4 or <= 128 for IPv6' ) ; } function _validSuffix ( ipVersion , suffix ) { if ( isNumberLike ( suffix ) ) { const asInt = toInteger ( suffix ) ; if ( asInt < 0 ) return false ; if ( ipVersion === 4 ) return asInt <= 32 ; if ( ipVersion === 6 ) return asInt <= 128 ; } return false ; } function createCIDR ( input , suffix ? ) { if ( suffix != null ) { return ip6addr . createCIDR ( input , suffix ) ; } return ip6addr . createCIDR ( input ) ; } export function ipToInt ( input ) { if ( isIP ( input ) ) { return toBigIntOrThrow ( parse ( input as string ) . number ) ; } throw Error ( 'input must be a valid ip address' ) ; } export function intToIP ( input , ipVersion ) { const versionAsInt = toInteger ( ipVersion ) ; if ( isNumberLike ( input ) && ( versionAsInt === 4 || versionAsInt === 6 ) ) { return stringify ( { number : BigInt ( input as string | number | bigint ) , version : versionAsInt , ipv4mapped : false , scopeid : false } ) ; } throw Error ( 'input should be a big int or string for large numbers. Version must be 4 or 6' ) ; } export function reverseIP ( input ) { if ( ! isIP ( input ) ) throw Error ( 'input must be a valid ip address' ) ; const parsedIp = ipaddr . parse ( input as string ) ; if ( parsedIp . kind ( ) === 'ipv4' ) { return ( parsedIp as IPv4 ) . octets . reverse ( ) . join ( '.' ) ; } return _reverseIPv6 ( parsedIp as IPv6 ) ; } function _reverseIPv6 ( ip ) { return ip . toNormalizedString ( ) . split ( ':' ) . reduce ( ( parts , part ) => { parts . push ( _expandIPv6Part ( part ) ) ; return parts ; } , [ ] ) . join ( '' ) . split ( '' ) . reverse ( ) . join ( '.' ) ; } function _expandIPv6Part ( part ) { let expandedPart = part ; while ( expandedPart . length < 4 ) { expandedPart = `0${ expandedPart }` ; } return expandedPart ; }