项目作者: TanNang

项目描述 :
V2Ray WebSocket + TLS 模式的 C 语言极简实现
高级语言: C
项目地址: git://github.com/TanNang/tls-proxy.git
创建时间: 2018-10-22T16:22:08Z
项目社区:https://github.com/TanNang/tls-proxy

开源协议:

下载


TLS-Proxy

本软件仅供内部学习交流,严禁用于任何商业用途,严禁违反国家相关法律法规,请于测试后 24 小时内删除,谢谢!

简单介绍

tls-proxy 可以看作为 V2Ray 的 WebSocket + TLS + Web 方案的 C 语言极简实现版,使用 libevent2 轻量级事件通知库编写。在硬件资源有限的环境中(如树莓派 3B,这也是我写 tls-proxy 的根本原因),tls-proxy 的资源占用更少,且代理速度比同等条件下的 v2ray 快的多(速度基本与 ss/ssr-libev 持平),同时又不降低安全性。

tls-proxy 支持 TCP 和 UDP 协议的代理(与 v2ray 一样,UDP 流量使用 TCP 传输,尽可能减少特征),通信过程:

source-socket <-> tls-client <-> web-server(eg: nginx) <-> tls-server <-> destination-socket

tls-clientweb-server 之间使用 HTTPS 协议(TLS1.2),web-servertls-server 之间使用 websocket 协议。

版本历史

tls-proxy v1.0

初始版本,TCP 和 UDP 都有问题(TCP 套接字处理不当,导致尾部数据丢失;UDP 实现过于简单粗暴,不支持 QUIC 协议),基本无法正常使用,所以此版本仅供娱乐。

tls-proxy v1.1

当前版本,在 v1.0 的基础上修复了 TCP 缓冲区数据残留问题,修正了 UDP 的代理逻辑,支持 QUIC 等”有状态”的 UDP 上层协议(LRU Cache),暂未发现日常使用问题。

相关依赖

tls-server

tls-client

编译方法

本地编译,以 linux x86_64 为例

  1. # uthash
  2. cd /tmp
  3. git clone https://github.com/troydhanson/uthash
  4. # base64
  5. cd /tmp
  6. git clone https://github.com/aklomp/base64
  7. cd base64
  8. make
  9. # openssl
  10. cd /tmp
  11. wget https://www.openssl.org/source/openssl-1.1.0i.tar.gz
  12. tar xvf openssl-1.1.0i.tar.gz
  13. cd openssl-1.1.0i
  14. ./Configure linux-x86_64 --prefix=/tmp/openssl --openssldir=/tmp/openssl no-shared # for linux x86_64
  15. make -j`nproc` && make install -j`nproc`
  16. # libevent
  17. cd /tmp
  18. git clone https://github.com/libevent/libevent libevent-sources
  19. cd libevent-sources
  20. ./autogen.sh
  21. ./configure --prefix=/tmp/libevent --enable-shared=no --enable-static=yes --disable-samples --disable-debug-mode --disable-malloc-replacement CPPFLAGS='-I/tmp/openssl/include' LDFLAGS='-L/tmp/openssl/lib' LIBS='-lssl -lcrypto -ldl'
  22. make && make install
  23. # tls-proxy
  24. cd /tmp
  25. git clone https://github.com/zfl9/tls-proxy
  26. cd tls-proxy
  27. gcc -I/tmp/uthash/include -I/tmp/base64/include -I/tmp/libevent/include -std=c11 -Wall -Wextra -Wno-format-overflow -O3 -s -pthread -o tls-server tls-server.c /tmp/base64/lib/libbase64.o /tmp/libevent/lib/libevent.a
  28. gcc -I/tmp/uthash/include -I/tmp/base64/include -I/tmp/libevent/include -I/tmp/openssl/include -std=c11 -Wall -Wextra -Wno-format-overflow -O3 -s -pthread -o tls-client tls-client.c /tmp/base64/lib/libbase64.o /tmp/libevent/lib/libevent.a /tmp/libevent/lib/libevent_openssl.a /tmp/openssl/lib/libssl.a /tmp/openssl/lib/libcrypto.a -ldl
  29. cp -af tls-client tls-server /usr/local/bin
  30. # delete files
  31. cd /
  32. rm -fr /tmp/uthash
  33. rm -fr /tmp/base64
  34. rm -fr /tmp/openssl*
  35. rm -fr /tmp/libevent*
  36. rm -fr /tmp/tls-proxy

交叉编译,在 linux x86_64 上编译 linux aarch64 上用的 tls-proxy(RPi3B)

  1. # 交叉编译工具链的前缀
  2. ARCH='aarch64-linux-gnu'
  3. # uthash
  4. cd /tmp
  5. git clone https://github.com/troydhanson/uthash
  6. # base64
  7. cd /tmp
  8. git clone https://github.com/aklomp/base64
  9. cd base64
  10. make CC=$ARCH-gcc LD=$ARCH-ld OBJCOPY=$ARCH-objcopy
  11. # openssl
  12. cd /tmp
  13. wget https://www.openssl.org/source/openssl-1.1.0i.tar.gz
  14. tar xvf openssl-1.1.0i.tar.gz
  15. cd openssl-1.1.0i
  16. ./Configure linux-aarch64 --prefix=/tmp/openssl --openssldir=/tmp/openssl no-shared
  17. make CC=$ARCH-gcc RANLIB=$ARCH-ranlib -j`nproc` && make install -j`nproc`
  18. # libevent
  19. cd /tmp
  20. git clone https://github.com/libevent/libevent libevent-sources
  21. cd libevent-sources
  22. ./autogen.sh
  23. ./configure --host=$ARCH --prefix=/tmp/libevent --enable-shared=no --enable-static=yes --disable-samples --disable-debug-mode --disable-malloc-replacement CPPFLAGS='-I/tmp/openssl/include' LDFLAGS='-L/tmp/openssl/lib' LIBS='-lssl -lcrypto -ldl'
  24. make && make install
  25. # tls-proxy
  26. cd /tmp
  27. git clone https://github.com/zfl9/tls-proxy
  28. cd tls-proxy
  29. $ARCH-gcc -I/tmp/uthash/include -I/tmp/base64/include -I/tmp/libevent/include -std=c11 -Wall -Wextra -Wno-format-overflow -O3 -s -pthread -o tls-server tls-server.c /tmp/base64/lib/libbase64.o /tmp/libevent/lib/libevent.a
  30. $ARCH-gcc -I/tmp/uthash/include -I/tmp/base64/include -I/tmp/libevent/include -I/tmp/openssl/include -std=c11 -Wall -Wextra -Wno-format-overflow -O3 -s -pthread -o tls-client tls-client.c /tmp/base64/lib/libbase64.o /tmp/libevent/lib/libevent.a /tmp/libevent/lib/libevent_openssl.a /tmp/openssl/lib/libssl.a /tmp/openssl/lib/libcrypto.a -ldl

交叉编译,在 linux x86_64 上编译 Android 8.1.0 (aarch64) 上用的 tls-proxy

  1. # 交叉编译工具链的前缀
  2. ARCH='aarch64-linux-android'
  3. # uthash
  4. cd /tmp
  5. git clone https://github.com/troydhanson/uthash
  6. # base64
  7. cd /tmp
  8. git clone https://github.com/aklomp/base64
  9. cd base64
  10. make CC=$ARCH-gcc LD=$ARCH-ld OBJCOPY=$ARCH-objcopy
  11. # openssl
  12. cd /tmp
  13. wget https://www.openssl.org/source/openssl-1.1.0i.tar.gz
  14. tar xvf openssl-1.1.0i.tar.gz
  15. cd openssl-1.1.0i
  16. ./Configure linux-aarch64 --prefix=/tmp/openssl --openssldir=/tmp/openssl no-shared
  17. make CC=$ARCH-gcc RANLIB=$ARCH-ranlib -j`nproc` && make install -j`nproc`
  18. # libevent
  19. cd /tmp
  20. git clone https://github.com/libevent/libevent libevent-sources
  21. cd libevent-sources
  22. ./autogen.sh
  23. ./configure --host=$ARCH --prefix=/tmp/libevent --enable-shared=no --enable-static=yes --disable-samples --disable-debug-mode --disable-malloc-replacement CPPFLAGS='-I/tmp/openssl/include' LDFLAGS='-L/tmp/openssl/lib' LIBS='-lssl -lcrypto -ldl'
  24. make && make install
  25. # tls-proxy
  26. cd /tmp
  27. git clone https://github.com/zfl9/tls-proxy
  28. cd tls-proxy
  29. $ARCH-gcc -I/tmp/uthash/include -I/tmp/base64/include -I/tmp/libevent/include -std=c11 -Wall -Wextra -Wno-format-overflow -O3 -s -pie -fPIE -pthread -o tls-server tls-server.c /tmp/base64/lib/libbase64.o /tmp/libevent/lib/libevent.a
  30. $ARCH-gcc -I/tmp/uthash/include -I/tmp/base64/include -I/tmp/libevent/include -I/tmp/openssl/include -std=c11 -Wall -Wextra -Wno-format-overflow -O3 -s -pie -fPIE -pthread -o tls-client tls-client.c /tmp/base64/lib/libbase64.o /tmp/libevent/lib/libevent.a /tmp/libevent/lib/libevent_openssl.a /tmp/openssl/lib/libssl.a /tmp/openssl/lib/libcrypto.a -ldl

使用方法

如果你会使用 v2ray 的 websocket + tls + web 模式,那么 tls-proxy 对你来说很容易上手,因为使用方法基本一致。

前提条件

  • 一个域名
  • 一个 SSL 证书
  • 一个 Web 服务器

SSL 证书免费的有很多,如果你没有 SSL 证书,请先申请一张(不建议使用自签发的 SSL 证书,因为不会被 tls-client 所信任,除非你将自签发的根证书添加到 tls-client 主机的 CA 文件中);为什么需要一个域名?因为 tls-client 强制校验 SSL 证书对应的域名,如果 SSL 证书上的域名与指定的域名不一致,则会断开与 Web 服务器的连接;Web 服务器需要配置 HTTPS,以下的 Web 服务器均以 Nginx 为例,其它服务器请自行斟酌。

配置 Nginx

1、修改 /etc/nginx/nginx.conf,在 http 配置段中添加如下配置(根据情况自行修改):

  1. http {
  2. ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 禁用 SSLv2、SSLv3
  3. ssl_dhparam /etc/nginx/ssl/dhparam.pem; # 指定 DH-param 文件
  4. ssl_session_cache shared:SSL:50m; # 启用 SSL 会话缓存,50 MB
  5. ssl_session_timeout 60m; # 设置 SSL 会话缓存超时时间,60 min
  6. ssl_session_tickets on; # 启用 SSL Session Ticket 会话恢复功能
  7. resolver 8.8.8.8; # 设置 DNS 域名解析服务器
  8. ssl_stapling on; # 启用 OCSP Stapling,优化 TLS 握手
  9. ssl_stapling_verify on; # 启用对 OCSP responses 响应结果的校验
  10. ssl_prefer_server_ciphers on; # 进行 TLS 握手时,优先选择服务器的加密套件
  11. ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";
  12. add_header Strict-Transport-Security "max-age=63072000" always;
  13. add_header X-Frame-Options SAMEORIGIN;
  14. add_header X-Content-Type-Options nosniff;
  15. }

2、使用 openssl 生成 dhparam.pem 文件(/etc/nginx/ssl/dhparam.pem

  1. # 需要执行很长时间,请不要中断它
  2. mkdir -p /etc/nginx/ssl
  3. cd /etc/nginx/ssl
  4. openssl dhparam -out dhparam.pem 4096

3、配置与 tls-server 相关的 vhost,根据实际情况修改

  1. server {
  2. listen 80 reuseport fastopen=3 default_server;
  3. server_name www.example.com;
  4. return 301 https://www.example.com$request_uri;
  5. }
  6. server {
  7. listen 443 ssl reuseport fastopen=3 default_server;
  8. server_name www.example.com;
  9. root /srv/http/www.example.com;
  10. index index.html;
  11. ssl on;
  12. ssl_certificate "/etc/nginx/ssl/www.example.com.crt";
  13. ssl_certificate_key "/etc/nginx/ssl/www.example.com.key";
  14. location ~* \.(jpg|jpeg|png|gif|ico|(css|js)(\?v=.*)?)$ {
  15. expires 60d;
  16. }
  17. ## tls-proxy
  18. location /tls-proxy {
  19. if ($http_some_header != 'some_header_value') {
  20. return 404;
  21. }
  22. proxy_http_version 1.1;
  23. proxy_read_timeout 3650d;
  24. proxy_send_timeout 3650d;
  25. proxy_connect_timeout 3s;
  26. proxy_pass http://127.0.0.1:60080;
  27. proxy_set_header Connection "Upgrade";
  28. proxy_set_header Upgrade $http_upgrade;
  29. }
  30. }

/tls-proxy 可以改为你喜欢的任意 URI;tls-server 默认监听地址为 127.0.0.1:60080/tcp;说明一下 $http_some_header 语句的作用:即使别人知道你的 URI,如果它没有设置正确的 HTTP 头部(在这里就是 Some-Header 咯,去掉开头的 http_,下划线换成连字符,大小写不敏感),并且对应的头部的值不是 some_header_value(这就相当于你的密码了,请任意发挥),那么还是白搭,Nginx 会返回 404 Not Found 给它(这种情况也适合于 GFW 的主动探测)。

在上面的配置中,我们的自定义头部(为叙述方便,以下将自定义头部称为 认证头部密码头部)只有一个 value(可以理解为只有一个密码),实际上我们可以为密码头部设置多个密码(即多个 value),假设我们的密码头部仍然为 Some-Header,现在我们需要为 tls-proxy 设置两个访问密码,一个是 some_header_value_a、一个是 some_header_value_b,则将上面的 location /tls-proxy 配置块改为:

  1. location /tls-proxy {
  2. set $verified 0;
  3. if ($http_some_header = 'some_header_value_a') {
  4. set $verified 1;
  5. }
  6. if ($http_some_header = 'some_header_value_b') {
  7. set $verified 1;
  8. }
  9. if ($verified != 1) {
  10. return 404;
  11. }
  12. proxy_http_version 1.1;
  13. proxy_read_timeout 3650d;
  14. proxy_send_timeout 3650d;
  15. proxy_connect_timeout 3s;
  16. proxy_pass http://127.0.0.1:60080;
  17. proxy_set_header Connection "Upgrade";
  18. proxy_set_header Upgrade $http_upgrade;
  19. }

依此类推,如果想要设置三个访问密码,再添加一个类似的 if ($http_some_header = 'some_header_value_c') 语句即可。

4、使用 nginx -t 检查配置文件是否有语法错误,然后 systemctl reload nginx.service 使其生效。

配置 tls-server

tls-proxy 目录下的 tls-server.service systemd 服务文件拷贝到 /etc/systemd/system/ 目录下,然后执行 systemctl daemon-reload 使其生效。tls-server 默认只启用一个工作线程(-j 参数),而在 tls-server.service 服务文件中,默认设置的是 -j2,也就是两个工作线程,一般推荐使用 1 ~ 4 个工作线程,再多也没啥用,浪费资源。如果需要修改线程数,请编辑 /etc/systemd/system/tls-server.service 服务文件,将 -j2 改为 -jN(N 为你想设置的线程数),注意,修改 service 文件之后需要执行 systemctl daemon-reload 生效。最后,执行 systemctl start tls-server.service 来启动 tls-server。

配置 tls-client

tls-proxy 目录下的 tls-client.service systemd 服务文件拷贝到 /etc/systemd/system/ 目录下,然后使用文本编辑器打开 /etc/systemd/system/tls-client.service 文件,主要需要修改 tls-client 的命令行参数,目前 tls-client 的命令行参数有:

  1. $ tls-client -h
  2. usage: tls-client <OPTIONS>. OPTIONS have these:
  3. -s <server_host> server host. can't use IP address
  4. -p <server_port> server port. the default port is 443
  5. -c <cafile_path> CA file location. eg: /etc/ssl/cert.pem
  6. -P <request_uri> websocket request line uri. eg: /tls-proxy
  7. -H <request_header> websocket request headers. allow multi line
  8. -b <listen_address> tcp & udp listen address. default: 127.0.0.1
  9. -t <tcp_proxy_port> tcp port (iptables xt_tproxy). default: 60080
  10. -u <udp_proxy_port> udp port (iptables xt_tproxy). default: 60080
  11. -j <thread_numbers> number of worker thread (for tcp). default: 1
  12. -T disable tcp transparent proxy
  13. -U disable udp transparent proxy
  14. -v show version and exit
  15. -h show help and exit

其中必须要指定的参数有:-s 指定服务器的域名、-c 指定本机 CA 文件路径、-P 指定请求的 URI。因为我们在 Nginx 中配置了自定义头部 Some-Header: some_header_value\r\n,所以还需要指定一个 -H 参数,注意,此参数指定的 HTTP 头部必须以 \r\n 结尾,且必须放在 $'' 里面(否则不会进行转义),即 -H $'Some-Header: some_header_value\r\n'-H 参数中允许设置多个自定义头部(HTTP 协议使用 \r\n 作为行结束符)。如果头部设置不正确,则 tls-client 会因为收到 404 Not Found 响应而提示 bad response。tls-client 和 tls-server 一样,默认都是启用一个工作线程,所以如果你需要启用多个线程,请指定 -j 参数(不要奇怪 tls-client 的 UDP 监听线程为什么只有一个,因为我需要在内部保持 UDP 的状态,所以必须只能有一个 UDP 套接字,也就是说,tls-client 的 -j 参数只针对 TCP 代理套接字)。

关于 tls-client 的 -c <cafile_path> 参数:前面说了,tls-client 会对 SSL 证书进行校验,确认 SSL 证书是否可信,所以 tls-client 需要知道本机的 CA 文件路径(每个发行版的 CA file 文件路径都不太相同,ArchLinux 中是 /etc/ssl/cert.pem)。为什么不取消证书验证这个步骤?原因不用我说吧,一切都是为了安全啊。如果你不知道当前系统的 CA 文件路径,请在 Bash 中执行 curl -v https://www.baidu.com |& awk '/CAfile:/ {print $3}' 命令,输出的字符串即为本机的 CA 文件路径(别跟我说 curl command not found,要么装 curl,要么自己找 CA file 去)。

最后,执行 systemctl daemon-reload 重载服务文件,然后执行 systemctl start tls-client.service 启动 tls-client。

配置 iptables 规则

tls-client 默认监听地址:127.0.0.1:60080/tcp127.0.0.1:60080/udp;TCP 和 UDP 的透明代理都必须使用 iptables-TPROXY 方式(注意不是 iptables-REDIRECT);这里给个简单的 bash 脚本,示例如何使用 iptables-TPROXY 来透明代理本机以及内网的 TCP 和 UDP 流量(没错,tls-client 一般用在 Linux 网关上,提供全局透明代理,当然也可以在普通 Linux 主机上使用,代理本机的 TCP 和 UDP)。注意,此脚本只是作为一个例子,实际上我们还需要配置分流规则(如 gfwlist、chnroute),如果你需要分流的话,请使用 ss-tproxy 代理脚本。

  1. #!/bin/bash
  2. server='www.example.com' # 服务器的域名
  3. intranet_nic='lan0' # 本机内网网卡
  4. extranet_nic='wan0' # 本机外网网卡
  5. function start {
  6. systemctl start tls-client.service
  7. iptables -t mangle -N SETMARK
  8. iptables -t mangle -A SETMARK -d 0/8 -j RETURN
  9. iptables -t mangle -A SETMARK -d 10/8 -j RETURN
  10. iptables -t mangle -A SETMARK -d 127/8 -j RETURN
  11. iptables -t mangle -A SETMARK -d 169.254/16 -j RETURN
  12. iptables -t mangle -A SETMARK -d 172.16/12 -j RETURN
  13. iptables -t mangle -A SETMARK -d 192.168/16 -j RETURN
  14. iptables -t mangle -A SETMARK -d 224/4 -j RETURN
  15. iptables -t mangle -A SETMARK -d 240/4 -j RETURN
  16. iptables -t mangle -A SETMARK -d $server -j RETURN
  17. iptables -t mangle -A SETMARK -j MARK --set-mark 0x2333
  18. iptables -t mangle -A OUTPUT -o $extranet_nic -p tcp -j SETMARK
  19. iptables -t mangle -A OUTPUT -o $extranet_nic -p udp -j SETMARK
  20. iptables -t mangle -A PREROUTING -i $intranet_nic -p tcp -j SETMARK
  21. iptables -t mangle -A PREROUTING -i $intranet_nic -p udp -j SETMARK
  22. iptables -t mangle -A PREROUTING -m mark --mark 0x2333 -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port 60080
  23. iptables -t mangle -A PREROUTING -m mark --mark 0x2333 -p udp -j TPROXY --on-ip 127.0.0.1 --on-port 60080
  24. ip route add local 0/0 dev lo table 100
  25. ip rule add fwmark 0x2333 table 100
  26. }
  27. function stop {
  28. ip rule del table 100 &>/dev/null
  29. ip route flush table 100 &>/dev/null
  30. iptables -t mangle -F
  31. iptables -t mangle -X
  32. systemctl stop tls-client.service
  33. }
  34. case $1 in
  35. start) start;;
  36. stop) stop;;
  37. *) echo "usage: $(basename $0) start|stop"; exit 1;;
  38. esac

本机的 DNS 需要修改为 8.8.8.88.8.4.4 等国外 DNS 服务器(/etc/resolv.conf),然后 curl ip.cn 测试吧。

查看 tls-proxy 的日志

  • tls-client:执行命令 journalctl -afu tls-client.service
  • tls-server:执行命令 journalctl -afu tls-server.service