diff --git a/src/middleware/ip-restriction/index.test.ts b/src/middleware/ip-restriction/index.test.ts index 602af2449..6c9530cef 100644 --- a/src/middleware/ip-restriction/index.test.ts +++ b/src/middleware/ip-restriction/index.test.ts @@ -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() diff --git a/src/utils/ipaddr.test.ts b/src/utils/ipaddr.test.ts index b8598793b..be76efe14 100644 --- a/src/utils/ipaddr.test.ts +++ b/src/utils/ipaddr.test.ts @@ -1,4 +1,5 @@ import { + convertIPv4BinaryToString, convertIPv4ToBinary, convertIPv6BinaryToString, convertIPv6ToBinary, @@ -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') @@ -35,6 +38,19 @@ 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) @@ -42,6 +58,7 @@ describe('convertIPv6ToBinary', () => { expect(convertIPv6ToBinary('::f')).toBe(15n) expect(convertIPv6ToBinary('1234:::5678')).toBe(24196103360772296748952112894165669496n) + expect(convertIPv6ToBinary('::ffff:127.0.0.1')).toBe(281472812449793n) }) }) @@ -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) }) diff --git a/src/utils/ipaddr.ts b/src/utils/ipaddr.ts index 925a98f50..1ad47c2bd 100644 --- a/src/utils/ipaddr.ts +++ b/src/utils/ipaddr.ts @@ -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 !== '') { @@ -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))