上一次介绍了ss源码中各个事件处理函数完成的工作,这次具体分析一下协议的建立以及请求数据的传输过程。

因为ss的local和server共用一个类以及一系列的事件处理函数,所以看起来稍显复杂。下面来将local和server端结合分析一下。

首先进程向local端发送发送请求协商版本和认证方法。local端对其响应05 00代表无需认证。

# local端

...
elif is_local and self._stage == STAGE_INIT:
# TODO check auth method
# 给进程发送确认信息
self._write_to_sock(b'\x05\00', self._local_sock)
# 状态转移至接收地址
self._stage = STAGE_ADDR
return
...

进程向local端发来请求细节,包含remote的ip和端口等信息。

# local端

...

    elif cmd == CMD_CONNECT:
# just trim VER CMD RSV
# 只截取从第四个字节开始的数据,包含请求访问的ip地址和端口
data = data[3:]
...
# 提取出地址类型,ip地址,端口号,头部总长度等信息
header_result = parse_header(data)
if header_result is None:
raise Exception('can not parse header')
addrtype, remote_addr, remote_port, header_length = header_result
logging.info('connecting %s:%d from %s:%d' %
(common.to_str(remote_addr), remote_port,
self._client_address[0], self._client_address[1]))
# 要代理请求的remote地址以及端口号
self._remote_address = (common.to_str(remote_addr), remote_port)
# pause reading
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
self._stage = STAGE_DNS
# local
if self._is_local:
# 响应进程
self._write_to_sock((b'\x05\x00\x00\x01'
b'\x00\x00\x00\x00\x10\x10'),
self._local_sock)
# data数据包括请求细节,加密发送给server
data_to_send = self._encryptor.encrypt(data)
# 要写给server的数据
self._data_to_write_to_remote.append(data_to_send)
# notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(self._chosen_server[0],
self._handle_dns_resolved)

local端将进程的请求细节发送给server端

# local端

    def _on_remote_write(self):
# 进入传输阶段
self._stage = STAGE_STREAM
# 如果有需要写的数据
if self._data_to_write_to_remote:
data = b''.join(self._data_to_write_to_remote)
self._data_to_write_to_remote = []
# 写到server
self._write_to_sock(data, self._remote_sock)
else:
# 否则,更新状态
self._update_stream(STREAM_UP, WAIT_STATUS_READING)

server端接受local发来的加密请求细节,并解密。

# server端

...
if not is_local:
# 服务端本地sock只可能是客户端发来的请求信息
# 将数据解密
data = self._encryptor.decrypt(data)
if not data:
return ... # 客户端,等待进程发来请求信息,或者服务器端,刚初始化
elif (is_local and self._stage == STAGE_ADDR) or \
(not is_local and self._stage == STAGE_INIT):
# 客户端:接受进程的请求信息,服务器:接受客户端的请求信息
self._handle_stage_addr(data)
...
... # 提取出地址类型,ip地址,端口号,头部总长度等信息
def _handle_stage_addr(self, data):
# server端 ...
header_result = parse_header(data)
if header_result is None:
raise Exception('can not parse header')
addrtype, remote_addr, remote_port, header_length = header_result
logging.info('connecting %s:%d from %s:%d' %
(common.to_str(remote_addr), remote_port,
self._client_address[0], self._client_address[1]))
# 要代理请求的remote地址以及端口号
self._remote_address = (common.to_str(remote_addr), remote_port)
# pause reading
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
self._stage = STAGE_DNS ... else:
# server
if len(data) > header_length:
self._data_to_write_to_remote.append(data[header_length:])
# notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(remote_addr,
self._handle_dns_resolved)

server将解密后的数据发送给remote

# server端

def _on_remote_write(self):
# 进入流传输阶段
self._stage = STAGE_STREAM
# 如果有需要写的数据
if self._data_to_write_to_remote:
data = b''.join(self._data_to_write_to_remote)
self._data_to_write_to_remote = []
self._write_to_sock(data, self._remote_sock)
else:
# 否则,更新状态
self._update_stream(STREAM_UP, WAIT_STATUS_READING)

server接收remote的响应,加密后回反给local

# server端

def _on_remote_read(self):
# handle all remote read events
data = None
try:
# 从远程套接字读取数据
data = self._remote_sock.recv(BUF_SIZE) except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in \
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
return
if not data:
self.destroy()
return
self._update_activity(len(data)) ... else:
# 为server,说明请求信息回反,加密数据
data = self._encryptor.encrypt(data)
try:
# 写到local
self._write_to_sock(data, self._local_sock)
except Exception as e:
shell.print_exception(e)
if self._config['verbose']:
traceback.print_exc()
# TODO use logging when debug completed
self.destroy()

local接收server的加密数据,解密后响应给进程。

# local端
def _on_remote_read(self):
# handle all remote read events
data = None
try:
# 从远程套接字读取数据
data = self._remote_sock.recv(BUF_SIZE) except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in \
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
return
if not data:
self.destroy()
return
self._update_activity(len(data))
# 为client,说明server回反了数据
if self._is_local:
# 解密数据
data = self._encryptor.decrypt(data) ... try:
# 响应给进程
self._write_to_sock(data, self._local_sock)
except Exception as e:
shell.print_exception(e)
if self._config['verbose']:
traceback.print_exc()
# TODO use logging when debug completed
self.destroy()

以上就是一次从协议的建立到代理请求访问远程服务器的完整过程。

有关socks5协议相关内容可以参考:

https://www.ietf.org/rfc/rfc1928.txt

http://blog.csdn.net/suifengdeshitou/article/details/48782667

ss源码学习--从协议建立到完成一次代理请求的更多相关文章

  1. quagga源码学习--BGP协议的初始化

    quagga支持BGP-4,BGP-4+协议,支持多协议(mpls,isis,ospf等等)以及单播,组播路由的导入和分发. 具体的协议,这里就不附录了,网络上有很多资料,或者RFC. 协议源码的学习 ...

  2. ss源码学习--工作流程

    ss的local端和server端的工作流程相似,因此复用了TCPRelay类和TCPRelayHandler类. 两端均是使用TCPRelay类监听连接,并使用TCPRelayHandler类处理请 ...

  3. quagga源码学习--BGP协议中的routemap

    路由策略的基础知识 定义 路由策略(Routing Policy)作用于路由,主要实现了路由过滤和路由属性设置等功能,它通过改变路由属性(包括可达性)来改变网络流量所经过的路径. 目的 路由器在发布. ...

  4. ss源码学习--事件处理

    为了方便区分,以下分别使用local,server,remote代表ss客户端,ss服务端,以及ss客户端请求访问的远程主机. 在shadowsocks中,无论对于local还是server,都需要建 ...

  5. quagga源码学习--BGP协议对等体连接建立的状态机

    创建完bgp peer之后,就要bgp start了,不然费那么大劲创建出来不做事情就销毁了,就很尴尬了. 那么对等体一旦start起来,就会进入各自的状态,在不同的状态下处理各自的事件消息. 下面列 ...

  6. quagga源码学习--BGP协议创建对等体

    现有的路由协议都是通过分布式协议逐个配置协商运行的,协议协议,一个就不需要协议咯,至少2个才能够协议着做事情嘛,不过呢,这样就出现网元过多配置困难的问题,对网管软件要求也越来越高, SDN或许可能改变 ...

  7. quagga源码学习--BGP协议对等体连接tcp md5签名认证选项

    bgp使用tcp连接,每个bgp实例自身是peer的一个tcp server端,同时也是peer的tcp client端. 1.在bgp_create之后都建立自己的socket服务端开始监听179端 ...

  8. quagga源码学习--BGP协议路由更新

    BGP的核心就是交换路由,所以关键的部分还是在路由的更新与撤销上面,这之间包含了冗长的属性,community等等处理过程,不做详述. bgp_read函数是路由更新的事件处理函数,在收到BGP_MS ...

  9. 物联网防火墙himqtt源码之MQTT协议分析

    物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...

随机推荐

  1. pip 国内源

    pip install django -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com pip in ...

  2. 47.纯 CSS 创作一个蝴蝶标本展示框

    html,body{ margin:; padding:; } body{ height: 100vh; display: flex; justify-content: center; align-i ...

  3. 《算法》第四章部分程序 part 4

    ▶ 书中第四章部分程序,加上自己补充的代码,图的深度优先遍历 ● 无向图的深度优先遍历,有向 / 无向图代码仅若干方法名不同,包括递归和非递归版本,去掉了顶点有效性的检查 package packag ...

  4. Mybatis学习2传统dao开发

    传统dao开发 在mybati基础上 dao和daoimpl 1.工厂工具类 获得SqlSessionFactory SqlSessionFactoryUtil.java package util; ...

  5. spark核心原理

    spark运行结构图如下: spark基本概念 应用程序(application):用户编写的spark应用程序,包含驱动程序(Driver)和分布在集群中多个节点上运行的Executor代码,在执行 ...

  6. <基础> PHP 字符串操作

    explode — 使用一个字符串分割另一个字符串 array explode ( string $delimiter , string $string [, int $limit ] ) implo ...

  7. C++/C#:类Class与结构体Struct的区别

    C++中: 默认的访问控制.继承访问权限不同:struct时public的,class时 private的: 其它基本一样. C#中: struct是值类型,class是引用类型的: struct S ...

  8. APP-3-百度地图应用

    1.百度地图开发平台 http://lbsyun.baidu.com/ 1.1申请账号 1.2Android创建应用 进入百度地图开发平台->控制台->创建应用 发布版SHA1:BA:AD ...

  9. 学习别人的rpc框架

    https://my.oschina.net/huangyong/blog/361751 https://gitee.com/huangyong/rpc 在此文基础上的另一个实现,解决了原文中一些问题 ...

  10. es6 初级之---const 和 默认参数

    1. const 的定义: 1.1 常量定义的时候要赋值,不赋值是会报错的: <!DOCTYPE html> <html lang="en"> <he ...