tls握手过程不会加密,客户端的第一个数据包ClientHello包含了请求的域名,服务端通过这个域名发送对应的公钥给客户端,这么做的目的是实现单个主机部署多个域名。ClientHello末尾包括多个扩展字段,SNI是其中的一个。

ClientHello结构

其中1+N/2+N表示一或两个字节长度后面附带N个字节。

  • Record Header:5 bytes
  • Handshake Header:4bytes
  • Client Version:2bytes
  • Client Random:32bytes
  • Session ID: 1 + N
  • Cipher Suites:2+N
  • Compression Methods:1+N
  • Extensions:2+N

Extension结构

  • Extension ID: 2 bytes
  • Extension Data: 2+N

SNI扩展具体结构可以参照:tls13.ulfheim.net,这是工具图解了tls1.3握手的整个过程。

SNI的对应的扩展ID0x0000,解析之后就能获取到请求的域名,所以即使TLS传输是加密,但是通过SNI依旧可以实现域名访问记录、拦截操作。为了解决这个问题后面出现ESNI,就是现在请求的域名上设定一个TXT记录保存一个公钥,每次请求域名之前先查询这个域名的TXT记录,浏览器请求的时候使用这个公钥加密请求域名并把加密后内容附带到ESNI扩展上,如果是明文DNS请求,这个就没有意义了,并且多了一次请求首次打开速度变慢,这个解决方案并不完美。Cloudflare目前在推动ECH,是ESNI的改进版本,不过支持的网站并不多。TLS协议属于互联网基础设施,不可能快速升级迭代,因此不管是ECH还是ESNI都是对TLS协议扩展,在不支持的时候自动关闭。

构建一个https代理

https协议是在tls协议上传输数据,有了上面的基础之后,我就打算构建一个透明网关,目的在不影响网络速度的情况下,对网页链接进行分流。主要实现:

  • 域名拦截
  • 请求代理
  • 请求记录

透明网关使用Linux作为服务器,配置iptable规则转发443端口流量到本地。

域名拦截包括拦截广告服务和拦截追踪服务器,这一部分直接调用AdGuard urlfilter库,并且配合AdGuard Home兼容规则。代理的话连接到代理服务器转发没什么难度。最后一个问题就是协议并不是tls协议的话,需要能回落到正常的流量转发,需要获取原先请求的IP地址,连接这个IP地址转发流量,这个需要执行系统调用(linux),具体参考可以搜索ss-redir实现。

中间遇到手机美团客户端一直报错问题,发现美团使用一种TCP复用的技术,通过长时间维持一个连接减少资源的占用,并且不是TLS协议但是走443端口,我由于设定了连接时长限制导致每过一段时间就会网络错误一次。http协议本身不加密,解析host请求头就可以获取目标地址。因此最后完成透明网关支持https/http协议代理,可以分流日常中的大部分流量,但是Google系的软件还是没法正常使用,因为Google采用了QUIC协议,使用的并不是tls协议,Chrome倒是可以关闭QUIC协议支持。

最后一个请求记录等有时间再折腾吧。