SOCKS5 代理?

SOCKS5 代理,啥远古的东西了?


昨天凑巧被问到这个,好久没碰过了,复习一下,希望能产出点东西。

协议与状态机

很多朋友一看到协议二字就紧张得不行,其实大可不必。

协议就是本质某种秩序,某种规则,在编排代码时按需即可。

就像是红灯停,绿灯行一样,他们是驾驶员与行人之间的协议,大家都遵守,就不会发生问题。

所有协议都可以被这么简化。

实际上现代计算机几乎所有的地方都离不开协议二字。

CPU上执行的二进制编码,可以视为硬件专家与软件专家之间的协议。

双击图标就能打开,是产品设计者与用户之间的协议。

。。。。或大或小,他们无处不在。

SOCKS5 则是一种用于网络代理的协议,仅此而已。

为了代理,很容易就能想到,他们二者有约定俗成的结构编排方式,其内部会包含一些信息。

同时,为了在不同阶段处理不同种类形式的任务,会告知对方需要做什么。

简单说就是一种状态的改变。为了降低这种状态转换时的心智负担,往往协议会伴随 状态机 一起出现,

状态机 可以被简单的认为是一种描述状态迁移过程的机器,虽然有着高大上的名字,但本质不复杂。

本文后面会给出一个 SOCKS5 的状态机模型。

先看一个伪代码,如下:

1
2
3
4
5
6
any s = S0;

for (;;) {
s = s==S0 ? S1:S0;
}

对,你没看错,这就是最最简单的状态机模型了。

描述一下:

  • 当 S 位于 s0 状态时,啥都不干,并迁移到 s1 状态。
  • 否则,啥都不干,迁移到 s0

像不像一个来回拨动的按钮?

无论是说起来多么高大上的协议,最终也逃不过这个最简单的框架,妙哇~

代理

再说一下代理,这个就是字面意思,举个形象的例子。

  • A 写了封信交给 B,B 再交给 C,他把回信也交给 B,最后由 B 交给 A 就是一个典型的代理过程。
1
2
A -> B -> C
A <- B <- C

这个过程中,B 会被称为中间人,而 SOCKS5 server 正是这种中间代理人的存在。

那么问题来了。

  1. 刚才只有 A,那假如 D、E、F 也想让 B 帮忙送信怎么办?
  2. 如果 A 要送给 G、H、I 怎么办?

那就需要和 B 协商要将信交给谁,信被回复时也会原路返回。 源头和目标之间就像 隧道一样连接起来。

再复杂化一点,假如 B 有好多个版本,类似于邮政、顺丰… 你到邮政版的 B 那里寄出顺丰,他当然不会帮到咯。

上述过程就可以直接被代入到具体的 SOCKS5 协议中,大概有了一个代理协议的雏形。

SOCKS5 协议

协议简单,资料很多,大同小异。

为了避免熵增,节能减排,扒拉一个排版干净的放这,就不再赘述。

Socks5 协议简介

核心框架实现

下图来自: Socks5 代理协议详解 & 基于 Netty 的实现

socks5时序图
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

// 启动网络框架
srv.loop(connRead, connWrite, connConnect ......);

// 连接事件
connConnect() {

}

// 写事件
connWrite() {
// ...
}

// 读事件
connRead(current_conn) {
do_next(current_conn, current_conn.dst);
}

do_parse_s5_request(src) {

assert (src.must_local());

// ...
// 提取 ip : port

// remote = connect(ip, port);
// 设置默认状态为转发,如有读事件发生,直接转发即可
src.dst = connect(...);
src.dst.is_remote = 1;
src.dst.status = do_streaming;

// 使 src 和 dst 都能知道对方的存在,建立了二者关联的隧道
make_tunnel(src, dst);
}

// 数据转发
do_streaming(src, dst) {
src.write(dst)
}

do_handshake(src) {
// ....
// do_handshake
src.status = session_handshake_auth;
}

do_handshake_auth(src) {
// ...
// do_handshake_auth
src.status = xxxx; // 迁移到下一个状态
}

// 状态机
do_next (src, dst) {
switch(state) {
case session_handshake: {
do_handshake(src);
} break;
case session_handshake_auth: {
do_handshake_auth(src);
} break;
// ....
case session_s5_request: {
do_parse_s5_request(src); // 在获取到必要的信息后, 就可以建立隧道了
} break;
case session_streaming: {
do_streaming(src, dst); // 数据转发, 将数据来回倒腾即可
} break;
}
}

上文伪代码部分实现了一个大致的框架,大致流程如此。

除了数据转发外,其它的网络事件也需要同时处理二者,例如 local 断开后,remote 也应断开。

UDP ASSOCIATE?

啥都不是,散会。

到此为止

虽然还想多讲一会儿,

但是这个东西实在是没有太多值得提的,到此为止吧,毁灭吧。