首页 » Web前端 » phpudpnginx技巧_若何运用nginx实现udp的反向代理

phpudpnginx技巧_若何运用nginx实现udp的反向代理

访客 2024-12-02 0

扫一扫用手机浏览

文章目录 [+]

图1 UDP报文的协议分层

在TCP/IP或者 OSI网络七层模型中,每层的任务都是如此明确:

phpudpnginx技巧_若何运用nginx实现udp的反向代理

物理层专注于供应物理的、机器的、电子的数据传输,但这是有可能涌现差错的;数据链路层在物理层的根本上通过差错的检测、掌握来提升传输质量,并可在局域网内使数据报文跨主机可达。
这些功能是通过在报文的前后添加Frame头尾部实现的,如上图所示。
每个局域网由于技能特性,都会设置报文的最大长度MTU(Maximum Transmission Unit),用netstat -i(linux)命令可以查看MTU的大小:而IP网络层的目标是确保报文可以跨广域网到达目的主机。
由于广域网由许多不同的局域网,而每个局域网的MTU不同,当网络设备的IP层创造待发送的数据字节数超过MTU时,将会把数据拆成多个小于MTU的数据块各自组成新的IP报文发送出去,而吸收主机则根据IP报头中的Flags和Fragment Offset这两个字段将吸收到的无序的多个IP报文,组合成一段有序的初始发送数据。
IP报头的格式如下图所示:

图2 IP报文头部

phpudpnginx技巧_若何运用nginx实现udp的反向代理
(图片来自网络侵删)

IP协议头(本文只谈IPv4)里最关键的是Source IP Address发送方的源地址、Destination IP Address目标方的目的地址。
这两个地址担保一个报文可以由一台windows主机到达一台linux主机,但并不能决定一个chrome浏览的GET要求可以到达linux上的nginx。

4、传输层紧张包括TCP协议和UDP协议。
这一层最紧张的任务是担保端口可达,由于端口可以归属到某个进程,当chrome的GET要求根据IP层的destination IP到达linux主机时,linux操作系统根据传输层头部的destination port找到了正在listen或者recvfrom的nginx进程。
以是传输层无论什么协议其头部都必须有源端口和目的端口。
例如下图的UDP头部:

图3 UDP的头部

TCP的报文头比UDP繁芜许多,由于TCP除了实现端口可达外,它还供应了可靠的数据链路,包括流控、有序重组、多路复用等高等功能。
由于上文提到的IP层报文拆分与重组是在IP层实现的,而IP层是不可靠的所有数组效率低下,以是TCP层还定义了MSS(Maximum Segment Size)最大报文长度,这个MSS肯定小于链路中所有网络的MTU,因此TCP优先在自己这一层拆成小报文避免的IP层的分包。
而UDP协议报文头部太大略了,无法供应这样的功能,以是基于UDP协议开拓的程序须要开拓职员自行把握不要把过大的数据一次发送。

对报文有所理解后,我们再来看看UDP协议的运用处景。
比较TCP而言UDP报文头不过8个字节,以是UDP协议的最大好处是传输本钱低(包括协议栈的处理),也没有TCP的拥塞、滑动窗口等导致数据延迟发送、吸收的机制。
但UDP报文不能担保一定送达到目的主机的目的端口,它没有重传机制。
以是,运用UDP协议的程序一定是可以容忍报文丢失、不接管报文重传的。
如果某个程序在UDP之上包装的运用层协议支持了重传、乱序重组、多路复用等特性,那么他肯定是选错传输层协议了,这些功能TCP都有,而且TCP还有更多的功能以担保网络通讯质量。
因此,常日实时声音、视频的传输利用UDP协议是非常得当的,我可以容忍正在看的视频少了几帧图像,但不能容忍溘然几分钟前的几帧图像溘然插进来:-)

UDP协议的会话保持机制

有了上面的知识储备,我们可以来搞清楚UDP是如何坚持会话连接的。
对话便是会话,A可以对B说话,而B可以针对这句话的内容再回一句,这句可以到达A。
如果能够坚持这种机制自然就有会话了。
UDP可以吗?当然可以。
例如客户端(要求发起者)首先监听一个端口Lc,就像他的耳朵,而做事供应者也在主机上监听一个端口Ls,用于吸收客户真个要求。
客户端任选一个源端口向做事器的Ls端口发送UDP报文,而做事供应者则通过任选一个源端口向客户真个端口Lc发送相应端口,这样会话是可以建立起来的。
但是这种机制有哪些问题呢?

问题一定要结合场景来看。
比如:1、如果客户端是windows上的chrome浏览器,怎么能让它监听一个端口呢?端口是会冲突的,如果有其他进程占了这个端口,还能不事情了?2、如果开了多个chrome窗口,那个第1个窗口发的要求对应的相应被第2个窗口收到怎么办?3、如果刚发完一个要求,进程挂了,新启的窗口收到老的相应怎么办?等等。
可见这套方案并不适宜消费者用户的做事与做事器通讯,以是视频会议等看来是弗成。

有其他办法么?有!
如果客户端利用的源端口,同样用于吸收做事器发送的相应,那么以上的问题就不存在了。
像TCP协议便是如此,其connect方的随机源端口将一贯用于连接上的数据传送,直到连接关闭。

这个方案对客户端有以下哀求:不要利用sendto这样的方法,险些任何措辞对UDP协议都供应有这样的方法封装。
应该先用connect方法获取到socket,再调用send方法把要求发出去。
这样做的缘故原由是既可以在内核中保存有5元组(源ip、源port、目的ip、目的端口、UDP协议),以使得该源端口仅吸收目的ip和端口发来的UDP报文,又可以反复利用send方法时比sendto每次都上通报目的ip和目的port两个参数。

对做事器端有以下哀求:不要利用recvfrom这样的方法,由于该方法无法获取到客户真个发送源ip和源port,这样就无法向客户端发送相应了。
应该利用recvmsg方法(有些编程措辞例如python2就没有该方法,但python3有)去吸收要求,把获取到的对端ip和port保存下来,而发送相应时可以仍旧利用sendto方法。

须要C/C++ Linux做事器架构师学习资料后台私信“资料”(资料包括C/C++,Linux,golang技能,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

接下来我们谈谈nginx如何做udp协议的反向代理。

Nginx的stream系列模块核心便是在传输层上做反向代理,虽然TCP协议的运用处景更多,但UDP协议在Nginx的角度看来也与TCP协议大同小异,比如:nginx向upstream转发要求时仍旧是通过connect方法得到的fd句柄,吸收upstream的相应时也是通过fd调用recv方法获取消息;nginx吸收客户真个时则是通过上文提到过的recvmsg方法,同时把获取到的客户端源ip和源port保存下来。
我们先看下recvmsg方法的定义:

ssize_t recvmsg(int sockfd, struct msghdr msg, int flags);

相对付recvfrom方法,多了一个msghdr构造体,如下所示:

struct msghdr { void msg_name; / optional address / socklen_t msg_namelen; / size of address / struct iovec msg_iov; / scatter/gather array / size_t msg_iovlen; / # elements in msg_iov / void msg_control; / ancillary data, see below / size_t msg_controllen; / ancillary data buffer len / int msg_flags; / flags on received message /};

个中msg_name便是对真个源IP和源端口(指向sockaddr构造体)。
以上是C库的定义,其他高等措辞类似方法会更大略,例如python里的同名方法是这么定义的:

(data, ancdata, msg_flags, address) = socket.recvmsg(bufsize[, ancbufsize[, flags]])

个中返回元组的第4个元素便是对真个ip和port。

配置nginx为UDP反向代理做事

以上是nginx在udp反向代理上的事情事理。
实际配置则很大略:

# Load balance UDP-based DNS traffic across two serversstream { upstream dns_upstreams { server 192.168.136.130:53; server 192.168.136.131:53; } server { listen 53 udp; proxy_pass dns_upstreams; proxy_timeout 1s; proxy_responses 1; error_log logs/dns.log; }}

在listen配置中的udp选项见告nginx这是udp反向代理。
而proxy_timeout和proxy_responses则是坚持住udp会话机制的紧张参数。

UDP协议自身并没有会话保持机制,nginx于是定义了一个非常大略的坚持机制:客户端每发出一个UDP报文,常日期待吸收回一个报文相应,当然也有可能不相应或者须要多个报文相应一个要求,此时proxy_responses可配为其他值。
而proxy_timeout则规定了在最长的等待韶光内没有相应则断开会话。

如何通过nginx向后端做事通报客户真实IP

末了我们来谈一谈经由nginx反向代理后,upstream做事如何才能获取到客户真个地址?如下图所示,nginx不同于IP转发,它事实上建立了新的连接,以是正常情形下upstream无法获取到客户真个地址:

图4 nginx反向代理粉饰了客户真个IP

上图虽然因此TCP/HTTP举例,但对UDP而言也一样。
而且,在HTTP协议中还可以通过X-Forwarded-For头部通报客户端IP,而TCP与UDP则弗成。
Proxy protocol本是一个好的办理方案,它通过在传输层header之上添加一层描述对真个ip和port来办理问题,例如:

但是,它哀求upstream上的做事要支持解析proxy protocol,而这个协议还是有些小众。
最关键的是,目前nginx对proxy protocol的支持则仅止于tcp协议,并不支持udp协议,我们可以看下其代码:

可见nginx目前并不支持udp协议的proxy protocol(笔者下的nginx版本为1.13.6)。

虽然proxy protocol是支持udp协议的。
怎么办呢?

方案1IP地址透传

可以用IP地址透传的办理方案。
如下图所示:

图5 nginx作为四层反向代理向upstream展示客户端ip时的ip透传方案

这里在nginx与upstream做事间做了一些hack的行为:

nginx向upstream发送包时,必须开启root权限以修正ip包的源地址为client ip,以让upstream上的进程可以直接看到客户真个IP。

server { listen 53 udp; proxy_responses 1; proxy_timeout 1s; proxy_bind $remote_addr transparent; proxy_pass dns_upstreams;}upstream上的路由表须要修正,由于upstream是在内网,它的网关是内网网关,并不知道把目的ip是client ip的包向哪里发。
而且,它的源地址端口是upstream的,client也不会认的。
以是,须要修正默认网关为nginx所在的机器。

# route del default gw 原网关ip# route add default gw nginx的ipnginx的机器上必须修正iptable以使得nginx进程处理目的ip是client的报文。

# ip rule add fwmark 1 lookup 100# ip route add local 0.0.0.0/0 dev lo table 100 # iptables -t mangle -A PREROUTING -p tcp -s 172.16.0.0/28 --sport 80 -j MARK --set-xmark 0x1/0xffffffff

这套方案实在对TCP也是适用的。

方案2DSR(上游做事无公网)

除了上述方案外,还有个Direct Server Return方案,即upstream回包时nginx进程不再参与处理。
这种DSR方案又分为两种,第1种假定upstream的机器上没有公网网卡,其办理方案图示如下:

图6 nginx做udp反向代理时的DSR方案(upstream无公网)

这套方案做了以下hack行为:

1、在nginx上同时绑定client的源ip和端口,由于upstream回包后将不再经由nginx进程了。
同时,proxy_responses也须要设为0。

server { listen 53 udp; proxy_responses 0; proxy_bind $remote_addr:$remote_port transparent; proxy_pass dns_upstreams;}

2、与第一种方案相同,修正upstream的默认网关为nginx所在机器(任何一台拥有公网的机器都行)。

3、在nginx的主机上修正iptables,使得nginx可以转发upstream发回的相应,同时把源ip和端口由upstream的改为nginx的。
例如:

# tc qdisc add dev eth0 root handle 10: htb# tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.11 match ip sport 53 action nat egress 172.16.0.11 192.168.99.10# tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.12 match ip sport 53 action nat egress 172.16.0.12 192.168.99.10# tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.13 match ip sport 53 action nat egress 172.16.0.13 192.168.99.10# tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.14 match ip sport 53 action nat egress 172.16.0.14 192.168.99.10方案3DSR(上游做事有公网)

DSR的另一套方案是假定upstream上有公网线路,这样upstream的回包可以直接向client发送,如下图所示:

图6 nginx做udp反向代理时的DSR方案(upstream有公网)

这套DSR方案与上一套DSR方案的差异在于:由upstream做事所在主机上修正发送报文的源地址与源端口为nginx的ip和监听端口,以使得client可以吸收到报文。
例如:

# tc qdisc add dev eth0 root handle 10: htb# tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.11 match ip sport 53 action nat egress 172.16.0.11 192.168.99.10结语

以上三套方案皆可以利用开源版的nginx向后端做事通报客户端真实IP地址,但都须要nginx的worker进程跑在root权限下,这对运维并不友好。
从协议层面,可以期待后续版本支持proxy protocol通报客户端ip以办理此问题。
在当下的诸多运用处景下,除非业务场景明确无误的谢绝超时重传机制,否则还是应该利用TCP协议,其完善的流量、拥塞掌握都是我们必须拥有的能力,如果在UDP层上重新实现这套机制就得不偿失落了。

标签:

相关文章

介绍百度码,技术革新背后的智慧之光

随着科技的飞速发展,互联网技术已经成为我们生活中不可或缺的一部分。而在这个信息爆炸的时代,如何快速、准确地获取信息,成为了人们关注...

Web前端 2025-01-03 阅读1 评论0

介绍皮箱密码,开启神秘之门的钥匙

皮箱,作为日常生活中常见的收纳工具,承载着我们的珍贵物品。面对紧闭的皮箱,许多人却束手无策。如何才能轻松打开皮箱呢?本文将为您揭秘...

Web前端 2025-01-03 阅读1 评论0

介绍盗号器,网络安全的隐忧与应对步骤

随着互联网的快速发展,网络安全问题日益突出。盗号器作为一种非法工具,对网民的个人信息安全构成了严重威胁。本文将深入剖析盗号器的原理...

Web前端 2025-01-03 阅读1 评论0