├── .gitignore ├── examples ├── 2-user-pass.js └── 1-normal.js ├── utils ├── buf.js ├── ipv6.js └── ipv4.js ├── node-socks5.js ├── .eslintrc.json ├── package.json ├── README.md ├── test └── ipv6.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .history 3 | -------------------------------------------------------------------------------- /examples/2-user-pass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const socks5 = require('..'); 4 | 5 | const users = { 6 | 'user': 'password', 7 | 'admin': '123456', 8 | }; 9 | 10 | const userPassAuthFn = (user, password) => { 11 | if (users[user] === password) return true; 12 | return false; 13 | }; 14 | 15 | const server = socks5.createServer({ 16 | userPassAuthFn, 17 | }); 18 | server.listen(1080); 19 | -------------------------------------------------------------------------------- /utils/buf.js: -------------------------------------------------------------------------------- 1 | function numberToBuffer(num, len = 2, byteOrder = 0) { 2 | if (len < 1) { 3 | throw Error('len must be greater than 0'); 4 | } 5 | 6 | const buf = Buffer.alloc(len); 7 | 8 | if (byteOrder === 0) { 9 | buf.writeUIntBE(num, 0, len); 10 | } else { 11 | buf.writeUIntLE(num, 0, len); 12 | } 13 | 14 | return buf; 15 | } 16 | exports.numberToBuffer = numberToBuffer; 17 | -------------------------------------------------------------------------------- /examples/1-normal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const socks5 = require('..'); 4 | 5 | const server = socks5.createServer({ 6 | logger: { 7 | // debug: function() {}, /* Disable debug messages */ 8 | debug: console.debug, 9 | info: console.info, 10 | warn: console.warn, 11 | error: console.error, 12 | }, 13 | // localAddress: "192.168.0.100", /* Local Interface address */ 14 | dns: '8.8.8.8', /* use Specific DNS */ 15 | }); 16 | 17 | server.listen(1080); 18 | -------------------------------------------------------------------------------- /node-socks5.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const socks5 = require('./index'); 6 | 7 | const argv = process.argv.slice(2); 8 | 9 | if (argv[0] === '-h' || argv[0] === '--help') { 10 | console.log('Socks5 server, default list port at 1080. You can use -p or --port to change listen port.'); 11 | process.exit(); 12 | } 13 | 14 | let port = 1080; 15 | if (argv[0] === '-p' || argv[0] === '--port') { 16 | if (argv[1] && parseInt(argv[1]) > 0) { 17 | port = parseInt(argv[1]); 18 | } 19 | } 20 | 21 | const server = socks5.createServer(); 22 | server.listen(port, () => { 23 | console.log(`socks5 server listen at ${port}`); 24 | }); 25 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018 14 | }, 15 | "rules": { 16 | "indent": [ 17 | "error", 18 | 2 19 | ], 20 | "linebreak-style": [ 21 | "error", 22 | "unix" 23 | ], 24 | "quotes": [ 25 | "error", 26 | "single" 27 | ], 28 | "semi": [ 29 | "error", 30 | "always" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-socks5-server", 3 | "version": "1.1.0", 4 | "description": "Socks5 server implements with nodejs", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test/ipv6.js" 8 | }, 9 | "bin": { 10 | "node-socks5": "./node-socks5.js" 11 | }, 12 | "author": "liqiang", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "eslint": "^7.32.0" 16 | }, 17 | "directories": { 18 | "example": "examples" 19 | }, 20 | "dependencies": {}, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/lqqyt2423/node-socks5-server.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/lqqyt2423/node-socks5-server/issues" 27 | }, 28 | "homepage": "https://github.com/lqqyt2423/node-socks5-server#readme" 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-socks5-server 2 | 3 | Provides the `socks5` package that implements a [SOCKS5 server](http://en.wikipedia.org/wiki/SOCKS). 4 | SOCKS (Secure Sockets) is used to route traffic between a client and server through 5 | an intermediate proxy layer. This can be used to bypass firewalls or NATs. 6 | 7 | ## Features 8 | 9 | - "No Auth" mode 10 | - User/Password authentication 11 | - Support for the CONNECT command 12 | - Support UDP 13 | - set localAddress interface 14 | - use specific DNS server 15 | 16 | ## Usage for command 17 | 18 | ### Install global 19 | 20 | ``` 21 | npm i -g node-socks5-server 22 | ``` 23 | 24 | ### Startup 25 | 26 | ``` 27 | node-socks5 28 | ``` 29 | 30 | ## Usage for package 31 | 32 | ### Install in your project 33 | 34 | ``` 35 | npm i node-socks5-server 36 | ``` 37 | 38 | ### Require 39 | 40 | Below is a simple example of usage. Go examples folder see more. 41 | 42 | ```javascript 43 | const socks5 = require('node-socks5-server'); 44 | 45 | const server = socks5.createServer(); 46 | server.listen(1080); 47 | ``` 48 | 49 | ## Test with curl 50 | 51 | ```bash 52 | curl http://www.baidu.com/ --socks5 localhost:1080 53 | curl http://www.baidu.com/ --socks5-hostname localhost:1080 54 | curl http://www.baidu.com/ --socks5 user:password@localhost:1080 55 | ``` 56 | 57 | ## TODO 58 | 59 | - bind 60 | 61 | ## Thanks 62 | 63 | - [socks](https://zh.wikipedia.org/wiki/SOCKS) 64 | - [rfc1928](https://tools.ietf.org/html/rfc1928) 65 | - [rfc1929](https://tools.ietf.org/html/rfc1929) 66 | - [go-socks](https://github.com/armon/go-socks5) 67 | -------------------------------------------------------------------------------- /test/ipv6.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const ipv6 = require('../utils/ipv6'); 5 | 6 | assert.deepEqual(ipv6.toBufArr('::'), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); 7 | assert.deepEqual(ipv6.toBufArr('::1'), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); 8 | assert.deepEqual( 9 | ipv6.toBufArr('2001:0db8:85a3:08d3:1319:8a2e:0370:7344'), 10 | [0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x08, 0xd3, 0x13, 0x19, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x44] 11 | ); 12 | assert.deepEqual( 13 | ipv6.toBufArr('2001:DB8:2de:0:0:0:0:e13'), 14 | [0x20, 0x01, 0x0D, 0xB8, 0x02, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x13] 15 | ); 16 | assert.deepEqual( 17 | ipv6.toBufArr('2001:DB8:2de::e13'), 18 | [0x20, 0x01, 0x0D, 0xB8, 0x02, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x13] 19 | ); 20 | assert.deepEqual(ipv6.toBufArr('::ffff:1.2.3.4'), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 1, 2, 3, 4]); 21 | 22 | assert.deepEqual( 23 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 24 | ipv6.toBufArr(ipv6.toStr([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) 25 | ); 26 | assert.deepEqual( 27 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 28 | ipv6.toBufArr(ipv6.toStr([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])) 29 | ); 30 | assert.deepEqual( 31 | [0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x08, 0xd3, 0x13, 0x19, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x44], 32 | ipv6.toBufArr(ipv6.toStr([0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x08, 0xd3, 0x13, 0x19, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x44])) 33 | ); 34 | assert.deepEqual( 35 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 1, 2, 3, 4], 36 | ipv6.toBufArr(ipv6.toStr([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 1, 2, 3, 4])) 37 | ); 38 | 39 | console.log('ipv6.js all test passed!'); 40 | -------------------------------------------------------------------------------- /utils/ipv6.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const assert = require('assert'); 5 | 6 | function toBufArr(addr) { 7 | const bufArr = new Array(16).fill(0); 8 | if (!net.isIPv6(addr)) return bufArr; 9 | 10 | let index = 0; 11 | let dColonIndex = -1; 12 | let ipv4Index = -1; 13 | let groupStr = ''; 14 | 15 | for (let i = 0, len = addr.length; i < len; i++) { 16 | // 58 : 17 | if (addr.charCodeAt(i) === 58) { 18 | if (groupStr) { 19 | const byte2 = parseInt(groupStr, 16); 20 | bufArr[index++] = byte2 >> 8; 21 | bufArr[index++] = byte2 & 0xff; 22 | groupStr = ''; 23 | } 24 | 25 | if (addr.charCodeAt(i + 1) === 58) { 26 | dColonIndex = index; 27 | i++; 28 | } 29 | } 30 | 31 | // 46 . 32 | else if (addr.charCodeAt(i) === 46) { 33 | if (ipv4Index === -1) ipv4Index = index; 34 | if (groupStr) { 35 | const byte1 = parseInt(groupStr); 36 | bufArr[index++] = byte1; 37 | groupStr = ''; 38 | } 39 | } else { 40 | groupStr += addr[i]; 41 | } 42 | } 43 | 44 | if (groupStr) { 45 | if (ipv4Index > -1) { 46 | const byte1 = parseInt(groupStr); 47 | bufArr[index++] = byte1; 48 | } else { 49 | const byte2 = parseInt(groupStr, 16); 50 | bufArr[index++] = byte2 >> 8; 51 | bufArr[index++] = byte2 & 0xff; 52 | } 53 | groupStr = ''; 54 | } 55 | 56 | if (dColonIndex > -1) { 57 | const offset = 16 - index; 58 | for (let i = index - 1; i >= dColonIndex; i--) { 59 | bufArr[i + offset] = bufArr[i]; 60 | bufArr[i] = 0x00; 61 | } 62 | } 63 | 64 | return bufArr; 65 | } 66 | 67 | function toStr(buf) { 68 | assert(buf.length === 16); 69 | 70 | const dwArr = []; 71 | for (let i = 0; i < 16; i += 2) { 72 | const dw = (buf[i] << 8) | buf[i + 1]; 73 | dwArr.push(dw.toString(16)); 74 | } 75 | return dwArr.join(':'); 76 | } 77 | 78 | exports.toBufArr = toBufArr; 79 | exports.toStr = toStr; 80 | -------------------------------------------------------------------------------- /utils/ipv4.js: -------------------------------------------------------------------------------- 1 | const ip = exports; 2 | const { Buffer } = require('buffer'); 3 | const os = require('os'); 4 | 5 | ip.toBuffer = function (ip, buff, offset) { 6 | offset = ~~offset; 7 | 8 | let result; 9 | 10 | if (this.isV4Format(ip)) { 11 | result = buff || Buffer.alloc(offset + 4); 12 | ip.split(/\./g).map((byte) => { 13 | result[offset++] = parseInt(byte, 10) & 0xff; 14 | }); 15 | } else if (this.isV6Format(ip)) { 16 | const sections = ip.split(':', 8); 17 | 18 | let i; 19 | for (i = 0; i < sections.length; i++) { 20 | const isv4 = this.isV4Format(sections[i]); 21 | let v4Buffer; 22 | 23 | if (isv4) { 24 | v4Buffer = this.toBuffer(sections[i]); 25 | sections[i] = v4Buffer.slice(0, 2).toString('hex'); 26 | } 27 | 28 | if (v4Buffer && ++i < 8) { 29 | sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex')); 30 | } 31 | } 32 | 33 | if (sections[0] === '') { 34 | while (sections.length < 8) sections.unshift('0'); 35 | } else if (sections[sections.length - 1] === '') { 36 | while (sections.length < 8) sections.push('0'); 37 | } else if (sections.length < 8) { 38 | for (i = 0; i < sections.length && sections[i] !== ''; i++); 39 | const argv = [i, 1]; 40 | for (i = 9 - sections.length; i > 0; i--) { 41 | argv.push('0'); 42 | } 43 | sections.splice(...argv); 44 | } 45 | 46 | result = buff || Buffer.alloc(offset + 16); 47 | for (i = 0; i < sections.length; i++) { 48 | const word = parseInt(sections[i], 16); 49 | result[offset++] = (word >> 8) & 0xff; 50 | result[offset++] = word & 0xff; 51 | } 52 | } 53 | 54 | if (!result) { 55 | throw Error(`Invalid ip address: ${ip}`); 56 | } 57 | 58 | return result; 59 | }; 60 | 61 | ip.toString = function (buff, offset, length) { 62 | offset = ~~offset; 63 | length = length || buff.length - offset; 64 | 65 | let result = []; 66 | if (length === 4) { 67 | // IPv4 68 | for (let i = 0; i < length; i++) { 69 | result.push(buff[offset + i]); 70 | } 71 | result = result.join('.'); 72 | } else if (length === 16) { 73 | // IPv6 74 | for (let i = 0; i < length; i += 2) { 75 | result.push(buff.readUInt16BE(offset + i).toString(16)); 76 | } 77 | result = result.join(':'); 78 | result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3'); 79 | result = result.replace(/:{3,4}/, '::'); 80 | } 81 | 82 | return result; 83 | }; 84 | 85 | const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/; 86 | const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; 87 | 88 | ip.isV4Format = function (ip) { 89 | return ipv4Regex.test(ip); 90 | }; 91 | 92 | ip.isV6Format = function (ip) { 93 | return ipv6Regex.test(ip); 94 | }; 95 | 96 | function _normalizeFamily(family) { 97 | if (family === 4) { 98 | return 'ipv4'; 99 | } 100 | if (family === 6) { 101 | return 'ipv6'; 102 | } 103 | return family ? family.toLowerCase() : 'ipv4'; 104 | } 105 | 106 | ip.fromPrefixLen = function (prefixlen, family) { 107 | if (prefixlen > 32) { 108 | family = 'ipv6'; 109 | } else { 110 | family = _normalizeFamily(family); 111 | } 112 | 113 | let len = 4; 114 | if (family === 'ipv6') { 115 | len = 16; 116 | } 117 | const buff = Buffer.alloc(len); 118 | 119 | for (let i = 0, n = buff.length; i < n; ++i) { 120 | let bits = 8; 121 | if (prefixlen < 8) { 122 | bits = prefixlen; 123 | } 124 | prefixlen -= bits; 125 | 126 | buff[i] = ~(0xff >> bits) & 0xff; 127 | } 128 | 129 | return ip.toString(buff); 130 | }; 131 | 132 | ip.mask = function (addr, mask) { 133 | addr = ip.toBuffer(addr); 134 | mask = ip.toBuffer(mask); 135 | 136 | const result = Buffer.alloc(Math.max(addr.length, mask.length)); 137 | 138 | // Same protocol - do bitwise and 139 | let i; 140 | if (addr.length === mask.length) { 141 | for (i = 0; i < addr.length; i++) { 142 | result[i] = addr[i] & mask[i]; 143 | } 144 | } else if (mask.length === 4) { 145 | // IPv6 address and IPv4 mask 146 | // (Mask low bits) 147 | for (i = 0; i < mask.length; i++) { 148 | result[i] = addr[addr.length - 4 + i] & mask[i]; 149 | } 150 | } else { 151 | // IPv6 mask and IPv4 addr 152 | for (i = 0; i < result.length - 6; i++) { 153 | result[i] = 0; 154 | } 155 | 156 | // ::ffff:ipv4 157 | result[10] = 0xff; 158 | result[11] = 0xff; 159 | for (i = 0; i < addr.length; i++) { 160 | result[i + 12] = addr[i] & mask[i + 12]; 161 | } 162 | i += 12; 163 | } 164 | for (; i < result.length; i++) { 165 | result[i] = 0; 166 | } 167 | 168 | return ip.toString(result); 169 | }; 170 | 171 | ip.cidr = function (cidrString) { 172 | const cidrParts = cidrString.split('/'); 173 | 174 | const addr = cidrParts[0]; 175 | if (cidrParts.length !== 2) { 176 | throw new Error(`invalid CIDR subnet: ${addr}`); 177 | } 178 | 179 | const mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); 180 | 181 | return ip.mask(addr, mask); 182 | }; 183 | 184 | ip.subnet = function (addr, mask) { 185 | const networkAddress = ip.toLong(ip.mask(addr, mask)); 186 | 187 | // Calculate the mask's length. 188 | const maskBuffer = ip.toBuffer(mask); 189 | let maskLength = 0; 190 | 191 | for (let i = 0; i < maskBuffer.length; i++) { 192 | if (maskBuffer[i] === 0xff) { 193 | maskLength += 8; 194 | } else { 195 | let octet = maskBuffer[i] & 0xff; 196 | while (octet) { 197 | octet = (octet << 1) & 0xff; 198 | maskLength++; 199 | } 200 | } 201 | } 202 | 203 | const numberOfAddresses = 2 ** (32 - maskLength); 204 | 205 | return { 206 | networkAddress: ip.fromLong(networkAddress), 207 | firstAddress: numberOfAddresses <= 2 ? ip.fromLong(networkAddress) : ip.fromLong(networkAddress + 1), 208 | lastAddress: numberOfAddresses <= 2 ? ip.fromLong(networkAddress + numberOfAddresses - 1) : ip.fromLong(networkAddress + numberOfAddresses - 2), 209 | broadcastAddress: ip.fromLong(networkAddress + numberOfAddresses - 1), 210 | subnetMask: mask, 211 | subnetMaskLength: maskLength, 212 | numHosts: numberOfAddresses <= 2 ? numberOfAddresses : numberOfAddresses - 2, 213 | length: numberOfAddresses, 214 | contains(other) { 215 | return networkAddress === ip.toLong(ip.mask(other, mask)); 216 | }, 217 | }; 218 | }; 219 | 220 | ip.cidrSubnet = function (cidrString) { 221 | const cidrParts = cidrString.split('/'); 222 | 223 | const addr = cidrParts[0]; 224 | if (cidrParts.length !== 2) { 225 | throw new Error(`invalid CIDR subnet: ${addr}`); 226 | } 227 | 228 | const mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); 229 | 230 | return ip.subnet(addr, mask); 231 | }; 232 | 233 | ip.not = function (addr) { 234 | const buff = ip.toBuffer(addr); 235 | for (let i = 0; i < buff.length; i++) { 236 | buff[i] = 0xff ^ buff[i]; 237 | } 238 | return ip.toString(buff); 239 | }; 240 | 241 | ip.or = function (a, b) { 242 | a = ip.toBuffer(a); 243 | b = ip.toBuffer(b); 244 | 245 | // same protocol 246 | if (a.length === b.length) { 247 | for (let i = 0; i < a.length; ++i) { 248 | a[i] |= b[i]; 249 | } 250 | return ip.toString(a); 251 | 252 | // mixed protocols 253 | } 254 | let buff = a; 255 | let other = b; 256 | if (b.length > a.length) { 257 | buff = b; 258 | other = a; 259 | } 260 | 261 | const offset = buff.length - other.length; 262 | for (let i = offset; i < buff.length; ++i) { 263 | buff[i] |= other[i - offset]; 264 | } 265 | 266 | return ip.toString(buff); 267 | }; 268 | 269 | ip.isEqual = function (a, b) { 270 | a = ip.toBuffer(a); 271 | b = ip.toBuffer(b); 272 | 273 | // Same protocol 274 | if (a.length === b.length) { 275 | for (let i = 0; i < a.length; i++) { 276 | if (a[i] !== b[i]) return false; 277 | } 278 | return true; 279 | } 280 | 281 | // Swap 282 | if (b.length === 4) { 283 | const t = b; 284 | b = a; 285 | a = t; 286 | } 287 | 288 | // a - IPv4, b - IPv6 289 | for (let i = 0; i < 10; i++) { 290 | if (b[i] !== 0) return false; 291 | } 292 | 293 | const word = b.readUInt16BE(10); 294 | if (word !== 0 && word !== 0xffff) return false; 295 | 296 | for (let i = 0; i < 4; i++) { 297 | if (a[i] !== b[i + 12]) return false; 298 | } 299 | 300 | return true; 301 | }; 302 | 303 | ip.isPrivate = function (addr) { 304 | return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || /^f[cd][0-9a-f]{2}:/i.test(addr) || /^fe80:/i.test(addr) || /^::1$/.test(addr) || /^::$/.test(addr); 305 | }; 306 | 307 | ip.isPublic = function (addr) { 308 | return !ip.isPrivate(addr); 309 | }; 310 | 311 | ip.isLoopback = function (addr) { 312 | return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(addr) || /^fe80::1$/.test(addr) || /^::1$/.test(addr) || /^::$/.test(addr); 313 | }; 314 | 315 | ip.loopback = function (family) { 316 | // 317 | // Default to `ipv4` 318 | // 319 | family = _normalizeFamily(family); 320 | 321 | if (family !== 'ipv4' && family !== 'ipv6') { 322 | throw new Error('family must be ipv4 or ipv6'); 323 | } 324 | 325 | return family === 'ipv4' ? '127.0.0.1' : 'fe80::1'; 326 | }; 327 | 328 | // 329 | // ### function address (name, family) 330 | // #### @name {string|'public'|'private'} **Optional** Name or security 331 | // of the network interface. 332 | // #### @family {ipv4|ipv6} **Optional** IP family of the address (defaults 333 | // to ipv4). 334 | // 335 | // Returns the address for the network interface on the current system with 336 | // the specified `name`: 337 | // * String: First `family` address of the interface. 338 | // If not found see `undefined`. 339 | // * 'public': the first public ip address of family. 340 | // * 'private': the first private ip address of family. 341 | // * undefined: First address with `ipv4` or loopback address `127.0.0.1`. 342 | // 343 | ip.address = function (name, family) { 344 | const interfaces = os.networkInterfaces(); 345 | 346 | // 347 | // Default to `ipv4` 348 | // 349 | family = _normalizeFamily(family); 350 | 351 | // 352 | // If a specific network interface has been named, 353 | // return the address. 354 | // 355 | if (name && name !== 'private' && name !== 'public') { 356 | const res = interfaces[name].filter((details) => { 357 | const itemFamily = _normalizeFamily(details.family); 358 | return itemFamily === family; 359 | }); 360 | if (res.length === 0) { 361 | return undefined; 362 | } 363 | return res[0].address; 364 | } 365 | 366 | const all = Object.keys(interfaces) 367 | .map((nic) => { 368 | // 369 | // Note: name will only be `public` or `private` 370 | // when this is called. 371 | // 372 | const addresses = interfaces[nic].filter((details) => { 373 | details.family = _normalizeFamily(details.family); 374 | if (details.family !== family || ip.isLoopback(details.address)) { 375 | return false; 376 | } 377 | if (!name) { 378 | return true; 379 | } 380 | 381 | return name === 'public' ? ip.isPrivate(details.address) : ip.isPublic(details.address); 382 | }); 383 | 384 | return addresses.length ? addresses[0].address : undefined; 385 | }) 386 | .filter(Boolean); 387 | 388 | return !all.length ? ip.loopback(family) : all[0]; 389 | }; 390 | 391 | ip.toLong = function (ip) { 392 | let ipl = 0; 393 | ip.split('.').forEach((octet) => { 394 | ipl <<= 8; 395 | ipl += parseInt(octet); 396 | }); 397 | return ipl >>> 0; 398 | }; 399 | 400 | ip.fromLong = function (ipl) { 401 | return `${ipl >>> 24}.${(ipl >> 16) & 255}.${(ipl >> 8) & 255}.${ipl & 255}`; 402 | }; 403 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const { Resolver } = require('node:dns').promises; 5 | const ipv4 = require('./utils/ipv4'); 6 | const ipv6 = require('./utils/ipv6'); 7 | const buf = require('./utils/buf'); 8 | const udp = require('dgram'); 9 | 10 | class SocketHandler { 11 | constructor(socket, options = {}) { 12 | this.socket = socket; 13 | this.logger = options.logger || console; 14 | this.port = options.port; 15 | this.localAddress = options.localAddress; 16 | this.dns = options.dns; 17 | 18 | if (options.userPassAuthFn) { 19 | if (typeof options.userPassAuthFn !== 'function') throw new TypeError('userPassAuthFn should be function'); 20 | this.userPassAuthFn = options.userPassAuthFn; 21 | } 22 | 23 | this.init(); 24 | } 25 | 26 | init() { 27 | this.socket.on('error', (err) => { 28 | this.logger.error(err); 29 | if (!this.socket.destroyed) { 30 | return this.socket.destroy(); 31 | } 32 | }); 33 | 34 | this.socket.on('timeout', () => { 35 | this.logger.warn('socket timeout'); 36 | this.socket.end(); 37 | }); 38 | } 39 | 40 | consume() { 41 | return new Promise((resolve) => { 42 | this.socket.once('data', resolve); 43 | }); 44 | } 45 | 46 | // +----+------+----------+------+----------+ 47 | // |VER | ULEN | UNAME | PLEN | PASSWD | 48 | // +----+------+----------+------+----------+ 49 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 50 | // +----+------+----------+------+----------+ 51 | 52 | async authUserPass() { 53 | const data = await this.consume(); 54 | if (data[0] != 0x01) { 55 | this.logger.error('Unsupported auth version: %d', data[0]); 56 | this.socket.end(); 57 | return true; 58 | } 59 | 60 | const ulen = data[1]; 61 | const uname = data.toString('ascii', 2, 2 + ulen); 62 | const plen = data[2 + ulen]; 63 | const passwd = data.toString('ascii', 2 + ulen + 1, 2 + ulen + 1 + plen); 64 | 65 | this.logger.debug('uname: %s, passwd: %s', uname, passwd); 66 | 67 | if (this.userPassAuthFn(uname, passwd)) { 68 | this.socket.write(Buffer.from([0x01, 0x00])); 69 | } else { 70 | this.socket.end(Buffer.from([0x01, 0x01])); 71 | return true; 72 | } 73 | } 74 | 75 | // +----+----------+----------+ 76 | // |VER | NMETHODS | METHODS | 77 | // +----+----------+----------+ 78 | // | 1 | 1 | 1 to 255 | 79 | // +----+----------+----------+ 80 | 81 | async authentication() { 82 | const data = await this.consume(); 83 | if (data[0] !== 0x05) { 84 | this.logger.error('Unsupported SOCKS version: %d', data[0]); 85 | this.socket.end(); 86 | return true; 87 | } 88 | 89 | // o X'00' NO AUTHENTICATION REQUIRED 90 | // o X'01' GSSAPI 91 | // o X'02' USERNAME/PASSWORD 92 | // o X'03' to X'7F' IANA ASSIGNED 93 | // o X'80' to X'FE' RESERVED FOR PRIVATE METHODS 94 | // o X'FF' NO ACCEPTABLE METHODS 95 | const nmethods = data[1]; 96 | const methods = data.slice(2, 2 + nmethods); 97 | // only support 0x00 0x02 98 | if ((methods.includes(0x00) || methods.includes(0x02)) && this.userPassAuthFn) { 99 | this.socket.write(Buffer.from([0x05, 0x02])); 100 | return await this.authUserPass(); 101 | } else if (methods.includes(0x00)) { 102 | this.socket.write(Buffer.from([0x05, 0x00])); 103 | } else { 104 | this.logger.error('auth methods not support'); 105 | this.socket.end(Buffer.from([0x05, 0xff])); 106 | return true; 107 | } 108 | } 109 | 110 | // +----+-----+-------+------+----------+----------+ 111 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 112 | // +----+-----+-------+------+----------+----------+ 113 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 114 | // +----+-----+-------+------+----------+----------+ 115 | 116 | // o X'00' succeeded 117 | // o X'01' general SOCKS server failure 118 | // o X'02' connection not allowed by ruleset 119 | // o X'03' Network unreachable 120 | // o X'04' Host unreachable 121 | // o X'05' Connection refused 122 | // o X'06' TTL expired 123 | // o X'07' Command not supported 124 | // o X'08' Address type not supported 125 | // o X'09' to X'FF' unassigned 126 | 127 | reply(rep, address) { 128 | const data = [0x05, rep, 0x00]; 129 | 130 | if (!address) { 131 | this.socket.write(Buffer.from(data.concat([0x01, 0, 0, 0, 0, 0, 0]))); 132 | return; 133 | } 134 | 135 | this.logger.debug(address); 136 | 137 | if (address.family === 'IPv4') { 138 | data.push(0x01); 139 | for (const str of address.address.split('.')) { 140 | data.push(Number(str)); 141 | } 142 | } else if (address.family === 'IPv6') { 143 | data.push(0x04); 144 | const ipv6BufArr = ipv6.toBufArr(address.address); 145 | for (const byte of ipv6BufArr) { 146 | data.push(byte); 147 | } 148 | } 149 | 150 | data.push(address.port >> 8); 151 | data.push(address.port & 0xff); 152 | 153 | this.socket.write(Buffer.from(data)); 154 | } 155 | 156 | async request() { 157 | // Requests 158 | 159 | // +----+-----+-------+------+----------+----------+ 160 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 161 | // +----+-----+-------+------+----------+----------+ 162 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 163 | // +----+-----+-------+------+----------+----------+ 164 | 165 | const data = await this.consume(); 166 | if (data[0] != 0x05) { 167 | this.logger.error('Unsupported SOCKS version: %d', data[0]); 168 | return this.socket.end(); 169 | } 170 | 171 | // o CONNECT X'01' 172 | // o BIND X'02' 173 | // o UDP ASSOCIATE X'03' 174 | if (data[1] == 0x01) { 175 | // CONNECT METHOD REQUEST 176 | if (data[2] !== 0x00) this.logger.warn('RESERVED should be 0x00'); 177 | let dstHost, dstPort; 178 | switch (data[3]) { 179 | case 0x01: // ipv4 180 | dstHost = `${data[4]}.${data[5]}.${data[6]}.${data[7]}`; 181 | dstPort = (data[8] << 8) | data[9]; 182 | break; 183 | case 0x03: { 184 | // domain 185 | const domainLen = data[4]; 186 | const domain = data.toString('ascii', 5, 5 + domainLen); 187 | try { 188 | const dnsResolver = new Resolver(); 189 | if (this.dns && typeof this.dns === 'string') { 190 | dnsResolver.setServers([this.dns]); 191 | } else if (this.dns && typeof this.dns === 'object') { 192 | dnsResolver.setServers(this.dns); 193 | } 194 | if (this.localAddress) { 195 | dnsResolver.setLocalAddress(this.localAddress); 196 | } 197 | const ips = await dnsResolver.resolve4(domain); 198 | dstHost = ips[0]; 199 | } catch (err) { 200 | //fix 201 | if (net.isIP(domain)) { 202 | dstHost = domain; 203 | } else { 204 | this.logger.error(err); 205 | this.reply(0x04); 206 | return this.socket.end(); 207 | } 208 | } 209 | dstPort = (data[5 + domainLen] << 8) | data[5 + domainLen + 1]; 210 | break; 211 | } 212 | case 0x04: { 213 | // ipv6 214 | const addrBuf = data.slice(4, 20); 215 | dstHost = ipv6.toStr(addrBuf); 216 | dstPort = (data[20] << 8) | data[21]; 217 | break; 218 | } 219 | default: 220 | this.logger.error(`ATYP ${data[3]} not support`); 221 | this.reply(0x08); 222 | return this.socket.end(); 223 | } 224 | let replyed = false; 225 | const proxy = net.createConnection({ 226 | host: dstHost, 227 | port: dstPort, 228 | localAddress: this.localAddress ? this.localAddress : undefined, 229 | }); 230 | proxy.on('error', (err) => { 231 | this.logger.error(err); 232 | if (!replyed) { 233 | // X'05' Connection refused 234 | this.reply(0x05); 235 | } 236 | if (!proxy.destroyed) proxy.destroy(); 237 | this.socket.end(); 238 | }); 239 | 240 | proxy.on('timeout', () => { 241 | this.logger.warn('proxy timeout'); 242 | if (!replyed) { 243 | // X'05' Connection refused 244 | this.reply(0x05); 245 | } 246 | proxy.end(); 247 | this.socket.end(); 248 | }); 249 | 250 | proxy.once('connect', () => { 251 | this.reply(0x00, proxy.address()); 252 | replyed = true; 253 | 254 | this.socket.pipe(proxy); 255 | proxy.pipe(this.socket); 256 | }); 257 | } else if (data[1] == 0x02) { 258 | //BIND METHOD REQUEST 259 | this.logger.error('BIND METHOD REQUEST not support'); 260 | this.reply(0x07); 261 | return this.socket.end(); 262 | } else if (data[1] == 0x03) { 263 | //UDP ASSOCIATE METHOD REQUEST 264 | this.reply(0x00, { address: '0.0.0.0', family: 'IPv4', port: this.port }); 265 | return this.socket.end(); 266 | } else { 267 | this.logger.error('Unsupported method: %d', data[1]); 268 | this.reply(0x07); 269 | return this.socket.end(); 270 | } 271 | } 272 | 273 | async handle() { 274 | const finished = await this.authentication(); 275 | if (finished) return; 276 | 277 | this.request(); 278 | } 279 | } 280 | 281 | function createServer(options = {}) { 282 | const logger = options.logger || console; 283 | const udpServer = udp.createSocket('udp4'); 284 | const server = net.createServer((socket) => { 285 | options.port = server.address().port; 286 | new SocketHandler(socket, options).handle(); 287 | }); 288 | server.on('listening', () => { 289 | logger.info('server listening', server.address().address, server.address().port); 290 | udpServer.on('error', (err) => { 291 | logger.error(`server error:\n${err.stack}`); 292 | udpServer.close(); 293 | }); 294 | udpServer.on('message', (msg, incoming_info) => { 295 | let buffer = Uint8Array.prototype.slice.call(msg); 296 | if (buffer.length < 10) { 297 | logger.warn('Buffer length is too short'); 298 | return null; 299 | } 300 | if (buffer[0] !== 0x00 || buffer[1] !== 0x00) { 301 | logger.warn('Reserved field should be 0x00'); 302 | return null; 303 | } 304 | const frag = buffer[2]; 305 | if (frag !== 0x00) { 306 | logger.warn('Fragment should be 0x00'); 307 | return null; 308 | } 309 | let host = null; 310 | let pos = 4; 311 | switch (buffer[3]) { 312 | case 0x01: 313 | host = ipv4.toString(buffer.slice(4, 8)); 314 | pos = pos + 4; 315 | break; 316 | case 0x03: 317 | host = buffer.slice(5, 5 + buffer[4]).toString(); 318 | pos = pos + 1 + buffer[4]; 319 | break; 320 | case 0x04: 321 | host = ipv4.toString(buffer.slice(4, 20)); 322 | pos = pos + 16; 323 | break; 324 | default: 325 | break; 326 | } 327 | let port = buffer.slice(pos, pos + 2).readUInt16BE(0); 328 | let data = buffer.slice(pos + 2); 329 | logger.debug('INCOMING UDP message from ' + incoming_info.address + ':' + incoming_info.port + ' FOR ' + host + ':' + port); 330 | //parse end 331 | //send data to outcoming 332 | const outcoming = udp.createSocket({ type: 'udp4', reuseAddr: true }); 333 | if (options.localAddress) { 334 | outcoming.bind({ 335 | address: options.localAddress, 336 | port: 0, 337 | exclusive: true, 338 | }); 339 | } 340 | outcoming.send(data, port, host, (err) => { 341 | if (err) { 342 | logger.error(err); 343 | return; 344 | } 345 | }); 346 | outcoming.on('message', (msg, outcoming_info) => { 347 | logger.debug('RESPONSE FROM HOST', outcoming_info, ' TO ', incoming_info); 348 | let buffer = Uint8Array.prototype.slice.call(msg); 349 | logger.debug('PREPARE UDP PACKET FOR INCOMING', incoming_info); 350 | let atyp = 0x03; 351 | if (outcoming_info.family === 'IPv4') { 352 | atyp = 0x01; 353 | } else if (outcoming_info.family === 'IPv6') { 354 | atyp = 0x04; 355 | logger.error('IPv6 not supported yet'); 356 | return; 357 | } 358 | let _host = atyp === 0x03 ? Buffer.from(outcoming_info.address) : outcoming_info.family === 'IPv4' ? ipv4.toBuffer(outcoming_info.address) : ipv6.toBufArr(outcoming_info.address); 359 | let _port = buf.numberToBuffer(outcoming_info.port); 360 | let data = Buffer.from([0x00, 0x00, 0x00, atyp, ...(atyp === 0x03 ? [_host.length] : []), ..._host, ..._port, ...buffer]); 361 | udpServer.send(data, incoming_info.port, incoming_info.address, function (err) { 362 | outcoming.close(); 363 | if (err) { 364 | logger.error(err); 365 | return; 366 | } 367 | logger.debug('UDP PACKET SENT TO INCOMING', incoming_info); 368 | }); 369 | }); 370 | outcoming.on('error', (err) => { 371 | logger.error(err); 372 | }); 373 | }); 374 | udpServer.on('listening', () => { 375 | const address = udpServer.address(); 376 | logger.debug(`UDP listening ${address.address}:${address.port}`); 377 | }); 378 | udpServer.bind(server.address().port); 379 | }); 380 | server.on('error', (err) => { 381 | logger.error(err); 382 | }); 383 | server.on('close', () => { 384 | if (udpServer) { 385 | udpServer.close(); 386 | } 387 | logger.info('SOCKS5 server closed'); 388 | }); 389 | return server; 390 | } 391 | 392 | exports.createServer = createServer; 393 | --------------------------------------------------------------------------------