作為IP路由的一種補充,uRPF(單播反向路徑轉發)可謂非常有用,它認證了IP數據報的源地址,在一定程度了保護了網絡的安全,比如有效防止了洪泛攻擊。然而直到Linux內核的2.6的高版本版本,Linux只能實現嚴格的uRPF,這是由fib_validate_source函數來完成的,具體配置在/proc/sys/net/ipv4/conf/$dev/rp_filter,對於Cisco上很簡單的松散的uRPF,Linux卻無能為力,kernel 2.6的高版本可以為/proc/sys/net/ipv4/conf/$dev/rp_filter設置3個值,分別為不檢查,嚴格uRPF,松散uRPF。kernel 3.3增加了rpfilter機制,將uRPF從協議棧移到了Netfilter,使得Linux可以在協議棧之外實現嚴格/松散uRPF,然而即便如此,對於VRF(虛擬路由轉發),Linux還是沒有實現,不過在rpfilter的基礎上,這個VRF的實現應該很簡單。
反向路由查找並非那麼簡單,因為需要考慮策略路由的問題,這一切從何道來呢?rpfilter技術作用在Netfilter的PREROUTING這個HOOK點上,這是合理的,因為必須在標准路由之前進行反向路徑查詢,以保證沒有任何數據從被拒絕的反向路徑發出,這裡主要是為了禁止ICMP包的回發,然而在PREROUTING這個點上,目標網卡是不知道的,因此也就不能像標准路由中的fib_validate_source那樣設置flowi4的iif,既然rpfilter的目的只是裁決一下反向路徑的出口,那麼其入口就是無關緊要的了,並且我們知道,本機loopback口的通信是可信的,那麼就將反向路徑查詢中的flowi4的iif設置成loopback即可,然而這還有問題,那就是策略路由的問題了,如果我們不查找策略路由表,就會漏掉在策略路由表中的條目而導致正向包被丟棄,而如果想查找策略路由表,由於我們將iif設置成了loopback,就可能會因為rule的iif不匹配而錯過:
- static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,
- struct flowi *fl, int flags)
- {
- int ret = 0;
- if (rule->iifindex && (rule->iifindex != fl->flowi_iif))
- goto out;
- if (rule->oifindex && (rule->oifindex != fl->flowi_oif))
- goto out;
- if ((rule->mark ^ fl->flowi_mark) & rule->mark_mask)
- goto out;
- ret = ops->match(rule, fl, flags);
- out:
- return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
- }
策略路由中的FIB_RULE_INVERT標志和rpfilter中的XT_RPFILTER_INVERT標志是相互獨立的兩個取反標志,然而代表的含義基本一致,這可以讓我們配置出各種組合,www.linuxidc.com 也就是說,你可能需要單獨的配置一些針對正方向策略路由的反方向策略路由,是不是有點VRF的意思啊!
rpfilter的核心代碼如下:
1.match函數:
- static bool rpfilter_mt(const struct sk_buff *skb, struct xt_action_param *par)
- {
- const struct xt_rpfilter_info *info;
- const struct iphdr *iph;
- struct flowi4 flow;
- bool invert;
-
- info = par->matchinfo;
- invert = info->flags & XT_RPFILTER_INVERT;
-
- if (par->in->flags & IFF_LOOPBACK)
- return true ^ invert;
-
- iph = ip_hdr(skb);
- if (ipv4_is_multicast(iph->daddr)) {
- if (ipv4_is_zeronet(iph->saddr))
- return ipv4_is_local_multicast(iph->daddr) ^ invert;
- flow.flowi4_iif = 0;
- } else {
- flow.flowi4_iif = dev_net(par->in)->loopback_dev->ifindex;
- }
-
- flow.daddr = iph->saddr;
- flow.saddr = rpfilter_get_saddr(iph->daddr);
- flow.flowi4_oif = 0;
- flow.flowi4_mark = info->flags & XT_RPFILTER_VALID_MARK ? skb->mark : 0;
- flow.flowi4_tos = RT_TOS(iph->tos);
- flow.flowi4_scope = RT_SCOPE_UNIVERSE;
-
- return rpfilter_lookup_reverse(&flow, par->in, info->flags) ^ invert;
- }
2.路由查找以及結果判斷邏輯:
- static bool rpfilter_lookup_reverse(struct flowi4 *fl4,
- const struct net_device *dev, u8 flags)
- {
- struct fib_result res;
- bool dev_match;
- struct net *net = dev_net(dev);
- int ret __maybe_unused;
-
- if (fib_lookup(net, fl4, &res))
- return false;
-
- if (res.type != RTN_UNICAST) {
- if (res.type != RTN_LOCAL || !(flags & XT_RPFILTER_ACCEPT_LOCAL))
- return false;
- }
- dev_match = false;
- #ifdef CONFIG_IP_ROUTE_MULTIPATH
- for (ret = 0; ret < res.fi->fib_nhs; ret++) {
- struct fib_nh *nh = &res.fi->fib_nh[ret];
-
- if (nh->nh_dev == dev) {
- dev_match = true;
- break;
- }
- }
- #else
- if (FIB_RES_DEV(res) == dev)
- dev_match = true;
- #endif
- if (dev_match || flags & XT_RPFILTER_LOOSE)
- return FIB_RES_NH(res).nh_scope <= RT_SCOPE_HOST;
- return dev_match;
- }
雖然基於Netfilter的rpfilter比內置的源地址判斷更合理,但是由於Linux協議棧以及Netfilter本身的機制,還是有一些副作用的。