IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver
Posted codestack
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver相关的知识,希望对你有一定的参考价值。
ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文;如果是转发出去,则调用ip_forward
处理报文。
一、ip报文转发到本地:
/* * Deliver IP Packets to the higher protocol layers. */ /* * 在ip_route_input_noref进行路由选择后,如果接收的包 * 是发送给本机,则调用ip_local_deliver来传递给上层协议 */ int ip_local_deliver(struct sk_buff *skb) { /* * Reassemble IP fragments. */ struct net *net = dev_net(skb->dev); /* * frag_off是16位,其中高3位用作标志位, * 低13位才是真正的偏移量. * 内核可通过设置的分片标识位或非0 * 的分片偏移量识别分片的分组。偏移 * 量字段为0,表明这是分组的最后一个分片。 * * 如果接收到的IP数据包时分片,则调用 * ip_defrag()进行重组,其标志位IP_DEFRAG_LOCAL_DELIVER。 */ if (ip_is_fragment(ip_hdr(skb))) { /* * 重新组合分片分组的各个部分。 * * 如果ip_defrag()返回非0,则表示IP数据包分片 * 尚未到齐,重组没有完成,或者出错,直接 * 返回。为0,则表示已完成IP数据包的重组, * 需要传递到传输层进行处理。 */ if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER)) return 0; } /* * 经过netfilter处理后,调用ip_local_deliver_finish(), * 将组装完成的IP数据包传送到传输层处理 */ return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, net, NULL, skb, skb->dev, NULL, ip_local_deliver_finish); }
//从这里进入L4传输层 /* * ip_local_deliver_finish()将输入数据包从网络层传递 * 到传输层。过程如下: * 1)首先,在数据包传递给传输层之前,去掉IP首部 * 2)接着,如果是RAW套接字接收数据包,则需要 * 复制一份副本,输入到接收该数据包的套接字。 * 3)最后,通过传输层的接收例程,将数据包传递 * 到传输层,由传输层进行处理。 */ /* ip 层处理报文过程中,回复制一份报文到raw_socket中去;有的是IPPROTO_TCP/IPPROTO_RAW 当 socket(AF_INET, SOCK_RAW, IPPROTO_RAW)时,它会接收所有协议的数据包,并且 IP_HDRINCL 是默认打开的,即是说应用层要提供 L3 和 L4 层的头。再如,如果是 IPPROTO_TCP 时,它只接收到 TCP 包。而 IP_HDRINCL 是默认不打开的,即系统会处理 L3 的头部 */ static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb) { /* * 在数据包传递给传输层之前,先去掉 * IP首部。 */ __skb_pull(skb, skb_network_header_len(skb)); rcu_read_lock(); { int protocol = ip_hdr(skb)->protocol; const struct net_protocol *ipprot; int raw; /* * 处理RAW套接字,先根据传输层协议号 * 得到哈希值,然后查看raw_v4_htable散列表 * 中以该值为关键字的哈希桶是否为空, * 如果不为空,则说明创建了RAW套接字, * 复制该数据包的副本输入到注册到 * 该桶中的所有套接字。 */ /* ip_local_deliver_finish函数会先检查哈希表raw_v4_htable。 因为在创建 socket时,inet_create会把协议号IPPROTO_ICMP的值赋给socket的成员num, 并以num为键值,把socket存入哈 项表raw_v4_htable?瑀aw_v4_htable[IPPROTO_ICMP&(MAX_INET_PROTOS-1)]上即存放了 这个socket,实际上是一个socket的链表, 如果其它还有socket要处理这个回显应答,也会被放到这里,组成一个链 表, ip_local_deliver_finish收到数据报后,取出这个socket链表(目前实际上只有一项), 调用raw_v4_input,把 skb交给每一个socket进行处理。 然后,还需要把数据报交给inet_protos[IPPROTO_ICMP& (MAX_INET_PROTOS-1)],即icmp_rcv处理, 因为对于icmp报文,每一个都是需要经过协议栈处理的, 但对回显应 答,icmp_rcv只是简单丢弃,并未实际处理。 */ resubmit: //之前开巨帧的时候,icmp不通就是在这里面的函数中sock_queue_rcv_skb丢的 raw = raw_local_deliver(skb, protocol); //如果是raw套接字,则则该函数里面会复制一份skb,然后送到 ,例如用ping 1.2.2.2的时候,会走这里面,不会走icmp_recv*/ ipprot = rcu_dereference(inet_protos[protocol]); if (ipprot) { int ret; /* * 通过查找inet_portos数组,确定是否 * 注册了与IP首部中传输层协议号 * 一致的传输层协议。若查找命中, * 则执行对应的传输层协议例程。 */ if (!ipprot->no_policy) { if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { kfree_skb(skb); goto out; } nf_reset(skb); } ret = ipprot->handler(skb);//这里面会进入udp tcp传输层 if (ret < 0) { protocol = -ret; goto resubmit; } __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); } else { if (!raw) { /* * 如果没有响应的协议传输层接收该数据包, * 则释放该数据包。在释放前,如果是RAW * 套接字没有接收或接收异常,则还需产生 * 一个目的不可达ICMP报文给发送方。表示该包raw没有接收并且inet_protos中没有注册该协议 */ if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS); icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0); } kfree_skb(skb); } else { __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); consume_skb(skb); } } } out: rcu_read_unlock(); return 0; }
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot){.........}
它首先查找 inet_protos 数组,看有没有相关的注册的协议,如果有,则执行它的处理例程
在 inet_init()的时候,系统会注册几个常用的 L4 层协议:
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n"); if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n"); if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0); printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n"); 其中,协议的结构如下: /* This is used to register protocols. */ struct net_protocol { void (*early_demux)(struct sk_buff *skb); /* 分组将传递到该函数进行进一步处理*/ /* * 传输层协议数据包接收处理函数指针,当网络层接收IP数据包 * 之后,根据IP数据包所指示传输层协议,调用对应传输层 * net_protocol结构的该例程接收报文。 * TCP协议的接收函数为tcp_v4_rcv(),UDP协议的接收函数为 * udp_rcv(),IGMP协议为igmp_rcv(),ICMP协议为icmp_rcv()。 */ int (*handler)(struct sk_buff *skb); /* * 在接收到ICMP错误信息并需要传递到更高层时, * 调用该函数 */ /* * 在ICMP模块中接收到差错报文后,会解析差错报文,并根据 * 差错报文中原始的IP首部,调用对应传输层的异常处理 * 函数err_handler。TCP协议为tcp_v4_err(),UDP为 * udp_err(),IGMP则无。 */ void (*err_handler)(struct sk_buff *skb, u32 info); /* * no_policy标识在路由时是否进行策略路由。TCP和UDP默认不进行 * 策略路由。 */ unsigned int no_policy:1, netns_ok:1, /* does the protocol do more stringent * icmp tag validation than simple * socket lookup? */ icmp_strict_tag_validation:1; };
关键之处在于 handler 域,它用于将数据包上传给 L4 层处理
以tcp协议为为例:
/*ipv4_specific是TCP传输层到网络层数据发送以及TCP建立过程的真正OPS, 在tcp_prot->init中被赋值给inet_connection_sock->icsk_af_ops 这里面有每种协议传输层的接收函数,后面的inetsw_array那几行是套接口层的相关函数 在函数中执行handler,见函数ip_local_deliver_finish family协议族通过sock_register注册 传输层接口tcp_prot udp_prot netlink_prot等通过proto_register注册 IP层接口通过inet_add_protocol(&icmp_protocol等注册 ,这些组成过程参考inet_init函数 IP层处理完后(包括ip_local_deliver_finish和icmp_unreach),走到这里, 这是IP层和传输层的邻借口,然后在由这里走到tcp_prot udp_prot raw_prot 这些是传输层的接收处理过程,传输层和套接口层的处理过程需 要使用udp_prot tcp_prot raw_prot过渡到socket层,处理过程参考inetsw_array */static const struct net_protocol tcp_protocol = { .early_demux = tcp_v4_early_demux, .handler = tcp_v4_rcv,/*当接收到报文后,ip层处理完后 在ip_local_deliver_finish 函数中ret = ipprot->handler(skb);走到这里 从这里面跳转到tcp_prot*/ .err_handler = tcp_v4_err,/*icmp_unreach当收到ICMP差错报文后, 如果引起差错的是TCP包就走到该函数*/ .no_policy = 1, .netns_ok = 1, .icmp_strict_tag_validation = 1, };
以上是关于IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver的主要内容,如果未能解决你的问题,请参考以下文章
IP 层收发报文简要剖析6--ip_forward 报文转发