nodejs 实现简单的socks代理服务器

本文实现的服务仅适用于内网测试等临时用途,请务必设置允许的客户端IP地址,防止被滥用。

SOCKS5 协议

SOCKS5是一种网络代理协议,相较于HTTP代理,它能更好地支持TCP/UDP协议的全流量转发。其工作流程分为三个阶段:

  • 协议版本协商
  • 认证方式协商(支持无认证)
  • 请求转发处理

核心实现代码

我太懒了,实在不想长篇大论的写实现过程,还是劳烦需要的看官,自行阅读代码注释吧。

const net = require('net');
const dns = require('dns');

const SERVER_PORT = 6532;
const ALLOWED_IP = '10.211.55.1';

const server = net.createServer((clientSocket) => {
    // 检查来源 IP,仅允许指定 IP 连接
    const remoteAddress = clientSocket.remoteAddress.replace(/^::ffff:/, '');
    if (remoteAddress !== ALLOWED_IP) {
        console.log('reject client', remoteAddress);
        clientSocket.destroy();
        return;
    }

    // SOCKS5 握手阶段
    clientSocket.once('data', (data) => {
        // 检查 SOCKS5 协议版本
        if (data[0] !== 0x05) {
            clientSocket.end();
            return;
        }

        // 仅支持无认证方式
        clientSocket.write(Buffer.from([0x05, 0x00]));

        // SOCKS5 请求阶段
        clientSocket.once('data', (req) => {
            // 只支持 CONNECT 命令
            if (req[0] !== 0x05 || req[1] !== 0x01) {
                clientSocket.end(Buffer.from([0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); // 不支持的命令
                return;
            }
            let addr, port, offset = 4;
            const atyp = req[3];
            if (atyp === 0x01) { // IPv4 地址
                addr = req.slice(offset, offset + 4).join('.');
                offset += 4;
            } else if (atyp === 0x03) { // 域名
                const len = req[offset];
                addr = req.slice(offset + 1, offset + 1 + len).toString();
                offset += 1 + len;
            } else if (atyp === 0x04) { // IPv6 地址
                addr = req.slice(offset, offset + 16);
                offset += 16;
            } else {
                clientSocket.end(Buffer.from([0x05, 0x08, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); // 不支持的地址类型
                return;
            }
            port = req.readUInt16BE(offset);

            // 建立到目标服务器的连接
            const connectToTarget = (ip) => {
                const remoteSocket = net.connect(port, ip, () => {
                    // 回复客户端连接成功
                    const resp = Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]);
                    clientSocket.write(resp);
                    // 数据双向转发
                    clientSocket.pipe(remoteSocket);
                    remoteSocket.pipe(clientSocket);
                });
                remoteSocket.on('error', () => {
                    clientSocket.end(Buffer.from([0x05, 0x05, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); // 连接被拒绝
                });
            };

            if (atyp === 0x03) {
                // 域名需先解析
                dns.lookup(addr, (err, address) => {
                    if (err) {
                        clientSocket.end(Buffer.from([0x05, 0x04, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); // 主机不可达
                        return;
                    }
                    connectToTarget(address);
                });
            } else {
                connectToTarget(addr);
            }
        });
    });

    clientSocket.on('error', () => { });
});

server.listen(SERVER_PORT, () => {
    console.log(`SOCKS5 proxy server listening on port ${SERVER_PORT}`);
});
文章作者: 若海; 原文链接: https://www.rehiy.com/post/597/; 转载需声明来自技术写真 - 若海

添加新评论