Linux 路由 学习笔记 之十一 输入、输出路由查找相关的接口函数


  对于路由功能模块的学习,也已经很长时间了。关于路由项的创建与查找、策略规则相关的创建与查找、路由缓存的创建与查找,都是分开来分析的,没有说明这些模块是如何配合使用的,以及模块之间的联系。本节就分析一下这几个模块是如何联系在一起的,也作为路由模块学习的小结。


   对于协议栈而言,内核中的路由功能模块主要就是提供路由查找功能,而查找功能就集合了策略规则、路由项、路由缓存的查找,而路由功能模块的查找接口对外是统一的。

目前而言,对于输入数据包与输出数据包,路由功能模块提供了两个接口函数,即ip_route_inputip_route_output_key。下面就分两部分分析一下这两个函数。


1.1 输入路由查找相关的接口

输入路由查找相关的接口有ip_route_input,下面分析一下这个函数。

 

该函数主要对输入数据包进行路由查找(netif_receive_skb->ip_rcv->ip_rcv_finish).

对于输入的数据包,会有三个输出结果:

1.本机接收

2.本机转发

3.丢掉数据包

 

这个函数根据不同的查找结果,会有两个分支:

1.当路由缓存中已存在该数据包对应的路由,则通过查找相应的路由缓存表即可

(rt_hash_table[hash].chain)

2.当路由缓存中不存在该数据包对应的路由时,则通过调用ip_route_input_slow,通过查找路由表,来决定数据包的命运。

 

这个函数就综合了策略规则的查找、路由项的查找、路由缓存的查找、路由缓存的生成、路由缓存与arp邻居项的绑定。

*/

int ip_route_input(struct sk_buff *skb, __be32 daddr, __be32 saddr,

   u8 tos, struct net_device *dev)

{

struct rtable * rth;

unsigned hash;

int iif = dev->ifindex;

 

tos &= IPTOS_RT_MASK;

hash = rt_hash(daddr, saddr, iif);

/*加上读锁*/

rcu_read_lock();

/*hash数组rt_hash_table中,根据hash值找到相应的hash链表,遍历链表中的所用rtable成员

,查找符合条件的路由缓存,若找到的则返回0,并将skb->dst指向该路由缓存。

ip_rcv_finish就会调用skb->dst->input,对数据包进行处理,而在创建路由缓存时,已经将

dst->input的值设置为ip_local_deliver或者ip_forward,根据skb->dst->input函数,就决定了数据包是

发送给本机上层协议进行处理还是转发出去。

*/

for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;

     rth = rcu_dereference(rth->u.dst.rt_next)) {

if (rth->fl.fl4_dst == daddr &&

    rth->fl.fl4_src == saddr &&

    rth->fl.iif == iif &&

    rth->fl.oif == 0 &&

    rth->fl.mark == skb->mark &&

    rth->fl.fl4_tos == tos) {

rth->u.dst.lastuse = jiffies;

dst_hold(&rth->u.dst);

rth->u.dst.__use++;

RT_CACHE_STAT_INC(in_hit);

rcu_read_unlock();

skb->dst = (struct dst_entry*)rth;

return 0;

}

RT_CACHE_STAT_INC(in_hlist_search);

}

rcu_read_unlock();

 

/* Multicast recognition logic is moved from route cache to here.

   The problem was that too many Ethernet cards have broken/missing

   hardware multicast filters :-( As result the host on multicasting

   network acquires a lot of useless route cache entries, sort of

   SDR messages from all the world. Now we try to get rid of them.

   Really, provided software IP multicast filter is organized

   reasonably (at least, hashed), it does not result in a slowdown

   comparing with route cache reject entries.

   Note, that multicast routers are not affected, because

   route cache entry is created eventually.

 */

 /*路由缓存没有命中,且目的地址为组播地址时,则进入组播处理流程*/

if (MULTICAST(daddr)) {

struct in_device *in_dev;

 

rcu_read_lock();

if ((in_dev = __in_dev_get_rcu(dev)) != NULL) {

int our = ip_check_mc(in_dev, daddr, saddr,

skb->nh.iph->protocol);

if (our

#ifdef CONFIG_IP_MROUTE

    || (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))

#endif

    ) {

rcu_read_unlock();

return ip_route_input_mc(skb, daddr, saddr,

 tos, dev, our);

}

}

rcu_read_unlock();

return -EINVAL;

}

/*当路由缓存查找没有命中,且目的地址不是组播地址时,则调用ip_route_input_slow

进行路由项的查找*/

return ip_route_input_slow(skb, daddr, saddr, tos, dev);

}

 

对于ip_route_input_slow函数,在路由项的查找一节中,没有进行分析,本节进行分析一下。

1.1.1 ip_route_input_slow

 

 

该函数主要完成两个功能:

1.通过调用fib_lookup进行路由表查找

2.若在路由表中查找到符合条件的路由,则调用ip_mkroute_input创建

   路由缓存,并将路由缓存与arp邻居项实现绑定。

 

对于fib_lookup来说,若系统支持策略路由,则首先进行策略规则的匹配,

然后根据策略规则找到相应的路由表,这就涉及到策略规则与路由表查找

相关的内容,关于这两方面的内容可以查看"路由表的查找""策略规则的查找"

的内容。

 

而对于ip_mkroute_input中,路由缓存与arp邻居项的绑定,可以查看“路由缓存相关的创建”与“邻居项相关”的分析文档。

 

这个函数将路由项查找、策略规则查找、路由缓存创建、路由缓存与邻居项的绑定关联了起来,是一个比较综合的接口函数。

 

*/

static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,

       u8 tos, struct net_device *dev)

{

struct fib_result res;

/*获取输入设备的三层参数*/

struct in_device *in_dev = in_dev_get(dev);

/*

根据传入的值,构造查找条件

*/

struct flowi fl = { .nl_u = { .ip4_u =

      { .daddr = daddr,

.saddr = saddr,

.tos = tos,

.scope = RT_SCOPE_UNIVERSE,

      } },

    .mark = skb->mark,

    .iif = dev->ifindex };

unsigned flags = 0;

u32 itag = 0;

struct rtable * rth;

unsigned hash;

__be32 spec_dst;

int err = -EINVAL;

int free_res = 0;

 

/* IP on this device is disabled. */

/*当该设备没有设置三层参数,则程序返回错误*/

if (!in_dev)

goto out;

 

/* Check for the most weird martians, which can be not detected

   by fib_lookup.

 */

/*当源ip地址为组播、广播或者环回地址,则返回错误。*/

if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr))

goto martian_source;

 

if (daddr == htonl(0xFFFFFFFF) || (saddr == 0 && daddr == 0))

goto brd_input;

 

/* Accept zero addresses only to limited broadcast;

 * I even do not know to fix it or not. Waiting for complains :-)

 */

if (ZERONET(saddr))

goto martian_source;

 

if (BADCLASS(daddr) || ZERONET(daddr) || LOOPBACK(daddr))

goto martian_destination;

 

/*

 * Now we are ready to route packet.

 */

if ((err = fib_lookup(&fl, &res)) != 0) {

if (!IN_DEV_FORWARD(in_dev))

goto e_hostunreach;

goto no_route;

}

free_res = 1;

 

RT_CACHE_STAT_INC(in_slow_tot);

 

if (res.type == RTN_BROADCAST)

goto brd_input;

/*对于路由类型为local的路由项,则*/

if (res.type == RTN_LOCAL) {

int result;

result = fib_validate_source(saddr, daddr, tos,

     loopback_dev.ifindex,

     dev, &spec_dst, &itag);

if (result < 0)

goto martian_source;

if (result)

flags |= RTCF_DIRECTSRC;

spec_dst = daddr;

goto local_input;

}

 

if (!IN_DEV_FORWARD(in_dev))

goto e_hostunreach;

if (res.type != RTN_UNICAST)

goto martian_destination;

 

err = ip_mkroute_input(skb, &res, &fl, in_dev, daddr, saddr, tos);

if (err == -ENOBUFS)

goto e_nobufs;

if (err == -EINVAL)

goto e_inval;

 

done:

in_dev_put(in_dev);

if (free_res)

fib_res_put(&res);

out: return err;

 

brd_input:

if (skb->protocol != htons(ETH_P_IP))

goto e_inval;

 

if (ZERONET(saddr))

spec_dst = inet_select_addr(dev, 0, RT_SCOPE_LINK);

else {

err = fib_validate_source(saddr, 0, tos, 0, dev, &spec_dst,

  &itag);

if (err < 0)

goto martian_source;

if (err)

flags |= RTCF_DIRECTSRC;

}

flags |= RTCF_BROADCAST;

res.type = RTN_BROADCAST;

RT_CACHE_STAT_INC(in_brd);

 

local_input:

rth = dst_alloc(&ipv4_dst_ops);

if (!rth)

goto e_nobufs;

 

rth->u.dst.output= ip_rt_bug;

 

atomic_set(&rth->u.dst.__refcnt, 1);

rth->u.dst.flags= DST_HOST;

if (in_dev->cnf.no_policy)

rth->u.dst.flags |= DST_NOPOLICY;

rth->fl.fl4_dst = daddr;

rth->rt_dst = daddr;

rth->fl.fl4_tos = tos;

rth->fl.mark    = skb->mark;

rth->fl.fl4_src = saddr;

rth->rt_src = saddr;

#ifdef CONFIG_NET_CLS_ROUTE

rth->u.dst.tclassid = itag;

#endif

rth->rt_iif =

rth->fl.iif = dev->ifindex;

rth->u.dst.dev = &loopback_dev;

dev_hold(rth->u.dst.dev);

rth->idev = in_dev_get(rth->u.dst.dev);

rth->rt_gateway = daddr;

rth->rt_spec_dst= spec_dst;

rth->u.dst.input= ip_local_deliver;

rth->rt_flags  = flags|RTCF_LOCAL;

if (res.type == RTN_UNREACHABLE) {

rth->u.dst.input= ip_error;

rth->u.dst.error= -err;

rth->rt_flags  &= ~RTCF_LOCAL;

}

rth->rt_type = res.type;

hash = rt_hash(daddr, saddr, fl.iif);

err = rt_intern_hash(hash, rth, (struct rtable**)&skb->dst);

goto done;

 

no_route:

RT_CACHE_STAT_INC(in_no_route);

spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);

res.type = RTN_UNREACHABLE;

goto local_input;

 

/*

 * Do not cache martian addresses: they should be logged (RFC1812)

 */

martian_destination:

RT_CACHE_STAT_INC(in_martian_dst);

#ifdef CONFIG_IP_ROUTE_VERBOSE

if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())

printk(KERN_WARNING "martian destination %u.%u.%u.%u from "

"%u.%u.%u.%u, dev %s\n",

NIPQUAD(daddr), NIPQUAD(saddr), dev->name);

#endif

 

e_hostunreach:

err = -EHOSTUNREACH;

goto done;

 

e_inval:

err = -EINVAL;

goto done;

 

e_nobufs:

err = -ENOBUFS;

goto done;

 

martian_source:

ip_handle_martian_source(dev, in_dev, skb, daddr, saddr);

goto e_inval;

}

 

 

以上就是输入路由查找相关的接口函数的分析,通过这个函数,我们就能将以上几节分析的内容结合在一起了。就有了一个整体的框架了。而对于输出路由查找相关的接口函数,其作用与输入路由查找相关的接口函数类似,下面简要分析下。

 

 

1.2 输出路由查找相关的接口函数

输出路由查找相关的接口函数有好几个,其中最外层为ip_route_output_key,其是

对函数ip_route_output_flow的封装,而ip_route_output_flow则是对

__ip_route_output_key的封装。所以我们主要分析一下函数__ip_route_output_key

 

该函数主要对输出数据包进行路由查找(本地发送udptcp数据包或者netfilter中修改了tosmark值后,都会进行输出路由查找).

对于输出的数据包,会有两个输出结果:

1.本机发送

2.丢掉数据包

 

这个函数根据不同的查找结果,会有两个分支:

1.当路由缓存中已存在该数据包对应的路由,则通过查找相应的路由缓存表即可

(rt_hash_table[hash].chain)

2.当路由缓存中不存在该数据包对应的路由时,则通过调用ip_route_output_slow,通过查找

路由表,来决定数据包的命运。

 

 

int __ip_route_output_key(struct rtable **rp, const struct flowi *flp)

{

unsigned hash;

struct rtable *rth;

 

hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif);

 

rcu_read_lock_bh();

/*路由缓存查找*/

for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;

rth = rcu_dereference(rth->u.dst.rt_next)) {

if (rth->fl.fl4_dst == flp->fl4_dst &&

    rth->fl.fl4_src == flp->fl4_src &&

    rth->fl.iif == 0 &&

    rth->fl.oif == flp->oif &&

    rth->fl.mark == flp->mark &&

    !((rth->fl.fl4_tos ^ flp->fl4_tos) &

    (IPTOS_RT_MASK | RTO_ONLINK))) {

 

/* check for multipath routes and choose one if

 * necessary

 */

if (multipath_select_route(flp, rth, rp)) {

dst_hold(&(*rp)->u.dst);

RT_CACHE_STAT_INC(out_hit);

rcu_read_unlock_bh();

return 0;

}

 

rth->u.dst.lastuse = jiffies;

dst_hold(&rth->u.dst);

rth->u.dst.__use++;

RT_CACHE_STAT_INC(out_hit);

rcu_read_unlock_bh();

*rp = rth;

return 0;

}

RT_CACHE_STAT_INC(out_hlist_search);

}

rcu_read_unlock_bh();

/*路由表查找*/

return ip_route_output_slow(rp, flp);

}

 

 

1.2.1 ip_route_output_slow

 

路由项查找函数

1.调用fib_lookup进行路由项的查找(根据是否开启策略路由,会对应不同的查找函数)

2.若查找到路由项,则调用ip_mkroute_output创建对应的路由缓存

 

该函数就实现了将策略规则的查找、路由项的查找、路由缓存的创建、arp邻居项与路由缓存的绑定关联起来。

static int ip_route_output_slow(struct rtable **rp, const struct flowi *oldflp)

{

u32 tos = RT_FL_TOS(oldflp);

struct flowi fl = { .nl_u = { .ip4_u =

      { .daddr = oldflp->fl4_dst,

.saddr = oldflp->fl4_src,

.tos = tos & IPTOS_RT_MASK,

.scope = ((tos & RTO_ONLINK) ?

  RT_SCOPE_LINK :

  RT_SCOPE_UNIVERSE),

      } },

    .mark = oldflp->mark,

    .iif = loopback_dev.ifindex,

    .oif = oldflp->oif };

struct fib_result res;

unsigned flags = 0;

struct net_device *dev_out = NULL;

int free_res = 0;

int err;

 

 

res.fi = NULL;

#ifdef CONFIG_IP_MULTIPLE_TABLES

res.r = NULL;

#endif

 

if (oldflp->fl4_src) {

err = -EINVAL;

if (MULTICAST(oldflp->fl4_src) ||

    BADCLASS(oldflp->fl4_src) ||

    ZERONET(oldflp->fl4_src))

goto out;

 

/* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */

dev_out = ip_dev_find(oldflp->fl4_src);

if (dev_out == NULL)

goto out;

 

/* I removed check for oif == dev_out->oif here.

   It was wrong for two reasons:

   1. ip_dev_find(saddr) can return wrong iface, if saddr is

      assigned to multiple interfaces.

   2. Moreover, we are allowed to send packets with saddr

      of another iface. --ANK

 */

 

if (oldflp->oif == 0

    && (MULTICAST(oldflp->fl4_dst) || oldflp->fl4_dst == htonl(0xFFFFFFFF))) {

/* Special hack: user can direct multicasts

   and limited broadcast via necessary interface

   without fiddling with IP_MULTICAST_IF or IP_PKTINFO.

   This hack is not just for fun, it allows

   vic,vat and friends to work.

   They bind socket to loopback, set ttl to zero

   and expect that it will work.

   From the viewpoint of routing cache they are broken,

   because we are not allowed to build multicast path

   with loopback source addr (look, routing cache

   cannot know, that ttl is zero, so that packet

   will not leave this host and route is valid).

   Luckily, this hack is good workaround.

 */

 

fl.oif = dev_out->ifindex;

goto make_route;

}

if (dev_out)

dev_put(dev_out);

dev_out = NULL;

}

 

 

if (oldflp->oif) {

dev_out = dev_get_by_index(oldflp->oif);

err = -ENODEV;

if (dev_out == NULL)

goto out;

 

/* RACE: Check return value of inet_select_addr instead. */

if (__in_dev_get_rtnl(dev_out) == NULL) {

dev_put(dev_out);

goto out; /* Wrong error code */

}

 

if (LOCAL_MCAST(oldflp->fl4_dst) || oldflp->fl4_dst == htonl(0xFFFFFFFF)) {

if (!fl.fl4_src)

fl.fl4_src = inet_select_addr(dev_out, 0,

      RT_SCOPE_LINK);

goto make_route;

}

if (!fl.fl4_src) {

if (MULTICAST(oldflp->fl4_dst))

fl.fl4_src = inet_select_addr(dev_out, 0,

      fl.fl4_scope);

else if (!oldflp->fl4_dst)

fl.fl4_src = inet_select_addr(dev_out, 0,

      RT_SCOPE_HOST);

}

}

 

if (!fl.fl4_dst) {

fl.fl4_dst = fl.fl4_src;

if (!fl.fl4_dst)

fl.fl4_dst = fl.fl4_src = htonl(INADDR_LOOPBACK);

if (dev_out)

dev_put(dev_out);

dev_out = &loopback_dev;

dev_hold(dev_out);

fl.oif = loopback_dev.ifindex;

res.type = RTN_LOCAL;

flags |= RTCF_LOCAL;

goto make_route;

}

 

if (fib_lookup(&fl, &res)) {

res.fi = NULL;

if (oldflp->oif) {

/* Apparently, routing tables are wrong. Assume,

   that the destination is on link.

 

   WHY? DW.

   Because we are allowed to send to iface

   even if it has NO routes and NO assigned

   addresses. When oif is specified, routing

   tables are looked up with only one purpose:

   to catch if destination is gatewayed, rather than

   direct. Moreover, if MSG_DONTROUTE is set,

   we send packet, ignoring both routing tables

   and ifaddr state. --ANK

 

 

   We could make it even if oif is unknown,

   likely IPv6, but we do not.

 */

 

if (fl.fl4_src == 0)

fl.fl4_src = inet_select_addr(dev_out, 0,

      RT_SCOPE_LINK);

res.type = RTN_UNICAST;

goto make_route;

}

if (dev_out)

dev_put(dev_out);

err = -ENETUNREACH;

goto out;

}

free_res = 1;

 

if (res.type == RTN_LOCAL) {

if (!fl.fl4_src)

fl.fl4_src = fl.fl4_dst;

if (dev_out)

dev_put(dev_out);

dev_out = &loopback_dev;

dev_hold(dev_out);

fl.oif = dev_out->ifindex;

if (res.fi)

fib_info_put(res.fi);

res.fi = NULL;

flags |= RTCF_LOCAL;

goto make_route;

}

 

#ifdef CONFIG_IP_ROUTE_MULTIPATH

if (res.fi->fib_nhs > 1 && fl.oif == 0)

fib_select_multipath(&fl, &res);

else

#endif

/*

同时满足以下三种情况时,才会调用函数fib_select_default查找默认路由

1.查找的路由项的掩码为0

2.查找的路由项的类型为RTN_UNICAST

3.查找的路由项的出口设备值为空

*/

 

if (!res.prefixlen && res.type == RTN_UNICAST && !fl.oif)

fib_select_default(&fl, &res);

 

if (!fl.fl4_src)

fl.fl4_src = FIB_RES_PREFSRC(res);

 

if (dev_out)

dev_put(dev_out);

dev_out = FIB_RES_DEV(res);

dev_hold(dev_out);

fl.oif = dev_out->ifindex;

 

 

make_route:

err = ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);

 

 

if (free_res)

fib_res_put(&res);

if (dev_out)

dev_put(dev_out);

out: return err;

}

 

    至此,对于路由模块的学习告一段落了, 经过这一段时间的学习,对于路由模块里各子模块的工作机制都有了整体的把握与理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值