Nginx代理服务应用实战
-
- 2、TCP/UDP代理
-
- 2.1、stream核心模块
- 2.2、stream辅助模块
- 2.3、TCP/UDP代理
-
- 2.3.1、核心配置指令
- 2.3.2、TCP反向代理配置样例
- [2.3.3、代理SSL TCP](#2.3.3、代理SSL TCP)
- 2.3.4、UDP反向代理配置
- 2.4、基于SSL的TCP代理
- 2.5、TCP/UDP代理的真实客户端IP
2、TCP/UDP代理
Nginx通过stream模块提供了对TCP/UDP代理的支持,stream模块是在Nginx 1.9.0版本上开始添加的,该模块在Nginx配置文件配置中增加了stream指令域,通过在stream指令域中对指令的配置,实现TCP/UDP协议的代理功能。
2.1、stream核心模块
Nginx的TCP/UDP代理功能的模块分为核心模块和辅助模块,核心模块stream需要在编译配置时增加"--with-stream"参数进行编译。核心模块的全局配置指令如表所示。


- 指令listen使用的指令域范围为server。
- 指令variables_hash_bucket_size和variables_hash_max_size使用的指令域范围为stream。
- stream核心模块其余指令使用的指令域范围为stream、server。
- resolver指令值可填写多个域名解析服务器地址,各个地址用空格分隔。
- listen指令值参数如表所示。

配置样例如下:
bash
stream {
resolver 114.114.114.114 valid=300s;
resolver_timeout 2s;
upstream backend {
server 192.168.0.1:333;
server www.example.com:333;
}
server {
listen 127.0.0.1:333 udp reuseport;
proxy_timeout 20s;
proxy_pass backend;
}
server {
listen [::1]:12345;
proxy_pass unix:/tmp/stream.socket;
}
}
2.2、stream辅助模块
2.2.1、ngx_stream_map_module
该模块的功能是在客户端每次连接时,Nginx按照map指令域中源变量的当前值,把设定的对应值赋给新变量。该指令的语法格式如下:
bash
map 源变量 新变量{}
-
这个指令使用的指令域只有stream。
-
指令值参数如表所示。

-
map指令域中,当源变量值存在相同匹配项时,匹配的顺序如下:
- 完全匹配的字符串;
- 有主机前缀的最长字符串;
- 有主机后缀的最长字符串;
- 在指令域中按自上而下的顺序最先匹配到的正则表达式;
- default参数给定的默认值。
-
map哈希表大小指令如表所示。

-
map哈希桶大小指令如表所示。

配置样例如下:
bash
stream{
map $remote_addr $limit {
127.0.0.1 "";
default $binary_remote_addr;
}
limit_conn_zone $limit zone=addr:10m;
limit_conn addr 1;
server {
listen 33060 reuseport;
access_log logs/tcp.log tcp;
proxy_timeout 20s;
proxy_pass 127.0.0.1:3306;
}
}
2.2.2、ngx_stream_geo_module
该模块的功能是从源变量获取IP地址,并根据设定的IP与对应值的列表对新变量进行赋值。该模块只有一个geo指令,指令格式如下:
bash
geo [源变量]新变量{}
- geo指令的默认源变量是$remote_addr,新变量默认值为空。
- 这个指令使用的指令域只有stream。
- 指令值参数如表所示。

配置样例如下:
bash
geo $country {
ranges;
default CN;
127.0.0.0-127.0.0.0 US;
10.1.0.0-10.1.255.255 RU;
192.168.1.0-192.168.1.255 UK;
}
geo $country {
default ZZ;
include conf/geo.conf;
delete 127.0.0.0/16;
127.0.0.0/24 US;
10.1.0.0/16 RU;
192.168.1.0/24 UK;
}
2.2.3、ngx_stream_geoip_module
该模块的功能首先是根据客户端的IP地址与MaxMind数据库中的城市地址信息做比对,然后再将对应的城市地址信息赋值给内置变量。
- 国家信息数据库指令

- 城市信息数据库指令

- 机构信息数据库指令

配置样例如下:
bash
stream {
geoip_country /usr/share/GeoIP/GeoIP.dat;
geoip_city /usr/share/GeoIP/GeoLiteCity.dat;
map $geoip_city_continent_code $nearest_server {
default example.com;
EU eu.example.com;
NA na.example.com;
AS as.example.com;
}
...
}
2.2.4、ngx_stream_split_clients_module
该模块会按照配置指令将一个0~232之间的数值根据设定的比例分割为多个数值范围,每个数值范围会被设定一个对应的给定值。用户每次请求时,指定的字符串会被计算出一个数值,该模块会将该数值所在范围对应的给定值赋值给配置中定义的变量。该功能常用来按照用户的来源IP进行访问流量分流。该指令的语法格式如下:
bash
split_clients 字符串 新变量 {}
配置样例如下:
bash
stream {
split_clients "${remote_addr}AAA" $upstream { # ${remote_addr}AAA会被计算出一个数值
0.5% backend1; # 数值在0 ~ 21474835之间,$upstream被赋值backend1
80.0% backend2; # 数值在21474836 ~ 3435973836之间,$upstream被赋值backend2
* backend; # 数值在3435973837 ~ 4294967295, $upstream被赋值backend
}
server {
listen 389;
proxy_pass $upstream;
}
}
- 这个指令使用的指令域只有stream。
- 客户端每次请求时,指定字符串会被使用MurmurHash2算法计算出一个0~232(0~4 294 967 295)之间的数值,该模块会将该数值所在范围对应的给定值赋值给配置中定义的变量。
2.2.5、ngx_stream_ssl_preread_module
该模块可以在预读取阶段从ClientHello消息中提取信息,赋值给内置变量后供用户调用。
SSL信息预读如表所示。

内置变量如表所示。

配置样例如下:
bash
stream {
map $ssl_preread_protocol $upstream {
"" ssh.example.com:22;
"TLSv1.2" new.example.com:443;
default tls.example.com:443;
}
server {
listen 192.168.0.1:443;
proxy_pass $upstream;
ssl_preread on;
}
}
2.2.6、ngx_stream_limit_conn_module
该模块对访问连接中含有指定变量且变量值相同的连接数进行计数,当计数值达到limit_conn指令设定的值时,Nginx服务器将关闭此类连接。由于Nginx采用的是多进程的架构,因此该模块通过共享内存存储计数状态并实现了多个进程间的计数状态共享。
- 计数存储区指令

- 连接数设置指令

- 连接数日志级别指令

配置样例如下:
bash
stream {
limit_conn_zone $binary_remote_addr zone=addr:10m; # 对客户端IP进行并发计数,计数内存区
# 命名为addr,计数内存区的大小为10MB
server {
limit_conn addr 1; # 限制客户端的并发连接数为1
...
}
}
-
limit_conn_zone的格式如下:
bashlimit_conn_zone key zone=name:size; -
limit_conn_zone的key可以是文本、变量或文本与变量的组合。
-
$binary_remote_addr为IPv4时,占用4B;为IPv6时,占用16B。
-
limit_conn_zone中,1MB的内存空间可以存储32 000个32B或16 000个64B的变量计数状态。
-
变量计数状态在32位系统平台占用32B或64B,在64位系统平台占用64B。
2.2.7、ngx_stream_access_module
这个模块可以允许或拒绝客户端的源IP地址进行连接。
- 允许连接指令

- 拒绝连接指令

配置样例如下:
bash
stream {
server {
deny 192.168.1.1; # 禁止192.168.1.1
allow 192.168.0.0/24; # 允许192.168.0.0/24的IP访问
allow 10.1.1.0/16; # 允许10.1.1.0/16的IP访问
allow 2001:0db8::/32;
deny all;
}
}
Nginx按照自上而下的顺序进行匹配。
2.2.8、ngx_stream_return_module
该模块向客户端返回指定值并关闭连接。
返回值指令如表所示。

配置样例如下:
bash
stream {
server {
listen 12345;
return $time_iso8601; # 返回当前连接的时间
}
}
2.3、TCP/UDP代理
Nginx并不直接提供TCP/UDP的应用响应,Nginx Stream模块的核心功能是将客户端的TCP/UDP连接反向代理给后端的被代理服务器。
2.3.1、核心配置指令
TCP/UDP代理功能的核心配置指令如表所示。



该模块的指令使用的指令域范围为stream、server。
2.3.2、TCP反向代理配置样例
配置样例如下:
bash
stream {
server {
listen 389 ; # 设置监听端口为389
proxy_pass 192.168.2.100:389; # 将连接代理到后端192.168.2.100:389
proxy_timeout 5s; # 与被代理服务器的连续通信间隔大于5s,
# 则认为通信超时,将关闭连接
proxy_connect_timeout 5s; # 与被代理服务器建立连接的超时时间为5s
access_log logs/ldap_access.log tcp; # 记录日志文件为logs/ldap_access.log,
# 日志模板为tcp
}
}
2.3.3、代理SSL TCP
代理模块stream可以实现基于SSL/TLS协议的被代理服务器的反向代理,部署方式为客户端→Nginx服务器(TCP)→被代理服务器(SSL TCP)。配置样例如下:
bash
stream {
server{
listen 636; # 设置监听端口为636
access_log logs/ldap_access.log tcp;
proxy_pass 192.168.2.100:636;
proxy_ssl on; # 启用SSL/TLS协议,与被代理服务器建立连接
proxy_ssl_session_reuse on; # 与被代理服务器SSL TCP连接的SSL会话重用功能
}
}
2.3.4、UDP反向代理配置
UDP协议是一种无连接的协议,发送端与接收端传输数据之前不需要建立连接,发送端会尽最大努力把数据发送出去,不能保证安全地传输到接收端。由于传输数据不建立连接,也不需要维持复杂的链路关系(包括连接状态、收发状态等),因此发送端可同时向多个接收端传输相同的消息。虽然UDP的数据传输是不可靠的,但如果有一个数据报丢失,另一个新的数据报会在几秒内替换它发送到接收端。UDP协议通常被用在单向传输无须返回响应及信息分发的场景,如日志收集或在屏幕上的航班信息、股票行情等多媒体场景。
bash
stream {
server {
listen 1514 udp; # 设置监听端口为1514并启用UDP协议
proxy_pass 192.168.2.123:1514;
proxy_responses 0; # 会话接收数据报后无须等待返回响应,立即关闭会话
}
}
2.4、基于SSL的TCP代理
Nginx可以通过代理模块实现上游服务器SSL/TLS协议的连接,同时Nginx还通过模块ngx_stream_ssl_module提供了基于SSL/TLS协议的TCP连接监听。Nginx还可以把SSL证书部署在Nginx服务器上,这就减轻了后端上游服务器的CPU运算量并实现SSL证书的统一管理和维护。ngx_stream_ssl_module模块默认不会被构建,这就需要在编译的时候通过--with-stream_ssl_module参数进行启用。相关配置指令如表所示。


-
该模块指令值使用的指令域范围为stream、server。
-
Nginx建立SSL TCP监听,用户发送SSL TCP连接时,由Nginx实现SSL终止并把TCP会话代理到上游服务器,部署方式为客户端→Nginx服务器(SSL TCP)→上游服务器(TCP)。配置样例如下:
bashstream { server { listen 636 ssl; # 设置监听端口为636 access_log logs/ldap_access.log tcp; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 设置使用的SSL协议版本 ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5; # 设置服务端使用的密码套件 ssl_certificate ssl/www_nginxbar_org.pem; # 主机名www.nginxbar.org证书文件 ssl_certificate_key ssl/www_nginxbar_org.key; # 主机名www.nginxbar.org证书密钥文件 ssl_session_cache shared:SSL:10m; # SSL TCP会话缓存设置共享内存区域名为 # SSL,区域大小为10MB ssl_session_timeout 10m; # SSL TCP会话缓存超时时间为10分钟 proxy_pass 192.168.2.100:389; } } -
也可以通过代理模块的proxy_ssl指令配置与上游服务器实现全链路的安全数据通信。部署方式为客户端→Nginx服务器(SSL TCP)→被代理服务器(SSL TCP)。配置样例如下:
bashstream { server { listen 636 ssl; # 设置监听端口为636 access_log logs/ldap_access.log tcp; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 设置使用的SSL协议版本 ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5; # 设置服务端使用的密码套件 ssl_certificate ssl/www_nginxbar_org.pem; # 主机名www.nginxbar.org证书文件 ssl_certificate_key ssl/www_nginxbar_org.key; # 主机名www.nginxbar.org证书密钥文件 ssl_session_cache shared:SSL:10m; # SSL TCP会话缓存设置共享内存区域名为SSL,区域大 小为10MB ssl_session_timeout 10m; # SSL TCP会话缓存超时时间为10分钟 proxy_ssl on; # 启用SSL/TLS协议,与被代理服务器建立连接 proxy_ssl_session_reuse on; # 与被代理服务器SSL TCP连接的SSL会话重用功能 } }
2.5、TCP/UDP代理的真实客户端IP
客户端TCP连接的会话经过Nginx反向代理才会转发到被代理服务器,在HTTP协议中的被代理服务器可以通过X-Forwarded-For头部获得传递的客户端IP,但TCP/UDP则不能使用该方法。对于支持proxy protocol协议的被代理服务器,Nginx通过在传输层header之上添加一层描述客户端IP和端口的proxy protocol来解决客户端IP传递问题。利用Nginx提供的ngx_stream_realip_module模块的配置指令set_real_ip_from,可以手动设置上层反向代理服务器的IP作为授信IP,用于甄别基于proxy protocol协议连到本机的真实客户端IP地址。该模块配置指令如表所示。

2.5.1、proxy_protocol配置样例
在proxy_protocol配置样例场景中,两台作为反向代理的Nginx服务器在客户端访问ldap服务器的链路中,通过proxy_protocol传递真实客户端IP,部署示意如图所示。

bash
stream {
server {
listen 3891; # 设置监听端口为3891
proxy_protocol on; # 与被代理服务器间启用proxy protocol支持
proxy_pass 192.168.2.159:389; # 转发TCP会话到被代理服务器
access_log logs/ldap_access.log tcp;
}
}
stream {
server {
listen 389 proxy_protocol; # 设置监听端口为389,并启用proxy protocol支持
set_real_ip_from 192.168.2.145; # 设置授信IP
proxy_pass 192.168.2.100:389; # 转发TCP会话到被代理服务器
proxy_timeout 5s;
proxy_connect_timeout 5s;
access_log logs/ldap_access.log tcp;
}
}
2.5.2、TCP透传配置样例
大多数TCP服务并不支持proxy protocol,且Nginx对UDP协议也不支持proxy protocol。为了让被代理服务器获得真实客户端IP,也可以使用proxy_bind指令将客户端IP透传给被代理服务器。当被代理服务器为Linux操作系统时,通过iptables将被代理服务器向客户端发送响应数据并由Nginx返回给客户端。
在TCP透传配置样例场景中,客户端通过反向代理服务器Nginx访问Redis服务器,Nginx通过proxy_bind指令的透传(transparent)参数将客户端IP传递给redis服务器。然后再返回数据,通过被代理服务器的iptables标记路由发送给Nginx服务器,在Nginx服务器上再通过iptables标记路由交由Nginx转发给客户端。Nginx透传如图所示。

bash
stream {
server {
listen 6379 ; # 设置监听端口为6379
proxy_bind $remote_addr transparent; # 启用客户端IP透传
proxy_pass 192.168.2.100:6379; # 转发TCP会话到被代理服务器
proxy_connect_timeout 5s; # 建立连接超时时间为5s
access_log logs/redis_access.log tcp;
}
}
被代理服务器的默认网关通常与Nginx不是同一个IP,proxy_bind指令透传参数模式下的被代理服务器接收到客户端TCP连接时,在默认网关的路由下无法找到透传过来的客户端IP地址和端口的路由,所以无法正常响应客户端的连接。为了使被代理服务器可以把连接响应返回给客户端,首先需要将被代理服务器连接响应的路由网关指定为Nginx服务器,其次在Nginx服务器上,通过iptables将目的IP为客户端IP的数据交由Nginx进程处理。在不影响被代理服务器原有网络通信的前提下,只需将被代理服务器中源端口为应用端口的数据默认网关设置为Nginx服务器IP即可。
首先在Redis服务器上配置iptables出包规则。
bash
## 关闭系统的源地址校验功能
echo "net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0 " >>/etc/sysctl.conf
sysctl -p
## 定义Nginx策略路由并设定在路由表中的优先级,数值越小优先级越高
echo "200 nginx" >> /etc/iproute2/rt_tables
## 使用iptables将源端口为6379的OUTPUT链数据标记值设置为1
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 6379 -j MARK --set-mark 1
## 将标记值为1的数据与策略路由表Nginx绑定
ip rule add fwmark 1 table nginx
## 设置策略路由表Nginx的默认网关为Nginx IP 192.168.2.159
ip route add default via 192.168.2.159 dev eth0 table nginx
其次在Nginx服务器上配置iptables监听规则。
bash
## 定义Nginx策略路由并设定在路由表中的优先级,数值越小优先级越高
echo "200 nginx" >> /etc/iproute2/rt_tables
## 使用iptables将源IP设置为被代理服务器IP且源端口为6379的PREROUTING链数据标记值设置为1
iptables -t mangle -A PREROUTING -p tcp -s 192.168.2.100 --sport 6379 -j MARK --set-mark 1
## 将标记值为1的数据与策略路由表Nginx绑定
ip rule add fwmark 1 table nginx
## 设置策略路由表Nginx的所有流量交由本地socket应用Nginx处理
ip route add local 0.0.0.0/0 dev lo table nginx
配置完毕后,当客户端访问Redis服务器时,在Redis服务器上获取的客户端IP就是真实的客户端IP,可以用如下命令验证查看:
bash
## 在Redis服务器上执行客户端查看命令
redis-cli client list