sock5代理node实现(1)

相信大家都使用过或者听说过shadowsocks, 它可以帮助我们访问一些“访问不到”的网站。在wiki上对于它的说明是“shadowsocks是一种基于Socks5代理方式的网络数据加密传输包”(百度上并没有这个词条了)。其实shadowsocks就是socks5协议的客户端和服务端的实现。接下来我们了解一下socks5的具体细节,并使用node来实现一个简单的服务端版本。

其中socks有socks4和socks5版本,socks5比socks4多了UDP、验证,以及IPv6的支持,所有socks5是socks4的升级版也支持了socks4所有的内容(我们下面说到的所有socks协议都是基于socks5版本)。同时socks协议位于表示层和传输层之间,而http和https位于应用层,所以使用socks协议来代理https可以不需要考虑https本身的安全限制。

关于socks5的协议,我们可以查看RFC的socks5说明

需要注意的是socks5传输的都是16进制的数据。

创建服务器

首先我们需要创建一个服务器来接收后面客服端传过来的数据。

下面是node的实现:

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
const net = require('net');
// 第一次握手连接的数据处理,后面会实现。
const shakehand = require('./shake-hand.js');

function connectServer(socket) {
try {
// 监听socket的错误信息
socket.on('error', (e) => {
console.error(e);
});

// 设置超时并监听timeout事件
socket.setTimeout(60 * 1000);

socket.on('timeout', () => {
socket.destroyed || socket.destroy();
});

// 用于下面的握手连接
// 因为socks5的整个握手和认证需要传输多次数据,每次都不一样,所以这里只需要监听一次。
socket.once('data', shakehand.bind(socket));

} catch (e) {
console.error('未捕捉错误');
console.error(e);
}
}
// 监听1080端口
net.createServer(connectServer).listen(1080).on('error', (e) => {
console.error(e);
});

第一次握手连接

可以看到第一步是创建连接。首先是由客户端发起请求,这里我们还没有写socks5的客户端,我们暂时使用curl来代替socks5客户端。

我们第一次收到这样格式的数据。

+—-+———-+———-+
|VER | NMETHODS | METHODS |
+—-+———-+———-+
| 1 | 1 | 1 to 255 |
+—-+———-+———-+

第一个VER表示客服端采用的socks版本,例如socks5版本就会是0x05

第二个参数NMETHODS表示客户端支持的验证类型的数量。

第三个参数METHODS,每一位表示一个验证类型。

当前定义的方法有:
· X’00’ 不需要认证
· X’01’ GSSAPI
· X’02’ 用户名/密码
· X’03’ – X’7F’ 由IANA分配
· X’80’ – X’FE’ 为私人方法所保留的
· X’FF’ 没有可以接受的方法

而我们接收到客服端信息后,判断是否和自己支持的版本是否一样,同时选择一个验证方式,如果没有则传输0xFF

下面是node的代码实现

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
// 密码验证处理,后面会实现
const passwordVerity = require('./password-verify.js');

// 暂时只写了三个验证方式
const AuthMethods = {
NOAUTH: 0, // 不需要验证
GSSAPI: 1, // GSSAPI验证
USERPASS: 2, // 用户名/密码 验证
};

function shakehand(chunk) {
let socket = this;
const VERSION = parseInt(chunk[0], 10);
// 客户端支持的认证方式的数量
const NMETHODS = parseInt(chunk[1], 10);

// 判断客户端socks的版本
if (VERSION !== 5) {
socket.destroyed || socket.destroy();
return false;
}

// 获取客户端支持的所有的类型
socket.methods = [];
for (let i = 1; i <= NMETHODS; i++) {
socket.methods.push(chunk[1 + i]);
}

// 判断是否支持用户名/密码 验证
let isSupportPassrod = socket.methods.find(method => method === AuthMethods.USERPASS);

// 如果不支持就返回 没有可以接受的方法
if (typeof isSupportPassrod === 'undefined') {
let res = new Buffer([VERSION, 0xFF]);
socket.write(res);
socket.destroyed || socket.destroy();
return false;
}
// 返回版本和需要账号密码来验证
let res = new Buffer([VERSION, AuthMethods.USERPASS]);
socket.write(res);

// 监听下一次的数据,来进行账号密码来验证
socket.once('data', passwordVerity.bind(socket));
return false;
}

module.exports = shakehand;

sock5代理node实现(2)
sock5代理node实现(3)