使用 HAProxy 分流 443 端口上的多个服务

由于众所周知的原因,我们可能会希望在一台 VPS 的 443 端口上部署多个服务。当然,对有钱人来说,尽可以买多台 VPS 或者多个 IP。而对我等穷逼来说,只能想办法在一台 VPS 上分流了。最近经我不断折腾研究终于摸索出了方法,分流的服务为 SSH、ocserv、nginx 和 shadowsocks-libev。以下描述均基于 Ubuntu Server 16.04。

ocserv 的搭建

关于 ocserv,网上文章很多,所以我就不详细说了。但这里提一些大多数文章没有提到的要点。

为了使用 HAProxy 来反向代理 ocserv,你需要在 /etc/ocserv/ocserv.conf 中把端口改成一个 443 以外的端口:

tcp-port = 8443
udp-port = 8443

此外,为了使用非 443 的 UDP 端口,我直接把 ocserv.socket 这个服务给禁用了:

sudo systemctl disable ocserv.socket

然后修改 /lib/systemd/system/ocserv.service,把 ocserv.service 改为独立启动,不依赖 ocserv.socket

# 注释掉这两行
#Requires=ocserv.socket
#Also=ocserv.socket

另外,为了让 HAProxy 可以根据 SNI 来转发,你需要给 ocserv 单独安排一个子域名,因此也需要给子域名单独申请证书。比如我使用的是 Let’s Encrypt,那么,就要如此修改 /etc/ocserv/ocserv.conf

server-cert = /etc/letsencrypt/live/ocserv.example.com/fullchain.pem
server-key = /etc/letsencrypt/live/ocserv.example.com/privkey.pem

使用 HAProxy V2 版本的代理协议,可以使 ocserv 可以识别连接的 IP:

listen-proxy-proto = true

另外在 Ubuntu 16.04 上有个配置如果不改就会出现 GnuTls 错误的日志:

isolate-workers = false

还有些配置不改的话我就连不上:

keepalive = 32400
cert-user-oid = 2.5.4.3
auth-timeout = 180
min-reauth-time = 300
cookie-timeout = 86400
cookie-rekey-time = 14400
deny-roaming = false
rekey-time = 172800
cisco-client-compat = true

然后就是一些优化:

dpd = 30
mobile-dpd = 90
try-mtu-discovery = true
compression = true

至于证书认证之类的,请参考其他文章吧,不详细讲了。

Nginx 配置

当然,还是要绑定到一个其他的端口上去:

listen 7443 ssl;
listen [::]:7443 ssl;

shadowsocks-libev 配置

需要启用 TLS 混淆,并设置 obfs-host,绑定在非 443 端口上:

{
"server":["::0", "0.0.0.0"],
"server_port":8388,
"local_port":1080,
"password":"change_to_your_password",
"timeout":60,
"method":"chacha20-ietf-poly1305",
"fast_open": true,
"mode": "tcp_only",
"plugin": "obfs-server",
"plugin_opts": "obfs=tls;obfs-host=cloudfront.net;failover=cloudfront.net;fast-open"
}

另外,为了让客户端可以使用 443 端口进行 UDP 转发,可以启动两个 ss-server。先新建一个配置文件:

sudo cp /etc/shadowsocks-libev/config.json /etc/shadowsocks-libev/udp.json

修改配置文件:

{
"server":["::0", "0.0.0.0"],
"server_port":443,
"local_port":1080,
"password":"change_to_your_password",
"timeout":60,
"method":"chacha20-ietf-poly1305",
"fast_open": true,
"mode": "udp_only"
}

新建一个 systemd 服务:

sudo systemctl enable shadowsocks-libev-server@udp.service
sudo systemctl start shadowsocks-libev-server@udp.service

HAProxy 配置

/etc/haproxy/haproxy.cfg

global
        log /dev/log local0
        log /dev/log local1 notice
        chroot /var/lib/haproxy
        user haproxy
        group haproxy
        daemon

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

frontend ssl
        mode tcp
        bind *:443
        tcp-request inspect-delay 5s
        tcp-request content accept if { req.ssl_hello_type 1 }

        acl ssh_payload payload(0,7) -m bin 5353482d322e30
        acl vpn-app req_ssl_sni -i ocserv.example.com
        acl web-app req_ssl_sni -i example.com
        acl web-app req_ssl_sni -i www.example.com
        acl shadowsocks-app req_ssl_sni -i cloudfront.net

        use_backend ocserv if vpn-app
        use_backend nginx if web-app
        use_backend shadowsocks if shadowsocks-app
        use_backend ocserv if { req.ssl_hello_type 1 } !vpn-app !web-app !shadowsocks-app
        use_backend openssh if ssh_payload
        use_backend openssh if !{ req.ssl_hello_type 1 } { req.len 0 }

backend openssh
        mode tcp
        server openssh 127.0.0.1:22

backend ocserv
        mode tcp
        option ssl-hello-chk
        server server-vpn 127.0.0.1:8443 send-proxy-v2

backend shadowsocks
        mode tcp
        server shadowsocks 127.0.0.1:8388

backend nginx
        mode tcp
        option ssl-hello-chk
        server server-web 127.0.0.1:7443

说明:

  • 用 payload 的前 8 个字节判断为 SSH 协议
  • 其他的使用 SNI 来判断

防火墙配置

只需要打开 443、443/udp、8443/udp 即可:

sudo ufw allow 443
sudo ufw allow 443/udp
sudo ufw allow 8443/udp

当然,为了使用 ocserv,还需要打开转发:

/etc/sysctl.conf

net.ipv4.ip_forward=1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.forwarding=1

/etc/ufw/sysctl.conf

net/ipv4/ip_forward=1
net/ipv6/conf/default/forwarding=1
net/ipv6/conf/all/forwarding=1

/etc/ufw/before.rules 最后加:

*nat
:POSTROUTING ACCEPT [0:0]

-A POSTROUTING -s 10.0.0.0/24 -o ens3 -j MASQUERADE

COMMIT

其中 -s 后面的 IP 段要跟 /etc/ocserv/ocserv.conf 中的 ipv4-networkipv4-netmask 配置一致。