Skip to content

Commit af5affc

Browse files
authored
fix(utils/ipaddr): support IPv6-mapped IPv4 address (#3727)
1 parent fc8fb0f commit af5affc

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

src/middleware/ip-restriction/index.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ describe('isMatchForRule', () => {
100100
it('Static Rules', async () => {
101101
expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '192.168.2.1')).toBeTruthy()
102102
expect(await isMatch({ addr: '1234::5678', type: 'IPv6' }, '1234::5678')).toBeTruthy()
103+
expect(
104+
await isMatch({ addr: '::ffff:127.0.0.1', type: 'IPv6' }, '::ffff:127.0.0.1')
105+
).toBeTruthy()
106+
expect(await isMatch({ addr: '::ffff:127.0.0.1', type: 'IPv6' }, '::ffff:7f00:1')).toBeTruthy()
103107
})
104108
it('Function Rules', async () => {
105109
expect(await isMatch({ addr: '0.0.0.0', type: 'IPv4' }, () => true)).toBeTruthy()

src/utils/ipaddr.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
convertIPv4BinaryToString,
23
convertIPv4ToBinary,
34
convertIPv6BinaryToString,
45
convertIPv6ToBinary,
@@ -13,12 +14,14 @@ describe('expandIPv6', () => {
1314
expect(expandIPv6('2001:2::')).toBe('2001:0002:0000:0000:0000:0000:0000:0000')
1415
expect(expandIPv6('2001:2::')).toBe('2001:0002:0000:0000:0000:0000:0000:0000')
1516
expect(expandIPv6('2001:0:0:db8::1')).toBe('2001:0000:0000:0db8:0000:0000:0000:0001')
17+
expect(expandIPv6('::ffff:127.0.0.1')).toBe('0000:0000:0000:0000:0000:ffff:7f00:0001')
1618
})
1719
})
1820
describe('distinctRemoteAddr', () => {
1921
it('Should result be valid', () => {
2022
expect(distinctRemoteAddr('1::1')).toBe('IPv6')
2123
expect(distinctRemoteAddr('::1')).toBe('IPv6')
24+
expect(distinctRemoteAddr('::ffff:127.0.0.1')).toBe('IPv6')
2225

2326
expect(distinctRemoteAddr('192.168.2.0')).toBe('IPv4')
2427
expect(distinctRemoteAddr('192.168.2.0')).toBe('IPv4')
@@ -35,13 +38,27 @@ describe('convertIPv4ToBinary', () => {
3538
expect(convertIPv4ToBinary('0.0.1.0')).toBe(1n << 8n)
3639
})
3740
})
41+
42+
describe('convertIPv4ToString', () => {
43+
// add tons of test cases here
44+
test.each`
45+
input | expected
46+
${'0.0.0.0'} | ${'0.0.0.0'}
47+
${'0.0.0.1'} | ${'0.0.0.1'}
48+
${'0.0.1.0'} | ${'0.0.1.0'}
49+
`('convertIPv4ToString($input) === $expected', ({ input, expected }) => {
50+
expect(convertIPv4BinaryToString(convertIPv4ToBinary(input))).toBe(expected)
51+
})
52+
})
53+
3854
describe('convertIPv6ToBinary', () => {
3955
it('Should result is valid', () => {
4056
expect(convertIPv6ToBinary('::0')).toBe(0n)
4157
expect(convertIPv6ToBinary('::1')).toBe(1n)
4258

4359
expect(convertIPv6ToBinary('::f')).toBe(15n)
4460
expect(convertIPv6ToBinary('1234:::5678')).toBe(24196103360772296748952112894165669496n)
61+
expect(convertIPv6ToBinary('::ffff:127.0.0.1')).toBe(281472812449793n)
4562
})
4663
})
4764

@@ -55,6 +72,7 @@ describe('convertIPv6ToString', () => {
5572
${'2001:2::'} | ${'2001:2::'}
5673
${'2001::db8:0:0:0:0:1'} | ${'2001:0:db8::1'}
5774
${'1234:5678:9abc:def0:1234:5678:9abc:def0'} | ${'1234:5678:9abc:def0:1234:5678:9abc:def0'}
75+
${'::ffff:127.0.0.1'} | ${'::ffff:127.0.0.1'}
5876
`('convertIPv6ToString($input) === $expected', ({ input, expected }) => {
5977
expect(convertIPv6BinaryToString(convertIPv6ToBinary(input))).toBe(expected)
6078
})

src/utils/ipaddr.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ import type { AddressType } from '../helper/conninfo'
1212
*/
1313
export const expandIPv6 = (ipV6: string): string => {
1414
const sections = ipV6.split(':')
15+
if (IPV4_REGEX.test(sections.at(-1) as string)) {
16+
sections.splice(
17+
-1,
18+
1,
19+
...convertIPv6BinaryToString(convertIPv4ToBinary(sections.at(-1) as string)) // => ::7f00:0001
20+
.substring(2) // => 7f00:0001
21+
.split(':') // => ['7f00', '0001']
22+
)
23+
}
1524
for (let i = 0; i < sections.length; i++) {
1625
const node = sections[i]
1726
if (node !== '') {
@@ -70,12 +79,30 @@ export const convertIPv6ToBinary = (ipv6: string): bigint => {
7079
return result
7180
}
7281

82+
/**
83+
* Convert a binary representation of an IPv4 address to a string.
84+
* @param ipV4 binary IPv4 Address
85+
* @return IPv4 Address in string
86+
*/
87+
export const convertIPv4BinaryToString = (ipV4: bigint): string => {
88+
const sections = []
89+
for (let i = 0; i < 4; i++) {
90+
sections.push((ipV4 >> BigInt(8 * (3 - i))) & 0xffn)
91+
}
92+
return sections.join('.')
93+
}
94+
7395
/**
7496
* Convert a binary representation of an IPv6 address to a string.
7597
* @param ipV6 binary IPv6 Address
7698
* @return normalized IPv6 Address in string
7799
*/
78100
export const convertIPv6BinaryToString = (ipV6: bigint): string => {
101+
// IPv6-mapped IPv4 address
102+
if (ipV6 >> 32n === 0xffffn) {
103+
return `::ffff:${convertIPv4BinaryToString(ipV6 & 0xffffffffn)}`
104+
}
105+
79106
const sections = []
80107
for (let i = 0; i < 8; i++) {
81108
sections.push(((ipV6 >> BigInt(16 * (7 - i))) & 0xffffn).toString(16))

0 commit comments

Comments
 (0)