请不要给
admin@learningman.top
发邮件,那个邮箱我一年检查一次的因为近期的更新,此文章的内容已经部分失效,可以参照 https://sunbk201public.notion.site/sunbk201public/OpenWrt-f59ae1a76741486092c27bc24dbadc59 ,或者自行阅读源码
起因
感谢工信部对大学生们的监督,国内的校园网有了一人一号的限制。在某些企业的变本加厉之下,甚至已经演变为了一设备一号。
但是我有一堆智能设备,没有一个网络环境无法使用。
调察
查了一些资料,加上自己的测试。基本可以确定我校所用的Dr.Com
检测多设备的方式基本上就是检测HTTP报文
里的User-Agent
字段 。目前来说,现成的解决方案有Privoxy
或者由CHN-beta 开发的
但是以上都有一个缺点:他们只能处理从80端口发送的HTTP请求。无论是Privoxy
的iptables
转发,还是xmurp-ua
的挂载netfilter
链,他们都没有考虑过对于HTTP的识别,而是处理所有通过80端口的流量。
但是事实上,并不是所有的HTTP流量都是通过80端口发送的。比如手机QQ的图片传送,使用的是HTTP协议的同时,走的是8080端口。你可能会想,那我把那些端口也处理了不就好了吗?但是在非标端口上的行为并没有规定,这些端口上并不是只有HTTP流量。如果你把非HTTP的TCP流量转发到了代理服务器,结果就是这些连接都被阻断了(比如小爱同学)。
我曾经想过用一个叫做sslh
的程序完成流量识别,作为代理服务器的前置代理,将识别出的HTTP流量转发到代理服务器。但是事实上这样效率很低,而且手动抓包,然后在防火墙里写上几十个端口,看起来也很蠢。
这样看来只有一个办法了——作为一个软件工程专业的学生,就应该自己写一个应用来处理这件事。
于是,就有了 UA2F
。
实验性项目
UA2F仍然是一个实验性项目,我不对此程序带来的可能的硬件损坏和人身伤害负责。
开源
技术细节
根据我日常生活的经验,学校使用的检测设备并不真正的去识别一个ua字符串,而是仅仅检测其中是否含有某个特定的子串。比如Android
或者iPhone
之类。所以我们只需要把所有的UA脱敏。在这里我选择将整个字符串全部替换成F,以表达我内心的感情。
框架
我之所以没有使用xmurp-ua
还有一个原因,这个程序在我的路由器上会随机引起内核挂起,只有强行重启路由器才能解决。这是因为这个程序是一个内核模块,他的任何错误都会连累到整个内核。
UA2F
解决了这个问题,因为它并不是一个内核模块。作为一个普普通通的用户程序,系统并不关心他的死活。
在防火墙中有一个target被称作NFQUEUE
,可以将防火墙接收到的包交由用户态程序进行处理。而它的参数--queue-bypass
,允许在用户态程序无响应时,直接放行所有的包。UA2F
使用这个来获取相应的包并进行处理。这大大提升了整个系统的鲁棒性。
当然这也是有代价的,在内核与用户态程序之间的通讯是需要时间和计算力的。我原本以为这会是一笔很大的开销,但是在实际的运行中看起来并没有那么糟糕。而且我相信,使用这种技术限制学生的上网行为的学校,大概也不会给校园网太高的带宽(逃)。
处理方式
在TCP连接建立起来以后,期望应该是第1个TCP报文中包含HTTP报文的首部。也就是说,我们只需要判断一个TCP包是否以HTTP method
开头(如GET
,POST
),就可以得知这是不是一个HTTP报文。
事实上,这是一个极度简化的模型,有非常多的意外情况没有考虑到。但是他现在看起来工作良好。而且因为我们替换的是同等长度的字符串,所以服务端返回的ACK是可以接受的。这也就让我们可以不需要追踪整个连接来修改所有返回的ACK。
当判断一个TCP包是一个HTTP报文以后,程序将会向下解析所有的头部字段,直到找到UA并替换为止。
修改后的包将会被放回防火墙队列中,继续进行原本应该进行的处理。
使用
写了一大堆东西,终于要讲到这个程序应该怎么用了。
首先,你要拥有一个你自己的路由器的编译环境。理论上SDK也是可以的。但是我推荐使用源码编译,这样可以免去很多无谓的小问题。
这个程序依赖libmnl
和libnetfilter-queue
,你在openwrt的make menuconfig
菜单中选上同时会帮你配置好的,但是请注意,还有一个你需要手动选择的选项,iptables-mod-nfqueue
。使用这个包以后,防火墙才会拥有这个程序所需要的NFQUEUE
target。
如果你已经把编译好的系统刷到你的路由器里了。你执行ua2f
的时候,终端应该挂起输出一条警告,同时,你应该会在系统日志里看到一条启动消息。
一般来说,你需要配置下列规则。
请确保添加此语句至开机自启
ipset create nohttp hash:ip,port hashsize 16384 timeout 300
UA2F
运行时依赖名称为 nohttp
,类型为 hash:ip,port
的 ipset
UA2F的2.0-13加入了实验性的CONNMARK支持。期望可以降低接近一半的负载。但是我并不确定这一定工作正常,有可能导致伪装效果下降。
无法正常工作,已被移除,但是会是一个可行的优化方向。
已经加入 ipset 和 CONNMARK 支持,现在看来工作正常
iptables -t mangle -N ua2f
iptables -t mangle -A ua2f -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A ua2f -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A ua2f -d 192.168.0.0/16 -j RETURN # 不处理流向保留地址的包
iptables -t mangle -A ua2f -p tcp --dport 443 -j RETURN
iptables -t mangle -A ua2f -p tcp --dport 22 -j RETURN # 不处理 SSH 和 https
iptables -t mangle -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44
iptables -t mangle -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流 (实验性)
iptables -t mangle -A ua2f -m set --set nohttp dst,dst -j RETURN
iptables -t mangle -A ua2f -j NFQUEUE --queue-num 10010
iptables -t mangle -A FORWARD -p tcp -m conntrack --ctdir ORIGINAL -j ua2f
iptables -t mangle -A FORWARD -p tcp -m conntrack --ctdir REPLY
随后执行service ua2f start
来将程序在后台启动。这个时候你应该可以通过路由器访问HTTP的网页,并且注意到自己的ua已经被修改了。
推荐执行service ua2f enable
来将ua2f
设为开机启动。
这里提供一个查看自己UA的HTTP网址:http://ua.233996.xyz/
。
不是我搭的,不保证服务质量。但是就算想自己搭一个也非常简单。
如果你碰巧与我一个学校,你还可以通过下面这个脚本来维持自己的登录状态。
脚本的地址是
https://gist.github.com/Zxilly/03300ea52e644188c019d47bf0879f1d
,但是已经因为性能原因被弃用。
其实并没有什么性能原因,只是我看着pid飙到10000+不爽
这个脚本寄了,直接用 JGSWeb 吧
现在来说,推荐使用另外一个程序。
虽然有潜在的内存非法访问,但是不会建一堆进程
大部分使用流量识别的校园网系统应该都是网页登录的(不排除特别惨的)。抓包写一个脚本,应该不会太难。
目前就我自己的使用体验来说,应该还算是比较稳定。之前只拦截80端口的时候,大概是半个小时登录一次。但是现在已经三个小时都没有再次登录过了。
额外规则
根据一些相关的论文,可以发现能够被检测到的流量特征并不只有UA这一项。事实上有很多流量特征,甚至是统计学上的方法,都可以发现路由器后的多设备。
下面这几条规则解决了NTP,DNS和TTL的问题,据说还能解决深信服的劫持问题。我向我们学校的网络中心咨询过,我们学校应该也是部署了深信服的设备的。但是我在实际中并没有见到被劫持的现象。
在实际使用中,我发现深信服防劫持依赖的字符串解析对性能的影响还是比较大的,是否使用自行斟酌。
其余规则期望的性能开销并不大,不会影响流量的转发。注意,其中的ntp规则,需要你在服务器本地有一个ntp服务器。而DNS规则也需要本地的Dnsmasq
的相应配置。
不要照抄,至少先把路由器IP换成你自己的。。。
iptables -t nat -N ntp_force_local
iptables -t nat -I PREROUTING -p udp --dport 123 -j ntp_force_local
iptables -t nat -A ntp_force_local -d 0.0.0.0/8 -j RETURN
iptables -t nat -A ntp_force_local -d 127.0.0.0/8 -j RETURN
iptables -t nat -A ntp_force_local -d 192.168.0.0/16 -j RETURN
iptables -t nat -A ntp_force_local -s 192.168.0.0/16 -j DNAT --to-destination 192.168.3.1
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53
iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53
iptables -t mangle -A PREROUTING -j TTL --ttl-set 64
# iptables -I FORWARD -p tcp --sport 80 --tcp-flags ACK ACK -m string --algo bm --string " src=\"http://1.1.1." -j DROP
你可以在上文提到过的CHN-Beta
的Github中找到一个修改IPID的软件包,但是我实际上并没有部署,因为感知不强,徒增功耗,方向错了。
缺点
这个程序有很多缺点。
之于xmurp-ua
,UA2F
最大的改进就是它是一个用户态程序,可以方便的完成灾难恢复。
但是具体的灾难恢复机制呢?没写。
已经有一个初始版本的灾难恢复机制,但是会造成网络闪断。
这个程序是一个单进程,单线程程序,所以期望性能非常非常低,路由器的羸弱性能更是雪上加霜。
非常理想化的HTTP检测机制。对于某些上了HTTP/2还死活不用https的网站毫无办法。
这个程序是照着样例代码写的,我甚至没有完整地理解整个处理流程,应该有一些处理是不必要的。比如说取出IP头再解析TCP头,应该可以直接用一个offset完成这件事。
代码的具体实现也有很多暴力的地方,理论上期望性能明显可以更高。比如使用memcpy
代替某几个暴力循环。
如果有路过的大佬正好也有这个需求,希望能够改进一下我的代码。
结尾
在这里我想再一次感谢校园网,如果不是校园网,我可能不会有机会如此抓狂的去读一些底层的代码(你可以从commit message
看出我的心情)。
感谢libmnl
和libnetfilter-quene
的开发者们,感谢openwrt社区,顺便 %Linus 。是他们慷慨地开放出这些源码,才让我们这些普通人有了能够进行这些操作的能力。
最后的最后,希望有一天我们不再需要进行这些复杂的操作,也不需要再进行另外一些复杂的操作,能够真正地自由地使用整个互联网。
希望有一天,阳光会照在大地上。
回复 星念 取消回复