Aione's blog

一起快乐摸鱼吧🐳

  1. 1. ss的目录结构
  2. 2. ss的基本架构
  3. 3. 事件处理
  4. 4. TCP服务器
  5. 5. UDP服务器
  6. 6. 异步DNS

虽然ss已经去世了很久,但它依旧活在我等网友心中。

一直想看看ss的实现方式,本次就通过python版本shadowsocks源码来解读ss,本系列文章会分成几个部分,我会尽可能做到详细。
首先祭奠原作者@clowwindy
代码为@ziggear的备份版本,版本号为2.8.2,地址在这

ss的目录结构

shadowsocks的目录如下(部分目录已略去)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── shadowsocks
│   ├── asyncdns.py 异步dns
│   ├── common.py 辅助函数
│   ├── crypto 数据加密
│   │   ├── __init__.py
│   │   ├── openssl.py
│   │   ├── rc4_md5.py
│   │   ├── sodium.py
│   │   ├── table.py
│   │   └── util.py
│   ├── daemon.py 守护进程
│   ├── encrypt.py 统一的加密解密接口
│   ├── eventloop.py io复用并提供统一的接口
│   ├── __init__.py
│   ├── local.py sslocal部分
│   ├── lru_cache.py lru缓存
│   ├── manager.py
│   ├── server.py ssserver部分
│   ├── shell.py shell支持
│   ├── tcprelay.py tcp代理
│   └── udprelay.py udp代理
|

上图所示的目录除掉了测试文件。

ss的基本架构

ss是基于socks5协议实现的,但和socks5又有所不同。
关于socks5协议的简析在这里

在ss的目录中有两个main函数,一个在local.py中,另一个在server.py,这两个组成了ss的基本架构

1
2
                             
req <---socks5---> sslocal <----crypto----> ssserver <----> remote_server

在ss当中,作者把proxy服务器拆成了客户端和服务端两个部分,并把中间传输数据进行加密,由此提高数据的安全性。

我们首先就从客户端部分代码看起,为了阅读方便,这里略去部分代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# python sslocal
# local.py
config = shell.get_config(True)

daemon.daemon_exec(config)

try:
dns_resolver = asyncdns.DNSResolver()
tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
udp_server = udprelay.UDPRelay(config, dns_resolver, True)
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
tcp_server.add_to_loop(loop)
udp_server.add_to_loop(loop)
...
loop.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# python ssserver
# server.py
daemon.daemon_exec(config)
...
def run_server():
def child_handler(signum, _):
logging.warn('received SIGQUIT, doing graceful shutting down..')
list(map(lambda s: s.close(next_tick=True),
tcp_servers + udp_servers))
signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM),
child_handler)

def int_handler(signum, _):
sys.exit(1)
signal.signal(signal.SIGINT, int_handler)

try:
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers))

daemon.set_user(config.get('user', None))
loop.run()
...

可以看到,sslocal和ssserver的代码差距不大,逻辑是基本一致的,其实我们稍作思考便可以明白,作为sslocal和ssserver可以复用的地方非常的多,差别就在sslocal要处理socks协议而server端不用。

c/s端有一个eventloop,并把dns_resolver,tcp_server,udp_server加入了事件循环,处理事件。让我们从这里开始来了解ss究竟是怎么写的。

事件处理

事件处理的代码都在eventloop.py里,里面主要对不同系统的io复用都封装成了epoll的形式,方便进行统一的调用。

事件处理的主要代码如下,(超时处理已略去):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def run(self):
events = []
while not self._stopping:
asap = False
try:
events = self.poll(TIMEOUT_PRECISION)
...
for sock, fd, event in events:
handler = self._fdmap.get(fd, None)
if handler is not None:
handler = handler[1]
try:
handler.handle_event(sock, fd, event)
...

一旦poll中有事件触发,就通过该事件的文件描述符去寻找对应的handler,并通过handler.handle_event处理该事件。
handle_event在tcp_relay.py

TCP服务器

当在eventloop中的handler是和TCP有关时,会调用tcp_relay.py中的hanlde_event函数,如果该socket是指向自己的,那么说明有新的链接,创建一个新的hanlder并对其进行处理。

如果不是,就找到与之对应的hanlder处理

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
class TCPRelay(object):
def __init__(self, config, dns_resolver, is_local, stat_callback=None):
...

def add_to_loop(self, loop):
...

def remove_handler(self, handler):
...

def update_activity(self, handler, data_len):
...

def _sweep_timeout(self):
...

def handle_event(self, sock, fd, event):
# handle events and dispatch to handlers
if sock:
...
if sock == self._server_socket:
if event & eventloop.POLL_ERR:
# TODO
raise Exception('server_socket error')
try:
logging.debug('accept')
conn = self._server_socket.accept()
TCPRelayHandler(self, self._fd_to_handlers,
self._eventloop, conn[0], self._config,
self._dns_resolver, self._is_local)
except (OSError, IOError) as e:
error_no = eventloop.errno_from_exception(e)
if error_no in (errno.EAGAIN, errno.EINPROGRESS,
errno.EWOULDBLOCK):
return
else:
shell.print_exception(e)
if self._config['verbose']:
traceback.print_exc()
else:
if sock:
handler = self._fd_to_handlers.get(fd, None)
if handler:
handler.handle_event(sock, event)
else:
logging.warning('poll removed fd')

def handle_periodic(self):
...

def close(self, next_tick=False):
...

UDP服务器

UDP对事件的处理更简单,就分成client和server

1
2
3
4
5
6
7
8
9
def handle_event(self, sock, fd, event):
if sock == self._server_socket:
if event & eventloop.POLL_ERR:
logging.error('UDP server_socket err')
self._handle_server()
elif sock and (fd in self._sockets):
if event & eventloop.POLL_ERR:
logging.error('UDP client_socket err')
self._handle_client(sock)

异步DNS

简单的错误处理,没出错就直接丢给_handle_data()

1
2
3
4
5
6
7
8
9
10
11
def handle_event(self, sock, fd, event):
if sock != self._sock:
return
if event & eventloop.POLL_ERR:
...
else:
data, addr = sock.recvfrom(1024)
if addr[0] not in self._servers:
logging.warn('received a packet other than our dns')
return
self._handle_data(data)


ref
[1]shadowsocks源码分析-协议与结构

Author : Aione
本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
Link to this article : https://aione.me/2019/05/21/ss_0x00/

This article was last updated on days ago, and the information described in the article may have changed.