linux网络协议栈--路由流程分析

转:http://blog.csdn.net/hsly_support/article/details/8797976

来吧,路由

路由是网络的核心,是linux网络协议栈的核心,我们找个入口进去看看


还记得在笔记5-IP层的处理1中ip_rcv_finish走到过一个岔口

->ip_rcv_finish()

->ip_route_input()  查找路由信息

->if (iph->ihl > 5 && ip_rcv_options(skb)) 如果IP头部大于20字节,则表示IP头部包含IP选项,需要进行选项处理.

goto drop;

->dst_input(skb);      dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的函数指针,

如果是则递交到本地的则为ip_local_deliver,若是转发则为ip_forward

两条路径:

1) ip_local_deliver

2) ip_forward

是什么导致路径不同呢,我们看一看ip_route_input() 干了啥


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

u8 tos, struct net_device *dev)

->net = dev_net(dev);

->hash = rt_hash(daddr, saddr, iif, rt_genid(net));        计算hash值,注意hash因子

->                                                                             既然hash值算出来了,我们就去找吧

rcu_read_lock();

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

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

if (((rth->fl.fl4_dst ^ daddr) |                    异或 相同为0

(rth->fl.fl4_src ^ saddr) |

(rth->fl.iif ^ iif) |

rth->fl.oif |

(rth->fl.fl4_tos ^ tos)) == 0 &&

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

net_eq(dev_net(rth->u.dst.dev), net) &&           判断路由和报文的struct net指针地址是否相同

!rt_is_expired(rth)) {                                        路由项是否过期

找到了

dst_use(&rth->u.dst, jiffies);                            表示路由的使用时间

RT_CACHE_STAT_INC(in_hit);

rcu_read_unlock();

skb_dst_set(skb, &rth->u.dst);                         设置到skb中去

return 0;

}

RT_CACHE_STAT_INC(in_hlist_search);

}

rcu_read_unlock();


static struct rt_hash_bucket      *rt_hash_table __read_mostly;

struct rt_hash_bucket {

struct rtable     *chain;

};

struct rtable

{

union

{

struct dst_entry     dst;

} u;

/* Cache lookup keys */

struct flowi          fl;                     存放的是查找该路由节点的哈希值,该哈希值用源IP,目的地址,TOS一起确定

struct in_device     *idev;

int               rt_genid;

unsigned          rt_flags;               一些结构性的标志,例如,RTF_UP表示这条路由可用

__u16               rt_type;               表明了目标地址的类型,例如RTN_LOCAL,RTN_MULTICAST

__be32               rt_dst;     /* Path destination     */              用来存放目标的IP地址

__be32               rt_src;     /* Path source          */               路由路径的起点ip地址

int               rt_iif;

/* Info on neighbour */

__be32               rt_gateway;                     网关信息

/* Miscellaneous cached information */

__be32               rt_spec_dst; /* RFC1122 specific destination */

struct inet_peer     *peer; /* long-living peer info */

};


当然,在rt_hash_table中查不到  则处理多播ipv4_is_multicast(daddr)   ->ip_route_input_mc

再进ip_route_input_slow(skb, daddr, saddr, tos, dev);  rt_hash_table为路由高速缓存

我们主要分析ip_route_input_slow()

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;

struct net    * net = dev_net(dev);

/* 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.

*/

if (ipv4_is_multicast(saddr) || ipv4_is_lbcast(saddr) ||         是否是多播、组播、环回地址等

ipv4_is_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 (ipv4_is_zeronet(saddr))               源地址是否是零网地址类型

goto martian_source;

if (ipv4_is_lbcast(daddr) || ipv4_is_zeronet(daddr) ||

ipv4_is_loopback(daddr))

goto martian_destination;

/*

*     Now we are ready to route packet.

*/

if ((err = fib_lookup(net, &fl, &res)) != 0) {                 通过路由函数查找目标地址   结果记录在res中

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;

if (res.type == RTN_LOCAL) {                  路由类型为本地类型

int result;

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;                              记录目标地址

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);         创建用于转发的路由表

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))            不是ip协议

goto e_inval;

if (ipv4_is_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, skb->mark);

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;                       设置输出方向的函数

rth->rt_genid = rt_genid(net);                         产生随机值

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

rth->u.dst.flags= DST_HOST;                            设置路由标志

if (IN_DEV_CONF_GET(in_dev, NOPOLICY))

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

rth->fl.fl4_dst     = daddr;                      记录目标地址

rth->rt_dst     = daddr;

rth->fl.fl4_tos     = 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;                          记录网络设备id

rth->u.dst.dev     = net->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;                   设置输入函数         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, rt_genid(net));          计算hash值

err = rt_intern_hash(hash, rth, NULL, skb);        将路由表插入hash队列并记录到数据包中

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;                          设置不可达

if (err == -ESRCH)

err = -ENETUNREACH;

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 %pI4 from %pI4, dev %s\n",

&daddr, &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;

}



在上面函数的分析中可以看到,对于目标地址是转发情况的,调用ip_mkroute_input()函数创建转发路由表

对于广播或者本地类型,直接分配路由表并初始化,并直接指定下一步处理函数为ip_local_deliver()


static int ip_mkroute_input(struct sk_buff *skb,

struct fib_result *res,

const struct flowi *fl,

struct in_device *in_dev,

__be32 daddr, __be32 saddr, u32 tos)

{

struct rtable* rth = NULL;

int err;

unsigned hash;

#ifdef CONFIG_IP_ROUTE_MULTIPATH                   多路径选择

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

fib_select_multipath(fl, res);

#endif

/* create a routing cache entry */

err = __mkroute_input(skb, res, in_dev, daddr, saddr, tos, &rth);        创建路由表

if (err)

return err;

/* put it into the cache */

hash = rt_hash(daddr, saddr, fl->iif,

rt_genid(dev_net(rth->u.dst.dev)));    计算hash值

return rt_intern_hash(hash, rth, NULL, skb);    将路由表插入路由高速缓存队列中,并记录到数据包中

}


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)

{

struct rtable *rth;

int err;

struct in_device *out_dev;

unsigned flags = 0;

__be32 spec_dst;

u32 itag;

/* get a working reference to the output device */

out_dev = in_dev_get(FIB_RES_DEV(*res));               取出输出设备的配置结构

if (out_dev == NULL) {

if (net_ratelimit())

printk(KERN_CRIT "Bug in ip_route_input" \

"_slow(). Please, report\n");

return -EINVAL;

}

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;

}

if (err)

flags |= RTCF_DIRECTSRC;

if (out_dev == in_dev && err &&

(IN_DEV_SHARED_MEDIA(out_dev) ||

inet_addr_onlink(out_dev, saddr, FIB_RES_GW(*res))))

flags |= RTCF_DOREDIRECT;

if (skb->protocol != htons(ETH_P_IP)) {               如果不是ip

/* Not IP (i.e. ARP). Do not create route, if it is

* invalid for proxy arp. DNAT routes are always valid.

*/

if (out_dev == in_dev) {

err = -EINVAL;

goto cleanup;

}

}

rth = dst_alloc(&ipv4_dst_ops);                申请路由项

if (!rth) {

err = -ENOBUFS;

goto cleanup;

}

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

rth->u.dst.flags= DST_HOST;

if (IN_DEV_CONF_GET(in_dev, NOPOLICY))

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

if (IN_DEV_CONF_GET(out_dev, NOXFRM))

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

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;

rth->rt_gateway     = daddr;

rth->rt_iif      =

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

rth->u.dst.dev     = (out_dev)->dev;

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

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

rth->fl.oif      = 0;

rth->rt_spec_dst= spec_dst;

rth->u.dst.input = ip_forward;                     设置输入函数         ip_forward在这里设置,(文章开头的疑问?)

rth->u.dst.output = ip_output;                      设置输出函数

rth->rt_genid = rt_genid(dev_net(rth->u.dst.dev));

rt_set_nexthop(rth, res, itag);

rth->rt_flags = flags;

*result = rth;

err = 0;

cleanup:

/* release the working reference to the output device */

in_dev_put(out_dev);

return err;

}



经过路由查找分析,我们看到ip层在选择转发或者本地上送的选择是从路由信息里来的,而查找路由信息的过程则是先从

rt_hash_table中查找,如果其中查不到则通过fib_lookup进行
查找,形成路由信息,根据目标地址类型是本地还是转发选择了不同的输入函数,这样ip层的后续投递就会有了两种选择路径, 当然
把查到的路由信息插入到rt_hash_table,更新skb的dst。

时间: 2024-10-15 03:35:55

linux网络协议栈--路由流程分析的相关文章

理解 Linux 网络栈 (Linux networking stack)(1):Linux 网络协议栈简单总结

本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM虚拟化 Linux 环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO 1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linux Socket 是从

由PPPOE看Linux网络协议栈的实现

http://www.cnblogs.com/zmkeil/archive/2013/05/01/3053545.html 这个标题起得比较纠结,之前熟知的PPPOE是作为PPP协议的底层载体,而实际上它也是一个完整的协议,不过它的实现比较简单,由它出发,可以很容易理清楚Linux网络栈的实现方式. 1.总述 Linux中用户空间的网络编程,是以socket为接口,一般创建一个sockfd = socket(family,type,protocol),之后以该sockfd为参数,进行各种系统调用

初探Linux网络协议栈

一点声明原文链接: http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/LinuxKernel.pdf 译者注: 原文写于2003年,文中描述的不少内容已经发生了改变,在不影响愿意的情况下,我擅自增删了一些内容. 翻译过程中找到的好资料: How SKBs Work Evaluation of TCP retransmission delays Congestion Control in Linux TCP Anatomy of the Li

Linux网络流量监控与分析工具Ntop和Ntopng

Ntop工具 Ntop是一个功能强大的流量监控.端口监控.服务监控管理系统 能够实现高效地监控多台服务器网络 Ntop功能介绍 Ntop提供了命令行界面和web界面两种工作方式,通过web'界面,可以清晰展示网络的整体使用情况.网络中各主机的流量状态与排名.各主机占用的带宽以及各时段的流量明细.局域网内各主机的路由.端口使用情况等. Ntop是网络流量监控中的新秀,它是一种网络嗅探器,在运维中,可以使用Ntop检测网络数据传输.排除网络故障,分析网络流量判断网络上存在的各种问题.同时监控是否有黑

[转]linux网络协议栈(1)——socket入门(1)(2)

[转自 https://www.cnblogs.com/hustcat/archive/2009/09/17/1568738.html https://www.cnblogs.com/hustcat/archive/2009/09/17/1568765.html ] socket入门(1) 1.TCP/IP参考模型 为了实现各种网络的互连,国际标准化组织(ISO)制定了开放式系统互连(OSI)参考模型.尽管OSI的体系结构从理论上讲是比较完整的,但实际上,完全符合OSI各层协议的商用产品却很少进

网络数据包头部在linux网络协议栈中的变化

接收时使用skb_pull()不断去掉各层协议头部:发送时使用skb_push()不断添加各层协议头部. 先说说接收: 150 * eth_type_trans - determine the packet's protocol ID. 151 * @skb: received socket data 152 * @dev: receiving network device 153 * 154 * The rule here is that we 155 * assume 802.3 if th

[转]linux 网络协议栈(1)——网络设备

[转自https://www.cnblogs.com/hustcat/archive/2009/09/22/1572108.html https://www.cnblogs.com/hustcat/archive/2009/09/23/1572884.html ] 原文地址:https://www.cnblogs.com/yi-mu-xi/p/10764123.html

linux内核网络协议栈架构分析,全流程分析-干货

https://download.csdn.net/download/wuhuacai/10157233 https://blog.csdn.net/zxorange321/article/details/75676063 LINUX内核协议栈分析 目  录 1      说明...4 2      TCP协议...4 2.1       分层...4 2.2       TCP/IP的分层...5 2.3       互联网的地址...6 2.4       封装...7 2.5       

linux 内核网络协议栈阅读理解--带详尽注释以及相关流程调用注释,附 github 注释后源码

linux 内核网络协议栈阅读理解--带详尽注释以及相关流程调用注释,对理解内核协议栈源码很有帮助 对理解阅读 linux 协议栈源码很用帮助 github 地址: https://github.com/y123456yz/Reading-and-comprehense-linux-Kernel-network-protocol-stack