Linux使用TPROXY进行UDP的透明代理

Linux使用TPROXY进行UDP的透明代理,第1张

在进行TCP的代理时,只要在NET表上无脑进行REDIRECT就好了。例如使用ss-redir,你只要把tcp的流量redirect到ss-redir监听的端口上就OK了。但是当你使用这种方法的时候,就会不正常,因为对于UDP进行redirect之后,原始的目的地址和端口就找不到了。

这是为什么呢?

ss-redir的原理很简单:使用iptables对PREROUTING与OUTPUT的TCP/UDP流量进行REDIRECT(REDIRECT是DNAT的特例),ss—redir在捕获网络流量后,通过一些技术手段获取REDIRECT之前的目的地址(dst)与端口(port),连同网络流量一起转发至远程服务器。

针对TCP连接,的确是因为Linux Kernel连接跟踪机制的实现才使获取数据包原本的dst和port成为可能,但这种连接跟踪机制并非只存在于TCP连接中,UDP连接同样存在,conntrack -p udp便能看到UDP的连接跟踪记录。内核中有关TCP与UDP的NAT源码/net/netfilter/nf_nat_proto_tcp.c和/net/netfilter/nf_nat_proto_udp.c几乎一模一样,都是根据NAT的类型做SNAT或DNAT。

那这究竟是怎么一回事?为什么对于UDP连接就失效了呢?

回过头来看看ss-redir有关获取TCP原本的dst和port的源码,核心函数是getdestaddr:

在内核源码中搜了下有关SO_ORIGINAL_DST的东西,看到了getorigdst:

We only do TCP and SCTP at the moment。Oh,shit!只针对TCP与SCTP才能这么做,并非技术上不可行,只是人为地阻止罢了。

为了在redirect UDP后还能够获取原本的dst和port,ss-redir采用了TPROXY。Linux系统有关TPROXY的设置是以下三条命令:

大意就是在mangle表的PREROUTING中为每个UDP数据包打上0x2333/0x2333标志,之后在路由选择中将具有0x2333/0x2333标志的数据包投递到本地环回设备上的1080端口;对监听0.0.0.0地址的1080端口的socket启用IP_TRANSPARENT标志,使IPv4路由能够将非本机的数据报投递到传输层,传递给监听1080端口的ss-redir。IP_RECVORIGDSTADDR与IPV6_RECVORIGDSTADDR则表示获取送达数据包的dst与port。

可问题来了:要知道mangle表并不会修改数据包,那么TPROXY是如何做到在不修改数据包的前提下将非本机dst的数据包投递到换回设备上的1080端口呢?

这个问题在内核中时如何实现的,还待研究,但是确定是TPROXY做了某些工作。

TPROXY主要功能:

TPROXY要解决的两个重要的问题

参考:

https://blog.csdn.net/ts__cf/article/details/78942294

https://vvl.me/2018/06/09/from-ss-redir-to-linux-nat/

socks5在socks4上多了对udp代理的支持,可以说是“全能代理”了。udp代理较对比tcp代理,流程上要复杂一些,下面从最开始流程讲起

客户端建立和代理服的tcp连接A,并发送第一帧数据:

代理服务器收到后,会鉴别VER,回应

流程和前一篇 socks5协议简介 完全相同

客户端发送第二帧数据:

CMD:命令,tcp代理0x01,udp代理0x03

RSV:保留字段

ATYP:地址类型 0x01:ipv4,0x03:域名,0x04:ipv6

DST.ADDR:本地使用udp的地址

DST.PORT:本地使用udp的端口

比如数据 :0x05|0x03|0x00|0x01|0x00000000|0x0000

代表需要代理的是udp连接,此时DST.ADDR和DST.PORT代表客户端UDP准备发送的地址和端口,用于服务器权限控制(只给DST.ADDR:DST.PORT发出来的udp包代理),当然可以为空即全是0

代理服收到后,回复

REP:状态位,0x00代表连接DST.ADDR成功了

RSV:保留字段

ATYP:地址类型 0x01:ipv4,0x03:域名,0x04:ipv6

BND.ADDR:udp代理地址

BND.PORT:udp代理端口

比如,返回0x05|0x00|0x00|0x01|0xc0a8c76a|0xcdf0,通知客户端UDP代理地址是192.168.199.106:52720,有什么udp包直接向这个地址发

客户端知道udp代理地址后,将所在发送的数据DATA需要包裹成下面的帧发送给192.168.199.106:52720

RSV:保留位

FRAG:分片位

ATYP:地址类型 0x01:ipv4,0x03:域名,0x04:ipv6

DST.PORT:目标地址

DST.PORT:目标端口

DATA:要发送的数据

其中FRAG分片位,用于控制大帧分成多帧用,一般情况下为0x00(不分帧)

UDP代理收到后,将DATA分离出来,发送给DST.ADDR:DST.PORT,同时,收到目标报的UDP的数据,也包裹成上面帧结构发给客户端

可以发现,udp代理的建立是在与代理服先建立tcp连接,tcp连接上先“握手”和“准备代理”,客户端知道udp代理地址后,就不用这个tcp连接了,直接udp代理了,理论上这个tcp连接无用了,但socks5协议指出,这个tcp连接要保持长连接,如果断开,则相应的udp代理也要结束,真正实现上,我看过的许多socks5代理源码没实现的这么细,我的golang实现socks5上,也只是保持这个tcp长连接,有时间再完善这个细节点

想详细了解的可以参照我的golang实现 https://github.com/0990/socks5

RFC1928-SOCKS Protocol Version 5

如果说你想让vmware中的linux互相访问,你这么做虚拟机的网卡应该是不可以的,你必须将两个虚拟机的网卡模式都做成一样的,要么NAT,要么桥接,NAT的话要看你NAT的是哪个虚拟网卡,说起来比较麻烦,还是建议用桥接模式,至于你说的UDP,没有什么穿透的概念,你只要将两个虚拟机能互相ping通,UDP就可以互相访问。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/8364719.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-15
下一篇 2023-04-15

发表评论

登录后才能评论

评论列表(0条)

保存