Skip to content

Commit a82ff82

Browse files
fix: add ipv6Check regex for private address (#2624)
Improves the handling of IPv6 addresses when checking for private addresses. Fixes #2623 --------- Co-authored-by: Alex Potsides <[email protected]>
1 parent 34cf1f7 commit a82ff82

File tree

2 files changed

+69
-5
lines changed

2 files changed

+69
-5
lines changed

packages/utils/src/private-ip.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,45 @@ function ipv4Check (ipAddr: string): boolean {
3838
return false
3939
}
4040

41+
function isIpv4MappedIpv6 (ipAddr: string): boolean {
42+
return /^::ffff:([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,4})$/.test(ipAddr)
43+
}
44+
45+
/**
46+
* @see https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2
47+
*/
48+
function ipv4MappedIpv6Check (ipAddr: string): boolean {
49+
const parts = ipAddr.split(':')
50+
51+
if (parts.length < 2) {
52+
return false
53+
}
54+
55+
const octet34 = parts[parts.length - 1].padStart(4, '0')
56+
const octet12 = parts[parts.length - 2].padStart(4, '0')
57+
58+
const ip4 = `${parseInt(octet12.substring(0, 2), 16)}.${parseInt(octet12.substring(2), 16)}.${parseInt(octet34.substring(0, 2), 16)}.${parseInt(octet34.substring(2), 16)}`
59+
60+
return ipv4Check(ip4)
61+
}
62+
63+
/**
64+
* @see https://datatracker.ietf.org/doc/html/rfc4291#section-2.2 example 3
65+
*/
66+
function isIpv4EmbeddedIpv6 (ipAddr: string): boolean {
67+
return /^::ffff:([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr)
68+
}
69+
70+
function ipv4EmbeddedIpv6Check (ipAddr: string): boolean {
71+
const parts = ipAddr.split(':')
72+
const ip4 = parts[parts.length - 1]
73+
74+
return ipv4Check(ip4)
75+
}
76+
4177
function ipv6Check (ipAddr: string): boolean {
4278
return /^::$/.test(ipAddr) ||
4379
/^::1$/.test(ipAddr) ||
44-
/^::f{4}:([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) ||
45-
/^::f{4}:0.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) ||
4680
/^64:ff9b::([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.test(ipAddr) ||
4781
/^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) ||
4882
/^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 {
5690

5791
export function isPrivateIp (ip: string): boolean | undefined {
5892
if (isIPv4(ip)) return ipv4Check(ip)
93+
else if (isIpv4MappedIpv6(ip)) return ipv4MappedIpv6Check(ip)
94+
else if (isIpv4EmbeddedIpv6(ip)) return ipv4EmbeddedIpv6Check(ip)
5995
else if (isIPv6(ip)) return ipv6Check(ip)
6096
else return undefined
6197
}

packages/utils/test/multiaddr/is-private.spec.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,44 @@ describe('multiaddr isPrivate', () => {
3030
it('identifies private ip6 multiaddrs', () => {
3131
[
3232
multiaddr('/ip6/fd52:8342:fc46:6c91:3ac9:86ff:fe31:7095/tcp/1000'),
33-
multiaddr('/ip6/fd52:8342:fc46:6c91:3ac9:86ff:fe31:1/tcp/1000')
33+
multiaddr('/ip6/fd52:8342:fc46:6c91:3ac9:86ff:fe31:1/tcp/1000'),
34+
multiaddr('/ip6/::ffff:0a00:0001/tcp/1000'), // 10.0.0.1
35+
multiaddr('/ip6/::ffff:ac10:0001/tcp/1000'), // 172.16.0.1
36+
multiaddr('/ip6/::ffff:c0a8:0001/tcp/1000'), // 192.168.0.1
37+
multiaddr('/ip6/::ffff:7f00:0001/tcp/1000'), // 127.0.0.1
38+
multiaddr('/ip6/::ffff:10.0.0.1/tcp/1000'),
39+
multiaddr('/ip6/::ffff:172.16.0.1/tcp/1000'),
40+
multiaddr('/ip6/::ffff:192.168.0.1/tcp/1000'),
41+
multiaddr('/ip6/::ffff:127.0.0.1/tcp/1000')
3442
].forEach(ma => {
35-
expect(isPrivate(ma)).to.eql(true)
43+
try {
44+
expect(isPrivate(ma)).to.eql(true)
45+
} catch (error) {
46+
throw new Error(`Failed for ${ma.toString()}`)
47+
}
3648
})
3749
})
3850

3951
it('identifies public ip6 multiaddrs', () => {
4052
[
4153
multiaddr('/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000'),
42-
multiaddr('/ip6/2000:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000')
54+
multiaddr('/ip6/2000:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/1000'),
55+
multiaddr('/ip6/::ffff:6500:1a5a/tcp/1000'), // 101.0.26.90
56+
multiaddr('/ip6/::ffff:2801:1409/tcp/1000'), // 40.1.20.9
57+
multiaddr('/ip6/::ffff:5ca8:0001/tcp/1000'), // 92.168.0.1 (not a private range)
58+
multiaddr('/ip6/::ffff:0200:0010/tcp/1000'), // 2.16.0.1 (not a private range)
59+
multiaddr('/ip6/::ffff:ac09:0001/tcp/1000'), // 172.15.0.1 (not a private range)
60+
multiaddr('/ip6/::ffff:ac20:0001/tcp/1000'), // 172.32.0.1 (not a private range)
61+
multiaddr('/ip6/::ffff:c0a7:0001/tcp/1000'), // 192.167.0.1 (not a private range)
62+
multiaddr('/ip6/::ffff:c0a9:0001/tcp/1000'), // 192.169.0.1 (not a private range)
63+
multiaddr('/ip6/::ffff:101.0.26.90/tcp/1000'),
64+
multiaddr('/ip6/::ffff:40.1.20.9/tcp/1000'),
65+
multiaddr('/ip6/::ffff:92.168.0.1/tcp/1000'), // not a private range
66+
multiaddr('/ip6/::ffff:2.16.0.1/tcp/1000'), // not a private range
67+
multiaddr('/ip6/::ffff:172.15.0.1/tcp/1000'), // not a private range
68+
multiaddr('/ip6/::ffff:172.32.0.1/tcp/1000'), // not a private range
69+
multiaddr('/ip6/::ffff:192.167.0.1/tcp/1000'), // not a private range
70+
multiaddr('/ip6/::ffff:192.169.0.1/tcp/1000') // not a private range
4371
].forEach(ma => {
4472
expect(isPrivate(ma)).to.eql(false)
4573
})

0 commit comments

Comments
 (0)