NodeJS实现简单的Socks5代理欧博官网服务

文章正文
发布时间:2024-05-23 08:23
NodeJS实现简单的Socks5代理服务

前言

最近在网上看到一篇文章《你也能写个 Shadowsocks》,欧博官网于是处于好奇就了解到了SOCKS5协议。

这里我就根据SOCKS5协议,通过NodeJS实现一个最简单的,没有加密,不需要认证Socks5代理服务。

我相信大家都听过Socket,而Socks5协议是一种网络传输协议,常用过代理,之前很有名的shadow socks也是基于此协议实现的。这里大家单词可不要看错了。

完整代码:https://gitee.com/baojuhua/node_simples/tree/master/node_simple_socks5_server

编写TCP服务

我们使用NodeJS的net模块编写tcp服务。

使用socket.once('data',.....)来获取首次代理请求的数据

const net = require('net'); let server = net.createServer(function (socket) { socket.once('data', function (data) { console.log(JSON.stringify(data)); }); socket.on('error', function (err) { console.error(`error:${err.message}`); }); }); server.listen(11100); 在浏览器中设置Socks5代理

我使用的插件是SwitchyOmega

测试效果:

首次响应请求

上面我们获取到了首次请求的数据,根据SOCKS5协议我们可以知道其含义如下:

- VERSION METHODS_COUNT METHODS...
data   0x05   0x01   0x00  
说明   协议版本,欧博目前固定0x05   客户端支持的认证方法数量   不需要认证  

而我们在无需验证的情况下直接返回的结果如下:

- VERSION METHOD
data   0x05   0x00  
说明   SOCKS协议版本,目前固定0x05   本次连接所用的认证方法,上例中为无需认证  

根据协议添加协议验证与响应:

//.... let server = net.createServer(function (socket) { socket.once('data', function (data) { if (!data || data[0] !== 0x05) return socket.destroy(); socket.write(Buffer.from([5, 0]), function (err) { if (err) socket.destroy(); socket.once('data', (data) => { console.log(JSON.stringify(data)); }); }); }); //.... }); //.... 测试效果:

实现请求解析

客户端在收到认证成功的消息后,会给代理服务器发送命令,其含义简介如下:

- VERSION COMMAND RSV ADDRESS_TYPE DST.ADDR DST.PORT
长度   1字节   1字节   1字节   1字节   1-255字节   2字节  
说明   SOCKS协议版本,固定0x05   命令(本文只实现CONNECT)   保留字段   目标服务器地址类型(本文仅实现IP V4与域名)   目标地址   端口  

需要说的是在域名类型下,域名地址第1个字节为域名长度,剩下字节为域名名称字节数组

而服务端解析成功后,需要情况进行对应的响应:

- VERSION RESPONSE RSV ADDRESS_TYPE BND.ADDR BND.PORT
长度   1字节   1字节   1字节   1字节   1-255字节   2字节  
说明   SOCKS协议版本,固定0x05   响应命令   保留字段   代理服务器地址类型   代理服务器地址   代理服务器端口  

RESPONSE 响应命令:

0x00 代理服务器连接目标服务器成功

0x01 代理服务器故障

0x02 代理服务器规则集不允许连接

0x03 网络无法访问

0x04 目标服务器无法访问(主机名无效)

0x05 连接目标服务器被拒绝

0x06 TTL已过期

0x07 不支持的命令

0x08 不支持的目标服务器地址类型

0x09 - 0xFF 未分配

我们响应的服务端地址默认是1,也就是ip v4,地址与端口可以直接就写0x00。

根据SOCKS5协议中命令部分,编写以下代码:

//... socket.once('data', (data) => { if (data.length < 7 || data[1] !== 0x01) return socket.destroy(); // 只支持 CONNECT try { addrtype = data[3];// ADDRESS_TYPE 目标服务器地址类型 if (addrtype === 3) {//0x03 域名地址(没有打错,就是没有0x02), addrLen = data[4];//域名地址的第1个字节为域名长度,剩下字节为域名名称字节数组 } else if (addrtype !== 1 && addrtype !== 4) { return socket.destroy(); } remotePort = data.readUInt16BE(data.length - 2);//最后两位为端口值 if (addrtype === 1) {// 0x01 IP V4地址 remoteAddr = data.slice(3, 7).join('.'); } else if (addrtype === 4) { //0x04 IP V6地址 return socket.write(Buffer.from([0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));//不支持IP V6 } else {//0x03 域名地址(没有打错,就是没有0x02),域名地址的第1个字节为域名长度,剩下字节为域名名称字节数组 remoteAddr = data.slice(5, 5 + addrLen).toString("binary"); } console.log(`connecting : ${remoteAddr}:${remotePort}`); } catch (e) { console.error(e); } }); //... 测试效果:

实现代理:

解析成功之后我们就可以实现正式的代理了,

代理部分很简单,创建一个TCP客户端请求,将请求信息转发给解析出来的IP与端口就行了。

//.... let remote = net.connect(remotePort, remoteAddr, function () { console.log(`connecting : ${remoteAddr}:${remotePort}`); socket.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), function (err) { if (err) { console.error(`error:${err.message}`); return socket.destroy(); } remote.pipe(socket); socket.pipe(remote); }); }); remote.on('error', function (err) { console.error(`连接到远程服务器 ${remoteAddr}:${remoteAddr} 失败,失败信息:${err.message}`); remote.destroy(); socket.destroy(); }); //.... 效果测试

本人目前在杭州,在自己老家有一台香橙派作为自己平时的测试服务器,

这里我通过远程将服务部署在香橙派上作为测试。

参考资料
首页
评论
分享
Top