OpenNJet支持维护proxy_protocol v2了

🙈 By 单雷 2023-09-22

proxy_protocol_V2功能

1. 背景

proxy_protocol 介绍:

代理协议(Proxy protocol),是HAProxy的作者Willy Tarreau于2010年开发和设计的一个Internet协议,通过为tcp添加一个很小的包头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取用户真实IP时非常有用。

代理协议分为V1和V2两个版本,V1是人类易读的,V2是二进制格式的,并且支持tlv 功能。

v1介绍 :

Proxy protocol V1的格式如下:

PROXY 协议栈 源IP 目的IP 源端口 目的端口 rn

例如:

PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n
    GET / HTTP/1.1\r\n
    Host: 192.168.0.11\r\n
    \r\n

v2 介绍 :

相比V1,v2利用二进制格式以实现更高的解析效率,并可以增加特定的扩展属性(TLV)

img

img

在标准的地址信息后,如果有额外的数据,则该数据是TLV数组。每个tlv结构包括类型(type),length,value。 其中如下的类型数值已经被标准化,应用应该遵循该type的含义,不应用作他途。


        #define PP2_TYPE_ALPN           0x01
        #define PP2_TYPE_AUTHORITY      0x02
        #define PP2_TYPE_CRC32C         0x03
        #define PP2_TYPE_NOOP           0x04
        #define PP2_TYPE_UNIQUE_ID      0x05
        #define PP2_TYPE_SSL            0x20
        #define PP2_SUBTYPE_SSL_VERSION 0x21
        #define PP2_SUBTYPE_SSL_CN      0x22
        #define PP2_SUBTYPE_SSL_CIPHER  0x23
        #define PP2_SUBTYPE_SSL_SIG_ALG 0x24
        #define PP2_SUBTYPE_SSL_KEY_ALG 0x25
        #define PP2_TYPE_NETNS          0x30

常见应用:

• 传递client的real ip(尤其是非http类的应用,http类可以用x-forward-realip等header传递)

• 代理协议版本2 支持额外的TLV 字段。他可以被前端ssl协议卸载器转发客户端证书信息转发到后端非http 协议的后端服务器。VerneMQ MQTT 代理就是示例之一,它可以利用代理协议版本2,获取客户端的证书的详细信息,进行身份授权。

• 用于在K8s环境中,ingress 对非Http 协议的ssl 终止。

• GCP/amazon/arure 利用特定TLV 实现负载均衡

2. 模块说明

NJet在兼容nginx的v1版本发送及解析的基础上,实现了发送protocol v2 ,及自定义TLV发送的能力。

3. 配置说明:

该配置在stream 模块生效。

必填 配置说明
proxy_protocol 开启proxy_protocol
proxy_pp2 开启proxy_protocol v2。 on 开启, off 关闭
proxy_pp2_set_tlv proxy_pp2_set_tlv key value 格式。key 是 16 进制(两位,要避免和标准的冲突,)。 value 可以是常量,变量。例如:proxy_pp2_set_tlv 0x31 test; 获取:$proxy_protocol_tlv_0x31 前缀变量。注意:包含前面的源ip,目标ip,源端口,目标端口,以及tlv 值,总长度不能超过 4096

4. 应用实例

NJetSidecar 应用v2传递原始目的地址

业务流程:

  • 1.在K8s环境中,client通过域名访问一个服务(例如, http请求 “api.test.com:8080”), 把请求发往service对应的service_ip, 防火墙劫持该请求,重定向到本地监听15001 端口的sidecar进程处理。
  • 2.egress sidecar 是一个4层代理,可以识别出http流量,并把http流量转发到本地的“egress policy”做进一步处理。
  • 3.egress policy是个7层的代理server,它会挑选合适的后端真实的server地址,(此例中, server instance1, ip:10.0.30.1 或 instance2, ip:10.0.30.2). 假设选中server1后 ,它会连接 10.0.30.1:8080,并发送请求。
  • 3.1 请求到10.0.30.1后, 入向的防火墙规则控制把请求都重定向到“ingress sidecar”,它也是一个4层代理 ,监听15006. 同时,防火墙把原始目的端口 (8080)记录在ip 包头(tcp option)里
  • 4.ingress process 也必须挑选出http请求,转发给ingress policy,后者是一个7层代理,监听8086
  • 5.ingress policy 在分析内容,并处理后,应该转发到原始的目的端口8080(阶段4.2),由于ingress sidecar是个4层带来,不会修改http协议,因此,无法把8080这个信息传递给ingress policy。

img

解决方案:

Ingress sidecar在发送请求到 ingress policy时(步骤4.2),通过代理协议,在TCP连接的最开始传递该信息。ingress policy通过标准的NJet变量方式获取。

Ingress sidecar配置


stream {
 
     server {
        listen 15006  ;
        njtmesh_dest on;    #参考下面该指令的备注 
        proxy_protocol on;  #开启protocol 功能
        proxy_pp2  on;      #开启protocol V2功能 
        
        proxy_pp2_set_tlv  0x41    $njtmesh_port;  #设置tlv 字段名,以及值
        proxy_pass 127.0.0.1:90;
     }
}

Ingress policy的配置



http {
 
   server {
        listen 8086 proxy_protocol;
        server_name server-90;
        location /{ 
          #todo: 其他的业务配置指令
          proxy_pass 127.0.0.1:$proxy_protocol_tlv_0x41;
        }
 }
}

注:# njtmesh_dest NJet自定义指令,是用于获取firewall规则设置的原始目标信息的指令,开启后,可以通过$njtmesh_port 获取原始的目标端口,同样$njtmesh_ip,可以获得原始的目的地址。

5. 参考及致谢

• NJet同nginx一样,通过变量访问传递的信息,http://nginx.org/en/docs/http/ngx_http_core_module.html#var_proxy_protocol_tlv_

• 部分代码参考了https://github.com/dedok/nginx-stream-proxy-protocol-v2,在此表示感谢

• Nginx 商业版本有模块https://nginx.org/en/docs/http/ngx_http_proxy_protocol_vendor_module.html,可以解析云服务商如GCP/amazon/Azure的自定义TLV,用于更好的LB控制,但未见设置模块。

• 协议rfc参考:https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt