diff --git a/packages/kad-dht/package.json b/packages/kad-dht/package.json index 2d084e2237..45c563de29 100644 --- a/packages/kad-dht/package.json +++ b/packages/kad-dht/package.json @@ -81,7 +81,6 @@ "p-defer": "^4.0.0", "p-event": "^6.0.0", "p-queue": "^8.0.0", - "private-ip": "^3.0.1", "progress-events": "^1.0.0", "protons-runtime": "^5.0.0", "race-signal": "^1.0.2", diff --git a/packages/kad-dht/src/utils.ts b/packages/kad-dht/src/utils.ts index 6bd44b2c5e..b50e0433d1 100644 --- a/packages/kad-dht/src/utils.ts +++ b/packages/kad-dht/src/utils.ts @@ -1,7 +1,7 @@ import { peerIdFromBytes } from '@libp2p/peer-id' +import { isPrivateIp } from '@libp2p/utils/private-ip' import { Key } from 'interface-datastore/key' import { sha256 } from 'multiformats/hashes/sha2' -import isPrivateIp from 'private-ip' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' diff --git a/packages/libp2p/package.json b/packages/libp2p/package.json index e7465b4a1a..e75eefbc03 100644 --- a/packages/libp2p/package.json +++ b/packages/libp2p/package.json @@ -102,7 +102,6 @@ "it-parallel": "^3.0.6", "merge-options": "^3.0.4", "multiformats": "^13.0.0", - "private-ip": "^3.0.1", "uint8arrays": "^5.0.0" }, "devDependencies": { diff --git a/packages/libp2p/src/config/connection-gater.browser.ts b/packages/libp2p/src/config/connection-gater.browser.ts index 5593c8dbe9..1196b2b2bc 100644 --- a/packages/libp2p/src/config/connection-gater.browser.ts +++ b/packages/libp2p/src/config/connection-gater.browser.ts @@ -1,4 +1,4 @@ -import isPrivate from 'private-ip' +import { isPrivateIp } from '@libp2p/utils/private-ip' import type { ConnectionGater } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' @@ -14,7 +14,7 @@ export function connectionGater (gater: ConnectionGater = {}): ConnectionGater { const tuples = multiaddr.stringTuples() if (tuples[0][0] === 4 || tuples[0][0] === 41) { - return Boolean(isPrivate(`${tuples[0][1]}`)) + return Boolean(isPrivateIp(`${tuples[0][1]}`)) } return false diff --git a/packages/protocol-autonat/package.json b/packages/protocol-autonat/package.json index dbc54c239a..7ee9990886 100644 --- a/packages/protocol-autonat/package.json +++ b/packages/protocol-autonat/package.json @@ -55,13 +55,13 @@ "@libp2p/interface-internal": "^1.0.7", "@libp2p/peer-id": "^4.0.5", "@libp2p/peer-id-factory": "^4.0.5", + "@libp2p/utils": "^5.2.2", "@multiformats/multiaddr": "^12.1.10", "it-first": "^3.0.3", "it-length-prefixed": "^9.0.3", "it-map": "^3.0.4", "it-parallel": "^3.0.6", "it-pipe": "^3.0.1", - "private-ip": "^3.0.1", "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.7" }, diff --git a/packages/protocol-autonat/src/autonat.ts b/packages/protocol-autonat/src/autonat.ts index 12d4b8a112..b55155fa6f 100644 --- a/packages/protocol-autonat/src/autonat.ts +++ b/packages/protocol-autonat/src/autonat.ts @@ -1,13 +1,13 @@ import { CodeError, ERR_TIMEOUT, setMaxListeners } from '@libp2p/interface' import { peerIdFromBytes } from '@libp2p/peer-id' import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { isPrivateIp } from '@libp2p/utils/private-ip' import { multiaddr, protocols } from '@multiformats/multiaddr' import first from 'it-first' import * as lp from 'it-length-prefixed' import map from 'it-map' import parallel from 'it-parallel' import { pipe } from 'it-pipe' -import isPrivateIp from 'private-ip' import { MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, diff --git a/packages/protocol-dcutr/package.json b/packages/protocol-dcutr/package.json index 66a002e1dc..e693d24382 100644 --- a/packages/protocol-dcutr/package.json +++ b/packages/protocol-dcutr/package.json @@ -53,11 +53,11 @@ "dependencies": { "@libp2p/interface": "^1.1.2", "@libp2p/interface-internal": "^1.0.7", + "@libp2p/utils": "^5.2.2", "@multiformats/multiaddr": "^12.1.10", "@multiformats/multiaddr-matcher": "^1.1.0", "delay": "^6.0.0", "it-protobuf-stream": "^1.1.1", - "private-ip": "^3.0.1", "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.7" }, diff --git a/packages/protocol-dcutr/src/utils.ts b/packages/protocol-dcutr/src/utils.ts index 900d0b3573..4cf30fbc83 100644 --- a/packages/protocol-dcutr/src/utils.ts +++ b/packages/protocol-dcutr/src/utils.ts @@ -1,6 +1,6 @@ +import { isPrivateIp } from '@libp2p/utils/private-ip' import { type Multiaddr } from '@multiformats/multiaddr' import { Circuit, IP, DNS } from '@multiformats/multiaddr-matcher' -import isPrivate from 'private-ip' import type { TransportManager } from '@libp2p/interface-internal' /** @@ -29,5 +29,5 @@ export function isPublicAndDialable (ma: Multiaddr, transportManager: TransportM return false } - return isPrivate(ma.toOptions().host) === false + return isPrivateIp(ma.toOptions().host) === false } diff --git a/packages/upnp-nat/package.json b/packages/upnp-nat/package.json index 5743b5f7e8..79229f4c81 100644 --- a/packages/upnp-nat/package.json +++ b/packages/upnp-nat/package.json @@ -54,7 +54,6 @@ "@libp2p/interface-internal": "^1.0.7", "@libp2p/utils": "^5.2.2", "@multiformats/multiaddr": "^12.1.10", - "private-ip": "^3.0.1", "wherearewe": "^2.0.1" }, "devDependencies": { diff --git a/packages/upnp-nat/src/upnp-nat.ts b/packages/upnp-nat/src/upnp-nat.ts index 9c40fbb531..03bcffce3d 100644 --- a/packages/upnp-nat/src/upnp-nat.ts +++ b/packages/upnp-nat/src/upnp-nat.ts @@ -1,8 +1,8 @@ import { upnpNat, type NatAPI } from '@achingbrain/nat-port-mapper' import { CodeError, ERR_INVALID_PARAMETERS } from '@libp2p/interface' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' +import { isPrivateIp } from '@libp2p/utils/private-ip' import { fromNodeAddress } from '@multiformats/multiaddr' -import isPrivateIp from 'private-ip' import { isBrowser } from 'wherearewe' import type { UPnPNATComponents, UPnPNATInit } from './index.js' import type { Logger, Startable } from '@libp2p/interface' diff --git a/packages/utils/package.json b/packages/utils/package.json index 35e9a517e4..ef1f20e9fe 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -80,6 +80,10 @@ "types": "./dist/src/peer-queue.d.ts", "import": "./dist/src/peer-queue.js" }, + "./private-ip": { + "types": "./dist/src/private-ip.d.ts", + "import": "./dist/src/private-ip.js" + }, "./queue": { "types": "./dist/src/queue/index.d.ts", "import": "./dist/src/queue/index.js" @@ -132,14 +136,15 @@ "is-loopback-addr": "^2.0.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", + "netmask": "^2.0.2", "p-defer": "^4.0.0", - "private-ip": "^3.0.1", "race-event": "^1.1.0", "race-signal": "^1.0.2", "uint8arraylist": "^2.4.7" }, "devDependencies": { "@libp2p/peer-id-factory": "^4.0.5", + "@types/netmask": "^2.0.5", "aegir": "^42.0.0", "delay": "^6.0.0", "it-all": "^3.0.3", diff --git a/packages/utils/src/multiaddr/is-private.ts b/packages/utils/src/multiaddr/is-private.ts index ab563e032c..a2667082ec 100644 --- a/packages/utils/src/multiaddr/is-private.ts +++ b/packages/utils/src/multiaddr/is-private.ts @@ -1,4 +1,4 @@ -import isIpPrivate from 'private-ip' +import { isPrivateIp } from '../private-ip.js' import type { Multiaddr } from '@multiformats/multiaddr' /** @@ -8,7 +8,7 @@ export function isPrivate (ma: Multiaddr): boolean { try { const { address } = ma.nodeAddress() - return Boolean(isIpPrivate(address)) + return Boolean(isPrivateIp(address)) } catch { return true } diff --git a/packages/utils/src/private-ip.ts b/packages/utils/src/private-ip.ts new file mode 100644 index 0000000000..66f6aa0eaf --- /dev/null +++ b/packages/utils/src/private-ip.ts @@ -0,0 +1,61 @@ +import { isIPv4, isIPv6 } from '@chainsafe/is-ip' +import { Netmask } from 'netmask' + +const PRIVATE_IP_RANGES = [ + '0.0.0.0/8', + '10.0.0.0/8', + '100.64.0.0/10', + '127.0.0.0/8', + '169.254.0.0/16', + '172.16.0.0/12', + '192.0.0.0/24', + '192.0.0.0/29', + '192.0.0.8/32', + '192.0.0.9/32', + '192.0.0.10/32', + '192.0.0.170/32', + '192.0.0.171/32', + '192.0.2.0/24', + '192.31.196.0/24', + '192.52.193.0/24', + '192.88.99.0/24', + '192.168.0.0/16', + '192.175.48.0/24', + '198.18.0.0/15', + '198.51.100.0/24', + '203.0.113.0/24', + '240.0.0.0/4', + '255.255.255.255/32' +] + +const NETMASK_RANGES = PRIVATE_IP_RANGES.map(ipRange => new Netmask(ipRange)) + +function ipv4Check (ipAddr: string): boolean { + for (const r of NETMASK_RANGES) { + if (r.contains(ipAddr)) return true + } + + return false +} + +function ipv6Check (ipAddr: string): boolean { + return /^::$/.test(ipAddr) || + /^::1$/.test(ipAddr) || + /^::f{4}:([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) || + /^::f{4}:0.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) || + /^64:ff9b::([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) || + /^100::([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/.test(ipAddr) || + /^2001::([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/.test(ipAddr) || + /^2001:2[0-9a-fA-F]:([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/.test(ipAddr) || + /^2001:db8:([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/.test(ipAddr) || + /^2002:([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/.test(ipAddr) || + /^f[c-d]([0-9a-fA-F]{2,2}):/i.test(ipAddr) || + /^fe[8-9a-bA-B][0-9a-fA-F]:/i.test(ipAddr) || + /^ff([0-9a-fA-F]{2,2}):/i.test(ipAddr) +} + +export function isPrivateIp (ip: string): boolean | undefined { + if (isIPv4(ip)) return ipv4Check(ip) + else if (isIPv6(ip)) return ipv6Check(ip) + else return undefined +} diff --git a/packages/utils/test/private-ip.spec.ts b/packages/utils/test/private-ip.spec.ts new file mode 100644 index 0000000000..dd3b63d570 --- /dev/null +++ b/packages/utils/test/private-ip.spec.ts @@ -0,0 +1,192 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import { isPrivateIp } from '../src/private-ip.js' + +describe('private-ip', function () { + const pubIps = [ + '44.37.112.180', + '46.192.247.73', + '71.12.102.112', + '101.0.26.90', + '111.211.73.40', + '156.238.194.84', + '164.101.185.82', + '223.231.138.242', + '226.84.185.150', + '227.202.96.196', + '::1fff:0.0.0.0', + '::1fff:10.0.0.0', + '::1fff:0:0.0.0.0', + '::1fff:0:10.0.0.0', + '2001:2:ffff:ffff:ffff:ffff:ffff:ffff', + '64:ff9a::0.0.0.0', + '64:ff9a::255.255.255.255', + '99::', + '99::ffff:ffff:ffff:ffff', + '101::', + '101::ffff:ffff:ffff:ffff', + '2000::', + '2000::ffff:ffff:ffff:ffff:ffff:ffff', + '2001:10::', + '2001:1f:ffff:ffff:ffff:ffff:ffff:ffff', + '2001:db7::', + '2001:db7:ffff:ffff:ffff:ffff:ffff:ffff', + '2001:db9::', + 'fb00::', + 'fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + 'fec0::' + ] + + const privIps = [ + '0.0.0.0', + '0.0.0.1', + '0.0.0.7', + '0.0.0.255', + '0.0.255.255', + '0.1.255.255', + '0.15.255.255', + '0.63.255.255', + '0.255.255.254', + '0.255.255.255', + '10.0.0.0', + '10.0.0.1', + '10.255.255.254', + '10.255.255.255', + '100.64.0.0', + '100.64.0.1', + '100.127.255.254', + '100.127.255.255', + '127.0.0.0', + '127.0.0.1', + '127.255.255.254', + '127.255.255.255', + '169.254.0.0', + '169.254.0.1', + '169.254.255.254', + '169.254.255.255', + '172.16.0.0', + '172.16.0.1', + '172.31.255.254', + '172.31.255.255', + '192.0.0.0', + '192.0.0.1', + '192.0.0.6', + '192.0.0.7', + '192.0.0.8', + '192.0.0.9', + '192.0.0.10', + '192.0.0.11', + '192.0.0.170', + '192.0.0.171', + '192.0.0.254', + '192.0.0.255', + '192.0.2.0', + '192.0.2.1', + '192.0.2.254', + '192.0.2.255', + '192.31.196.0', + '192.31.196.1', + '192.31.196.254', + '192.31.196.255', + '192.52.193.0', + '192.52.193.1', + '192.52.193.254', + '192.52.193.255', + '192.88.99.0', + '192.88.99.1', + '192.88.99.254', + '192.88.99.255', + '192.168.0.0', + '192.168.0.1', + '192.168.255.254', + '192.168.255.255', + '192.175.48.0', + '192.175.48.1', + '192.175.48.254', + '192.175.48.255', + '198.18.0.0', + '198.18.0.1', + '198.19.255.254', + '198.19.255.255', + '198.51.100.0', + '198.51.100.1', + '198.51.100.254', + '198.51.100.255', + '203.0.113.0', + '203.0.113.1', + '203.0.113.254', + '203.0.113.255', + '240.0.0.0', + '240.0.0.1', + '255.0.0.0', + '255.192.0.0', + '255.240.0.0', + '255.254.0.0', + '255.255.0.0', + '255.255.255.0', + '255.255.255.248', + '255.255.255.254', + '255.255.255.255', + '::', + '::1', + '::ffff:0.0.0.0', + '::ffff:255.255.255.255', + '64:ff9b::0.0.0.0', + '64:ff9b::16.10.11.1', + '64:ff9b::255.255.255.255', + '100::', + '100::0:0:0:0', + '100::1:eabc:0:2', + '100::ffff:ffff:ffff:ffff', + '2001::', + '2001::a:b:c', + '2001::ffff:ffff:ffff:ffff:ffff:ffff', + '2001:20::', + '2001:20::a:b:c', + '2001:2f::a:b:c', + '2001:2f:ffff:ffff:ffff:ffff:ffff:ffff', + '2001:db8::', + '2001:db8::1', + '2001:db8:abc::1', + '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff', + '2002::', + '2002::1', + '2002::abc:1', + '2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + 'fe80::', + 'fe80::1', + 'fe80::abc:1', + 'febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + 'fc00::', + 'fc00::1', + 'fc00::abc:1', + 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + 'ff00::', + 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' + ] + + const invalidIps = [ + '100::ffff::', + '::ffff:0.0.255.255.255', + '::ffff:0.255.255.255.255' + ] + + it('identifies public ips', function () { + for (const ip of pubIps) { + expect(isPrivateIp(ip)).to.be.false(`isPrivateIp('${ip}') should be false`) + } + }) + + it('identifies private ips', function () { + for (const ip of privIps) { + expect(isPrivateIp(ip)).to.be.true(`isPrivateIp('${ip}') should be true`) + } + }) + + it('identifies invalid ips', function () { + for (const ip of invalidIps) { + expect(isPrivateIp(ip)).to.be.undefined(`isPrivateIp('${ip}') should be undefined`) + } + }) +})