Linux如何做到将外来数据包DNAT到Loopback

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介:
前面写了篇文章《 使用iptables为何不能将外部进入的包NAT到127.0.0.1 》,牵扯到了很多知识,最终的结论就是不能那么做。这个结论让人有些不舒服,说了半天就是阐述它为何做不到,如果我非要将包NAT到loopback呢。比如为了不将端口以及地址暴露给外部,我就是想让代理服务侦听127.0.0.1这个地址,有没有什么办法做到呢?
        因为你用的是Linux,答案无疑是肯定的,所要做的无非就是把路由时候的限制条件给去掉。我们知道,在进和出两个方向,有两个路由查找逻辑,一个是
ip_route_input_slow,另一个是ip_route_output_slow,其中前一个函数的限制在: if (ipv4_is_lbcast(daddr) || ipv4_is_zeronet(daddr) ||         ipv4_is_loopback(daddr))     goto martian_destination;
而后一个函数限制在其调用的__mkroute_output中:
if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))     return -EINVAL;
我们到底应该怎么绕过去呢?如今我们知道,NAT逻辑已经将目标地址改成了127.0.0.1,而源地址保持不变,这种数据包在进入的时候会被ip_route_input_slow拦截并且由于源地址非loopback而目标为loopback而丢弃,丢弃的时候注意还没有查找路由呢?现在我们假设这个包已经过去了,那么返回包能不能顺利通过ip_route_output_slow呢?答案无疑也是否定的,由于标准路由是基于目标地址的,对于返回包的目标地址其实就是正向包的源地址,路由查找无疑是可以通过的,但是路由前由于还不知道目标设备,因此不能简单地丢弃,只有到了路由之后确定目标设备非loopback之后才能丢弃。
        以上就是Linux协议栈路由模块对待“火星地址”的逻辑。总结一下就是:
1.对于进入包,若是loopback发来的包则不通过路由查找逻辑,凡是通过路由查找的,都是外来包,路由前知道源地址和目标地址,可以根据目标地址是否loopback而判断是否丢弃;
2.对于发出包,无条件(考虑下路由cache,意义是一样的)都要经过路由查找,路由前不一定知道源地址,即使知道源地址也不能确定目标设备是否是loopback设备,只有通过路由查找(local表命中的目标设备为loopback设备)才可以知道全部信息,因此在路由后,生成路由cache之前判断源地址是loopback地址而却是发往非loopback设备而决定丢弃之。

        现在我们已经知道了大致的逻辑,我们分两步来绕开它们。

0.需要做的工作

我们需要修改NAT内核模块,涉及修改的有$KERL/net/ipv4/netfilter/nf_nat_standalone.c这个文件。

1.绕开对进入的正向包的地址限制

此即绕开ip_route_input_slow的限制,当然修改协议栈代码是最有效的,可是那样需要重新编译kernel,于是我们在DNAT之后就直接将一个dst_entry附着在这个skb上,以便协议栈认为已经查找过了路由而不再经过路由查找逻辑从而绕开上述的第一个限制。由于对于所有被DNAT到127.0.0.1的包都使用同一个dst_entry,因此我们只需要在系统中保留一份即可:
struct rtable *dummy_rth = (struct rtable*)kmalloc(sizeof(struct rtable), GFP_ATOMIC);
注意,如果是准备在insmod的时候,也就是调用module的init时生成这个rtable的话,大可不必使用GFP_ATOMIC标志。
然后我们在nf_nat_standalone_init中填充其字段:
struct dst_entry dst = dummy_rth->u.dst; memset(&dummy_rth->u.dst, 0, sizeof(struct dst_entry)); dummy_rth->u.dst.ops = NULL; dummy_rth->u.dst.path = &dst; dummy_rth->u.dst.flags= DST_HOST; dummy_rth->u.dst.flags |= DST_NOXFRM; dummy_rth->u.dst.flags |= DST_NOPOLICY; dummy_rth->u.dst.dev = init_net.loopback_dev; dummy_rth->rt_iif = init_net.loopback_dev->ifindex;  dummy_rth->rt_type = RTN_LOCAL; dummy_rth->u.dst.input = (int (*)(struct sk_buff*))0xffffffff812734eb;  //ip_local_deliver的地址 atomic_set(&dummy_rth->u.dst.__refcnt, 1);
接下来,我们需要在每一个被DNAT到loopback的数据包上应用上述的rtable,那么在哪里应用呢?很显然是在PREROUTING这个HOOK点的NAT之后了,也就是nf_nat_in钩子函数里面:
static unsigned int nf_nat_in(unsigned int hooknum,           struct sk_buff *skb,           const struct net_device *in,           const struct net_device *out,           int (*okfn)(struct sk_buff *)) {         unsigned int ret;         __be32 daddr = ip_hdr(skb)->daddr;          ret = nf_nat_fn(hooknum, skb, in, out, okfn);         if (ret != NF_DROP && ret != NF_STOLEN &&             daddr != ip_hdr(skb)->daddr)                 skb_dst_drop(skb);         if (daddr != ip_hdr(skb)->daddr && ipv4_is_loopback(ip_hdr(skb)->daddr)) {                 atomic_inc(&dummy_rth->u.dst.__refcnt);    //增加一个引用计数。                 skb_dst_set(skb, &dummy_rth->u.dst);      //将rtable的dst_entry附着到skb上。     }     return ret; }
以上就完成了正向包的路由前火星地址检查,注意我们并不打算将上述的dummy_rth插入到系统的路由cache哈希表中,大多数的字段也没有填充,这是因为我们从来不想把它用作什么路由cache,也从不想将其删除,也不想被常规的管理,我们只是在NAT模块还在的时候使用它将数据包路由到本地传输层或者以上而已。

2.绕开对返回包的地址限制

和第1点一样,我们也可以通过修改__mkroute_output的源码来做到这一点,然而这不是什么好方法,比较好的方法的思路和第1点类似,那就是通过增加一个rtable来绕开标准的路由查找,然而却比上述第1点要难很多,这是因为这个rtable不能上上面那样做一个dummy,毕竟来源地址可能是不同的,并且几乎所有rtable的字段都需要,这次它是真正被用作路由的,而不是仅仅将述举包导入本地,这就涉及更底层的诸如arp,neighbour等问题了,另外,这个rtable由于不唯一,不固定,因此最好将其纳入到系统的路由cache哈希表中管理。
        具体来讲就是在nf_nat_in中再添加一个逻辑:
if (daddr != ip_hdr(skb)->daddr && ipv4_is_loopback(ip_hdr(skb)->daddr)) {     struct rtable *rev_dummy = dst_alloc(&ipv4_dst_ops);     int err;     unsigned hash;     atomic_inc(&dummy_rth->u.dst.__refcnt);    //增加一个引用计数。     skb_dst_set(skb, &dummy_rth->u.dst);      //将rtable的dst_entry附着到skb上。     //以下构造返回包使用的rtable,注意是反向的,源地址就是目标地址,目标地址成了源地址     atomic_set(&rev_dummy->u.dst.__refcnt, 1);     rev_dummy->u.dst.flags= DST_HOST;     rev_dummy->u.dst.flags |= DST_NOXFRM;     rev_dummy->u.dst.flags |= DST_NOPOLICY;     rev_dummy->fl.fl4_dst    = ip_hdr(skb)->saddr;     rev_dummy->fl.fl4_tos    = (u32)(RT_TOS(ip_hdr(skb)->tos) & (IPTOS_RT_MASK | RTO_ONLINK));;     rev_dummy->fl.fl4_src    = ip_hdr(skb)->daddr;     ...     rev_dummy->rt_dst    = ip_hdr(skb)->saddr;     rev_dummy->rt_src    = ip_hdr(skb)->daddr;     hash = rt_hash(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr, 0,                                rt_genid(dev_net(skb->dev)));     err = rt_intern_hash(hash, rev_dummy, rev_dummy, NULL); }
如此一来也就成了,具体的测试方式很简单,那就是写一个侦听127.0.0.1:abc的TCP程序,重新编译并加载nf_nat.ko以及iptable_nat.ko模块,然后设置以下的规则:
iptables -t nat -A PREROUTING -p tcp --dport 1234 -j DNAT --to-destination 127.0.0.1

即可。

        另外如果对Windows上做hack比较熟悉的话,还有一种更加高效的方式来处理上述第2点,那就是“二进制重置”,通过内核符号表找到__mkroute_output的地址,并且通过特征码找到:

if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))

if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))
这个判断,然后用二进制指令覆盖的方式将其重写,直接跳过或者通过增加一个标志判断一下是否是DNAT到loopback的返回包,然后根据判断结果进行抉择...

附:调试中遇到的问题

1.由于是在VMWare虚拟机里面调试的,而且没有安装X系统和VMWare-tools,开始的时候由于rtable的字段没有设置全,比如dev字段没有设置,而后面的tcp处理逻辑还需要这个字段,从而导致了系统panic,打印出了一堆trace,然而由于虚拟机里面分辨率很低,只能显示N行,N行上面的都看不到,而且用sysrq也没有效果,于是想办法提高终端的分辨率,google了一下,发现使用grub的kernel中的vga内核启动参数即可设置,设置了之后,马上看出了问题之所在。
2.很多符号没有导出来,因此必须使用其地址,还要强制转化一下。当然想办法使用导出的符号也不是不可的,然而却需要做更多的工作,比如链接特定的obj文件等,索性还是直接使用地址吧...

3.其实想用kprobe了,然而不知怎么回事总感觉那是Windows程序员的风格,再说又不是没有别的办法,不到万不得已,不用这种。(难道为了将二进制覆盖技术练得炉火纯青,哥必须把所有机器的汇编都掌握了吗?实际上,哥只会x86上的-以前搞Windows的时候学的,x86-64的都不怎么懂啊)



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1268951

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
6月前
|
网络协议 Shell Linux
【Shell 命令集合 网络通讯 】Linux 追踪数据包在网络中的路径 traceroute命令 使用指南
【Shell 命令集合 网络通讯 】Linux 追踪数据包在网络中的路径 traceroute命令 使用指南
158 0
|
3月前
|
存储 运维 安全
在Linux中,如何使用tcpdump和tshark进行实时数据包捕获?
在Linux中,如何使用tcpdump和tshark进行实时数据包捕获?
|
3月前
|
负载均衡 Linux 网络虚拟化
在Linux中,什么是NAT,常见分为那几种,DNAT与SNAT有什么不同,应用事例有那些?
在Linux中,什么是NAT,常见分为那几种,DNAT与SNAT有什么不同,应用事例有那些?
|
3月前
|
机器学习/深度学习 网络协议 安全
在Linux中,如何追踪TCP连接和网络数据包,如使用tcpdump或Wireshark?
在Linux中,如何追踪TCP连接和网络数据包,如使用tcpdump或Wireshark?
|
6月前
|
网络协议 Linux 网络安全
Linux-SNAT和DNAT
Linux-SNAT和DNAT
102 0
|
弹性计算 网络协议 Linux
linux服务器自建snat和dnat
为了节省成本,购买了云服务器的时候只买了一个公网IP,但是有多台机器需要实现上网或者是被访问,一个IP只能绑定一个机器,就只能使用NAT网关或者是内网源进行下载,受限较大。
|
弹性计算 Kubernetes 网络协议
k8s网络诊断之被丢弃的SYN--linux数据包的接收过程(k8s+flannel+ ipvs)
某客户反馈,ECS上自建nginx server 通过proxy_pass 反向代理 云上k8s集群 nodeport类型的svc,存在大量1s的延迟请求的问题,在nginx所在的ecs上,使用netstat可以看到syn_sent状态的connection,如下图所示,但是在pod所在的worker节点上是看不到syn_RECV状态的connection(nodeport上也无)
1288 0
|
Linux
Linux—traceroute命令 – 追踪数据包在网络上的传输时的全部路径
traceroute命令用于追踪数据包在网络上的传输时的全部路径,它默认发送的数据包大小是40字节。通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。
637 0
Linux—traceroute命令 – 追踪数据包在网络上的传输时的全部路径
|
网络协议 Linux
|
缓存 网络协议 Linux