本文来自网易云社区。
3. ***工作原理
终于说到***工作原理了,在说这个之前必须先介绍以下普通socks 5的工作原理,将之与***的“变异版”进行对比,就可以看出***处理的妙处了!!
1) 普通Socks 5 工作原理
普通的代理是直接应用Socks 5 协议进行交互,也就是说客户端便是你的主机,Socks 5的Server是远程的代理机,具体的工作模式如下:
Socks 5客户端 <---Socks 5---> Socks 5服务器 <---正常请求---> 目标主机
Socks 5客户端在与Socks 5服务器交互的整个过程是有可能暴露在整个互联网中的,因此很容易被监控到,根据协议特征也可以很容易识别出来,若采取普通的Socks 5代理方式的话,若用于FQ去看外边的世界,这种方式很容易被墙,代理服务器的IP极容易被加入黑名单,也就导致此代理的寿终正寝,因此一种新的方式***出现了。
2) *** Socks 5工作原理
***的处理方式是将Socks 5客户端与Socks5服务器的连接提前,Socks5协议的交互完全是在本地进行的,在网络中传输的完全是利用加密算法加密后的密文,这就很好的进行了去特征化,使得传输的数据不是很容易的被特征识别,本人认为这是***的最大的创新了,具体的流程如下:
Socks 5客户端 <---Socks 5---> sslocal
∧
|
密文
|
∨
ssserver <---正常请求---> 目标主机
其它方面的处理都与普通代理一样,特殊之处将Socks 5服务器拆成了两个部分:
- 本地的sslocal:sslocal对于Socks 5客户端便是Socks 5服务器,对于Socks 5客户端是透明的,sslocal完成与Socks 5客户端所有的交互。
- 远程的ssserver:ssserver对于目标主机同样也是Socks 5服务器,对于目标主机是透明的,完成Socks 5服务器与目标主机的所有操作。
- sslocal-ssserver:sslocal接收到Socks 5客户端发送的数据,会将数据加密,并将配置信息发送到ssserver,ssserver接收到配置信息进行权限验证,然后将数据进行解密,然后将明文发往目标主机;当目标主机响应ssserver,ssserver将接收到的数据进行解包,并将数据加密,发送到sslocal,sslocal接收到加密后的数据进行解密,再发送给Socks 5客户端,这就完成了一次交互。
1) 源码文件结构
***有多中语言的版本,Python是原版,整个项目的代码不多,总共4000多行,因此整体看起来并不是很困难,有时间的同学可以仔细去看下,我下载的是2.9.1版本的代码。
|-shadowsocks-2.9.1
| |---- LICENSE
| |-debian
| | |---- shadowsocks.manpages
| | |---- compat
| | |---- install
| | |---- sslocal.1
| | |---- changelog
| | |---- init.d
| | |---- config.json
| | |---- ssserver.1
| | |-source
| | | `---- format
| | |---- docs
| | |---- rules
| | |---- copyright
| | |---- control
| | `---- shadowsocks.default
| |---- Dockerfile
| |---- CHANGES
| |-tests
| | |---- server-dnsserver.json
| | |---- nose_plugin.py
| | |---- coverage_server.py
| | |---- graceful_cli.py
| | |---- graceful_server.py
| | |---- aes.json
| | |---- server-multi-passwd-empty.json
| | |-socksify
| | | |---- socks.conf
| | | `---- install.sh
| | |---- jenkins.sh
| | |---- aes-cfb8.json
| | |---- test_udp_src.sh
| | |---- gen_multiple_passwd.py
| | |---- salsa20.json
| | |---- test_graceful_restart.sh
| | |---- test.py
| | |---- client-multi-server-ip.json
| | |---- setup_tc.sh
| | |---- server-multi-passwd-performance.json
| | |---- rc4-md5.json
| | |---- test_command.sh
| | |---- test_large_file.sh
| | |---- chacha20.json
| | |---- server-multi-passwd-table.json
| | |---- table.json
| | |-libsodium
| | | `---- install.sh
| | |---- ipv6.json
| | |---- workers.json
| | |---- graceful.json
| | |---- chacha20-ietf.json
| | |---- fastopen.json
| | |---- salsa20-ctr.json
| | |---- test_udp_src.py
| | |---- server-multi-passwd.json
| | |---- ipv6-client-side.json
| | |---- rc4-md5-ota.json
| | |---- test_daemon.sh
| | |---- assert.sh
| | |---- server-multi-passwd-client-side.json
| | |---- aes-cfb1.json
| | |---- server-multi-ports.json
| | `---- aes-ctr.json
| |---- MANIFEST.in
| |-utils
| | |-fail2ban
| | | `---- shadowsocks.conf
| | |---- README.md
| | `---- autoban.py
| |---- README.md
| |---- setup.py
| |---- .gitignore
| |---- CONTRIBUTING.md
| |---- README.rst
| |---- .travis.yml
| |-shadowsocks
| | |---- encrypt.py
| | |---- lru_cache.py
| | |-crypto
| | | |---- openssl.py
| | | |---- util.py
| | | |---- __init__.py
| | | |---- rc4_md5.py
| | | |---- sodium.py
| | | `---- table.py
| | |---- server.py
| | |---- local.py
| | |---- udprelay.py
| | |---- shell.py
| | |---- __init__.py
| | |---- eventloop.py
| | |---- tcprelay.py
| | |---- common.py
| | |---- manager.py
| | |---- asyncdns.py
| | `---- daemon.py
***的主要代码都在shadowsocks目录下,其他目录提供了一下打包、测试和许可协议的内容,以下简单介绍一下各个文件的内容:
shadowsocks
|---- encrypt.py 提供加密函数调用
|---- lru_cache.py 实现LRU缓存,用于应对并发量比较大的状况
|-crypto 加密功能包
| |---- openssl.py:openssl库调用
| |---- util.py:工具类
| |---- __init__.py
| |---- rc4_md5.py:rc4-md5加密
| |---- sodium.py:sodium加密
| `---- table.py
|---- server.py:ssserver实现
|---- local.py:sslocal实现
|---- udprelay.py:udp代理方式实现
|---- shell.py:shell命令实现
|---- __init__.py
|---- eventloop.py:事件循环
|---- tcprelay.py:tcp代理方式实现
|---- common.py:公用类
|---- manager.py:管理事件处理、连接处理等等
|---- asyncdns.py:异步dns解析
`---- daemon.py:守护线程实现
2) 源码分析
接下来分析一下ss的主流程代码,在整体对ss的工作原理有一个深入的了解。
I. local & server
local.py中的代码实现的是本地客户端的实现,代码很短,几十行,将日志等对主流程无用代码精简过后,如下:
# 加载配置文件
config = shell.get_config(True)
# 是否运行为守护进程
daemon.daemon_exec(config)
# 创建异步dns查询对象
dns_resolver = asyncdns.DNSResolver()
# 创建tcp代理方式转发对象
tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
# 创建udp代理方式转发对象
udp_server = udprelay.UDPRelay(config, dns_resolver, True)
# 创建事件循环处理对象
loop = eventloop.EventLoop()
# 将dns查询、tcp代理方式转发、udp代理方式转发绑定到事件循环
dns_resolver.add_to_loop(loop)
tcp_server.add_to_loop(loop)
udp_server.add_to_loop(loop)
# 预设信号处理函数,接收到正常的退出信号
signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler)
# SIGINT是键盘ctrl+c
signal.signal(signal.SIGINT, int_handler)
# 开启事件循环
loop.run()
server.py和local.py的基本流程时差不多的,与local.py不同的地方需要对多个客户端连接过来的请求进行处理,因此会多一些流程,下面是精简过的代码:
# 加载配置文件
config = shell.get_config(False)
# 是否运行为守护进程
daemon.daemon_exec(config)
# 创建异步dns查询对象
dns_resolver = asyncdns.DNSResolver()
# 添加tcp代理方式转发
tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False))
# 添加udp代理方式转发
udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False))
# 预设信号处理函数,接收到正常的退出信号
signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM),child_handler)
# SIGINT是键盘ctrl+c
signal.signal(signal.SIGINT, int_handler)
# 创建事件循环处理对象
loop = eventloop.EventLoop()
# 将dns绑定到事件循环
dns_resolver.add_to_loop(loop)
# 实现多个线程处理
def run_server():
...
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
# 开启事件处理无限循环
loop.run()
...
if int(config['workers']) > 1:
... # 生成多个线程来处理,也就是配置的workers
else:
run_server()
II. udp & tcp
udprelay.py为UDP代理方式的实现,一下为精简后代码,抽取主要流程。
SOCKS5 UDP 请求
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
SOCKS5 UDP 响应
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
shadowsocks UDP 请求 (加密前)
+------+----------+----------+----------+
| ATYP | DST.ADDR | DST.PORT | DATA |
+------+----------+----------+----------+
| 1 | Variable | 2 | Variable |
+------+----------+----------+----------+
shadowsocks UDP 响应 (加密前)
+------+----------+----------+----------+
| ATYP | DST.ADDR | DST.PORT | DATA |
+------+----------+----------+----------+
| 1 | Variable | 2 | Variable |
+------+----------+----------+----------+
shadowsocks UDP 请求和响应 (加密后)
+-------+--------------+
| IV | PAYLOAD |
+-------+--------------+
| Fixed | Variable |
+-------+--------------+
命名:
dest 目标主机
local ss的本地server
remote ss的远程server
client 向其他UDPserver发送请求的UDP clients
server 处理用户请求的UDP server
#构造函数,config为配置文件,dns_resolver为dns解析,is_local用于区分是local还是server,stat_callback为回调函数用于统计,在manager.py中有调用
def __init__(self, config, dns_resolver, is_local, stat_callback=None):
... #全局参数声明、赋值
#创建socket、绑定端口、设置阻塞方式
server_socket = socket.socket(af, socktype, proto)
server_socket.bind((self._listen_addr, self._listen_port))
server_socket.setblocking(False)
...
# 获取服务器IP+端口
def _get_a_server(self):
...
# 关闭连接
def _close_client(self, client):
...
# 处理server
def _handle_server(self):
# 获取udp server
server = self._server_socket
# 接收数据
data, r_addr = server.recvfrom(BUF_SIZE)
# 解密数据,如果是local的话,因为接收的是本地的数据,是不需要解密的
data, key, iv = encrypt.dencrypt_all(self._password,self._method,data)
# 解析头
header_result = parse_header(data)
addrtype, dest_addr, dest_port, header_length = header_result
...
#如果是local需要将数据发送到remote,如果是remote需要将数据发送到dest
if self._is_local:
server_addr, server_port = self._get_a_server()
else:
server_addr, server_port = dest_addr, dest_port
...
# 加密数据
data = encrypt.encrypt_all_m(key, iv, m, self._method, data)
...
#发送
client.sendto(data, (server_addr, server_port))
# 处理client
def _handle_client(self, sock):
# 接收数据
data, r_addr = sock.recvfrom(BUF_SIZE)
...
# 加密数据
data = encrypt.encrypt_all(self._password, self._method, 0, data)
...
# 获取地址
client_addr = self._client_fd_to_server_addr.get(sock.fileno())
...
# 发送
self._server_socket.sendto(response, client_addr)
# 添加事件
def add_to_loop(self, loop):
...
# 处理事件
def handle_event(self, sock, fd, event):
# 区分处理client或server
if sock == self._server_socket:
self._handle_server()
elif sock and (fd in self._sockets):
self._handle_client(sock)
# 周期性处理
def handle_periodic(self):
...
# 关闭UDP代理
def close(self, next_tick=False):
tcprelay.py 抽取主流程代理如下:
# 对于每个端口,有一个tcp relay,对于每个tcp relay
# 对于每个连接,有一个tcp relay handler来处理
# 对于每个handler,有两个socket,
# - local 连接到client,
# - remote 连接到远程主机(要访问的主机)
# 对于每个handler可以有如下几个阶段:
# sslocal
# - stage 0 接收从本地的method请求,并返回选择信息
# - stage 1 从本地接收请求地址,并进行dns解析
# - stage 2 udp相关
# - stage 3 dns解析完毕,连接远程主机
# - stage 4 连接中,并接收本地发送的数据
# - stage 5 远程主机连接成功,发送数据
# ssserver
# - stage 0 直接跳到阶段1
# - stage 1 从本地接收请求地址,并进行dns解析
# - stage 2 udp相关
# - stage 3 dns解析完毕,连接远程主机
# - stage 4 连接中,并接收本地发送的数据
# - stage 5 远程主机连接成功,发送数据
# 对于每个handler,有两个流方向:
# upstream:client->server,读本地,写入远程主机
# downstream:server->client,读远程主机,写入本地
# config为配置,dns_resolver为dns解析,is_local是否为client,stat_callback回调用于统计
def __init__(self, config, dns_resolver, is_local, stat_callback=None):
... # 全局参数声明、赋值
# 创建socket、绑定端口、设置阻塞方式
server_socket = socket.socket(af, socktype, proto)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(sa)
server_socket.setblocking(False)
...
# 添加到事件循环
def add_to_loop(self, loop):
...
# 处理事件
def handle_event(self, sock, fd, event):
...
# 如果是server_socket 监听
if sock == self._server_socket:
...
# 监听
conn = self._server_socket.accept()
...
# 如果不是,进行事件处理
else:
...
handler.handle_event(sock, event)
# 周期性处理
def handle_periodic(self):
...
# 关闭TCP代理
def close(self, next_tick=False):
相关阅读:***原理详解(上篇)
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易云社区,经作者范鹏程授权发布。