Skip to content

Commit 838053d

Browse files
deepview-autofixclaudeChALkeR
authored
fix(socks5): correctly expand IPv6 '::' compressed notation (#5046)
The previous implementation used `doubleColonIndex / 3` to map the character offset of '::' to a parts-array index, which only works when every group before '::' is exactly three characters wide. For typical addresses like `2001:db8::1` or `fe80::1` the zero-fill gap was never applied, producing a wrong 16-byte buffer and, in a SOCKS5 proxy context, connections to unintended destinations. Rewrite parseIPv6 to split around '::' and write the trailing groups at their correct offsets from the end of the buffer. Signed-off-by: Nikita Skovoroda <chalkerx@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Nikita Skovoroda <chalkerx@gmail.com>
1 parent e0e61d3 commit 838053d

2 files changed

Lines changed: 31 additions & 24 deletions

File tree

lib/core/socks5-utils.js

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,29 @@ function parseAddress (address) {
4646
*/
4747
function parseIPv6 (address) {
4848
const buffer = Buffer.alloc(16)
49-
const parts = address.split(':')
50-
let partIndex = 0
51-
let bufferIndex = 0
5249

5350
// Handle compressed notation (::)
5451
const doubleColonIndex = address.indexOf('::')
5552
if (doubleColonIndex !== -1) {
56-
// Count non-empty parts
57-
const nonEmptyParts = parts.filter(p => p.length > 0).length
58-
const skipParts = 8 - nonEmptyParts
59-
60-
for (let i = 0; i < parts.length; i++) {
61-
if (parts[i] === '' && i === doubleColonIndex / 3) {
62-
// Skip empty parts for ::
63-
bufferIndex += skipParts * 2
64-
} else if (parts[i] !== '') {
65-
const value = parseInt(parts[i], 16)
66-
buffer.writeUInt16BE(value, bufferIndex)
67-
bufferIndex += 2
68-
}
53+
const before = address.slice(0, doubleColonIndex)
54+
const after = address.slice(doubleColonIndex + 2)
55+
const beforeParts = before === '' ? [] : before.split(':')
56+
const afterParts = after === '' ? [] : after.split(':')
57+
58+
let bufferIndex = 0
59+
for (const part of beforeParts) {
60+
buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
61+
bufferIndex += 2
62+
}
63+
bufferIndex = 16 - afterParts.length * 2
64+
for (const part of afterParts) {
65+
buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
66+
bufferIndex += 2
6967
}
7068
} else {
71-
// No compression, parse normally
72-
for (const part of parts) {
73-
if (part === '') continue
74-
const value = parseInt(part, 16)
75-
buffer.writeUInt16BE(value, partIndex * 2)
76-
partIndex++
69+
const parts = address.split(':')
70+
for (let i = 0; i < parts.length; i++) {
71+
buffer.writeUInt16BE(parseInt(parts[i], 16), i * 2)
7772
}
7873
}
7974

test/socks5-utils.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,31 @@ test('parseAddress - Domain', async (t) => {
4848
})
4949

5050
test('parseIPv6', async (t) => {
51-
const p = tspl(t, { plan: 3 })
51+
const p = tspl(t, { plan: 9 })
5252

5353
// Test full IPv6
5454
const buffer1 = parseIPv6('2001:0db8:0000:0042:0000:8a2e:0370:7334')
5555
p.equal(buffer1.length, 16, 'should return 16-byte buffer')
56+
p.equal(buffer1.toString('hex'), '20010db80000004200008a2e03707334', 'should parse full IPv6 correctly')
5657

57-
// Test compressed IPv6
58+
// Test compressed IPv6 (zero-fill gap must land in the middle, not after `db8`)
5859
const buffer2 = parseIPv6('2001:db8::1')
5960
p.equal(buffer2.length, 16, 'should return 16-byte buffer for compressed')
61+
p.equal(buffer2.toString('hex'), '20010db8000000000000000000000001', 'should expand :: correctly for 2001:db8::1')
6062

6163
// Test loopback
6264
const buffer3 = parseIPv6('::1')
6365
p.equal(buffer3.length, 16, 'should return 16-byte buffer for loopback')
66+
p.equal(buffer3.toString('hex'), '00000000000000000000000000000001', 'should expand ::1 correctly')
67+
68+
// Test :: in the middle with short groups on both sides
69+
p.equal(parseIPv6('1::2').toString('hex'), '00010000000000000000000000000002', 'should expand 1::2 correctly')
70+
71+
// Test link-local with single group after ::
72+
p.equal(parseIPv6('fe80::1').toString('hex'), 'fe800000000000000000000000000001', 'should expand fe80::1 correctly')
73+
74+
// Test trailing ::
75+
p.equal(parseIPv6('fe80::').toString('hex'), 'fe800000000000000000000000000000', 'should expand fe80:: correctly')
6476

6577
await p.completed
6678
})

0 commit comments

Comments
 (0)