Janz Blog

Janz Blog

Haproxy同时在443端口上使用tcp和http协议

93
2024-11-26

最近发现同时在443上监听tcp和http协议会有ssl握手的问题,具体表现就是需要握手失败3次才能正确的sni转发。

前情提要

之前写过一篇利用haproxy复用443端口同时使用tcp和http协议,在配置中实现了tcp和http协议利用sni分流同时监听在443的端口,但是这样会造成一定的问题,就是谁先谁后的问题,经常需要握手几次失败之后才能正确的识别到。

故重新配置了一下配置文件,实现了全部由tcp443进站,然后利用sni分流给后端,利用tcp或者sock文件转成http协议。

配置文件

global
    log /dev/log local0
    log /dev/log local1 notice
    user haproxy
    group haproxy
    unix-bind user haproxy
    unix-bind group haproxy
    unix-bind mode 660
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    daemon

    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
    log global
    mode tcp
    option tcplog
    option dontlognull
    timeout connect 24h
    timeout client 24h
    timeout server 24h

# http转https
frontend http_in
    mode http
    bind *:80
    http-request redirect scheme https

# 配置http入站
frontend https_in
    # export DEPLOY_HAPROXY_PEM_PATH=/usr/local/etc/haproxy/cert/
    # export DEPLOY_HAPROXY_RELOAD="/bin/systemctl restart haproxy"
    # acme.sh --deploy -d <domain> --deploy-hook haproxy
    mode http
    # bind *:443 ssl crt /usr/local/etc/haproxy/cert alpn h2,http/1.1
    bind 127.0.0.1:9443 ssl crt /usr/local/etc/haproxy/cert alpn h2,http/1.1 accept-proxy

    # 配置日志
    option httplog
    log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
    # 捕获更多请求信息
    capture request header Host len 40
    capture request header User-Agent len 100
    capture request header X-Forwarded-For len 15

    # 处理web流量转发给后端
    acl host_blog hdr(host) -i xx.xx.xx
    acl host_webdav hdr(host) -i xx.xx.xx
    acl host_s3 hdr(host) -i xx.xx.xx
    use_backend webdav_backend if host_webdav
    use_backend s3_backend if host_s3
    use_backend blog_backend if host_blog
    default_backend default_backend

#配置tcp入站,主要复用443端口
frontend tcp_in
    mode tcp
    bind *:443

    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }
  
    # 域名匹配规则
    ## 1. ssl透传
    acl host_app req_ssl_sni -m sub zxxx
    ## 2. 复用443 tcp代理
    acl host_trojan req_ssl_sni -i xxx
    acl host_vless req_ssl_sni -i xxxx
    # 1. ssl穿透,由后端服务器负责处理ssl,负载均衡服务器只负责加密转发,适合原域名转发的情况
    use_backend forward_backend if host_app
    # 2. tcp 转发后端处理
    use_backend trojan_backend if host_trojan 
    use_backend vless_backend if host_vless
    default_backend tcp_to_https

# TCP 后端解密 HTTPS
backend tcp_to_https
    mode tcp
    server local-https 127.0.0.1:9443 send-proxy-v2 
    timeout connect 5s
    timeout server 30s


backend webdav_backend
    mode http
    server filen_webdav 127.0.0.1:8190 check

backend s3_backend
    mode http
    server filen_s3 127.0.0.1:8191 check
# web后端,sni转发到其他服务器
backend blog_backend
    mode http
    option forwardfor
    option http-server-close
  
    # 保持客户端真实IP
    http-request set-header X-Forwarded-For %[src]
    http-request set-header X-Real-IP %[src]
    http-request set-header Host xxxx
  
    # 转发到远程nginx服务器
    server nginx_server xxxxxx ssl check verify none check-sni blog.zdawn.net sni str(blog.zdawn.net) inter 2000 rise 2 fall 4

# ssl穿透
backend forward_backend
    mode tcp
    #balance roundrobin
    option ssl-hello-chk
    server blog_server 2xxxxx weight 1 check inter 2000 rise 2 fall 4

# Trojan 后端配置
backend trojan_backend
    mode tcp
    server trojan_server 127.0.0.1:9000 check

backend vless_backend
    mode tcp
    server vless_server 127.0.0.1:9010 check
# 默认后端
backend default_backend
    mode http
    http-request den

后记

利用sock的方式我一直有权限的问题,故更换了tcp的方式,这种方式更加的稳定一些。

现在就可以愉快的玩耍了。