Proxy_protocol_V2

1. 功能说明

代理协议(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)

在标准的地址信息后,如果有额外的数据,则该数据是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. 指令说明

2.1 proxy_protocol

Syntax: proxy_protocol   on/off
Default:
Context: stream,server

说明:开启proxy_protocol。

2.2 proxy_pp2

Syntax: proxy_pp2   on/off
Default:
Context: stream,server

说明:开启proxy_protocol v2。

2.3 proxy_pp2_set_tlv

Syntax: proxy_pp2_set_tlv  key value
Default:
Context: stream,server

说明:key 是 16 进制(两位,要避免和标准的冲突)。 value 可以是常量,变量。例如:proxy_pp2_set_tlv 0x31 test; 获取:$proxy_protocol_tlv_0x31 前缀变量。注意:1.包含前面的源ip,目标ip,源端口,目标端口,以及tlv 值,总长度不能超过 40962.变量可以是set 的变量。以及预读取阶段之前 产生的官方变量(njt_stream_ssl_preread_module 模块的变量无法使用)。

2.4 proxy_session_protocol_enable

Syntax: proxy_session_protocol_enable $session_var
Default:
Context: stream,server

说明:Stream 连接级别的开关。通过变量来决定单个连接是否走proxy protocol 协议。proxy_session_protocol_enable $session_var 的值: 变量值为1 代表走proxy protocol 协议,结合proxy_pp2 ,proxy_pp2_set_tlv 传递tlv。 变量值不为1,不走proxy protocol 协议。没有proxy_session_protocol_enable 设置变量。(如果配置了proxy_protocol on ; 则走proxy protocol 协议, off 或 没配置则不走proxy protocol 协议)。

3 配置说明

http {
    dyn_kv_conf conf/iot-work.conf;
    include       mime.types;
    default_type  application/octet-stream;
  
    access_log  logs/access.log;

    variables_hash_max_size  2048;
  
    sendfile        on;
    keepalive_timeout  65;

   server {
        listen 5555 proxy_protocol;
        server_name server-5555;
        location /test {     #return 返回结果
          return 200 "src:$proxy_protocol_addr:$proxy_protocol_port,dst:$proxy_protocol_server_addr:$proxy_protocol_server_port,tlv01:$proxy_protocol_tlv_0x31,tlv02:$proxy_protocol_tlv_0x41";
        }
}

stream {
      upstream backend1 {
         zone backend1_zone 128k;
         server 127.0.0.1:22222;
    }

     server {
        listen 22222  mesh;
        listen [::1]:22222
        proxy_protocol on;  #开启protocol 功能
        proxy_pp2  on;      #开启protocol V2功能
  
        set $test 123;
        set $test2 11111111111111111112;
        proxy_pp2_set_tlv  0x31    $test;   #设置tlv 字段名,以及值
        proxy_pp2_set_tlv  0x41    $test2;  #设置tlv 字段名,以及值
        proxy_pass 127.0.0.1:5555;
     }
}

4. 调用样例

4.1 IPv4访问stream模块22222端口

curl 127.0.0.1:22222/test

src:127.0.0.1:48304,dst:127.0.0.1:22222,tlv01:123,tlv02:11111111111111111112

4.2 IPv6访问stream模块22222端口

curl -g -6 [::1]:22222/test

src:::1:48312,dst:::1:22222,tlv01:123,tlv02:11111111111111111112

4.3 NJETSidecar 应用v2传递原始目的地址

Ingress sidecar配置

stream {
 
     server {
        listen 15006 mesh ;
        proxy_protocol on;  #开启protocol 功能
        proxy_pp2  on;      #开启protocol V2功能 
    
        proxy_pp2_set_tlv  0x41    $njtmesh_port;  #设置tlv 字段名,以及值
        proxy_pass 127.0.0.1:22222;
     }
}

Ingress policy的配置

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

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

4.4 验证指令proxy_session_protocol_enable

helper broker modules/njt_helper_broker_module.so conf/mqtt.conf;
helper ctrl modules/njt_helper_ctrl_module.so conf/ctrl.conf;
load_module modules/njt_http_split_clients_2_module.so;
load_module modules/njt_agent_dynlog_module.so;
load_module modules/njt_http_dyn_bwlist_module.so;
load_module modules/njt_dyn_ssl_module.so;
load_module modules/njt_http_vtsc_module.so;
#load_module modules/njt_http_dyn_server_module.so;  #server验证
load_module modules/njt_http_location_module.so;  #location验证

user  root;
worker_processes 1;
#daemon off;
#worker_rlimit_nofile 65535;

cluster_name helper;
node_name node-u01;

error_log  logs/error.log error;
pid        logs/njet.pid;

events {
    worker_connections  1024;
}


http {
    dyn_kv_conf conf/iot-work.conf;
    include       mime.types;
    default_type  application/octet-stream;

    access_log  logs/access.log;


    keepalive_timeout  65;
   server {
        listen  0.0.0.0:5557  proxy_protocol;
        server_name  dev.test.com;

        location / {
            charset utf-8;
            default_type text/html;
            return 200 "5557 proxy_protocol  $proxy_protocol_tlv_0x31 \n";
        }
     }
   server {
        listen  0.0.0.0:5558 ;
        server_name  dev.test.com;
        location / {
            charset utf-8;
            default_type text/html;
            return 200 "5558 no proxy_protocol  $proxy_protocol_tlv_0x31 \n";
        }
     }

}
stream {

        map $session_var $ssl_server {
                1  127.0.0.1:5557;
                0  127.0.0.1:5558;
                default 127.0.0.1:5558;
        }
        map $njtmesh_dest $session_var {
                127.0.0.1:443   1;
                192.168.40.119:443 0;
                default            0;
        }
        server {

                listen  443 mesh;
                proxy_pp2  on;
                proxy_session_protocol_enable  $session_var;
                proxy_pp2_set_tlv  0x31    $njtmesh_dest;
                proxy_pass  $ssl_server;


        }
}

测试结果:

使用curl访问127.0.0.1:443,走proxy protocol协议,tlv 携带的$proxy_protocol_tlv_0x31 值。
curl -v http://127.0.0.1:443/

* processing: http://127.0.0.1:443/
*   Trying 127.0.0.1:443...
* Connected to 127.0.0.1 (127.0.0.1) port 443
> GET / HTTP/1.1
> Host: 127.0.0.1:443
> User-Agent: curl/8.2.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: njet/1.2.3
< Date: Tue, 14 Nov 2023 06:32:07 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 36
< Connection: keep-alive
<
5557 proxy_protocol  127.0.0.1:443    #返回值中携带了$proxy_protocol_tlv_0x31变量的值
* Connection #0 to host 127.0.0.1 left intact
使用curl访问192.168.40.119:443,不走proxy protocol协议,,没有携带$proxy_protocol_tlv_0x31 值。
curl -v http://192.168.40.119:443/

* processing: http://192.168.40.119:443/
*   Trying 192.168.40.119:443...
* Connected to 192.168.40.119 (192.168.40.119) port 443
> GET / HTTP/1.1
> Host: 192.168.40.119:443
> User-Agent: curl/8.2.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: njet/1.2.3
< Date: Tue, 14 Nov 2023 07:04:09 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 26
< Connection: keep-alive
<
5558 no proxy_protocol   #返回值中未携带$proxy_protocol_tlv_0x31变量的值
* Connection #0 to host 192.168.40.119 left intact