Websocks 开发记录

杂谈 | 共 3701 字 | 2018/3/24 发表 | 2018/3/24 更新

最近自己写个基于 WebSocket 的代理工具——Websocks练练手,这里来记录一下开发过程以及一些心得吧。

sticker

多年后注:后来申请高三比较忙就没再开发了,感觉还是 ShadowSocks 用着舒服,所以上大学后也没折腾了,弃坑了。

开始

首先我是想既然我会一点 go,那就要写点什么有用的东西。因为翻墙的需求很大,而主流翻墙软件在隐蔽性上不太行。所以我想开发一个尽量不怕被墙速度还要能保证的代理软件。那么我首先想到的就是 WebSocket,这是之前 V2Ray 用过的。但我自己总觉得 V2 不好用太复杂,于是就想自己写一个,正好也是练练手嘛。

那么首先我当然是要先看看已有的代理软件的代码啦,我之前看过一篇你也能写个 Shadowsocks,但是没仔细研究。我顺着这篇教程的思路,一边看 lightsocks 和 shadowsocks-go 的代码一边对比。

流程

我们先来看看 lightsocks 作者的教程:

Shadowsocks 由两部分组成,运行在本地的 ss-local 和运行在防火墙之外服务器上的 ss-server,下面来分别详细介绍它们的职责(以下对 Shadowsocks 原理的解析只是我的大概估计,可能会有细微的差别)。

ss-local 的职责是在本机启动和监听着一个服务,本地软件的网络请求都先发送到 ss-local,ss-local 收到来自本地软件的网络请求后,把要传输的原数据根据用户配置的加密方法和密码进行加密,再转发到墙外的服务器去。

ss-server 的职责是在墙外服务器启动和监听一个服务,该服务监听来自本机的 ss-local 的请求。在收到来自 ss-local 转发过来的数据时,会先根据用户配置的加密方法和密码对数据进行对称解密,以获得加密后的数据的原内容。同时还会解 SOCKS5 协议,读出本次请求真正的目标服务地址(例如 Google 服务器地址),再把解密后得到的原数据转发到真正的目标服务。

当真正的目标服务返回了数据时,ss-server 端会把返回的数据加密后转发给对应的 ss-local 端,ss-local 端收到数据再解密后,转发给本机的软件。这是一个对称相反的过程。

由于 ss-local 和 ss-server 端都需要用对称加密算法对数据进行加密和解密,因此这两端的加密方法和密码必须配置为一样。Shadowsocks 提供了一系列标准可靠的对称算法可供用户选择,例如 rc4、aes、des、chacha20 等等。Shadowsocks 对数据加密后再传输的目的是为了混淆原数据,让途中的防火墙无法得出传输的原数据。但其实用这些安全性高计算量大的对称加密算法去实现混淆有点“杀鸡用牛刀”。

我看到“ss-server 同时还会解 SOCKS5 协议”就不太明白了,因为 socks5 是需要握手的,如果服务器在美国解 socks5 的话,仅仅是握手就要过三四次太平洋,延时会很大。后来自己看了看 ss 源码又问了问别人发现果然不是这样,正确的做法应该在本地解 Socks5。

那么,如果不考虑 WebSocket 复用,整个程序的流程是这样的。

监听本地端口=>收到本地连接=>创建协程=>本地 Socks5 握手=>连接远程服务器=>Copy 数据流

Socks5 解析

我对 Socks5 理解的还不是很深,也仅仅是刚刚看懂 RFC。本来是想照猫画虎自己写代码解析的,但是自己水平确实还差点,正好 shadowsocks-go 的代码满足我的需求,就直接抄过来了。毕竟这部分也不是重点嘛 😂。

编译部署

使用了 GoReleaser,可以参考博客里的这篇文章

WebSocks 不是我自己一个人使用的,所以代码管理上不能像原来那么简单地使用 git,关于其它 branch 的使用参考了 Git 分支管理策略

作为一个 App

一开始软件结构是仿照 LightSocks 的,分为了 server 和 local 两部分。这样做缺点很明显——麻烦,不仅写代码麻烦,编译也麻烦,运行更麻烦。

于是,我把这两部分利用 cli 合到了一起。这样可以一个软件同时担任 client 和 server 的角色,还可以加入其它功能,比如生成证书,打开官网之类的~

伪装混淆

众所周知,TLS 是个十分安全的协议,可以保护数据不被 ISP 或者 GFW 获取到,这是选择 HTTPS 作为底层协议的原因。但是 TLS 在 Client Hello 阶段是明文的,server name 会作为明文发给服务端,这样就会暴露要访问的域名。但我们也可以利用这个特性达到伪装的目的。

来抓包对比一下。首先可以看到在伪装前,即使是启用了 TLS,访问的主机名依然可以被检测到。

未经处理

而通过设置 TlsConfig.ServerName 可以达到伪装的目的。

伪装后

WebSocket

一开始用的 WebSocket 包是官方包,虽然简单方便,但是总有些问题,比如套上 CF 就用不了(其实可能是别的原因)。

后来看了很多其它 go 写的代理工具比如v2raygost,基本上用的都是用的第三方库,甚至 go 官方推荐用它,我也选择换成了github.com/gorilla/websocket这个库。

godoc-websocket

这个库看起来功能挺多,但是实际上我也用不了多少。最重要的功能就是接受 http 请求后 upgrade 成一个io.ReadWriter,然后在进行 Copy 之类的操作。

UI

一开始想的是把基础的功能都做好再开发 UI,但是我发现不做 UI 根本没人用,甚至我自己都懒得开命令行启动。

所以,UI 还是必须要的。

但是,由于程序是用 go 写的,无法或者说难以直接开发 GUI。因此,我经过多方面考虑,放弃了 Elcetron 这类框架,选择直接开发 Web。因为反正原理都是差不多的,为何不用浏览器呢?还正好能练练开发网站的能力。

不过由于我 js/css 基础贼差,再加上自己学习上很忙,开发进度十分缓慢。更不用说手机客户端了,还是要一点一点来啊 。

多路复用

慢慢填坑~

致谢

~~其实这项应该放在最后,但是由于我整个学习与思考的过程都是一边看一边问一边学一边写的,所以就把致谢放在最前面了。~~我还是把这段放最后了

先感谢一下帮助我的小伙伴们~

参考文章的顺序是大致是时间顺序