表示对linu内核查路由的的效率表示堪忧。
一个经过linux的报文,无论是上送本机,还是转发,都会进行大于等于2次的查路由动作。
即执行大于等于2次的fib_lookup。


    外部包过来,ip_rcv->ip_rcv_finish->ip_route_input->ip_route_input_slow
ip_route_input_slow判断报文是否转发,还是上送本机即ip_forward还是ip_local_deliver
但是在执行到ip_forward/ip_local_deliver前,还是会再次查询一次路由,无论是否开启某些安全防护功能。即无条件再会查询一次路由。
    如果查询结果有问题,那再判断是否开启了某些安全防护,若开启,则丢包。。。。。。。
如果是我写,那肯定先判断安全防护是否开启,如果开启我再去执行查路由操作。。。。。


废话不多说了,ip_route_input_slow判断报文上送本机,还是转发时,执行了一次fib_lookup


ip_route_input_slow中我们可以看到这话
 /*
  *    Now we are ready to route packet.
  */
查不到路由,就goto了
 if ((err = fib_lookup(net, &fl, &res)) != 0) {
  if (!IN_DEV_FORWARD(in_dev))
   goto e_hostunreach;
  goto no_route;
 }

如果查到路由,则判断包是上送本机,还是转发。
 if (res.type == RTN_LOCAL) {
//这是发给我自己的,即上送本机的
  int result;
//罪魁祸首出现了。就是这家伙,我先不分析这个函数,最后再分析,先看看转发流程在哪里也调用了fib_validate_source。
  result = fib_validate_source(saddr, daddr, tos,
          net->loopback_dev->ifindex,
          dev, &spec_dst, &itag, skb->mark);
  if (result < 0)
   goto martian_source;
  if (result)
   flags |= RTCF_DIRECTSRC;
  spec_dst = daddr;
//执行local_input
  goto local_input;
 }

 if (!IN_DEV_FORWARD(in_dev))
  goto e_hostunreach;
 if (res.type != RTN_UNICAST)
  goto martian_destination;
//如果不是上送本机,而是需要转发的,执行 ip_mkroute_input
 err = ip_mkroute_input(skb, &res, &fl, in_dev, daddr, saddr, tos);
ip_mkroute_input调用__mkroute_input

static int __mkroute_input(struct sk_buff *skb,
      struct fib_result *res,
      struct in_device *in_dev,
      __be32 daddr, __be32 saddr, u32 tos,
      struct rtable **result)
{
.................................
//再次见到它了,我们可以看到,无论是需要转发还是需要上送本机,几乎都是无条件执行fib_validate_source
 err = fib_validate_source(saddr, daddr, tos, FIB_RES_OIF(*res),
      in_dev->dev, &spec_dst, &itag, skb->mark);
 if (err < 0) {
  ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,
      saddr);

  err = -EINVAL;
  goto cleanup;
 }
 }

是时候来看看fib_validate_source这个函数了。
/* Given (packet source, input interface) and optional (dst, oif, tos):
   - (main) check, that source is valid i.e. not broadcast or our local
     address.
   - figure out what "logical" interface this packet arrived
     and calculate "specific destination" address.
   - check, that packet arrived from expected physical interface.
 */

int fib_validate_source(__be32 src, __be32 dst, u8 tos, int oif,
   struct net_device *dev, __be32 *spec_dst,
   u32 *itag, u32 mark)
{
 struct in_device *in_dev;
//组装查找key
//注意源地址和目的地址互换,然后查路由之所以何种方式查路由,是有一定意图的。
//首先valid_source顾名思义,就是看源地址是否有效,说明才是有效,看我有没有到你的路由
//如果我有到你的路由,好吧,暂且认为你有效。如果没有到你的路由,而你却偏偏给我发了一个报文
//那么我认为你是伪造原地址了。(该逻辑的前提是“对称路由”,即连在相同网络中的设备,都有相同的路由)

 struct flowi fl = { .nl_u = { .ip4_u =
          { .daddr = src,
     .saddr = dst,
     .tos = tos } },
       .mark = mark,
       .iif = oif };

 struct fib_result res;
 int no_addr, rpf;
 int ret;
 struct net *net;

 no_addr = rpf = 0;
 rcu_read_lock();
 in_dev = __in_dev_get_rcu(dev);
 if (in_dev) {
//入接口没有ip地址 no_addr =1
  no_addr = in_dev->ifa_list == NULL;
//该接口开启防护,rpf = 1
//纯复制而已.....
  rpf = IN_DEV_RPFILTER(in_dev);
  if (mark && !IN_DEV_SRC_VMARK(in_dev))
   fl.mark = 0;
 }
 rcu_read_unlock();

 if (in_dev == NULL)
  goto e_inval;

 net = dev_net(dev);
//........又查了一次路由
//如果没有查到,即我没有到你的路由goto last_resort;在 last_resort中判断if (rpf),即判断是否开启了防护
 if (fib_lookup(net, &fl, &res))
  goto last_resort;

//如果查询的结果不是RTN_UNICAST,不行。你这个报文到这是RTN_UNICAST的,而我到你却不是RTN_UNICAST的,扔掉。
 if (res.type != RTN_UNICAST)
  goto e_inval_res;
 *spec_dst = FIB_RES_PREFSRC(res);
 fib_combine_itag(itag, &res);

//如果我查询的出接口,和你报文的入接口时一个,那么ok,我认为你是合法的。
#ifdef CONFIG_IP_ROUTE_MULTIPATH
 if (FIB_RES_DEV(res) == dev || res.fi->fib_nhs > 1)
#else
 if (FIB_RES_DEV(res) == dev)
#endif
 {
  ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
  fib_res_put(&res);
  return ret;
 }

//到这步了,表示查询到了我到你的路由,但是出接口和你入接口不匹配
 fib_res_put(&res);

//我的入接口压根没有ip地址,你怎么可能发给我的?扔了
 if (no_addr)
  goto last_resort;

//我的入接口有ip,但是我开启了攻击防护,我认为“我发给你的出接口和你发给我的入接口不匹配”这个行为,是攻击行为,扔掉
 if (rpf == 1)
  goto e_inval;

//到这表示,我没有开启安全防护,入接口有ip,而你给我的报文不是从这我查询的出接口出去的
//勉强通过,不过我得看看是否可以从我查询的出接口,有到你的路由,即指定出接口查询路由。如果查询成功,就表示,
//从你这个入口出去,其实也可以到达你,所以算了,放过你吧,不扔掉你了。
 fl.oif = dev->ifindex;

 ret = 0;
 if (fib_lookup(net, &fl, &res) == 0) {
  if (res.type == RTN_UNICAST) {
   *spec_dst = FIB_RES_PREFSRC(res);
   ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
  }
  fib_res_put(&res);
 }
 return ret;

//扔掉
last_resort:
 if (rpf)
  goto e_inval;
 *spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);
 *itag = 0;
 return 0;

e_inval_res:
 fib_res_put(&res);
e_inval:
 return -EINVAL;
}


Logo

更多推荐