读 ShadowSocks 源码
编程    Python 翻墙    2015-09-11 03:49:10    2002    0    0

ShadowSocks 是一个基于 socket 加密通信的管道软件,用途也很多.但因为其数据完全加密\密码也不需要同步\支持DNS协议等特性,成为一代翻墙利器。为了学习 Python,我阅读了 Shadowsocks 的源码,代码总量让我震惊的少。

从 github 上检出 Shadowsocks 的源码,目录结构是这样的.

│ .gitignore
│ .travis.yml
│ CHANGES
│ CONTRIBUTING.md
│ LICENSE
│ MANIFEST.in
│ README.md
│ README.rst
│ setup.py

├─debian
│ │ changelog
│ │ compat
│ │ config.json
│ │ control
│ │ copyright
│ │ docs
│ │ init.d
│ │ install
│ │ rules
│ │ shadowsocks.default
│ │ shadowsocks.manpages
│ │ sslocal.1
│ │ ssserver.1
│ │
│ └─source
│ format

├─shadowsocks
│ │ asyncdns.py
│ │ common.py
│ │ encrypt.py
│ │ eventloop.py
│ │ local.py
│ │ lru_cache.py
│ │ server.py
│ │ tcprelay.py
│ │ udprelay.py
│ │ utils.py
│ │ _init.py
│ │
│ └─crypto
│ ctypes_openssl.py
│ m2.py
│ rc4_md5.py
│ salsa20_ctr.py
│ table.py
│ util.py
│ _
init__.py

└─tests

│  aes-cfb1.json
│  aes-cfb8.json
│  aes-ctr.json
│  aes.json
│  fastopen.json
│  ipv6-client-side.json
│  ipv6.json
│  nose_plugin.py
│  rc4-md5.json
│  salsa20.json
│  server-multi-passwd-client-side.json
│  server-multi-passwd-table.json
│  server-multi-passwd.json
│  server-multi-ports.json
│  table.json
│  test.py
│  workers.json
│
└─socksify
        install.sh
        socks.conf

主要代码就是 shadowsocks 子目录下的 10个文件和 crypto 加密算法目录。

local.py 和 server.py 分别是 Shadowsocks 的 local(本地端)和 server(服务端)。浏览器开启 sockes 代理之后,数据会以明文的形式发给 local,local会把数据加密,转发给 server ,server 还原数据,完成实际网页请求,获得结果之后,重新加密,返还给 local ,local 将数据解密,发给浏览器,至此,完成一次完整的请求。

tcprelay.py 和 udprelay.py 分别是 TCP 方式的 socket 实现和 UDP 的socket 实现。在 socks5 代理协议里面用到了两种 socket 实现。

eventloop.py 是一切异步调用基础,通过封装 epoll 实现的事件循环。

lru_cache 是一个内存中的带失效时间的 KV缓存。

asyncdns 是DNS ,encrypt.py 是加密, common.py 和 utils.py 是辅助工具.

lru_cache

了解完整体工作后,从最简单的 lru_cache 看起,这是一个完全独立的模块,get 和 set 都是 O(1) 的时间复杂度; 但是不是多线程安全的。

collections 提供了一些高级类型
['Callable','Container','Counter','Hashable','ItemsView','Iterable','Iterator','KeysView','Mapping','MappingView','MutableMapping','MutableSequence','MutableSet','OrderedDict','Sequence','Set','Sized','ValuesView','defaultdict','deque','namedtuple']

这里用到了 MutableMapping\defaultdict\deque 创建了一个具有超时时间的内存 KV 数据库. 改写的 get 和 set 都对列表的操作,顺便更新时间.最重要的函数是 sweep

    def sweep(self):
        # O(m)
        now = time.time()
        c = 0
        while len(self._last_visits) > 0:
            least = self._last_visits[0]
            if now - least <= self.timeout:
                break
            if self.close_callback is not None:
                for key in self._time_to_keys[least]:
                    if self._store.__contains__(key):
                        if now - self._keys_to_last_time[key] > self.timeout:
                            value = self._store[key]
                            self.close_callback(value)
            for key in self._time_to_keys[least]:
                self._last_visits.popleft()
                if self._store.__contains__(key):
                    if now - self._keys_to_last_time[key] > self.timeout:
                        del self._store[key]
                        del self._keys_to_last_time[key]
                        c += 1
            del self._time_to_keys[least]
        if c:
            logging.debug('%d keys swept' % c)

功能是检查最近变动的元素是否应该被删除.如果应该被删除,又分为是否需要调用回调函数,如果需要就调用,如果不需要则直接删除,多打一个 log . 我觉得这个函数应该写成这样更好一点:

    def sweep(self):
        # O(m)
        now = time.time()
        c = 0
        while len(self._last_visits) > 0:
            least = self._last_visits[0]
            if now - least <= self.timeout:
                break
            for key in self._time_to_keys[least]:
                self._last_visits.popleft()
                if self._store.__contains__(key):
                    if now - self._keys_to_last_time[key] > self.timeout:
                        if self.close_callback is not None:
                            self.close_callback(value)
                        del self._store[key]
                        del self._keys_to_last_time[key]
                        c += 1
            del self._time_to_keys[least]
        if c:
            logging.debug('%d keys swept' % c)

eventloop

eventloop 是整个工程的驱动基础. 他封装了 Epoll,Select,Kqueue ,这三个模型是解决相同的问题的, 其中 Epoll 的效率最高,这里也是优先使用 Epoll 模型,如果操作系统不支持,就选择 Kqueue ,Kqueue也不支持,才选择 Select 模型.

经过 eventloop 封装之后,就无需关心操作系统的支持情况和个模型的使用区别,全部用类似 epoll 的操作方法,将需要响应的对象和响应的方法传递进去,事件触发之后调用回调函数.

tcprelay.py 和 udprelay.py

tcprelay.py 和 udprelay.py 就是直接调用 eventloop 来实现功能的层. _handle_events 是接受回调的函数.tcprelay 中的两个类 TCPRelayHandler 和 TCPRelay. TCPRelay就是一开始就开启的一个 socket 服务端, 用来接收请求.TCPRelayHandler是个 socket 的客户端 用来发送请求.udprelay 也是类似.

local.py 和 server.py

local.py 和 server.py 是基于 tcprelay.py 和 udprelay.py 的.不管是 local端 还是 server端,都各自开了一个 TCP 一个 UDP 的服务端,收到请求之后又会新开对应的客户端将数据发送出去.

文档导航