51学通信论坛2017新版

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 2487|回复: 0
打印 上一主题 下一主题

关于Linux策略路由和iptables OUTPUT链的一点看法

[复制链接]

 成长值: 15613

  • TA的每日心情
    开心
    2022-7-17 17:50
  • 2444

    主题

    2544

    帖子

    7万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    74104
    跳转到指定楼层
    楼主
    发表于 2017-11-15 21:14:17 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    前言

    如果想实现哪个网口进来的流量从哪个网口返回这么一个需求,有一个范式,我先贴出来:iptables -t mangle -A PREROUTING -j CONNMARK --restore-markiptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPTiptables -t mangle -A PREROUTING -i XXX .... -j MARK --set-mark YYYiptables -t mangle -A PREROUTING -m mark ! --mark 0 -j CONNMARK --save-markiptables -t mangle -A OUTPUT -j CONNMARK --restore-markip rule add fwmark YYY table YYYip route add $net/$mask via $gw dev $device table YYY

    netfilter与iptables


    Linux数据包路由原理


    iptables命令参数


    问题


    但是这里面有一个细节,对于本机流量而言,上述的OUTPUT链上的那条规则不一定能匹配成功,其实并不是没有匹配成功,而是说数据包在匹配这条规则之前就已经被丢弃了,根本就没有机会去匹配它!这是为什么呢?

    因为Netfilter的OUTPUT HOOK点是发生在标准IP路由之后的,如果在标准的路由查找(而不是策略路由查找)中没有成功找到路由,那么数据包在进入OUTPUT链之前就会被丢掉。
    也许你会问,为什么不直接查找策略路由表呢?
    很显然,这里发生了循环依赖,策略路由依赖fwmark,而要想把fwmark打给一个数据包,就必然要让数据包经过OUTPUT链,要想让数据包经过OUTPUT链,则必然要成功找到一条常规的路由。如果你的服务器没有配置这种恰好能被数据包使用的常规路由,那么上述的范式相当于就废掉了!不过值得注意的是,这条常规路由并不是真的给感兴趣数据包路由的,它存在的目的仅仅是为了让数据包有机会进入OUTPUT链去打mark。注意到这点,有助于我们设想出一个优雅的解决方案。
    那么我们怎么解决这个问题呢?
    方法1:使用socket来设置mark

    既然是本地流量,那么应用层一定会有一个服务来接纳这些流量并将本地始发流量注入协议栈,做这件事的无一例外就是socket,而socket可以通过下面的方式为协议栈的数据包提前打上mark:int mark = 100; setsockopt(client_socket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark));
    反正数据包上的mark就是为了策略路由,因此,协议栈里的数据包就不需要下面OUTPUT链上的规则了:
    iptables -t mangle -A OUTPUT -j CONNMARK --restore-mark;
    这是一个好的方案,但却并不一定可行,为什么呢?因为这要涉及到编程,超出了系统管理的范畴,而且服务程序代码不是说动就动的,即便是改一行代码,也要经过无穷尽的回归测试再测试…
    因此,我们来看看方法2。
    方法2:使用dummy网卡

    我觉得这是一个更加优雅的方案,不需要改代码,且不会造成流量泄漏!
    不是说在OUTPUT前必须找到一条路由吗?好吧,我给!然而这条路由真的就不是让你发包的,而只是一个Dummy路由!值得注意的是,Dummy路由并不是unreachable路由,也不是Linux上的blackhole路由,它是确确实实通过网卡发数据的路由,只是该网卡发数据的方式比较怪异,它只是默默丢弃数据包。
    很好,这并不会带来任何流量的泄漏。
    这一招依赖于你对Linux系统的足够熟悉,它依赖于一个特殊设备,叫做dummy网卡,你可以通过下面的命令加载它:modprobe dummy;ifconfig dummy0 up;
    然后设置一条默认路由:ip route add 0.0.0.0/0 dev dummy0;
    OK,就这样!
    如果你的系统配置中已经有了默认路由,就不必添加dummy默认路由了,因为彼种情况下数据包会被既有默认路由引导到OUTPUT链,待成功restore-mark后再重新路由。因此,为了不让默认路由冲突,可能需要封装一个脚本,添加基于fwmark的策略路由或者默认路由的时候,相互check一下,逻辑比较复杂,这里就不说了。
    后记

    其实这个问题早在几年前就遇到并思考过,近期有位朋友在工作中也遇到了同样的问题,所以就再次追忆了往事。
    当时第一次遇到这个问题的时候,我发誓一定要解决它,这并不是一个很难解决的问题,只需要把OUPUT移到路由前面即可,类似PREROUTING那样,但是这样又产生了循环依赖,比如OUTPUT链可以匹配-o这个match,如果OUTPUT在路由前,那么-o参数哪里来呢?
    也难怪OUTPUT的特殊性了,虽然它和PREROUTING看上去差不多,但是方向却截然相反,只能作罢!后来,我想到了修改路由查找的代码,如果没有找到路由,就撸一遍OUTPUT HOOK,然后重新路由,但依然太复杂了,不优雅…抑或说,在应用程序没有绑定网卡设备的时候再撸OUTPUT?
    再往后,各种繁乱的工作任务交织,就把这事遗忘了。

    声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。
    扫描并关注51学通信微信公众号,获取更多精彩通信课程分享。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    Archiver|手机版|小黑屋|51学通信技术论坛

    GMT+8, 2025-1-31 15:00 , Processed in 0.110481 second(s), 33 queries .

    Powered by Discuz! X3

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表