热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linuxicmp学习笔记之二icmp数据处理流程

在分析icmp数据包处理流程之前,我有如下疑问:1、为什么要为每一个cpu创建一个仅用于发送icmp报文的socket呢,不使用sock


 

在分析icmp数据包处理流程之前,我有如下疑问:

1、为什么要为每一个cpu创建一个仅用于发送icmp报文的socket呢,不使用socket不也是可以把icmp报文发送出去吗?

2、ping的工作原理是什么呢?

3、Traceroute的工作原理是什么呢?

 

一、imcp协议的初始化

1)ICMP接收处理函数的初始化

我们知道icmp协议是附属于ip层的3层协议,且是将icmp数据存放于ip数据包的数据部分的3层协议。而tcp、udp也是将tcp、udp数据存放于ip数据包的数据部分的4层协议。

虽然icmp与tcp等协议不属于同一个网络层,但是都是在3层ip协议处理完以后,才会交给icmp、tcp的处理函数去处理。因此在linux中,都是调用inet_add_protocol将其接收处理函数相关的数据结构添加到数组inet_protos中去的(关于三、四层接收数据处理函数的注册相关的知识请参看http://blog.csdn.net/lickylin/article/details/22900401)。

       Icmp的接收处理函数相关的结构体定义如下:

static const struct net_protocol icmp_protocol = {

    .handler =       icmp_rcv,

    .no_policy =   1,

    .netns_ok =     1,

};

在inet_init初始化时,即会调用inet_add_protocol将tcp、udp、icmp、igmp等协议相关的接收处理结构体注册,并保存在数组inet_protos中。

 

当接收的数据包的协议为icmp时,即会调用icmp_rcv进行后续处理。

 

 

2)  icmp协议模块的初始化

主要是调用函数register_pernet_subsys(关于该函数的工作流程,请参看http://blog.csdn.net/lickylin/article/details/18013879),将icmp协议模块注册到网络命令空间中,并调用ops->init进行协议初始化相关的代码。

对于icmp,其pernet_operations的定义如下:

static structpernet_operations __net_initdata icmp_sk_ops = {

       .init = icmp_sk_init,

       .exit = icmp_sk_exit,

};

在调用register_pernet_subsys将icmp协议模块注册到网络命名空间后,即会调用icmp_sk_init进行icmp协议初始化相关的功能,我们分析下icmp_sk_init。

 

 

该函数主要实现以下功能:

/*

1、  为每一个cpu创建一个用于发生icmp数据包的socket

2、  设置一些限制条件,包括速率限制、接收数据包的条件等

*/

static int __net_init icmp_sk_init(struct net *net)

{

    int i, err;

 

/*为icmp_sk申请空间,该icmp_sk数组中存放了所有cpu相关的socket指针*/

    net->ipv4.icmp_sk =

           kzalloc(nr_cpu_ids *sizeof(struct sock *), GFP_KERNEL);

    if (net->ipv4.icmp_sk ==NULL)

           return -ENOMEM;

 

    /*为每一个cpu创建一个RAW套接字*/

    for_each_possible_cpu(i) {

           struct sock *sk;

           /*

创建一个 RAW 类型的套接字,并调用(*sk)->sk_prot->unhash,将该socket从hash链表raw_v4_hashinfo.ht[RAW_HTABLE_SIZE]中删除与该socket的关联

*/

           err =inet_ctl_sock_create(&sk, PF_INET,

                                   SOCK_RAW, IPPROTO_ICMP, net);

           if (err <0)

                  goto fail;

 

           net->ipv4.icmp_sk[i]&#61; sk;

 

           /* Enough space for2 64K ICMP packets, including

            * sk_buff struct overhead.

            */

           sk->sk_sndbuf &#61;

                  (2 * ((64 *1024) &#43; sizeof(struct sk_buff)));

 

           /*

            * Speedup sock_wfree()

            */

           sock_set_flag(sk,SOCK_USE_WRITE_QUEUE);

           inet_sk(sk)->pmtudisc&#61; IP_PMTUDISC_DONT;

    }

 

    net->ipv4.sysctl_icmp_echo_ignore_all&#61; 0;

    /*忽略广播的echo请求 */

    net->ipv4.sysctl_icmp_echo_ignore_broadcasts&#61; 1;

 

    /* 忽略广播的icmp 错误回复信息*/

    net->ipv4.sysctl_icmp_ignore_bogus_error_responses&#61; 1;

 

    net->ipv4.sysctl_icmp_ratelimit&#61; 1 * HZ; //速率限制值

/*进行速率限制的icmp数据包类型&#xff0c;主要有dest unreachable 、source quench time exceeded 、parameter problem*/

    net->ipv4.sysctl_icmp_ratemask&#61; 0x1818;

    net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr&#61; 0;

 

    return 0;

 

fail:

    for_each_possible_cpu(i)

           inet_ctl_sock_destroy(net->ipv4.icmp_sk[i]);

    kfree(net->ipv4.icmp_sk);

    return err;

}

 

疑问&#xff1a;当新创建的socket时&#xff0c;为什么要将其从hash链表raw_v4_hashinfo.ht[RAW_HTABLE_SIZE]中删除呢&#xff1f;

 

因为我们只使用这个socket进行发送数据包&#xff0c;而不需要使用该socket接收数据包。所以此处将其从hash链表raw_v4_hashinfo.ht[RAW_HTABLE_SIZE]中删除。

为什么不使用该socket直接接收icmp报文呢&#xff0c;我的理解是如果使用该socket接收报文&#xff0c;就需要在kernel创建一个内核线程&#xff0c;用于侦听是否有数据到达该socket&#xff0c;然后再进行处理。

而直接使用内核四层协议接收处理函数的注册流程&#xff0c;可以很方便的就能对接收的icmp报文进行处理&#xff0c;而且使用的内核资源比较少&#xff0c;所以对于kernel创建的socket&#xff0c;其接收操作基本上是使用内核四层协议接收处理函数的注册流程实现的。而对于应用层创建的icmp相关的socket则不会执行上述操作。

 

 

 

二、ICMP协议的接收处理函数

Icmp接收处理函数为icmp_rcv,下面分析这个函数。

 

主要功能&#xff1a;

1、  对数据包进行合理性检查

2、  根据icmp的类型&#xff0c;

int icmp_rcv(struct sk_buff *skb)

{

       structicmphdr *icmph;

       structrtable *rt &#61; skb_rtable(skb);

       structnet *net &#61; dev_net(rt->u.dst.dev);

 

       /*

       基于策略的高扩展性的网络安全架构&#xff0c;对于这个内核子架构不清楚

     此处分析不了&#xff0c;跳过。

       */

       if(!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {

              structsec_path *sp &#61; skb_sec_path(skb);

              intnh;

 

              if(!(sp && sp->xvec[sp->len - 1]->props.flags &

                             XFRM_STATE_ICMP))

                     gotodrop;

 

              if(!pskb_may_pull(skb, sizeof(*icmph) &#43; sizeof(struct iphdr)))

                     gotodrop;

 

              nh&#61; skb_network_offset(skb);

              skb_set_network_header(skb,sizeof(*icmph));

 

              if(!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))

                     gotodrop;

 

              skb_set_network_header(skb,nh);

       }

 

       ICMP_INC_STATS_BH(net,ICMP_MIB_INMSGS);

       /*验证校验和信息*/

       switch(skb->ip_summed) {

       caseCHECKSUM_COMPLETE:

              if(!csum_fold(skb->csum))

                     break;

              /*fall through */

       caseCHECKSUM_NONE:

              skb->csum&#61; 0;

              if(__skb_checksum_complete(skb))

                     gotoerror;

       }

 

       if(!pskb_pull(skb, sizeof(*icmph)))

              gotoerror;

       /*获取icmp头部*/

       icmph&#61; icmp_hdr(skb);

 

       ICMPMSGIN_INC_STATS_BH(net,icmph->type);

       /*

       对于不支持的icmp报文&#xff0c;直接丢掉

        */

       if(icmph->type > NR_ICMP_TYPES)

              gotoerror;

 

 

       /*

       判断是否丢弃掉多播类型的icmp数据包

       1、只处理echo、timestamp、address_mask_request、address_mask_reply类型的多播icmp数据包

        */

 

       if(rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {

              /*

               *    RFC1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be

               *      silently ignored (we let user decide with asysctl).

               *    RFC1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently

               *      discarded if to broadcast/multicast.

               */

              if((icmph->type &#61;&#61; ICMP_ECHO ||

                   icmph->type &#61;&#61; ICMP_TIMESTAMP)&&

                 net->ipv4.sysctl_icmp_echo_ignore_broadcasts) {

                     gotoerror;

              }

              if(icmph->type !&#61; ICMP_ECHO &&

                  icmph->type !&#61; ICMP_TIMESTAMP &&

                  icmph->type !&#61; ICMP_ADDRESS &&

                  icmph->type !&#61; ICMP_ADDRESSREPLY) {

                     gotoerror;

              }

       }

       /*根据icmp数据包类型&#xff0c;调用相应的处理函数*/

       icmp_pointers[icmph->type].handler(skb);

 

drop:

       kfree_skb(skb);

       return0;

error:

       ICMP_INC_STATS_BH(net,ICMP_MIB_INERRORS);

       gotodrop;

}

 

 

 

对于icmp_pointers的定义如下&#xff1a;

/*

 *    This table is the definition of how wehandle ICMP.

 */

static const struct icmp_controlicmp_pointers[NR_ICMP_TYPES &#43; 1] &#61; {

       [ICMP_ECHOREPLY]&#61; {

              .handler&#61; icmp_discard,

       },

       [1]&#61; {

              .handler&#61; icmp_discard,

              .error&#61; 1,

       },

       [2]&#61; {

              .handler&#61; icmp_discard,

              .error&#61; 1,

       },

       [ICMP_DEST_UNREACH]&#61; {

              .handler&#61; icmp_unreach,

              .error&#61; 1,

       },

       [ICMP_SOURCE_QUENCH]&#61; {

              .handler&#61; icmp_unreach,

              .error&#61; 1,

       },

       [ICMP_REDIRECT]&#61; {

              .handler&#61; icmp_redirect,

              .error&#61; 1,

       },

       [6]&#61; {

              .handler&#61; icmp_discard,

              .error&#61; 1,

       },

       [7]&#61; {

              .handler&#61; icmp_discard,

              .error&#61; 1,

       },

       [ICMP_ECHO]&#61; {

              .handler&#61; icmp_echo,

       },

       [9]&#61; {

              .handler&#61; icmp_discard,

              .error&#61; 1,

       },

       [10]&#61; {

              .handler&#61; icmp_discard,

              .error&#61; 1,

       },

       [ICMP_TIME_EXCEEDED]&#61; {

              .handler&#61; icmp_unreach,

              .error&#61; 1,

       },

       [ICMP_PARAMETERPROB]&#61; {

              .handler&#61; icmp_unreach,

              .error&#61; 1,

       },

       [ICMP_TIMESTAMP]&#61; {

              .handler&#61; icmp_timestamp,

       },

       [ICMP_TIMESTAMPREPLY]&#61; {

              .handler&#61; icmp_discard,

       },

       [ICMP_INFO_REQUEST]&#61; {

              .handler&#61; icmp_discard,

       },

       [ICMP_INFO_REPLY]&#61; {

              .handler&#61; icmp_discard,

       },

       [ICMP_ADDRESS]&#61; {

              .handler&#61; icmp_address,

       },

       [ICMP_ADDRESSREPLY]&#61; {

              .handler&#61; icmp_address_reply,

       },

};

 

目前内核处理的icmp报文有icmp_unreach、icmp_address、icmp_address_reply、icmp_timestamp、icmp_echo、icmp_redirect。

 

 

icmp_echo

 

/*

该函数主要是将icmp的type设置为ICMP_ECHOREPLY&#xff0c;并调用icmp_reply将该数据包发送出去

*/

 

static void icmp_echo(struct sk_buff *skb)

{

       structnet *net;

 

       net&#61; dev_net(skb_dst(skb)->dev);

       if(!net->ipv4.sysctl_icmp_echo_ignore_all) {

              structicmp_bxm icmp_param;

 

              icmp_param.data.icmph    &#61;*icmp_hdr(skb);

              icmp_param.data.icmph.type&#61; ICMP_ECHOREPLY;

              icmp_param.skb              &#61; skb;

              icmp_param.offset     &#61; 0;

              icmp_param.data_len        &#61;skb->len;

              icmp_param.head_len       &#61;sizeof(struct icmphdr);

              icmp_reply(&icmp_param,skb);

       }

}

 

Timestamp

/*

设置时间戳的值&#xff0c;并将icmp的type设置为ICMP_TIMESTAMPREPLY&#xff0c;并通过icmp_reply发送出去

*/

static void icmp_timestamp(struct sk_buff*skb)

{

       structtimespec tv;

       structicmp_bxm icmp_param;

       /*

        *    Tooshort.

        */

       if(skb->len <4)

              gotoout_err;

 

       /*

        *    Fillin the current time as ms since midnight UT:

        */

       getnstimeofday(&tv);

       icmp_param.data.times[1]&#61; htonl((tv.tv_sec % 86400) * MSEC_PER_SEC &#43;

                                    tv.tv_nsec / NSEC_PER_MSEC);

       icmp_param.data.times[2]&#61; icmp_param.data.times[1];

       if(skb_copy_bits(skb, 0, &icmp_param.data.times[0], 4))

              BUG();

       icmp_param.data.icmph    &#61;*icmp_hdr(skb);

       icmp_param.data.icmph.type&#61; ICMP_TIMESTAMPREPLY;

       icmp_param.data.icmph.code&#61; 0;

       icmp_param.skb              &#61; skb;

       icmp_param.offset     &#61; 0;

       icmp_param.data_len       &#61;0;

       icmp_param.head_len       &#61;sizeof(struct icmphdr) &#43; 12;

       icmp_reply(&icmp_param,skb);

out:

       return;

out_err:

       ICMP_INC_STATS_BH(dev_net(skb_dst(skb)->dev),ICMP_MIB_INERRORS);

       gotoout;

}

 

 

Unreach 数据处理

 

功能&#xff1a;根据icmp中有效载荷数据的值&#xff0c;调用传输层的错误处理函数进行处理

static void icmp_unreach(struct sk_buff*skb)

{

       structiphdr *iph;

       structicmphdr *icmph;

       inthash, protocol;

       conststruct net_protocol *ipprot;

       u32info &#61; 0;

       structnet *net;

 

       net&#61; dev_net(skb_dst(skb)->dev);

 

       /*

        *    Incompleteheader ?

        *   Onlychecks for the IP header, there should be an

        *    additionalcheck for longer headers in upper levels.

        */

 

       if(!pskb_may_pull(skb, sizeof(struct iphdr)))

              gotoout_err;

 

       /*获取icmp首部*/

       icmph&#61; icmp_hdr(skb);

       iph   &#61; (struct iphdr *)skb->data;

 

       /*判断ip首部是否完整*/

       if(iph->ihl <5) /* Mangled header, drop. */

              gotoout_err;

 

       /*仅处理type类型为3或者12的数据包

       1、当类型为3时&#xff0c;仅处理code为frag needed的报文

           a)当系统不支持pmtu时&#xff0c;丢弃该数据包

           b)当系统支持pmtu时&#xff0c;调用ip_rt_frag_needed修改pmtu的值

       2、当type类型为12时&#xff0c;则通过icmph->un.gateway获取出错偏移值(相对于数据包)

       */

       if(icmph->type &#61;&#61; ICMP_DEST_UNREACH) {

              switch(icmph->code & 15) {

              caseICMP_NET_UNREACH:

              caseICMP_HOST_UNREACH:

              caseICMP_PROT_UNREACH:

              caseICMP_PORT_UNREACH:

                     break;

              caseICMP_FRAG_NEEDED:

                     if(ipv4_config.no_pmtu_disc) {

                            LIMIT_NETDEBUG(KERN_INFO"ICMP: %pI4: fragmentation needed and DF set.\n",

                                          &iph->daddr);

                     }else {

                            info&#61; ip_rt_frag_needed(net, iph,

                                                  ntohs(icmph->un.frag.mtu),

                                                  skb->dev);

                            if(!info)

                                   gotoout;

                     }

                     break;

              caseICMP_SR_FAILED:

                     LIMIT_NETDEBUG(KERN_INFO"ICMP: %pI4: Source Route Failed.\n",

                                   &iph->daddr);

                     break;

              default:

                     break;

              }

              if(icmph->code > NR_ICMP_UNREACH)

                     gotoout;

       }else if (icmph->type &#61;&#61; ICMP_PARAMETERPROB)

              info&#61; ntohl(icmph->un.gateway) >> 24;

 

       /*

        *    Throwit at our lower layers

        *

        *    RFC1122: 3.2.2 MUST extract the protocol ID from the passed

        *             header.

        *    RFC1122: 3.2.2.1 MUST pass ICMP unreach messages to the

        *             transport layer.

        *    RFC1122: 3.2.2.2 MUST pass ICMP time expired messages to

        *             transport layer.

        */

 

       /*

        *    Checkthe other end isnt violating RFC 1122. Some routers send

        *    bogusresponses to broadcast frames. If you see this message

        *    firstcheck your netmask matches at both ends, if it does then

        *    getthe other vendor to fix their kit.

        */

 

   /*

       对于目的地址是广播的icmp数据包&#xff0c;且需要忽略时&#xff0c;则打印错误并

       忽略该数据包

       */

       if(!net->ipv4.sysctl_icmp_ignore_bogus_error_responses &&

           inet_addr_type(net, iph->daddr) &#61;&#61;RTN_BROADCAST) {

              if(net_ratelimit())

                     printk(KERN_WARNING"%pI4 sent an invalid ICMP "

                                       "type %u, code %u "

                                       "error to a broadcast: %pI4 on%s\n",

                            &ip_hdr(skb)->saddr,

                            icmph->type, icmph->code,

                            &iph->daddr,

                            skb->dev->name);

              gotoout;

       }

 

       /*

       检测icmp报文中有效载荷部分内容长度是否大于等于ip头部信息加上8字节

       在发送icmp差错报文时&#xff0c;会将icmp数据部分的值设置为ip头部信息&#43; ip有效载荷的前8个字节&#xff0c;

       这样就可以判断是传输层的那个应用数据发送出错

        */

       if(!pskb_may_pull(skb, iph->ihl * 4 &#43; 8))

              gotoout;

 

       /*

       此时的iph&#xff0c;是icmp有效载荷中的ip头部信息&#xff0c;在icmp_rcv中已经将skb->data指向icmp报文的

       有效载荷部分了。

       */

       iph&#61; (struct iphdr *)skb->data;

       /*获取传输层协议值*/

       protocol&#61; iph->protocol;

 

       /*

       首先调用raw_icmp_error&#xff0c;将差错信息发送给感兴趣的raw sockte

        */

       raw_icmp_error(skb,protocol, info);

 

       /*根据protocol值&#xff0c;查找符合条件的4层接收处理hash数组inet_protos&#xff0c;

       调用其错误处理函数进行后续处理*/

       hash&#61; protocol & (MAX_INET_PROTOS - 1);

       rcu_read_lock();

       ipprot&#61; rcu_dereference(inet_protos[hash]);

       if(ipprot && ipprot->err_handler)

              ipprot->err_handler(skb,info);

       rcu_read_unlock();

 

out:

       return;

out_err:

       ICMP_INC_STATS_BH(net,ICMP_MIB_INERRORS);

       gotoout;

}

 

 

重定向处理&#xff1a;

功能&#xff1a;根据icmp中数据部分中的值&#xff0c;调用ip_rt_redirect&#xff0c;进行后续处理&#xff08;对于路由重定向的处理代码不熟悉&#xff0c;下次分析路由分支时再仔细分析&#xff09;。

static void icmp_redirect(struct sk_buff*skb)

{

       structiphdr *iph;

       /*数据包有效性检查*/

       if(skb->len

              gotoout_err;

       if(!pskb_may_pull(skb, sizeof(struct iphdr)))

              gotoout;

 

       /*获取icmp数据部分携带的ip头部信息*/

       iph&#61; (struct iphdr *)skb->data;

       /*对于code为ICMP_REDIR_NET 、ICMP_REDIR_NETTOS 、ICMP_REDIR_HOST 、ICMP_REDIR_HOSTTOS &#xff0c;调用ip_rt_redirect 进行路由重定向的处理*/

       switch(icmp_hdr(skb)->code & 7) {

       caseICMP_REDIR_NET:

       caseICMP_REDIR_NETTOS:

              /*

               * As per RFC recommendations now handle it asa host redirect.

               */

       caseICMP_REDIR_HOST:

       caseICMP_REDIR_HOSTTOS:

              ip_rt_redirect(ip_hdr(skb)->saddr,iph->daddr,

                            icmp_hdr(skb)->un.gateway,

                            iph->saddr, skb->dev);

              break;

       }

out:

       return;

out_err:

       ICMP_INC_STATS_BH(dev_net(skb->dev),ICMP_MIB_INERRORS);

       gotoout;

}

 

 

 

Icmp_reply

在前面介绍icmp echo的应对以及icmp timestamp的应答时&#xff0c;函数都是调用icmp_reply发送数据的&#xff0c;下面分析一下这个函数

功能&#xff1a;

1、查找路由&#xff0c;若查找失败&#xff0c;直接返回&#xff1b;查找成功执行第二步

2、调用速率限制函数icmpv4_xrlim_allow进行速率限制&#xff0c;当允许发送时&#xff0c;执行第三步&#xff0c;否则返回

3、调用icmp_push_reply发送数据

 

/*

 *    Driving logic for building and sending ICMPmessages.

 */

 

static void icmp_reply(struct icmp_bxm*icmp_param, struct sk_buff *skb)

{

       structipcm_COOKIE ipc;

       structrtable *rt &#61; skb_rtable(skb);

       structnet *net &#61; dev_net(rt->u.dst.dev);

       structsock *sk;

       structinet_sock *inet;

       __be32daddr;

 

       if(ip_options_echo(&icmp_param->replyopts, skb))

              return;

 

       sk&#61; icmp_xmit_lock(net);

       if(sk &#61;&#61; NULL)

              return;

       inet&#61; inet_sk(sk);

 

       icmp_param->data.icmph.checksum&#61; 0;

 

       inet->tos&#61; ip_hdr(skb)->tos;

       daddr&#61; ipc.addr &#61; rt->rt_src;

       ipc.opt&#61; NULL;

       ipc.shtx.flags&#61; 0;

       if(icmp_param->replyopts.optlen) {

              ipc.opt&#61; &icmp_param->replyopts;

              if(ipc.opt->srr)

                     daddr&#61; icmp_param->replyopts.faddr;

       }

       {

              structflowi fl &#61; { .nl_u &#61; { .ip4_u &#61;

                                         { .daddr &#61; daddr,

                                          .saddr&#61; rt->rt_spec_dst,

                                          .tos&#61; RT_TOS(ip_hdr(skb)->tos) } },

                                .proto &#61; IPPROTO_ICMP };

              security_skb_classify_flow(skb,&fl);

              if(ip_route_output_key(net, &rt, &fl))

                     gotoout_unlock;

       }

       if(icmpv4_xrlim_allow(net, rt, icmp_param->data.icmph.type,

                            icmp_param->data.icmph.code))

              icmp_push_reply(icmp_param,&ipc, &rt);

       ip_rt_put(rt);

out_unlock:

       icmp_xmit_unlock(sk);

}

 

a)      速率控制函数icmpv4_xrlim_allow

对于速率控制函数&#xff0c;主要是由两个函数完成icmpv4_xrlim_allow、xrlim_allow。

其中xrlim_allow是进行真正的限速操作&#xff1b;而icmpv4_xrlim_allow主要是返回是否成功&#xff0c;其对于不需要限速的数据&#xff0c;即返回允许通过

 

功能&#xff1a;判断当前是否有时间片用于数据发送&#xff0c;这个函数的流程还是比较简单的。

int xrlim_allow(struct dst_entry *dst, inttimeout)

{

       unsignedlong now, token &#61; dst->rate_tokens;

       intrc &#61; 0;

 

       now&#61; jiffies;

       token&#43;&#61; now - dst->rate_last;

       dst->rate_last&#61; now;

       if(token > XRLIM_BURST_FACTOR * timeout)

              token&#61; XRLIM_BURST_FACTOR * timeout;

       if(token >&#61; timeout) {

              token-&#61; timeout;

              rc&#61; 1;

       }

       dst->rate_tokens&#61; token;

       returnrc;

}

 

功能&#xff1a;判断是否允许发送数据

1、  对于不支持的icmp type类型&#xff0c;返回允许发送

2、  对于type类型为ICMP_DEST_UNREACH code为ICMP_FRAG_NEEDED的数据包&#xff0c;允许发送

3、  对于目的设备为回环设备的&#xff0c;返回允许发送

4、  对于其他类型的icmp报文&#xff0c;只有ipv4.sysctl_icmp_ratemask中对应位为1的数据包才会进行限速&#xff0c;对于其他类型的数据包&#xff0c;直接返回允许发送&#xff08;即不限速&#xff09;

static inline int icmpv4_xrlim_allow(structnet *net, struct rtable *rt,

              inttype, int code)

{

       structdst_entry *dst &#61; &rt->u.dst;

       intrc &#61; 1;

 

       if(type > NR_ICMP_TYPES)

              gotoout;

 

       /*Don&#39;t limit PMTU discovery. */

       if(type &#61;&#61; ICMP_DEST_UNREACH && code &#61;&#61; ICMP_FRAG_NEEDED)

              gotoout;

 

       /*No rate limit on loopback */

       if(dst->dev && (dst->dev->flags&IFF_LOOPBACK))

              gotoout;

 

       /*Limit if icmp type is enabled in ratemask. */

       if((1 <ipv4.sysctl_icmp_ratemask)

              rc&#61; xrlim_allow(dst, net->ipv4.sysctl_icmp_ratelimit);

out:

       returnrc;

}



b)      数据发送icmp_push_reply

功能&#xff1a;

1、  调用ip_append_data&#xff0c;将数据缓存起来

2、  调用ip_flush_pending_frames将数据直接发送出去

static void icmp_push_reply(struct icmp_bxm*icmp_param,

                         struct ipcm_COOKIE *ipc, struct rtable**rt)

{

       structsock *sk;

       structsk_buff *skb;

       /*获取socket*/

       sk&#61; icmp_sk(dev_net((*rt)->u.dst.dev));

       /*调用ip_append_data&#xff0c;将要发送的数据缓存到sk->sk_write_queue

       并调用ip_push_pending_frames&#xff0c;将数据发送出去*/

       if(ip_append_data(sk, icmp_glue_bits, icmp_param,

                       icmp_param->data_len&#43;icmp_param->head_len,

                        icmp_param->head_len,

                        ipc, rt, MSG_DONTWAIT) <0)

              ip_flush_pending_frames(sk);

       elseif ((skb &#61; skb_peek(&sk->sk_write_queue)) !&#61; NULL) {

              structicmphdr *icmph &#61; icmp_hdr(skb);

              __wsumcsum &#61; 0;

              structsk_buff *skb1;

 

              skb_queue_walk(&sk->sk_write_queue,skb1) {

                     csum&#61; csum_add(csum, skb1->csum);

              }

              csum&#61; csum_partial_copy_nocheck((void *)&icmp_param->data,

                                           (char *)icmph,

                                           icmp_param->head_len, csum);

              icmph->checksum&#61; csum_fold(csum);

              skb->ip_summed&#61; CHECKSUM_NONE;

              ip_push_pending_frames(sk);

       }

}




icmp_send函数

对于由与入口数据包处理失败等操作时&#xff0c;上层协议会调用icmp_send发送数据&#xff0c;下面分析这个函数


功能:发送一个icmp error 数据包
不能发送icmp error 数据包的条件
1、对于入口数据包是多播的数据包(硬件或者ip地址为多播地址)&#xff0c;不发送icmp error 数据包
2、对于入口数据包有分段的&#xff0c;仅对首个分段的入口数据包&#xff0c;发送icmp error数据包
3、入口数据包本身是icmp error类型的&#xff0c;不发送针对该入口数据包的icmp error 


若入口数据包不满足上述条件&#xff0c;则需要发送针对该数据包的icmp error类型数据
1、查找路由
2、当路由查找成功后&#xff0c;则会调用icmp_push_reply 将数据发送出去

void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
{
struct iphdr *iph;
int room;
struct icmp_bxm icmp_param;
struct rtable *rt &#61; skb_rtable(skb_in);
struct ipcm_COOKIE ipc;
__be32 saddr;
u8  tos;
struct net *net;
struct sock *sk;


if (!rt)
goto out;
net &#61; dev_net(rt->u.dst.dev);


/*
* Find the original header. It is expected to be valid, of course.
* Check this, icmp_send is called from the most obscure devices
* sometimes.
*/
iph &#61; ip_hdr(skb_in);
/*
    1、对sk_buff做合理性检查&#xff0c;保证ipheader在sk_buff->head与sk_buff->tail之间的范围内
*/
if ((u8 *)iph head ||
   (skb_in->network_header &#43; sizeof(*iph)) > skb_in->tail)
goto out;


/*
* No replies to physical multicast/broadcast
*/
/*判断入口数据包的数据链路层的地址是否是广播或组播地址&#xff0c;若是则退出*/
if (skb_in->pkt_type !&#61; PACKET_HOST)
goto out;


/*
* Now check at the protocol level
*/
/*
        1、检查入口数据包是否广播、组播数据
        */
if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
goto out;


/*
* Only reply to fragment 0. We byte re-order the constant
* mask for efficiency.
*/
/*
        1、对于IP分段数据&#xff0c;仅对首个分段数据包发送ICMP错误信息
         */
if (iph->frag_off & htons(IP_OFFSET))
goto out;


/*
* If we send an ICMP error to an ICMP error a mess would result..
*/
/*
        1、判断接收的数据包是否是一个ICMP 错误信息数据包&#xff0c;若是则不对该数据包回复ICMP错误信息
        */
if (icmp_pointers[type].error) {
/*
* We are an error, check if we are replying to an
* ICMP error
*/
if (iph->protocol &#61;&#61; IPPROTO_ICMP) {
u8 _inner_type, *itp;


itp &#61; skb_header_pointer(skb_in,
skb_network_header(skb_in) &#43;
(iph->ihl <<2) &#43;
offsetof(struct icmphdr,
 type) -
skb_in->data,
sizeof(_inner_type),
&_inner_type);
if (itp &#61;&#61; NULL)
goto out;


/*
* Assume any unknown ICMP type is an error. This
* isn&#39;t specified by the RFC, but think about it..
*/
if (*itp > NR_ICMP_TYPES ||
   icmp_pointers[*itp].error)
goto out;
}
}


        /*关闭软中断&#xff0c;并为该socket添加自旋锁&#xff0c;确保同一时刻只有一个icmp报文被发送出去*/
sk &#61; icmp_xmit_lock(net);
if (sk &#61;&#61; NULL)
return;


/*
* Construct source address and options.
*/


       /*
        1、对于目的地址为本地的入口数据包&#xff0c;则将本地地址作为icmp包的源ip地址
        2、对于目的地址非背地的入口数据包&#xff0c;则根据sysctl_icmp_errors_use_inbound_ifaddr的值来设置源ip地址
      */
saddr &#61; iph->daddr;
if (!(rt->rt_flags & RTCF_LOCAL)) {
struct net_device *dev &#61; NULL;


rcu_read_lock();
if (rt->fl.iif &&
net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr)
dev &#61; dev_get_by_index_rcu(net, rt->fl.iif);


if (dev)
saddr &#61; inet_select_addr(dev, 0, RT_SCOPE_LINK);
else
saddr &#61; 0;
rcu_read_unlock();
}


       /*设置tos值*/
tos &#61; icmp_pointers[type].error ? ((iph->tos & IPTOS_TOS_MASK) |
  IPTOS_PREC_INTERNETCONTROL) :
 iph->tos;


if (ip_options_echo(&icmp_param.replyopts, skb_in))
goto out_unlock;




/*
* Prepare data for ICMP header.
*/
       /*设置icmp的头部信息*/
icmp_param.data.icmph.type&#61; type;
icmp_param.data.icmph.code&#61; code;
icmp_param.data.icmph.un.gateway &#61; info;
icmp_param.data.icmph.checksum&#61; 0;
icmp_param.skb &#61; skb_in;
icmp_param.offset &#61; skb_network_offset(skb_in);
inet_sk(sk)->tos &#61; tos;
ipc.addr &#61; iph->saddr;
ipc.opt &#61; &icmp_param.replyopts;
ipc.shtx.flags &#61; 0;


{
struct flowi fl &#61; {
.nl_u &#61; {
.ip4_u &#61; {
.daddr &#61; icmp_param.replyopts.srr ?
icmp_param.replyopts.faddr :
iph->saddr,
.saddr &#61; saddr,
.tos &#61; RT_TOS(tos)
}
},
.proto &#61; IPPROTO_ICMP,
.uli_u &#61; {
.icmpt &#61; {
.type &#61; type,
.code &#61; code
}
}
};
int err;
struct rtable *rt2;
               /*xfrm 架构相关的代码对于xfrm的代码不懂&#xff0c;此处直接跳过&#xff0c;认为内核没有开启xfrm*/
security_skb_classify_flow(skb_in, &fl);




               /*当内核没有开启xfrm时
               a)若没有查找到路由&#xff0c;则直接调用icmp_xmit_unlock&#xff0c;开启软中断并释放自旋锁
               b)若查找到路由&#xff0c;则调用icmpv4_xrlim_allow进行限速操作&#xff0c;并调用icmp_push_reply将数据发送出去*/
if (__ip_route_output_key(net, &rt, &fl))
goto out_unlock;


/* No need to clone since we&#39;re just using its address. */
rt2 &#61; rt;
               /*xfrm 架构相关的代码&#xff0c;对于xfrm的代码不懂&#xff0c;此处直接跳过&#xff0c;认为内核没有开启xfrm*/
err &#61; xfrm_lookup(net, (struct dst_entry **)&rt, &fl, NULL, 0);
switch (err) {
case 0:
if (rt !&#61; rt2)
goto route_done;
break;
case -EPERM:
rt &#61; NULL;
break;
default:
goto out_unlock;
}


if (xfrm_decode_session_reverse(skb_in, &fl, AF_INET))
goto relookup_failed;


if (inet_addr_type(net, fl.fl4_src) &#61;&#61; RTN_LOCAL)
err &#61; __ip_route_output_key(net, &rt2, &fl);
else {
struct flowi fl2 &#61; {};
struct dst_entry *odst;


fl2.fl4_dst &#61; fl.fl4_src;
if (ip_route_output_key(net, &rt2, &fl2))
goto relookup_failed;


/* Ugh! */
odst &#61; skb_dst(skb_in);
err &#61; ip_route_input(skb_in, fl.fl4_dst, fl.fl4_src,
    RT_TOS(tos), rt2->u.dst.dev);


dst_release(&rt2->u.dst);
rt2 &#61; skb_rtable(skb_in);
skb_dst_set(skb_in, odst);
}


if (err)
goto relookup_failed;


err &#61; xfrm_lookup(net, (struct dst_entry **)&rt2, &fl, NULL,
 XFRM_LOOKUP_ICMP);
switch (err) {
case 0:
dst_release(&rt->u.dst);
rt &#61; rt2;
break;
case -EPERM:
goto ende;
default:
relookup_failed:
if (!rt)
goto out_unlock;
break;
}
}


route_done:
if (!icmpv4_xrlim_allow(net, rt, type, code))
goto ende;


/* RFC says return as much as we can without exceeding 576 bytes. */


room &#61; dst_mtu(&rt->u.dst);
if (room > 576)
room &#61; 576;
room -&#61; sizeof(struct iphdr) &#43; icmp_param.replyopts.optlen;
room -&#61; sizeof(struct icmphdr);


icmp_param.data_len &#61; skb_in->len - icmp_param.offset;
if (icmp_param.data_len > room)
icmp_param.data_len &#61; room;
icmp_param.head_len &#61; sizeof(struct icmphdr);


icmp_push_reply(&icmp_param, &ipc, &rt);
ende:
ip_rt_put(rt);
out_unlock:
icmp_xmit_unlock(sk);
out:;
}




其他函数&#xff1a;

static struct sock *icmp_sk(struct net *net)
{
       /*
        1、获取当前执行CPU 所有的sock&#xff0c;主要用于发送ICMP数据包
       */
return net->ipv4.icmp_sk[smp_processor_id()];
}




/*
功能:
1、关闭软中断
2、为该sock获取自旋锁
*/
static inline struct sock *icmp_xmit_lock(struct net *net)
{
struct sock *sk;


local_bh_disable();
        /*
        1、获取ICMP sock
        2、为该sock加自旋锁&#xff0c;若失败则返回NULL&#xff0c;若成功则返回sock
        */
sk &#61; icmp_sk(net);


if (unlikely(!spin_trylock(&sk->sk_lock.slock))) {
/* This can happen if the output path signals a
* dst_link_failure() for an outgoing ICMP packet.
*/
local_bh_enable();
return NULL;
}
return sk;
}
/*
功能:
1、开启软中断
2、释放自旋锁
*/
static inline void icmp_xmit_unlock(struct sock *sk)
{
spin_unlock_bh(&sk->sk_lock.slock);
}



至此&#xff0c;完成icmp数据收发流程的分析







推荐阅读
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 判断数组是否全为0_连续子数组的最大和的解题思路及代码方法一_动态规划
    本文介绍了判断数组是否全为0以及求解连续子数组的最大和的解题思路及代码方法一,即动态规划。通过动态规划的方法,可以找出连续子数组的最大和,具体思路是尽量选择正数的部分,遇到负数则不选择进去,遇到正数则保留并继续考察。本文给出了状态定义和状态转移方程,并提供了具体的代码实现。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了一种图的存储和遍历方法——链式前向星法,该方法在存储带边权的图时时间效率比vector略高且节省空间。然而,链式前向星法存图的最大问题是对一个点的出边进行排序去重不容易,但在平行边无所谓的情况下选择这个方法是非常明智的。文章还提及了图中搜索树的父子关系一般不是很重要,同时给出了相应的代码示例。 ... [详细]
author-avatar
aaron飞飞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有