nginx的tcp透明代理设置

背景

目前,接入层access使用nginx作为反向代理。客户端连接nginx打开的5000端口后,nginx通过客户端ip哈希的方式,将客户端的接入请求负载均衡到后台的4个access服务。
使用nginx作为反向代理时,客户端与nginx直连,同时nginx为每一个客户端连接建立了单独的tcp连接到access服务。此时,nginx代理带来的问题是,access无法获取到客户端的真实ip,而只能拿到nginx代理服务所在机器的ip。(所有的代理都有类似的问题,因为与上游服务(例如这里的access服务)连接的实际上是代理服务器,ip包里的ip地址是代理服务器的地址)。
但是,很时候我们需要拿到客户端的真实ip做一些业务上的判断。这个时候,就需要通过某些方式获取客户端的真实ip。如果代理服务器和上游服务之前是通过http通讯,则可以通过http的首部X-Forwarded-For将客户端的ip信息传给上游服务(nginx可以很方便的设置该首部设置)。
对于纯tcp代理,则获取客户端ip会麻烦一些。主要有两种方式:

  1. 类似http协议,在业务服务上包装一层proxy protocol,通过proxy protocol传递客户端的ip地址。
    nginx可以通过protocol_proxy on启用proxy protocol,同时,需要上游服务也增加对proxy protocol的支持。
  2. 通过Linux 2.6.24中,socket新增的选项IP_TRANSPARENT,使socket可以接收目的地址没有配置的数据包,也可以发送源地址不是本地地址的数据包实现。使用这种方式,上游服务不需要任何修改就可以得到客户端的ip。下文主要说明这种方式。

原理

nginx作为上游服务的反向代理时,每个客户端的连接方式如下:

           tcp                 tcp
client <--------->  nginx  <---------> upstream server(access)

实现透明代理的原理是,nginx使用获取到的客户端ip来建立nginx与上游服务直接的tcp连接。也即:

  1. nginx与上游服务的tcp链接建立和通讯时,从nginx发布的ip数据包,源地址由nginx服务所在机器的地址替换为客户端的ip地址;
  2. 从上游服务器返回的ip数据包,目的地址也是客户端的ip地址,通过特殊的路由方式,将返回的ip数据包路由到nginx进程;
  3. 最后nginx将收到的数据发送给对应的客户端。

设置

服务和机器部署说明

总共有两台服务器,内网ip地址分别为172.19.228.32172.19.228.33。两台机器部署的服务如下:

  • 172.19.228.32
    主服务器。nginx绑定5000端口。两个access服务绑定15010以及15011端口。

  • 172.19.229.33
    副服务器。两个access服务绑定15010以及15011端口。

  • nginx与所有access实例都通过局域网ip连接,包括与nginx同机器部署的access。

nginx配置

  1. nginx需要以root的用户运行,因为socket的IP_TRANSPARENT选项需要超级用户权限。
# in the "main"  context
use root;
  1. nginx在stream中设置proxy_bind,使nginx在连接上游服务器时,启用socket的IP_TRANSPARENT选项。
# in the "stream"  context
server {
...
proxy_bind $remote_addr transparent;
}

nginx设置完成并reload配置后,所有从nginx发送到上游服务器的ip数据包将使用客户端的ip地址作为源地址。后续的设置,需要将上游服务器返回的ip数据包(其中的目的地址是客户端的ip)返回到nginx所在的机器,并投递给nginx进程。

172.19.228.32服务器设置

32机器上的上游服务返回ip数据包给nginx时,指定的目的ip地址是客户端的ip地址。32机器需要将这些数据返回给nginx进程。

# 1. 将32上的上游服务回复的数据路由到nginx进程
# 1.1 对32上的上游服务返回的ip数据设置标记1
# NOTE: 如果32上增加access服务,需要修改这里--sport源端口,将新增的access服务端口包含进来
iptables -t mangle -A OUTPUT -p tcp --src 172.19.228.32 --sport 15010:15011 \
         -j MARK --set-xmark 0x1/0xffffffff
         
# 1.2 新增路由表,表id为100,将所有ip数据路由到lo网络接口         
# NOTE: 以下两条ip命令设置的route和rule都需要保存到/etc/rc.local中,以便在开机时启动
ip route add local 0.0.0.0/0 dev lo table 100
# 1.3 为标记为1的ip数据使用路由表100
ip rule add fwmark 1 lookup 100

设置完成后,32上的上游服务(端口为15010以及15011)发出的ip数据包,因为被设置了标记而匹配新增的策略路由,所有ip数据被路由到lo网络接口。同时,因为这些数据包中包含的端口是nginx连接上游服务时打开的端口,所以这些数据最终被分用到nginx进程。
这样,nginx和同在一个机器上的上游服务之间的透明代理设置完整。上游服务将可以得到客户端的ip地址,同时可以将数据返回给nginx,nginx再将返回的数据返回给对应的客户端。
此时,如果使用lsof(1)命令查看上游服务打开的tcp链接,显示的源ip地址也将是客户端的ip地址。

172.19.228.33服务器设置

33上的上游服务与nginx在不同的主机上。主要设置如下:

# 1. 允许来自局域网网络接口bond0的ip数据
# 1.1 该设置主要避免来自32的ip数据被33屏蔽
iptables -I INPUT -i bond0 -j ACCEPT

# 1.2 禁用33主机上的reverse path filter。
# NOTE:因为nginx启动透明代理后,
#       33将在局域网网络接口bond0上收到源地址是客户端ip的数据(一般是公网ip), 
#       如果不禁用reverse path filter, 则因为无法在局域网网络接口回复公网的数据包,
#       导致33上收到的tcp sync包被33直接丢弃,导致tcp连接无法建立。
echo "net.ipv4.conf.all.rp_filter = 0" >> /etc/sysctl.conf
echo "net.ipv4.conf.bond0.rp_filter = 0" >> /etc/sysctl.conf

# 2. 将33上的上游服务回复的数据路由到32机器
# 2.1 对33上的上游服务发出的包设置标记1
# NOTE: 如果33上增加access服务,需要修改这里--sport源端口,将新增的access服务端口包含进来
iptables -t mangle -A OUTPUT -p tcp --src 172.19.228.33 --sport 15010:15011 \
         -j MARK --set-xmark 0x1/0xffffffff

# 2.2 新增路由表,表id为100,将所有ip数据路由到32机器
# NOTE: 以下两条ip命令设置的route和rule都需要保存到/etc/rc.local中,以便在开机时启动
ip route add default via 172.19.228.32 table 100
# 2.3 为标记为1的ip数据使用路由表100
ip rule add fwmark 1 lookup 100

# 3. 将33的上游服务返回的ip数据路由到32机器之后,再把这些数据路由到nginx进程
# 3.1 32上收到的来自33上游服务的数据设置标记1,使这些数据可以路由到nginx进程
# NOTE: 注意这一条命令在32主机上执行;
#       33上的增加access服务,需要修改这里的--sport源端口,将新增的access服务端口包含进来
# NOTE: 因为32上已经设置了对标记为1的ip数据使用策略路由表100,将其路由到lo网络接口
#       所以只需要对33返回的数据设置标记1即可
iptables -t mangle -A PREROUTING -p tcp --src 172.19.228.33 --sport 15010:15011 \
         -j MARK --set-xmark 0x1/0xffffffff

设置完成后,33可以接收nginx发出的ip数据包,并可以将上游服务回复的ip数据路由给32机上的nginx进程。
同时,使用lsof(1)命令查看33的上游服务打开的tcp链接,源ip将是客户端的ip。其他所有的与nginx不在同一个主机上的上游服务都可以参考这个配置设置。

参考:
nginx的透明代理实现
IP Transparency and Direct Server Return with NGINX and NGINX Plus as Transparent Proxy
Linux kernel rp_filter settings

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343