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

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

时间: 2024-10-01 01:36:35

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

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

ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并调用链路层的输出接口将数据输出到链路层.在输入输出数据时,需要查找路由表 通过netfiler处理等操作. 一.ip数据报文输入 ip_rcv &ip_rcv_finish &ip_rcv_finish2 1.在inet_init中注册了类型为ETH_P_IP协议的数据包的回调ip_rcv 2

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的方法"将多个小工具结合起来"实现我的那些没有实现的想法,抛砖引玉一下. 声明: 本文没有技术含量,甚