IP 层收发报文简要剖析1-ip报文的输入
阅读原文时间:2023年07月10日阅读:1

ip层数据包处理场景如下:

网络层处理数据包文时需要和路由表以及邻居系统打交道。输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层。

在输出数据时,提供输出接口给传输层,并调用链路层的输出接口将数据输出到链路层。在输入输出数据时,需要查找路由表 通过netfiler处理等操作。

一、ip数据报文输入 ip_rcv &ip_rcv_finish &ip_rcv_finish2

1、在inet_init中注册了类型为ETH_P_IP协议的数据包的回调ip_rcv

2、ip_rcv处理完成后交给 NETFILTER(PRE-ROUTING)过滤,再上递给 ip_rcv_finish(),

3、ip_rcv_finish根据 skb 包中的路由信息,决定这个数据包是转发还是上交给本机,由此产生两条路径,一为 ip_local_deliver(),另一个为ip_forward()

4、ip_local_deliver它首先检查这个包是否是一个分 片 包 , 如 果 是 , 它 要 调 动 ip_defrag() 将 分 片 重 装 , 然 后 再 次 将 包 将 给 NETFILTER(LOCAL_IN)过滤后,

  再由 ip_local_deliver_finish()将数据上传到 L4 层,这样就完成了 IP 层的处理;它负责将数据上传

5、ip_forward(),它负责将数据转发,经由 NETFILTER(FORWARD)过滤后将给 ip_forward_finish(),然后调用 dst_output()将数据包发送出去。

/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
const struct iphdr *iph;
struct net *net;
u32 len;

/\* When the interface is in promisc. mode, drop all the crap  
 \* that it receives, do not try to analyse it.  
 \*/  
 /\*数据包不是发给我们的,这里所说的“不属于”这个主机,  
 是指在这个包目标主机的MAC地址不是本机,而不是L3层的ip地址\*/  
if (skb->pkt\_type == PACKET\_OTHERHOST)  
    goto drop;

net = dev\_net(dev);  
\_\_IP\_UPD\_PO\_STATS(net, IPSTATS\_MIB\_IN, skb->len);

skb = skb\_share\_check(skb, GFP\_ATOMIC);  
if (!skb) {  
    \_\_IP\_INC\_STATS(net, IPSTATS\_MIB\_INDISCARDS);  
    goto out;  
}

if (!pskb\_may\_pull(skb, sizeof(struct iphdr)))  
    goto inhdr\_error;

iph = ip\_hdr(skb);

/\*  
 \*    RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.  
 \*  
 \*    Is the datagram acceptable?  
 \*  
 \*    1.    Length at least the size of an ip header  
 \*    2.    Version of 4  
 \*    3.    Checksums correctly. \[Speed optimisation for later, skip loopback checksums\]  
 \*    4.    Doesn't have a bogus length  
 \*/

if (iph->ihl < 5 || iph->version != 4)  
    goto inhdr\_error;

BUILD\_BUG\_ON(IPSTATS\_MIB\_ECT1PKTS != IPSTATS\_MIB\_NOECTPKTS + INET\_ECN\_ECT\_1);  
BUILD\_BUG\_ON(IPSTATS\_MIB\_ECT0PKTS != IPSTATS\_MIB\_NOECTPKTS + INET\_ECN\_ECT\_0);  
BUILD\_BUG\_ON(IPSTATS\_MIB\_CEPKTS != IPSTATS\_MIB\_NOECTPKTS + INET\_ECN\_CE);  
\_\_IP\_ADD\_STATS(net,  
           IPSTATS\_MIB\_NOECTPKTS + (iph->tos & INET\_ECN\_MASK),  
           max\_t(unsigned short, 1, skb\_shinfo(skb)->gso\_segs));  

/*
对数据报的头长度进行检查
iph->ihl*4是20,是首部最长的长度,
此语句是说如果头部长度不能pull,则error
*/
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error;

iph = ip\_hdr(skb);  

/* 校验和错误 */
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto csum_error;
/* 取总长度 */
len = ntohs(iph->tot_len);
if (skb->len < len) { __IP_INC_STATS(net, IPSTATS_MIB_INTRUNCATEDPKTS); goto drop; } else if (len < (iph->ihl*4))
goto inhdr_error;

/\* Our transport medium may have padded the buffer out. Now we know it  
 \* is IP we can trim to the true length of the frame.  
 \* Note this now means skb->len holds ntohs(iph->tot\_len).  
 \*/ /\* 设置总长度为ip包的长度//根据ip包总长度,  
 重新计算skb的长度,去掉末尾的无用信息 \*/  
if (pskb\_trim\_rcsum(skb, len)) {  
    \_\_IP\_INC\_STATS(net, IPSTATS\_MIB\_INDISCARDS);  
    goto drop;  
}  

/*获取传输层的头部*/
skb->transport_header = skb->network_header + iph->ihl*4;

/\* Remove any debris in the socket control block \*/  
//这里面后面会存ip填充信息  
memset(IPCB(skb), 0, sizeof(struct inet\_skb\_parm));  
IPCB(skb)->iif = skb->skb\_iif; /\* 保存输入设备信息 \*/

/\* Must drop socket now because of tproxy. \*/  
skb\_orphan(skb);  

/*
* 最后通过netfilter模块处理后,调用ip_rcv_finish()
* 完成IP数据包的输入。
*/
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);//hook 在nf_register_hooks注册

csum_error:
__IP_INC_STATS(net, IPSTATS_MIB_CSUMERRORS);
inhdr_error:
__IP_INC_STATS(net, IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}

ip_rcv_finish()在ip_rcv()中当IP数据包经过netfilter模块 处理后被调用。完成的主要功能是,如 还没有为该数据包查找输入路由缓存,则 调用ip_route_input()为其查找输入路由缓存。

接着处理IP数据包首部中的选项,最后 根据输入路由缓存输入到本地或转发。

/*
* ip_rcv_finish()在ip_rcv()中当IP数据包经过netfilter模块
* 处理后被调用。完成的主要功能是,如果
* 还没有为该数据包查找输入路由缓存,则
* 调用ip_route_input()为其查找输入路由缓存。
* 接着处理IP数据包首部中的选项,最后
* 根据输入路由缓存输入到本地或转发发。
*/
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;

/\* if ingress device is enslaved to an L3 master device pass the  
 \* skb to its handler for processing  
 \*/  
skb = l3mdev\_ip\_rcv(skb);  
if (!skb)  
    return NET\_RX\_SUCCESS;  

/*
启用了early_demux
skb路由缓存为空
skb的sock为空
不是分片包
*/
if (net->ipv4.sysctl_ip_early_demux &&
!skb_dst(skb) &&
!skb->sk &&
!ip_is_fragment(iph)) {
const struct net_protocol *ipprot;
int protocol = iph->protocol;
/* 找到上层协议 */
/* 获取协议对应的prot */
ipprot = rcu_dereference(inet_protos[protocol]);
/* 找到early_demux函数,如tcp_v4_early_demux */
if (ipprot && ipprot->early_demux) {
/* 调用该函数,将路由信息缓存到skb->refdst */
ipprot->early_demux(skb);

        /\* must reload iph, skb->head might have changed \*/  
        /\* 重新取ip头 \*/  
        iph = ip\_hdr(skb);  
    }  
}

/\*  
 \*    Initialise the virtual path cache for the packet. It describes  
 \*    how the packet travels inside Linux networking.  
 \*/  
 /\*  
 \* 如果还没有为该数据包查找输入路由缓存,  
 \* 则调用ip\_route\_input\_noref()为其查找输入路由缓存。  
 \* 若查找失败,则将该数据包丢弃。  
 \*/  
if (!skb\_valid\_dst(skb)) {  
    int err = ip\_route\_input\_noref(skb, iph->daddr, iph->saddr,  
                       iph->tos, skb->dev);  
    if (unlikely(err)) {  
        if (err == -EXDEV)  
            \_\_NET\_INC\_STATS(net, LINUX\_MIB\_IPRPFILTER);  
        goto drop;  
    }  
}

#ifdef CONFIG_IP_ROUTE_CLASSID
if (unlikely(skb_dst(skb)->tclassid)) {
struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
u32 idx = skb_dst(skb)->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes += skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes += skb->len;
}
#endif
/*
* 根据长度判断IP首部中是否存在选项,如果有,
* 则调用ip_rcv_options()处理IP选项。
*/
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
/*
* 最后根据输入路由缓存决定输入到本地或
* 转发,最终前者调用ip_local_deliver(),后者调用
* ip_forward()。
* 对于输入到本地或转发的组播报文,在经过netfilter处理
* 之后会调用ip_rcv_finish()正式进入输入的处理。先调用
* ip_route_input()进行输入路由的查询,如果发现目的地址
* 为组播地址,就会按照组播地址的规则查找路由,查找
* 到组播的输入路由后,组播报文接收处理函数为ip_mr_input()。
*/
rt = skb_rtable(skb);
if (rt->rt_type == RTN_MULTICAST) {
__IP_UPD_PO_STATS(net, IPSTATS_MIB_INMCAST, skb->len);
} else if (rt->rt_type == RTN_BROADCAST) {
__IP_UPD_PO_STATS(net, IPSTATS_MIB_INBCAST, skb->len);
} else if (skb->pkt_type == PACKET_BROADCAST ||
skb->pkt_type == PACKET_MULTICAST) {
struct in_device *in_dev = __in_dev_get_rcu(skb->dev);

    /\* RFC 1122 3.3.6:  
     \*  
     \*   When a host sends a datagram to a link-layer broadcast  
     \*   address, the IP destination address MUST be a legal IP  
     \*   broadcast or IP multicast address.  
     \*  
     \*   A host SHOULD silently discard a datagram that is received  
     \*   via a link-layer broadcast (see Section 2.4) but does not  
     \*   specify an IP multicast or broadcast destination address.  
     \*  
     \* This doesn't explicitly say L2 \*broadcast\*, but broadcast is  
     \* in a way a form of multicast and the most common use case for  
     \* this is 802.11 protecting against cross-station spoofing (the  
     \* so-called "hole-196" attack) so do it for both.  
     \*/  
    if (in\_dev &&  
        IN\_DEV\_ORCONF(in\_dev, DROP\_UNICAST\_IN\_L2\_MULTICAST))  
        goto drop;  
}  
/\* 调用路由项的input函数,可能为ip\_local\_deliver或者ip\_forward \*/  
return dst\_input(skb);

drop:
kfree_skb(skb);
return NET_RX_DROP;
}