基于Libevent转发的内网端口暴露(三):添加SS5代理功能

  比较好奇Shadowsocks代理是怎么工作的:为什么这么流行这么火?而且实际用起来的速度比vpn、https等代理的速度快的多!查了一下,其是基于sockets5(后面简称ss5)的代理,而ss5的规范文档RFC1928/RFC1929就两三页,算是我看到的最简短的RFC文件了。实际数据流上,就是ss_client告诉服务器自己要访问的主机和端口,然后把本地请求的数据发到ss_server,ss_server再请求目标服务器并将得到的结果返回回来。卧槽!这不就是我sshinner做的事情么!!!
  所以在现有的设计上,添加代理支持应该是很简单的:我只需要保证本地应用程序和ss_client通信满足ss5协议之规定,而ss_client和ss_server通信格式完全可以用自己原先设计的一套就好了。其实开始本来想着客户端和服务器的协议也进行模拟,这样就可以直接用Shadowsocks的多个跨平台客户端了,但是后面还是放弃了,因为需要改动的东西太多了。
  如果想要了解ss5协议,除了有上面两个RFC可供参考之外,可以使用wireshark抓包分析(Windows居然没法抓回环网口的包,差评!!!),此外Shadowsocks早期代码也很具有参考价值,只是后来作者引入了Eventloop异步机制,项目显得比较复杂,同时Shadowsocks-libev是基于Libev实现的,我觉得其最强大的是集成了大量的加密解密算法,牛逼啊!

sshinner

一、概况

  前面已经说到了,创建ss5代理比之前我的端口暴露处理在链路上还简单一些,但也有一些挑战。
  端口暴露的程序比如ssh或者mysql,基本都是长链接,所以一旦链路建成就会比较稳定,后面就是不断的数据转发了。
  ss5代理的操作,当我用chrome做测试客户端的时候,发现链接特别多,而且连接拆建特别活跃。这也好理解,因为根据ss5代理协议的规定,在代理之前需要把目标主机和端口号提供给服务器,现在的前端复杂的要死,即使chrome优化的再好:每一个ip:port在所有资源请求完后再关闭连接,也还是会有很多ss5代理的不断建立和释放,而且chrome的请求是多线程并发的,代理请求十分的迅猛。
  这种情况下,对程序的稳定性是一个很大的考验:数据结构操作必须正确,没有严重的内存泄露,没有竞争条件等,不然过不了多久服务就会被拖垮挂掉;客户端也必须由之前的单线程改为多线程的,否则根本发挥不了异步的优势。

二、数据通信流程

2.1 通信流程

  (1) ss_server程序启动,建立套接字侦听;
  (2) ss_daemon程序启动,连接ss_server,进行用户名、ID号等信息的认证,ss_server建立相应的数据结构;同时ss_daemon建立本地代理的套接字侦听(比如127.0.0.1:1083);
  (3) 应用程序代理请求,ss_daemon采用阻塞的方式进行交互,取得代理程序需要访问的远程主机地址:remote_addr:remote_port;然后将这个连接分配到某个线程池的处理队列中去;
  (4) 线程池调度启动,同时连接ss_server,然后建立两个事件侦听bev_local和bev_srv,但是此时还是未使能的;
  (5) ss_server发送HD_CMD_SS5_ACTIV命令,然后ss_daemon向代理程序返回确认信息,并使能对应的事件侦听bev_local和bev_srv;
  (6) 连接完毕,本地程序和远程服务器交换数据。

2.2 注意事项

  根据协议要求,程序请求的可以是IP地址,也可以是域名的形式,如果是域名的形式,需要做域名解析后才能连接套接字。域名解析是比较花费时间的,所以Libevent支持域名解析的异步模式。
  后面考虑加一个DNS解析代理的功能,国内的DNS环境太恶劣了(防火墙投毒、运营商劫持等),虽然解析得到的IP可能对CDN这样的访问不利,但是起码是干净的吧,用的放心!(本人暂且用的DNSCrypt)

三、数据加密

  在直接转发的情况下,默认谷歌还是打不开的,所以对数据包的加密还是必须的。正如Shadowsocks作者所说的,这边的加密不需要太强的加密算法,因为不是追求对数据保密,对信息敏感的协议本身链路数据就是加密的(比如ssh、https等等),这里主要是对转发数据进行混淆扰动,可以让防火墙过滤器无法检测包的内容和特征,从而避免被阻断连接。
  Libevent本身支持Bufferevent的SSL接口,但是速度比较慢。相对而言这种情况觉得还是用对称加密比较简单高效,所以思路跟之前st_utils的一样:先用RSA对来传输加密KEY,然后后续数据基于这个KEY进行加密解密。
  数据加密一般分为块加密和流加密,分别有典型的aes-256-cfb以及rc4-md5。大名鼎鼎的OpenSSL其实已经提供了各种加密解密的封装接口,对于选择某种算法其实用上层封装接口差异都不是很大。这里用了rc4-md5加密算法,没有深究其原理,感觉就是对某个数据进行“一次异或加密,再次异或解密”这么个意思了,而且每一个会话用一套KEY,每一个连接用一个IV,IV都是双方通过固定的方式计算出来的,因为如果把IV封装到数据包中,而rc4-md5是流加密长度不定,会加大双方通信的复杂度。
  也有人说现在的AES如果新的CPU指令集支持的话,加密解密速度会快很多。呵呵,我觉的现在真正的制约是网速,而不是计算速度(常常都是挂代理反而比不挂代理访问速度更快,我也是日了狗了)。

ss5

四、小结

  到这里,sshinner算是基本完工了,实现了内网端口暴露,以及ss5代理的功能,后面可能有机会再做一个DNS查询代理的功能。从数据包的转发、DNS的查询等充斥着异步操作,而Libevent像个骨架一样支撑着软件的高速运作,各种回调机制方便了程序的开发,真不愧是一款优秀的基于事件的异步开发库,而且维护者写了一份很好的文档,真心赞啊!

  注意:目前端口暴露工作的不错,而dropbox等简易ss5代理也工作的很好,用proxychains wget有时候速度是Shadowsocks的两倍,但是对于chrome代理,一小段时间后就不能工作了,稳定性不行,等我清醒一下再慢慢Debug吧!

参考