443端口共用的方案

注意:本文最后更新于 1326 天前,有关的内容可能已经发生变化,请参考使用。

简述

TCP协议中,主机的IP地址加上端口号作为TCP连接的端点,这种端点就叫做套接字(socket)。一般情况下,一个socket同一时刻只能由一个应用监听,对于只有单个公网IP的主机来说,一个对外的端口就是一个socket,例如常用端口约定的服务:

  • 22:SSH服务
  • 80:HTTP服务
  • 443:HTTPS服务
    ...

在某些特殊需求的情况下,我们可能需要一个端口同时提供多个服务,例如:ssh/http 共用 80 端口的讨论。

总的来说,实现端口共用的思路就好比是Nginx的SNI:用某个前端程序(类比:Nginx)监听需要共用的对外端口A(类比:域名),将需要共用的程序(例如:php-fpm、uwsgi)分别监听环路上的不同端口A2、A3(类比:本地套接字),前端程序通过区分特定的包格式(类比:域名)将外部发往端口A的数据分别发给端口A2、A3再根据后端响应发回。

因为Nginx提供的Web服务工作在应用层,所以反向代理时可以直接通过URI来确定使用哪个后端;而一般工作在传输层的端口复用,实际上要解决最主要问题是如何区分复用数据并正确发给处理后端。


实现

预期目标

为了尽可能避免被QoS和受限网络的影响,现希望将某科学上网方式作为复用数据与正常的HTTPS服务共用443端口,在两者都能够提供服务的同时,HTTPS能够获取前端访客的IP地址

端口复用.png

服务结构

除了自己写前端处理程序,现成的方案有用Haproxy来区分不同数据来实现前端,Nginx和科学上网工具分别按照原配置改为在本地回环监听,其中Nginx需要稍作修改以获取前端发来的IP地址。

端口复用服务.png

Haproxy配置

2020.08.01更新:Haproxy 2.0以上版本的请看这里


这里用的Haproxy版本为1.5.18,安装过程不再赘述,主要提提配置的要点。

    listen https-in
        bind :443
        tcp-request inspect-delay 5s
        acl is_ssl req_ssl_ver 3:4
        tcp-request content accept if is_ssl
        server server-https :4431 check send-proxy
        use_backend ss-out if !is_ssl

https-in是一个listen,这是好比原来Haproxy里fronted和backend的合并体,上面配置语句的功能如下:

  • bind :443 设置监听所有IP的443端口
  • tcp-request inspect-delay 设置等待数据传输的最大超时时间
  • acl is_ssl req_ssl_ver 3:4 设置一条acl规则,判断是否为ssl连接
  • tcp-request content accept if is_ssl 设置如果上一条件成立则直接使用本listen里的server
  • server server-https :4431 check send-proxy
    设置本listen里名为server-https的后端服务,send-proxy表示向后端发送相关代理信息,后端获取访客的IP地址就是通过这条指令实现的
  • use_backend ss-out if !is_ssl 如果acl规则不成立,则使用名为ss-out的后端

这里最主要的配置就是send-proxy,网上后端获取前端传去的真实IP地址方法是通过Haproxy解析,然后使用option httpclose和option forwardfor之类的指令。这种方法在转发四层TCP数据的时候是无效的!要实现四层TCP数据带源地址转发需要修改前端源码通过重新改包实现。

好在Haproxy支持代理协议(PROXY protocol),这是是Haproxy的作者Willy Tarreau于2010年开发和设计的一个协议,通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取客户IP时非常有用。这里的send-proxy指令就是使用了这套协议来向后端传值。

    backend ss-out
        mode tcp
        server D-T-us0 127.0.0.1:1234
        server D-T-us1 127.0.0.1:4321 backup

backend是Haproxy的后端,用来提供服务,上面的配置提供了一个127.0.0.1:1234的服务和一个127.0.0.1:4321备用服务。

Nginx配置

Nginx主要是修改绑定的端口,增加proxy_protocol标记表示使用代理协议,再修改好set_real_ip_from、real_ip_header以及日志格式,其他的没什么实质性内容,不再多说。

    log_format  main  '$proxy_protocol_addr - [$remote_addr] - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    server
    {
    listen 4431 ssl http2 proxy_protocol;
    set_real_ip_from xx.xx.xx.xx;
    real_ip_header proxy_protocol;

    include /etc/nginx/sslport.conf;
    server_name  m.holmesian.org;
    ssl on;
    
    include /etc/nginx/sslon.conf;
    root  /data/www/sgk/;
    
    try_files $uri $uri/ /index.php?$args;
    location ~ \.php$
        {
          fastcgi_pass  unix:/var/run/php7-fpm.socket;
          fastcgi_index index.php;
          include fastcgi_params;
        }
    }

Dtunnel配置

这个在通过KCP协议加速科学上网里讲过,其他类型的服务也是可以的。

效果

在443端口复用的情况下,Dtunnel的话基本能跑满带宽(请忽略下图我的渣带宽)。

DTtest.png

Nginx能够获取Haproxy传来的访客IP地址,性能影响微弱。

Nginx-Log.png

这个方案在服务器上已经跑了200多天,目前还没发现什么大问题,所以分享出来供有需要的TX参考。


「倘若有所帮助,不妨酌情赞赏!」

Holmesian

感谢您的支持!

使用微信扫描二维码完成支付


相关文章

发表新评论
已有 21 条评论
  1. Ai

    厉害!马上就去试试!

    Ai 回复
  2. neko

    请问tls1.2_ticket_auth的ssr流量怎么和https的流量在haproxy这边用acl区分开?

    neko 回复
    1. Holmesian

      @neko

      尝试用req.ssl_sni指令 https://holmesian.org/Haproxy-distinguishes-flow-characteristics#%E5%86%8D%E6%8B%93%E5%B1%95%E4%B8%80%E7%82%B9

      Holmesian 回复
      1. neko

        @Holmesian

        嗯,当时我确实通过这个指令,通过域名把 acl 把流量分开了,不过这就无法达到同一个域名走 https 伪装 ssr 的目的了。
        当时我还有一个不靠谱的解决方法,是根据经验值判断个头大小,不过后来测试中发现 mac 上的 chrome 访问 https 会走偏,但是 ios 和 windows 是 ok 的。
        穿越时光的交流 =。=

        neko 回复
        1. Holmesian

          @neko

          哈哈,有的时候没注意评论。都是被网络环境逼成这样的。 PS:你博客的证书过期啦。

          Holmesian 回复
          1. neko

            @Holmesian

            万年不更新的博客 8 说了…… 自动续期死活不生效,每 3 个月都手动刷的,平时的技术分享都奉献给公司去了

            neko
  3. 网络营销

    厉害了,大佬

    网络营销 回复
  4. cd

    你好,想问这个方法适合于tcp的反向代理中端口复用嚒?比如445这种 实现配置代理多个445端口的情况

    cd 回复
    1. Holmesian

      @cd

      我没记错的话,445端口一般是用于SMB服务的。你的具体场景是不是想通过一个对外IP,访问不同的后端共享资源?

      Holmesian 回复
      1. cd

        @Holmesian

        是的,但是因为端口都是445了,不知道nginx要根据什么来判断转发到哪个server

        cd 回复
    2. Holmesian

      @cd

      如果你是提供相同服务的445端口,直接用均衡负载不更好吗?

      Holmesian 回复
      1. cd

        @Holmesian

        我是用反向代理,然后需要代理很多条tcp的 445端口的,所以想找个可以实现tcp代理时候端口复用的方法

        cd 回复
        1. Holmesian

          @cd

          你的需求用不了这个方法

          Holmesian 回复
  5. 新闻头条

    文章不错非常喜欢

    新闻头条 回复
  6. jacc

    hi,不错哦,我用上啦

    jacc 回复
  7. 微信资源网

    谢谢博主分享:我们是一个非常有潜力的微信资源网站。欢迎博主回访。

    微信资源网 回复
  8. 钟水洲

    本站已开通投稿,欢迎博主支持!(采纳后提供版权保护,开通作者专栏)

    钟水洲 回复
  9. chendian

    你好,这个在nginx的负载均衡里也可以实现端口复用嚒?

    chendian 回复
    1. Holmesian

      @chendian

      可以的

      Holmesian 回复
  10. c0sMx

    非常大佬

    c0sMx 回复
  11. 学习笔记Blog

    这个真的太高大上了!呵呵,感觉负载匀衡的时候用的上!

    学习笔记Blog 回复