您的位置:首页 > 编程语言 > PHP开发

ip_route_output_flow

2015-04-23 16:59 796 查看
[ip_route_output_flow]

struct rtable *ip_route_output_flow(struct net *net, struct flowi4 *flp4, struct sock *sk)
{
struct rtable *rt = __ip_route_output_key(net, flp4);
return rt;
}


[ip_route_output_flow->__ip_route_output_key]

struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{
__u8 tos = RT_FL_TOS(fl4);  // tos
struct fib_result res;  // 查找结果
int orig_oif;

res.tclassid    = 0;
res.fi      = NULL; // fib_info
res.table   = NULL; // fib_table

orig_oif = fl4->flowi4_oif; // 输出设备ID

fl4->flowi4_iif = LOOPBACK_IFINDEX; // 输入设备ID设为环回接口
fl4->flowi4_tos = tos & IPTOS_RT_MASK;
fl4->flowi4_scope = ((tos & RTO_ONLINK) ?  RT_SCOPE_LINK : RT_SCOPE_UNIVERSE);


在路由表中找查的结果会放在res当中,res.fi指向fib_info,res.table指向fib_table,保存fl4的输出设备ID,将fl4的输入设备设为环回接口,根据tos设置fls的scope,RT_SCOPE_LINK为局域网;RT_SCOPE_UNIVERSE表示没有直接相连的路由,使用下一站路由(next hop gateway)。下面看源地址不为0的情况:

if (fl4->saddr) {
if (ipv4_is_multicast(fl4->saddr) ||
ipv4_is_lbcast(fl4->saddr) ||
ipv4_is_zeronet(fl4->saddr))
goto out;


当源地址为多播地址,广播地址,全0时,直接返回。

if (fl4->flowi4_oif == 0 &&
(ipv4_is_multicast(fl4->daddr) || ipv4_is_lbcast(fl4->daddr))) {
dev_out = __ip_dev_find(net, fl4->saddr, false);
if (dev_out == NULL)
goto out;


当输出设备ID为0并且目标地址为多播地址或广播地址时,查找与源地址绑定的设备,如果没找到,返回

[ip_route_output_flow->__ip_route_output_key->__ip_dev_find]

struct net_device *__ip_dev_find(struct net *net, __be32 addr, bool devref)
{
u32 hash = inet_addr_hash(net, addr);   // 地址的哈希值
hlist_for_each_entry_rcu(ifa, &inet_addr_lst[hash], hash) {
if (ifa->ifa_local == addr) {
struct net_device *dev = ifa->ifa_dev->dev;
if (!net_eq(dev_net(dev), net))
continue;
result = dev;
break;
}
}


inet_addr_lst是一个哈希表:static struct hlist_head inet_addr_lst[256]; 里面保存的列表项类型为in_ifaddr,表示一个地址。先计算地址的哈希值,然后开始查找。如果地址相同,并且设备的网络名字空间也相同,成功找到。

if (!result) {
struct flowi4 fl4 = { .daddr = addr };
struct fib_table *local;
local = fib_get_table(net, RT_TABLE_LOCAL); // 得到本地路由表
if (local &&
!fib_table_lookup(local, &fl4, &res, FIB_LOOKUP_NOREF) && res.type == RTN_LOCAL)
result = FIB_RES_DEV(res);
}

return result;
}


如果没找到,在本地路由表中查找与addr匹配的项,结果放在res中。如果找到并且类型为RTN_LOCAL时,得到设备。

[ip_route_output_flow->__ip_route_output_key->__ip_dev_find->fib_table_lookup]

int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp, struct fib_result *res, int fib_flags)
{
struct trie *t = (struct trie *) tb->tb_data;   // 根节点
t_key key = ntohl(flp->daddr);  // 地址作为key
struct rt_trie_node *n;
n = rcu_dereference(t->trie);   // 字典树节点
if (!n)
goto failed;


路由表被组织为一棵树,tb->tb_data指向根节点,查找的key是地址,从第一个子节点开始查找:

/* Just a leaf? */
if (IS_LEAF(n)) {
ret = check_leaf(tb, t, (struct leaf *)n, key, flp, res, fib_flags);
goto found;
}


如果第一个子节点是一个叶子,说明整个树只有一个叶子,只要查找它就可以了:

[ip_route_output_flow->__ip_route_output_key->__ip_dev_find->fib_table_lookup->check_leaf]

static int check_leaf(struct fib_table *tb, struct trie *t, struct leaf *l,
t_key key,  const struct flowi4 *flp,
struct fib_result *res, int fib_flags)
{
struct leaf_info *li;
struct hlist_head *hhead = &l->list;
hlist_for_each_entry_rcu(li, hhead, hlist) {
struct fib_alias *fa;

if (l->key != (key & li->mask_plen))
continue;


每个叶子中都有一个leaf_info列表。先找到key相同的项

list_for_each_entry_rcu(fa, &li->falh, fa_list) {
struct fib_info *fi = fa->fa_info;
int nhsel, err;

if (fa->fa_tos && fa->fa_tos != flp->flowi4_tos)
continue;
if (fi->fib_dead)
continue;
if (fa->fa_info->fib_scope < flp->flowi4_scope)
continue;

if (!(fa->fa_state & FA_S_ACCESSED))
fa->fa_state |= FA_S_ACCESSED;


一个地址有可能有多个路由,li->falh指向一个路由列表。要从中选出一个满足条件的。它们的tos要相同,fib_dead为false,路由的scope要匹配。这些条件都满足后,设置fa的状态为FA_S_ACCESSED

err = fib_props[fa->fa_type].error;
if (err) {
return err;
}
if (fi->fib_flags & RTNH_F_DEAD)
continue;


fib_props是一个全局数组,它对不同的路由类型定义了一个错误值,如:RTN_UNREACHABLE的error值为-EHOSTUNREACH。如果路由的状态不正确,直接返回。还要检查路由的flags不能为RTNH_F_DEAD(Nexthop is dead)

for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) {
const struct fib_nh *nh = &fi->fib_nh[nhsel];

if (nh->nh_flags & RTNH_F_DEAD)
continue;
if (flp->flowi4_oif && flp->flowi4_oif != nh->nh_oif)
continue;
res->prefixlen = li->plen;
res->nh_sel = nhsel;
res->type = fa->fa_type;
res->scope = fa->fa_info->fib_scope;
res->fi = fi;
res->table = tb;
res->fa_head = &li->falh;
if (!(fib_flags & FIB_LOOKUP_NOREF))
atomic_inc(&fi->fib_clntref);
return 0;
}
}
}

return 1;
}


下一站路由地址会cache在fi->fib_nh中,此数组的大小为fi->fib_nhs。如果flp设置了输出设备,要与下一站地址的相同。然后设置返回值,返回0表示成功。

[ip_route_output_flow->__ip_route_output_key->__ip_dev_find->fib_table_lookup]

// 是一个根节点
pn = (struct tnode *) n;
chopped_off = 0;

while (pn) {
pos = pn->pos;
bits = pn->bits;

if (!chopped_off)
cindex = tkey_extract_bits(mask_pfx(key, current_prefix_length), pos, bits);

n = tnode_get_child_rcu(pn, cindex);    // 得到子树
if (n == NULL) {
goto backtrace;
}


如果有多个叶子,就要通过循环来查找了。通过计算key,得到子树索引,并得到子树

if (IS_LEAF(n)) {
ret = check_leaf(tb, t, (struct leaf *)n, key, flp, res, fib_flags);
if (ret > 0)
goto backtrace;
goto found;
}
cn = (struct tnode *)n;


如果是叶子,在当中查找。

if (current_prefix_length < pos+bits) {
if (tkey_extract_bits(cn->key, current_prefix_length,
cn->pos - current_prefix_length) || !(cn->child[0]))
goto backtrace;
}
pref_mismatch = mask_pfx(cn->key ^ key, cn->pos);
if (pref_mismatch) {
/* fls(x) = __fls(x) + 1 */
int mp = KEYLENGTH - __fls(pref_mismatch) - 1;

if (tkey_extract_bits(cn->key, mp, cn->pos - mp) != 0)
goto backtrace;

if (current_prefix_length >= cn->pos)
current_prefix_length = mp;
}

pn = (struct tnode *)n; /* Descend */
chopped_off = 0;
continue;
backtrace:
chopped_off++;

/* As zero don't change the child key (cindex) */
while ((chopped_off <= pn->bits)
&& !(cindex & (1<<(chopped_off-1))))
chopped_off++;

/* Decrease current_... with bits chopped off */
if (current_prefix_length > pn->pos + pn->bits - chopped_off)
current_prefix_length = pn->pos + pn->bits
- chopped_off;

/*
* Either we do the actual chop off according or if we have
* chopped off all bits in this tnode walk up to our parent.
*/

if (chopped_off <= pn->bits) {
cindex &= ~(1 << (chopped_off-1));
} else {
struct tnode *parent = node_parent_rcu((struct rt_trie_node *) pn);
if (!parent)
goto failed;

/* Get Child's index */
cindex = tkey_extract_bits(pn->key, parent->pos, parent->bits);
pn = parent;
chopped_off = 0;
goto backtrace;
}
}
failed:
ret = 1;
found:
return ret;
}


通过一系列计算得到下一个叶子。如果成功找到就返回0。

[ip_route_output_flow->__ip_route_output_key]

if (dev_out == NULL)
goto out;

fl4->flowi4_oif = dev_out->ifindex; // 设置输出设备的ID
goto make_route;
}


如果输出设备没找到,出错返回。此时输出设备为与在路由表中与源地址对应的设备。转到make_route

if (!(fl4->flowi4_flags & FLOWI_FLAG_ANYSRC)) {
if (!__ip_dev_find(net, fl4->saddr, false))
goto out;
}
}


总结下,当源地址不为0时,如果是多播地址或广播地址,直接返回。如果目标地址是多播地址或广播地址并且输出设备ID为0,通过源地址在路由表中去找设备,如果找到,转到make_route处。然后如果没有设置FLOWI_FLAG_ANYSRC,调用__ip_dev_find查找,如果没找到,出错返回。到这里,要么是源地址为0,要么源地址不为0并且通过源地址找到了输出设备。下面来看对目标地址的处理:

if (fl4->flowi4_oif) {  // 输出设备ID不为0
dev_out = dev_get_by_index_rcu(net, fl4->flowi4_oif);   // 从ID得到设备
if (dev_out == NULL)
goto out;

/* RACE: Check return value of inet_select_addr instead.
*/
if (!(dev_out->flags & IFF_UP) || !__in_dev_get_rcu(dev_out)) {
goto out;
}
/* 本地多播
* 广播
*/
if (ipv4_is_local_multicast(fl4->daddr) ||
ipv4_is_lbcast(fl4->daddr)) {
if (!fl4->saddr)    // 源地址不为0
fl4->saddr = inet_select_addr(dev_out, 0, RT_SCOPE_LINK);
goto make_route;
}
if (!fl4->saddr) {// 源地址为0
if (ipv4_is_multicast(fl4->daddr)) // 多播
fl4->saddr = inet_select_addr(dev_out, 0, fl4->flowi4_scope);
else if (!fl4->daddr) // 目标地址为0
fl4->saddr = inet_select_addr(dev_out, 0, RT_SCOPE_HOST);
}
}


如果输出设备不为0,先从ID得到设备,如果设备没有有被激活或因为竞争关系无法得到设备,出错。扣果目标地址为本地多播或广播,转到make_route,此时如果源地址不为0,将其设置为scope为RT_SCOPE_LINK的地址。再判断如果源地址为0,目的地址为多播,将其设置为scope为fl4->flowi4_scope的地址;目的地址为0,将其设置为scope为RT_SCOPE_HOST的地址。

[ip_route_output_flow->__ip_route_output_key->inet_select_addr]

__be32 inet_select_addr(const struct net_device *dev, __be32 dst, int scope)
{
__be32 addr = 0;
struct in_device *in_dev;
in_dev = __in_dev_get_rcu(dev); // 设备
if (!in_dev)
goto no_in_dev;

for_primary_ifa(in_dev) {   // 设备的所有primary地址
if (ifa->ifa_scope > scope) // 地址的范围大于参数传进来的
continue;
/* 目标地址为空
* 与目标地址相同
*/
if (!dst || inet_ifa_match(dst, ifa)) {
addr = ifa->ifa_local;  // 得到地址
break;
}
if (!addr)  // 如果没设地址初始化addr
addr = ifa->ifa_local;
} endfor_ifa(in_dev);
if (addr)
goto out_unlock;


得到设备,在设备的primary地址列表中查找。地址的scope要大于要求的,如果目标地址为0或与列表中的地址匹配,就找到了。如果上面条件不符合,就选择列表中的第一个地址。如果找到,就返回了。

no_in_dev:
/* Not loopback addresses on loopback should be preferred
in this case. It is importnat that lo is the first interface
in dev_base list.
*/
for_each_netdev_rcu(net, dev) { // 所有网络设备
in_dev = __in_dev_get_rcu(dev); // 设备
if (!in_dev)
continue;

for_primary_ifa(in_dev) {   // 设备的所有primary地址
if (ifa->ifa_scope != RT_SCOPE_LINK &&
ifa->ifa_scope <= scope) {
addr = ifa->ifa_local;  // 得到地址
goto out_unlock;
}
} endfor_ifa(in_dev);
}
out_unlock:
return addr;
}


如果没找到,在同一个网络名字空间的所有设备上查找。设备不能为空,然后在每个设备的primary地址列表中查找scope不大于目标地址并不等于RT_SCOPE_LINK的地址。

[ip_route_output_flow->__ip_route_output_key]

if (!fl4->daddr) {
fl4->daddr = fl4->saddr;    // 目标地址设为源地址
if (!fl4->daddr)    // 目标地址为0
fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK);
dev_out = net->loopback_dev;    // 输出设备设为环回接口
fl4->flowi4_oif = LOOPBACK_IFINDEX; // 输出设备ID设为环回接口
res.type = RTN_LOCAL;   // 本机地址
flags |= RTCF_LOCAL;    // 本机地址
goto make_route;
}


目标地址为0的情况。先把目标地址设为源地址,如果两者都为0,都设为环回地址。输出设备设为环回接口,地址类型设为本机地址。转到make_route。

if (fib_lookup(net, fl4, &res)) {   // 路由表中查找


[ip_route_output_flow->__ip_route_output_key->fib_lookup]

static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
struct fib_result *res)
{
struct fib_table *table;

table = fib_get_table(net, RT_TABLE_LOCAL);
if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
return 0;

table = fib_get_table(net, RT_TABLE_MAIN);
if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
return 0;
return -ENETUNREACH;
}


在本地路由表和main路由表中分别查找。

[ip_route_output_flow->__ip_route_output_key]

res.fi = NULL;
res.table = NULL;
if (fl4->flowi4_oif) {  // 输出设备ID不为0
if (fl4->saddr == 0)    // 源地址为0
fl4->saddr = inet_select_addr(dev_out, 0, RT_SCOPE_LINK);
res.type = RTN_UNICAST; /* Gateway or direct route  */
goto make_route;
}
goto out;
}


没有找到的情况:如果输出设备不为0,结果设为前往默认路由,此时如果源地址为0,设为scope为RT_SCOPE_LINK的本机地址,然后转向make_route。如果输出设备为0,直接返回。

if (res.type == RTN_LOCAL) {    // 本机地址
if (!fl4->saddr) {  // 源地址为0
if (res.fi->fib_prefsrc)
fl4->saddr = res.fi->fib_prefsrc;// 源IP地址
else
fl4->saddr = fl4->daddr;    // 源地址设为目标地址
}
dev_out = net->loopback_dev;    // 环回接口
fl4->flowi4_oif = dev_out->ifindex; // 输出设备 ID
flags |= RTCF_LOCAL;    // 本机地址
goto make_route;
}


如果是本机地址,输出设备设为环回接口,fl4的输出设备号设为其输入设备号;此时如果源地址为0,如果结果设置了fib_prefsrc,将其设为源地址,否则设为目标地址。转到make_route。

if (!res.prefixlen &&
res.table->tb_num_default > 1 &&
res.type == RTN_UNICAST && !fl4->flowi4_oif)
fib_select_default(&res);   // 默认路由


如果路由表长度为0,并且路由表tb_num_default > 1并且路由指向gateway,并且输出设备为0,选择默认路由

[ip_route_output_flow->__ip_route_output_key->fib_select_default]

void fib_select_default(struct fib_result *res)
{
struct fib_info *fi = NULL, *last_resort = NULL;
struct list_head *fa_head = res->fa_head;
struct fib_table *tb = res->table;
int order = -1, last_idx = -1;
struct fib_alias *fa;


有可能多个路由项指向同一个目标地址,而这些路由仅仅是因为TOS不同。这些不同的路由项放在fa_head中

list_for_each_entry_rcu(fa, fa_head, fa_list) {
struct fib_info *next_fi = fa->fa_info;

if (next_fi->fib_scope != res->scope ||
fa->fa_type != RTN_UNICAST)
continue;

if (next_fi->fib_priority > res->fi->fib_priority)
break;
if (!next_fi->fib_nh[0].nh_gw ||
next_fi->fib_nh[0].nh_scope != RT_SCOPE_LINK)
continue;

fib_alias_accessed(fa);


在fa_head列表中查找,scope不同或类型不是RTN_UNICAST的跳过;优先级大的,结束查找,因为列表是按照优先级来排序的;下上站路由的gateway为0或scope不为RT_SCOPE_LINK的跳过。

if (fi == NULL) {
if (next_fi != res->fi)
break;
} else if (!fib_detect_death(fi, order, &last_resort,
&last_idx, tb->tb_default)) {
fib_result_assign(res, fi);
tb->tb_default = order;
goto out;
}
fi = next_fi;
order++;
}


第一次循环时如果它们指向的fib_info不同(目标地址不同),结束查找。

[ip_route_output_flow->__ip_route_output_key->fib_select_default->fib_detect_death]

static int fib_detect_death(struct fib_info *fi, int order,
struct fib_info **last_resort, int *last_idx,
int dflt)
{
struct neighbour *n;
int state = NUD_NONE;

n = neigh_lookup(&arp_tbl, &fi->fib_nh[0].nh_gw, fi->fib_dev);
if (n) {
state = n->nud_state;
neigh_release(n);
}


arp_tbl主要用来缓冲ARP地址,这里来查找下一站地址。找到后,设备状态state

[ip_route_output_flow->__ip_route_output_key->fib_select_default->fib_detect_death->neigh_lookup]

struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n;
int key_len = tbl->key_len; // 地址长度
u32 hash_val;
struct neigh_hash_table *nht;

NEIGH_CACHE_STAT_INC(tbl, lookups);

rcu_read_lock_bh();
nht = rcu_dereference_bh(tbl->nht); // neighbour对象的哈希表
hash_val = tbl->hash(pkey, dev, nht->hash_rnd) >> (32 - nht->hash_shift);


计算出pkey的哈希值,这里的tbl为arp_table

for (n = rcu_dereference_bh(nht->hash_buckets[hash_val]);   // 哈希表子表
n != NULL;
n = rcu_dereference_bh(n->next)) {
if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {  // 设备和KEY都相同
if (!atomic_inc_not_zero(&n->refcnt))   // 增加引用计数
n = NULL;
NEIGH_CACHE_STAT_INC(tbl, hits);    // 记录状态
break;
}
}

rcu_read_unlock_bh();
return n;
}


在哈希表中查找。

[ip_route_output_flow->__ip_route_output_key->fib_select_default->fib_detect_death]

if (state == NUD_REACHABLE)
return 0;
if ((state & NUD_VALID) && order != dflt)
return 0;
if ((state & NUD_VALID) ||
(*last_idx < 0 && order > dflt)) {
*last_resort = fi;
*last_idx = order;
}
return 1;
}


NUD_REACHABLE表示处在连接状态;

if (!fl4->saddr)    // 源地址为0
fl4->saddr = FIB_RES_PREFSRC(net, res);

dev_out = FIB_RES_DEV(res);
fl4->flowi4_oif = dev_out->ifindex; // 输出设备ID


如果源地址为0,如果结果设置了fib_prefsrc,将其设为源地址,否则设为目标地址。

make_route:
rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags);    // 新增路由缓冲

out:
rcu_read_unlock();
return rth;
}


把结果放入缓冲中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  route