一、协议
Shadowsocks is a secure split proxy loosely based on SOCKS5
ss是建立在socks5协议上的一种代理协议,它的职责是建立两个通道,一个用于把客户端数据传输到服务端,一个用于把服务端的数据传输到客户端。
ss协议可以分为socks5部分和shadowsocks部分,其数据传输流向可以表示为
1 | client <---> ss-local <--[encrypted]--> ss-remote <---> target |
ss-local
扮演了一个传统socks5
代理的角色,为客户端请求提供代理,它将会加密并转发来自客户端的包到ss-remote
,ss-remote
收到数据后会进行解密并向client请求的目标地址发起真正的请求,来自目标的回复也同样将被加密,并由ss-remote
转发回ss-local
,后者解密并最终返回给原始客户端
socks5协议
SOCKS is an Internet protocol that exchanges network packets between a client and server through a proxy server
socks5
协议包括握手阶段和请求阶段在握手阶段,客户端将往ss-local发送如下格式的报文
+----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 | +----+----------+----------+
ss-local收到了客户端的请求之后,响应报文的格式为
+----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 | +----+--------+
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14fn handshake(stream: TcpStream) -> Result<(), Error> {
let mut buf_reader = BufReader::new(stream);
let ver = buf_reader.read_u8()?;
if ver != SOCKS5_VER {
return Err(Error::new(ErrorKind::Other, "not supported ver"));
}
let methods = buf_reader.read_u8()?;
let mut buf = vec![0; methods as usize];
buf_reader.read_exact(&mut buf[..])?;
// 简单起见,这里METHOD字段的值为0x00,表示不需要验证
buf_reader.get_mut().write([SOCKS5_VER, 0].as_ref())?;
Ok(())
}socks5
客户端和服务端进行握手&授权验证通过之后,就可以开始建立连接。连接由客户端发起,告诉socks5
服务端客户端需要访问的目标地址,其中包含远程服务器的地址和端口,地址可以是ipv4/ipv6/domain1
2
3
4
5+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+socks5
服务端返回的报文格式如下1
2
3
4
5+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+代码实现:
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
66fn connect(mut stream: TcpStream, cfg: Cfg) -> Result<(), Error> {
// 读取socks5客户端请求
let mut reader = BufReader::new(&stream);
let mut buf = [0; 4];
reader.read_exact(&mut buf)?;
let (ver, cmd, atype) = (buf[0], buf[1], buf[3]);
if ver != SOCKS5_VER {
return Err(Error::new(ErrorKind::Other, "not supported ver"));
}
if cmd != CMD_BIND {
return Err(Error::new(ErrorKind::Other, "not supported cmd"));
}
let mut raw_addr = vec![atype];
let addr = match atype {
ATYP_IPV4 => {
reader.read_exact(&mut buf)?;
let ipv4 = IpAddr::V4(Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]));
raw_addr.append(&mut buf[..].to_owned());
let mut buf = [0; 2];
reader.read_exact(&mut buf)?;
raw_addr.append(&mut buf[..].to_owned());
let port = Cursor::new(&buf).read_u16::<BigEndian>()?;
Ok(SocketAddr::new(ipv4, port))
}
ATYPE_IPV6 => {
let mut buf = [0; 18];
reader.read_exact(&mut buf)?;
raw_addr.append(&mut buf[..].to_owned());
let ipv6 = IpAddr::V6(Ipv6Addr::new(
Cursor::new(&buf[0..2]).read_u16::<BigEndian>()?,
Cursor::new(&buf[2..4]).read_u16::<BigEndian>()?,
Cursor::new(&buf[4..6]).read_u16::<BigEndian>()?,
Cursor::new(&buf[6..8]).read_u16::<BigEndian>()?,
Cursor::new(&buf[8..10]).read_u16::<BigEndian>()?,
Cursor::new(&buf[10..12]).read_u16::<BigEndian>()?,
Cursor::new(&buf[12..14]).read_u16::<BigEndian>()?,
Cursor::new(&buf[14..16]).read_u16::<BigEndian>()?,
));
let port = Cursor::new(&buf[16..]).read_u16::<BigEndian>()?;
Ok(SocketAddr::new(ipv6, port))
}
ATYPE_HOST => {
let host_byte = reader.read_u8()?;
raw_addr.push(host_byte);
let mut buf = vec![0; host_byte as usize];
reader.read_exact(&mut buf)?;
raw_addr.append(&mut buf[..].to_owned());
Ok(String::from_utf8_lossy(&buf[..])
.to_socket_addrs()?
.next()
.unwrap())
}
_ => Err(Error::new(ErrorKind::Other, "not supported atype")),
};
// 写入响应
// +------+-------+---------+--------+-------------+-------------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +------+-------+---------+--------+-------------+-------------+
// | 0x5 | 0x0 | 0x0 | 0x1 | 0x00000000 | 0x00 |
// +------+-------+---------+--------+-------------+-------------+
stream.write(&[SOCKS5_VER, 0, 0, 1, 0, 0, 0, 0, 0, 0])?;
// start proxying
}ss协议
ss支持tcp/udp包的转发,这篇文章只讨论tcp协议下ss的报文结构
从ss-local或ss-server发送shadowsocks tcp连接的第一个数据包必须包含随机生成的用于加密的IV。1
2
3
4
5+-------+----------+
| IV | Payload |
+-------+----------+
| Fixed | Variable |
+-------+----------+iv
表示一个随机生成的向量payload
是加密传输的数据,可以是target address
或user data
,target address
会被加在第一个user data
的前面一旦收到这个数据包,就能根据
iv
和password
进行解密出原始数据。对于ss-server
,数据随后将被转发到目标地址。对于ss-local
,数据被转发到客户端。之后进入tcp stream传输阶段,在这个阶段,数据被用相同的iv
加密,并直接传输,不需要再额外添加iv
。tcp stream可以表示为:1
[iv][target address][user data][user data][user data].....
二、RC4-MD5加密算法
本文选用rc4-md5加密算法对ss流量进行加密,需要注意的是RC4
的加密算法已不再安全,如果自己使用的话最好选择AEAD加密方式
RC4-MD5
本质上是RC4
的扩展。它对key进行MD5运算,得到RC4
的key,最终被RC4
算法用于生成密钥流。用户的密码会经过下面步骤转换为RC4
的密钥流
- k1 = bytes(password)
- k2 = generate_key(k1)
- rc4-md5-key = md5(k2,iv)
生成key的算法如下
1 | fn generate_key(data: &[u8], key_len: usize) -> Vec<u8> { |
生成rc4-md5-key的算法如下
1 | fn generate_rc4_key(password: &[u8], iv: &[u8]) -> Vec<u8> { |
为了对tcp steam进行rc4加密,需要增加两个struct:
1 | pub struct CryptoRead<R: Read> { |
在了解加解密过程之后就可以继续完成之前的代码了:
1 | fn connect(mut stream: TcpStream, cfg: Cfg) -> Result<(), Error> { |
三、总结
本文介绍了ss协议,并用rust实现了一个简单的ss客户端,算是自己对rust学习过程的一个记录吧,项目总共耗时大约三个晚上,其中大量时间花在了和编辑器斗智斗勇上,对rust还是不够熟练。文中的完整代码可以参考ss