shadowsocks 协议
shadowsocks协议包格式为IV向量加Playload,这是加密后的结果.
+-------+----------+
| IV | Payload |
+-------+----------+
| Fixed | Variable |
+-------+----------+
解密后为
+--------------+---------------------+------------------+----------+
| Address Type | Destination Address | Destination Port | Data |
+--------------+---------------------+------------------+----------+
| 1 | Variable | 2 | Variable |
+--------------+---------------------+------------------+----------+
服务端解密需要这个IV向量和原来就协商好的密钥进行解密,握手完成后后续的所有TCP 数据包不会再带上IV,而是使用握手时协商的那个IV。
源码解析
这里分析shadowsocks-server的源代码,整体流程为:
监听服务
初始化加密对象
解密客户端数据
连接远程主机
建立通讯连接
监听服务
这里根据配置文件,进行端口监听,后面就是常规的Accept()等待连接.
for port, password := range config.PortPassword {
go run(port, password, config.Auth)
if udp {
go runUDP(port, password, config.Auth)
}
}
初始化加密对象
当有链接来到后首先检查是否已经初始化解密对象,然后封装net.Conn.这里的cipher根据不同的加密算法实现了流加密解密XORKeyStream(dst, src []byte)接口,具体可以看golang自带标准库cipher.Stream,当然这个接口要读取iv向量后才能实现.
//如果没有初始化,解析初始化.
if cipher == nil {
log.Println("creating cipher for port:", port)
cipher, err = ss.NewCipher(config.Method, password)
if err != nil {
log.Printf("Error generating cipher for port: %s %v\n", port, err)
conn.Close()
continue
}
}
//这里要根据password产生key.
key := evpBytesToKey(password, mi.keyLen)
解密客户端数据
net.Conn被封装后Read方法读出的数据即为解密后的数据
func (c *Conn) Read(b []byte) (n int, err error) {
//如果解密接口没有实现,读取iv向量初始化解密接口.
if c.dec == nil {
iv := make([]byte, c.info.ivLen)
if _, err = io.ReadFull(c.Conn, iv); err != nil {
return
}
//读取到iv向量后就可以初始化解密接口
if err = c.initDecrypt(iv); err != nil {
return
}
if len(c.iv) == 0 {
c.iv = iv
}
}
//如果读取buff大于内置buff则分配内存
cipherData := c.readBuf
if len(b) > len(cipherData) {
cipherData = make([]byte, len(b))
} else {
cipherData = cipherData[:len(b)]
}
//读取数据后解密返回
n, err = c.Conn.Read(cipherData)
if n > 0 {
c.decrypt(b[0:n], cipherData[0:n])
}
return
}
连接远程主机
当读取到解密的数据后就要开始解包了,这里只复制主要代码.
//这里判断一个字节的地址类型
addrType := buf[idType]
//这里根据地址类型的不同读取地址
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
return
}
//这里读取2个字节的端口
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
//当获取到地址和端口后调用dial连接远程主机
remote, err := net.Dial("tcp", host)
剩下可能还会有ota数据,根据ota已经被弃用了吧,这里就不再说明了,有兴趣的同学可以自己去查资料.
建立通讯连接
这里建立通讯连接有点像使用了io.Copy(),分别连接两个conn.
//PipeThenClose有点像io.Copy
if ota {
go ss.PipeThenCloseOta(conn, remote)
} else {
go ss.PipeThenClose(conn, remote)
}
ss.PipeThenClose(remote, conn)
//PipeThenClose的原理就是不断读取然后写入,直到出错.
for{
n, err := src.Read(buf)
// read may return EOF with n > 0
// should always process n > 0 bytes before handling error
if n > 0 {
// Note: avoid overwrite err returned by Read.
if _, err := dst.Write(buf[0:n]); err != nil {
Debug.Println("write:", err)
break
}
}
}
结语
这里分析了shadowsocks-server的源代码,还有shadowsocks-local的感觉大同小异就是多了一个加密的过程.
有疑问加站长微信联系(非本文作者)