分享
  1. 首页
  2. 文章

shadowsocks-go源代码分析

pinecone · · 3613 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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的源代码,整体流程为:

  1. 监听服务

  2. 初始化加密对象

  3. 解密客户端数据

  4. 连接远程主机

  5. 建立通讯连接

监听服务

这里根据配置文件,进行端口监听,后面就是常规的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的感觉大同小异就是多了一个加密的过程.


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:pinecone

查看原文:shadowsocks-go源代码分析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
3613 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏