F-Stack 对 HTTP/3 的支持使用说明

Nginx 主线在1.25.x 版本中已经加入了对 HTTP/3 的支持,F-Stack 在等待了两个小版本之后,也移植了 Nginx-1.25.2 版本到 F-Stack 上,目前可以支持HTTP/3的测试使用,本文介绍移植过程中的一些兼容性改造及使用注意事项。

主要兼容项

主要是 F-Stack 的一些接口兼容和 FreeBSD 不支持一些 Linux 的部分选项,而 Nginx 的自动配置检测的是 Linux 是否支持,需要进行一些修改。

  1. 对 F-Stack 的 ff_recvmsg 和 ff_sendmsg 接口进行修改,兼容 Linux 接口,主要是部分结构体字段类型不一致的兼容,虽然结构体编译对齐后总长度是一致的。
  2. 关闭了 BPF sockhash 功能(bpf,SO_COOKIE)的功能检测和开始,该功能主要用于通过从 bpf 的 socket_cookie 直接获取数据,提升性能。
  3. 关闭了 UDP_SEGMENT 功能,主要功能设置 UDP 分段大小。
  4. IP_PKTINFO 选项不探测是否支持,强制使用 FreeBSD 的 IP_RECVDSTADDR 和 IP_SENDSRCADDR 选项。
  5. IP_MTU_DISCOVER 选项不探测是否支持, 强制使用 FreeBSD 的 IP_DONTFRAG 选项。
  6. IPV6_MTU_DISCOVER 选项不探测是否支持, 强制使用 IPV6_DONTFRAG 选项,该选项目前 FreeBSD 和 Linux 都支持。

编译过程

SSL 库

此处以 OpenSSL quic 为例,可以参考以下方式编译

cd /data/
wget https://github.com/quictls/openssl/archive/refs/tags/OpenSSL_1_1_1v-quic1.tar.gz
tar xzvf OpenSSL_1_1_1v-quic1.tar.gz
cd /data/openssl-OpenSSL_1_1_1v-quic1/
./config enable-tls1_3 no-shared --prefix=/usr/local/OpenSSL_1_1_1v-quic1
make
make install_sw

DPDK 和 F-Stack lib

总体编译方式不变,额外需要注意的是如果系统的 OpenSSL 库版本与上面使用的 OpenSSL quic 版本不兼容时,编译 DPDK lib 库时需要也使用上面的OpenSSL quic 库(通过配置 PKG_CONFIG_PATH 使用),参考以下方式编译

export FF_PATH=/data/f-stack
export PKG_CONFIG_PATH=/usr/local/OpenSSL_1_1_1v-quic1/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/local/lib64/pkgconfig:/usr/lib/pkgconfig

mkdir -p /data/f-stack
git clone https://github.com/F-Stack/f-stack.git /data/f-stack

# DPDK lib
cd /data/f-stack/dpdk/
meson -Denable_kmods=true build
ninja -C build
ninja -C build install

# F-Stack lib
cd /data/f-stack/lib/
make
make install

# ff tools
cd /data/f-stack/tools
make
make install

F-Stack Nginx-1.25.2

Nginx 可以参考以下参数进行编译,如果有更多额外需求,自行调整相关配置

export FF_PATH=/data/f-stack
export PKG_CONFIG_PATH=/usr/local/OpenSSL_1_1_1v-quic1/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/local/lib64/pkgconfig:/usr/lib/pkgconfig

cd /data/f-stack/app/nginx-1.25.2/
./configure --prefix=/usr/local/nginx_fstack --with-ff_module --with-http_ssl_module --with-http_v2_module --with-http_v3_module --with-cc-opt=-I/usr/local/OpenSSL_1_1_1v-quic1/include --with-ld-opt='-L/usr/local/OpenSSL_1_1_1v-quic1/lib/'
make
make install

测试使用注意事项

  1. keepalive_timeout = 65 # 因为 Nginx 的 quic 中将 keepalive_timeout参数值作为了读超时时间,所以不能设置为 0
  2. listen 443 quic; # 监听HTTP/3的时候不能设置REUSEPORT,否则多进程会有异常
  3. ulimit -n 100000 # 调大该参数值
  4. 其他使用注意事项可以参考 F-Stack 和 HTTP/3 相关配置文档

性能测试对比

这里不考虑现网实际客户端访问网站的延迟对比,仅考虑 F-Stack Nginx 和源生 Nginx 的性能对比测试。

但是在尝试了多种客户端后,仅 curl8 测试成功,但是只能测试单连接的延迟,这里不太关注。其他压测客户端工具 wrk-quic、h2load、Nighthawk 等在编译测试时都遇到了各种各样的问题,暂时未能成功测试,性能对比数据暂时缺失,如果有人有压测客户端,欢迎进行对比测试并提供测试数据。

Nginx TCP 多证书透明代理及 Linux/F-Stack(FreeBSD) 路由相关设置

某个 TCP 服务对外有多个域名提供相同的服务,且每个域名都是基于 TLS 的,需要通过 Nginx 对 TLS 进行卸载后转发到实际的上游服务,且上游服务必须使用客户端的源 IP,所以 Nginx 需要使用透明代理。分别需要对Nginx 和系统路由进行配置。

Nginx 配置

需要 Nginx 1.15.9 以上版本,简化配置如下所示,

stream {       
  upstream up_server {
      server 192.168.1.3:8081;
  }
   
  # 通过 map 配置不同域名(SNI)使用不同的证书文件
  # 证书为泛解析证书, 匹配泛解析域名
  # 会降低性能
  map $ssl_server_name $targetCert {
      ~*domain1.com$ /usr/local/cert/domain1.crt;
      ~*domain2.com$ /usr/local/cert/domain2.crt;
      ~*domain2.com$ /usr/local/cert/domain3.crt;
      default /usr/local/cert/domain1.crt;
  }
   
  map $ssl_server_name $targetCertKey {
      ~*domain1.com$ /usr/local/cert/domain1.key;
      ~*domain2.com$ /usr/local/cert/domain2.key;
      ~*domain2.com$ /usr/local/cert/domain3.key;
      default /usr/local/cert/domain1.key;
  }
   
  server {
      listen 8080 ssl reuseport;
   
      ssl_certificate $targetCert;
      ssl_certificate_key $targetCertKey;
      ssl_protocols TLSv1.2 TLSv1.3;
      ssl_session_tickets off;

      proxy_pass up_server;
      proxy_bind $remote_addr transparent; # 透明代理
  }
}

Linux 系统路由配置

因为透明代理的源 IP 是实际客户的 IP,在实际服务接受处理完响应包返回时会返回给实际的客户 IP,所以需要配置将回包发到 Nginx 进行处理,这里的上游服务为本机服务,需进行如下配置。如上游在其他服务器上,可以查看参考资料中文章并进行对应配置。

# 新建一个 DIVERT 给包打标签
iptables -t mangle -N DIVERT;
iptables -t mangle -A DIVERT -j MARK --set-mark 1;
iptables -t mangle -A DIVERT -j ACCEPT;

# 把本机 TCP 服务的回包给 DIVERT 处理
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8081 -j DIVERT

# 有标签的包去查名为 100 的路由表
ip rule add fwmark 1 lookup 100

# 100的路由表里就一条默认路由,把所有包都扔到lo网卡上去
ip route add local 0.0.0.0/0 dev lo table 100

F-Stack(FreeBSD) 路由配置

F-Stack(FreeBSD) 上游回包路由配置,

# upstream 为本机时
# 假设 f-stack-0 的 IP 为 192.168.1.3,将 upstream 往外发的所有出包都转发到 F-Stack Nginx 监听的 IP 和端口即可
# 因为转发到本机地址时目的端口会被忽略,可不设置端口
ff_ipfw add 100 fwd 192.168.1.3,8080 tcp from 192.168.1.2 8081 to any out

# upstream 为其他机器时
# 将 upstream 通过设置网关或者 IP 隧道(需额外进行隧道配置)等方式发过来的所有入包都转发到 F-Stack Nginx 监听的 IP 和端口即可
# 因为转发到本机地址时目的端口会被忽略,可不设置端口
ff_ipfw add 100 fwd 192.168.1.3 tcp from 192.168.1.2 8081 to any in via f-stack-0

参考资料

  1. nginx TLS SNI routing, based on subdomain pattern
  2. 使用nginx的proxy_bind选项配置透明的反向代理
  3. [sslh] Using sslh transparent proxy on FreeBSD?