Skip to content

Commit

Permalink
fix(utils/ipaddr): support IPv6-mapped IPv4 address (#3727)
Browse files Browse the repository at this point in the history
  • Loading branch information
usualoma authored Dec 6, 2024
1 parent fc8fb0f commit af5affc
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/middleware/ip-restriction/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ describe('isMatchForRule', () => {
it('Static Rules', async () => {
expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '192.168.2.1')).toBeTruthy()
expect(await isMatch({ addr: '1234::5678', type: 'IPv6' }, '1234::5678')).toBeTruthy()
expect(
await isMatch({ addr: '::ffff:127.0.0.1', type: 'IPv6' }, '::ffff:127.0.0.1')
).toBeTruthy()
expect(await isMatch({ addr: '::ffff:127.0.0.1', type: 'IPv6' }, '::ffff:7f00:1')).toBeTruthy()
})
it('Function Rules', async () => {
expect(await isMatch({ addr: '0.0.0.0', type: 'IPv4' }, () => true)).toBeTruthy()
Expand Down
18 changes: 18 additions & 0 deletions src/utils/ipaddr.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
convertIPv4BinaryToString,
convertIPv4ToBinary,
convertIPv6BinaryToString,
convertIPv6ToBinary,
Expand All @@ -13,12 +14,14 @@ describe('expandIPv6', () => {
expect(expandIPv6('2001:2::')).toBe('2001:0002:0000:0000:0000:0000:0000:0000')
expect(expandIPv6('2001:2::')).toBe('2001:0002:0000:0000:0000:0000:0000:0000')
expect(expandIPv6('2001:0:0:db8::1')).toBe('2001:0000:0000:0db8:0000:0000:0000:0001')
expect(expandIPv6('::ffff:127.0.0.1')).toBe('0000:0000:0000:0000:0000:ffff:7f00:0001')
})
})
describe('distinctRemoteAddr', () => {
it('Should result be valid', () => {
expect(distinctRemoteAddr('1::1')).toBe('IPv6')
expect(distinctRemoteAddr('::1')).toBe('IPv6')
expect(distinctRemoteAddr('::ffff:127.0.0.1')).toBe('IPv6')

expect(distinctRemoteAddr('192.168.2.0')).toBe('IPv4')
expect(distinctRemoteAddr('192.168.2.0')).toBe('IPv4')
Expand All @@ -35,13 +38,27 @@ describe('convertIPv4ToBinary', () => {
expect(convertIPv4ToBinary('0.0.1.0')).toBe(1n << 8n)
})
})

describe('convertIPv4ToString', () => {
// add tons of test cases here
test.each`
input | expected
${'0.0.0.0'} | ${'0.0.0.0'}
${'0.0.0.1'} | ${'0.0.0.1'}
${'0.0.1.0'} | ${'0.0.1.0'}
`('convertIPv4ToString($input) === $expected', ({ input, expected }) => {
expect(convertIPv4BinaryToString(convertIPv4ToBinary(input))).toBe(expected)
})
})

describe('convertIPv6ToBinary', () => {
it('Should result is valid', () => {
expect(convertIPv6ToBinary('::0')).toBe(0n)
expect(convertIPv6ToBinary('::1')).toBe(1n)

expect(convertIPv6ToBinary('::f')).toBe(15n)
expect(convertIPv6ToBinary('1234:::5678')).toBe(24196103360772296748952112894165669496n)
expect(convertIPv6ToBinary('::ffff:127.0.0.1')).toBe(281472812449793n)
})
})

Expand All @@ -55,6 +72,7 @@ describe('convertIPv6ToString', () => {
${'2001:2::'} | ${'2001:2::'}
${'2001::db8:0:0:0:0:1'} | ${'2001:0:db8::1'}
${'1234:5678:9abc:def0:1234:5678:9abc:def0'} | ${'1234:5678:9abc:def0:1234:5678:9abc:def0'}
${'::ffff:127.0.0.1'} | ${'::ffff:127.0.0.1'}
`('convertIPv6ToString($input) === $expected', ({ input, expected }) => {
expect(convertIPv6BinaryToString(convertIPv6ToBinary(input))).toBe(expected)
})
Expand Down
27 changes: 27 additions & 0 deletions src/utils/ipaddr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import type { AddressType } from '../helper/conninfo'
*/
export const expandIPv6 = (ipV6: string): string => {
const sections = ipV6.split(':')
if (IPV4_REGEX.test(sections.at(-1) as string)) {
sections.splice(
-1,
1,
...convertIPv6BinaryToString(convertIPv4ToBinary(sections.at(-1) as string)) // => ::7f00:0001
.substring(2) // => 7f00:0001
.split(':') // => ['7f00', '0001']
)
}
for (let i = 0; i < sections.length; i++) {
const node = sections[i]
if (node !== '') {
Expand Down Expand Up @@ -70,12 +79,30 @@ export const convertIPv6ToBinary = (ipv6: string): bigint => {
return result
}

/**
* Convert a binary representation of an IPv4 address to a string.
* @param ipV4 binary IPv4 Address
* @return IPv4 Address in string
*/
export const convertIPv4BinaryToString = (ipV4: bigint): string => {
const sections = []
for (let i = 0; i < 4; i++) {
sections.push((ipV4 >> BigInt(8 * (3 - i))) & 0xffn)
}
return sections.join('.')
}

/**
* Convert a binary representation of an IPv6 address to a string.
* @param ipV6 binary IPv6 Address
* @return normalized IPv6 Address in string
*/
export const convertIPv6BinaryToString = (ipV6: bigint): string => {
// IPv6-mapped IPv4 address
if (ipV6 >> 32n === 0xffffn) {
return `::ffff:${convertIPv4BinaryToString(ipV6 & 0xffffffffn)}`
}

const sections = []
for (let i = 0; i < 8; i++) {
sections.push(((ipV6 >> BigInt(16 * (7 - i))) & 0xffffn).toString(16))
Expand Down

0 comments on commit af5affc

Please sign in to comment.