IP 层收发报文简要剖析1-ip报文的输入

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;
}

原文地址:https://www.cnblogs.com/codestack/p/9194080.html

时间: 2024-10-03 16:57:53

IP 层收发报文简要剖析1-ip报文的输入的相关文章

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 s

IP 层收发报文简要剖析3--ip输入报文分片重组

在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { struct inet_frag_queue q; u32 user;//分片来源 __be32 saddr;//原地址 __be32 daddr;//目的地址 __be16 id;//ip报文序列号 u8 protocol;//上层协议号 //这四个字段来自ip首部是为了确定来自哪个ip数据报文

IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames

L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据. 把数据放在缓冲区有两个优点,一方面,缓冲区的数据可以被后续的一些函数使用,构成一些片段:另一方面,把数据放缓冲区,等缓冲区满了(达到PMTU)再传送数据,可以更有效率. 如果在一些情况下,L4层希望去放在缓冲区的数据立即被传输,那么在调用ip_append_data把数据放缓冲区后,立即调用ip_push_pending_fr

IP 层收发报文简要剖析4--ip 报文发送

无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数 路由的时候,dst_output函数设置为ip_output ip_mc_output等 1.TCP输出接口 L4 层在发送数据时会根据协议的不同调用上面提到的几个辅助函数之一,tcp协议打包成ip数据包文的方法根据tcp段的不同而选择不同的接口, 其中ip_queue_xmit为常用接口,ip_build_and_send_pkt.ip_send_repl

IP 层收发报文简要剖析6--ip_forward 报文转发

//在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * 通过输入路由缓存被调用. */ int ip_forward(struct sk_buff *skb) { u32 mtu; struct iphdr *iph; /* Our header */ struct rtable *rt; /* Route we use */ struct ip_optio

Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函数创建一个套接字: fd = socket(AF_INET, SOCK_STREAM, 0); 以上指定了连接协议,socket调用返回一个文件句柄,与socket文件相应的inode不在磁盘上,而是存在于内存. 之后我们指定监听的port.同意与哪些ip建立连接,并调用bind完毕port绑定:

老斜两宗事-七层代理模式还是IP层VPN

1.七层代理模式还是IP层VPN 很多人会问,我到底是使用代理模式呢,还是使用VPN模式,如果我想数据在中间不安全的链路上实现加密保护的话.这个问题有一个背景,那就是,你想保护你的数据,可以使用VPN,但是有时候,第七层的代理模式或许更好,比如SSL卸载器,比如内置SSL处理的代理,分为正向代理和反向代理.正向代理:代理的是访问者.一般位于访问者一端,访问者能意识到代理的存在,直接访问代理,由代理向服务器发起访问.反向代理:反向代理代理的是被访问者.位于被访问者一端,访问者意识不到代理的存在,访

第二十二章 TCP/IP层的实现

                      第二十二章    TCP/IP层的实现        我比较喜欢先难后易,如果把GPU显示管理.和网络管理拿下后:我会从头整理.改写一遍APO操作系统.这样,就会形成APO操作系统的锥形.也获得了全局观.内核CPU线路.和用户CPU线路,你可以将它们看成是独立的2个32位CPU核:内核CPU主要任务是实时处理.硬件中断,256个实时线程包含了一些中断程序的后半部.用户CPU主要是动态优先级进程.线程调度,各种应用程序的运行:2个核之间是通过消息交互.句

用Netcat,SSH构建的IP层加密隧道搭建VPN

[关于题外话在最后] 写作本文主要基于两点,首先是因为我前段时间写了几篇关于VPN的新解,收到了很多的邮件反馈,我也思考了很多,另一个方面是因为很多人问我怎么用QQ,P2P搭建一个IP层的VPN,我的回答是"我也不知道".我确实不知道,根本就没有试过,只是有个这样那样的想法...我主要是没有能力去Hack这些非Linux上的东西...所以说,我写这篇文章,用UNIX的方法"将多个小工具结合起来"实现我的那些没有实现的想法,抛砖引玉一下. 声明: 本文没有技术含量,甚