sticker

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

请注意,本项目目前还在开发中,更多功能仍在完善中。

开始

首先我是想既然我会一点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

慢慢填坑~

致谢

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

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

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