diff --git a/packages/utils/src/private-ip.ts b/packages/utils/src/private-ip.ts index 66f6aa0eaf..3b538fb09d 100644 --- a/packages/utils/src/private-ip.ts +++ b/packages/utils/src/private-ip.ts @@ -38,11 +38,45 @@ function ipv4Check (ipAddr: string): boolean { return false } +function isIpv4MappedIpv6 (ipAddr: string): boolean { + return /^::ffff:([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,4})$/.test(ipAddr) +} + +/** + * @see https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 + */ +function ipv4MappedIpv6Check (ipAddr: string): boolean { + const parts = ipAddr.split(':') + + if (parts.length < 2) { + return false + } + + const octet34 = parts[parts.length - 1].padStart(4, '0') + const octet12 = parts[parts.length - 2].padStart(4, '0') + + const ip4 = `${parseInt(octet12.substring(0, 2), 16)}.${parseInt(octet12.substring(2), 16)}.${parseInt(octet34.substring(0, 2), 16)}.${parseInt(octet34.substring(2), 16)}` + + return ipv4Check(ip4) +} + +/** + * @see https://datatracker.ietf.org/doc/html/rfc4291#section-2.2 example 3 + */ +function isIpv4EmbeddedIpv6 (ipAddr: string): boolean { + return /^::ffff:([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) +} + +function ipv4EmbeddedIpv6Check (ipAddr: string): boolean { + const parts = ipAddr.split(':') + const ip4 = parts[parts.length - 1] + + return ipv4Check(ip4) +} + 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) || @@ -56,6 +90,8 @@ function ipv6Check (ipAddr: string): boolean { export function isPrivateIp (ip: string): boolean | undefined { if (isIPv4(ip)) return ipv4Check(ip) + else if (isIpv4MappedIpv6(ip)) return ipv4MappedIpv6Check(ip) + else if (isIpv4EmbeddedIpv6(ip)) return ipv4EmbeddedIpv6Check(ip) else if (isIPv6(ip)) return ipv6Check(ip) else return undefined } diff --git a/packages/utils/test/multiaddr/is-private.spec.ts b/packages/utils/test/multiaddr/is-private.spec.ts index a5a12f6406..37ec3fc27a 100644 --- a/packages/utils/test/multiaddr/is-private.spec.ts +++ b/packages/utils/test/multiaddr/is-private.spec.ts @@ -30,16 +30,44 @@ describe('multiaddr isPrivate', () => { it('identifies private ip6 multiaddrs', () => { [ multiaddr('/ip6/fd52:8342:fc46:6c91:3ac9:86ff:fe31:7095/tcp/1000'), - multiaddr('/ip6/fd52:8342:fc46:6c91:3ac9:86ff:fe31:1/tcp/1000') + multiaddr('/ip6/fd52:8342:fc46:6c91:3ac9:86ff:fe31:1/tcp/1000'), + multiaddr('/ip6/::ffff:0a00:0001/tcp/1000'), // 10.0.0.1 + multiaddr('/ip6/::ffff:ac10:0001/tcp/1000'), // 172.16.0.1 + multiaddr('/ip6/::ffff:c0a8:0001/tcp/1000'), // 192.168.0.1 + multiaddr('/ip6/::ffff:7f00:0001/tcp/1000'), // 127.0.0.1 + multiaddr('/ip6/::ffff:10.0.0.1/tcp/1000'), + multiaddr('/ip6/::ffff:172.16.0.1/tcp/1000'), + multiaddr('/ip6/::ffff:192.168.0.1/tcp/1000'), + multiaddr('/ip6/::ffff:127.0.0.1/tcp/1000') ].forEach(ma => { - expect(isPrivate(ma)).to.eql(true) + try { + expect(isPrivate(ma)).to.eql(true) + } catch (error) { + throw new Error(`Failed for ${ma.toString()}`) + } }) }) it('identifies public ip6 multiaddrs', () => { [ multiaddr('/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000'), - multiaddr('/ip6/2000:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000') + multiaddr('/ip6/2000:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000'), + multiaddr('/ip6/::ffff:6500:1a5a/tcp/1000'), // 101.0.26.90 + multiaddr('/ip6/::ffff:2801:1409/tcp/1000'), // 40.1.20.9 + multiaddr('/ip6/::ffff:5ca8:0001/tcp/1000'), // 92.168.0.1 (not a private range) + multiaddr('/ip6/::ffff:0200:0010/tcp/1000'), // 2.16.0.1 (not a private range) + multiaddr('/ip6/::ffff:ac09:0001/tcp/1000'), // 172.15.0.1 (not a private range) + multiaddr('/ip6/::ffff:ac20:0001/tcp/1000'), // 172.32.0.1 (not a private range) + multiaddr('/ip6/::ffff:c0a7:0001/tcp/1000'), // 192.167.0.1 (not a private range) + multiaddr('/ip6/::ffff:c0a9:0001/tcp/1000'), // 192.169.0.1 (not a private range) + multiaddr('/ip6/::ffff:101.0.26.90/tcp/1000'), + multiaddr('/ip6/::ffff:40.1.20.9/tcp/1000'), + multiaddr('/ip6/::ffff:92.168.0.1/tcp/1000'), // not a private range + multiaddr('/ip6/::ffff:2.16.0.1/tcp/1000'), // not a private range + multiaddr('/ip6/::ffff:172.15.0.1/tcp/1000'), // not a private range + multiaddr('/ip6/::ffff:172.32.0.1/tcp/1000'), // not a private range + multiaddr('/ip6/::ffff:192.167.0.1/tcp/1000'), // not a private range + multiaddr('/ip6/::ffff:192.169.0.1/tcp/1000') // not a private range ].forEach(ma => { expect(isPrivate(ma)).to.eql(false) })