File size: 6,063 Bytes
3401f26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
'use strict'

const { Buffer } = require('node:buffer')
const net = require('node:net')
const { InvalidArgumentError } = require('./errors')

/**
 * Parse an address and determine its type
 * @param {string} address - The address to parse
 * @returns {{type: number, buffer: Buffer}} Address type and buffer
 */
function parseAddress (address) {
  // Check if it's an IPv4 address
  if (net.isIPv4(address)) {
    const parts = address.split('.').map(Number)
    return {
      type: 0x01, // IPv4
      buffer: Buffer.from(parts)
    }
  }

  // Check if it's an IPv6 address
  if (net.isIPv6(address)) {
    return {
      type: 0x04, // IPv6
      buffer: parseIPv6(address)
    }
  }

  // Otherwise, treat as domain name
  const domainBuffer = Buffer.from(address, 'utf8')
  if (domainBuffer.length > 255) {
    throw new InvalidArgumentError('Domain name too long (max 255 bytes)')
  }

  return {
    type: 0x03, // Domain
    buffer: Buffer.concat([Buffer.from([domainBuffer.length]), domainBuffer])
  }
}

/**
 * Parse IPv6 address to buffer
 * @param {string} address - IPv6 address string
 * @returns {Buffer} 16-byte buffer
 */
function parseIPv6 (address) {
  const buffer = Buffer.alloc(16)
  let normalizedAddress = address

  // Expand an embedded IPv4 tail into the last two IPv6 groups.
  if (address.includes('.')) {
    const lastColonIndex = address.lastIndexOf(':')
    const ipv4Part = address.slice(lastColonIndex + 1)

    if (net.isIPv4(ipv4Part)) {
      const octets = ipv4Part.split('.').map(Number)
      const high = ((octets[0] << 8) | octets[1]).toString(16)
      const low = ((octets[2] << 8) | octets[3]).toString(16)
      normalizedAddress = `${address.slice(0, lastColonIndex)}:${high}:${low}`
    }
  }

  // Handle compressed notation (::)
  const doubleColonIndex = normalizedAddress.indexOf('::')
  if (doubleColonIndex !== -1) {
    const before = normalizedAddress.slice(0, doubleColonIndex)
    const after = normalizedAddress.slice(doubleColonIndex + 2)
    const beforeParts = before === '' ? [] : before.split(':')
    const afterParts = after === '' ? [] : after.split(':')

    let bufferIndex = 0
    for (const part of beforeParts) {
      buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
      bufferIndex += 2
    }
    bufferIndex = 16 - afterParts.length * 2
    for (const part of afterParts) {
      buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
      bufferIndex += 2
    }
  } else {
    const parts = normalizedAddress.split(':')
    for (let i = 0; i < parts.length; i++) {
      buffer.writeUInt16BE(parseInt(parts[i], 16), i * 2)
    }
  }

  return buffer
}

/**
 * Build a SOCKS5 address buffer
 * @param {number} type - Address type (1=IPv4, 3=Domain, 4=IPv6)
 * @param {Buffer} addressBuffer - The address data
 * @param {number} port - Port number
 * @returns {Buffer} Complete address buffer including type, address, and port
 */
function buildAddressBuffer (type, addressBuffer, port) {
  const portBuffer = Buffer.allocUnsafe(2)
  portBuffer.writeUInt16BE(port, 0)

  return Buffer.concat([
    Buffer.from([type]),
    addressBuffer,
    portBuffer
  ])
}

/**
 * Parse address from SOCKS5 response
 * @param {Buffer} buffer - Buffer containing the address
 * @param {number} offset - Starting offset in buffer
 * @returns {{address: string, port: number, bytesRead: number}}
 */
function parseResponseAddress (buffer, offset = 0) {
  if (buffer.length < offset + 1) {
    throw new InvalidArgumentError('Buffer too small to contain address type')
  }

  const addressType = buffer[offset]
  let address
  let currentOffset = offset + 1

  switch (addressType) {
    case 0x01: { // IPv4
      if (buffer.length < currentOffset + 6) {
        throw new InvalidArgumentError('Buffer too small for IPv4 address')
      }
      address = Array.from(buffer.subarray(currentOffset, currentOffset + 4)).join('.')
      currentOffset += 4
      break
    }

    case 0x03: { // Domain
      if (buffer.length < currentOffset + 1) {
        throw new InvalidArgumentError('Buffer too small for domain length')
      }
      const domainLength = buffer[currentOffset]
      currentOffset += 1

      if (buffer.length < currentOffset + domainLength + 2) {
        throw new InvalidArgumentError('Buffer too small for domain address')
      }
      address = buffer.subarray(currentOffset, currentOffset + domainLength).toString('utf8')
      currentOffset += domainLength
      break
    }

    case 0x04: { // IPv6
      if (buffer.length < currentOffset + 18) {
        throw new InvalidArgumentError('Buffer too small for IPv6 address')
      }
      // Convert buffer to IPv6 string
      const parts = []
      for (let i = 0; i < 8; i++) {
        const value = buffer.readUInt16BE(currentOffset + i * 2)
        parts.push(value.toString(16))
      }
      address = parts.join(':')
      currentOffset += 16
      break
    }

    default:
      throw new InvalidArgumentError(`Invalid address type: ${addressType}`)
  }

  // Parse port
  if (buffer.length < currentOffset + 2) {
    throw new InvalidArgumentError('Buffer too small for port')
  }
  const port = buffer.readUInt16BE(currentOffset)
  currentOffset += 2

  return {
    address,
    port,
    bytesRead: currentOffset - offset
  }
}

/**
 * Create error for SOCKS5 reply code
 * @param {number} replyCode - SOCKS5 reply code
 * @returns {Error} Appropriate error object
 */
function createReplyError (replyCode) {
  const messages = {
    0x01: 'General SOCKS server failure',
    0x02: 'Connection not allowed by ruleset',
    0x03: 'Network unreachable',
    0x04: 'Host unreachable',
    0x05: 'Connection refused',
    0x06: 'TTL expired',
    0x07: 'Command not supported',
    0x08: 'Address type not supported'
  }

  const message = messages[replyCode] || `Unknown SOCKS5 error code: ${replyCode}`
  const error = new Error(message)
  error.code = `SOCKS5_${replyCode}`
  return error
}

module.exports = {
  parseAddress,
  parseIPv6,
  buildAddressBuffer,
  parseResponseAddress,
  createReplyError
}