使用ping命令发现局域网内延迟大,且变化较大。需要分析耗时在那一层。
上图可以看出,本机ping延时非常低。在本机网络 IO 的过程中,流程会有一些差别。有差异的地方总共有两个,分别是路由和驱动程序。
路由:
对于本机网络 IO 来说,特殊之处在于在 local 路由表中就能找到路由项,对应的设备都将使用 loopback 网卡,也就是我们常见的 lO。
驱动程序:
网络设备子系统的入口函数是 dev_queue_xmit。对于真的有队列的物理设备,在该函数中进行了一系列复杂的排队等处理以后,才调用 dev_hard_start_xmit,从这个函数再进入驱动程序来发送。
但是对于启动状态的回环设备来说(q->enqueue 判断为 false),就简单多了:没有队列的问题,直接进入 dev_hard_start_xmit。接着进入回环设备的“驱动”里的发送回调函数 loopback_xmit,将 skb “发送”出去。
因此,可以从上面两层入手,排查问题!
驱动:
监控icmp包发送和接受的时刻,看时间是否过大。
但有一个问题,链路上运行的不一定只有ICMP数据包。因此,我们需要在每一层对我们发送和接收的ICMP包进行过滤。
路由:
ICMP报文包含在IP数据报中,IP报头在ICMP报文的最前面。一个ICMP报文包括IP报头(至少20字节)、ICMP报头(至少八字节)和ICMP报文(属于ICMP报文的数据部分)。当IP报头中的协议字段值为1时,就说明这是一个ICMP报文。ICMP报头如下图所示。
这里只讲解与ping有关的ICMP消息类型,主机发送回送消息(Type = 8),被请求主机回送响应消息(Type = 0),基本格式如下:
回送消息[ECHO]
回送响应消息[ECHO REPLY]
•CheckSum为校验和,重点注意从ICMP的头部(即Type开始),到data结束(即到整个数据包结束)
•Identifier为标识符,由主机设定,一般设置为进程号,回送响应消息与回送消息中identifier保持一致
•Sequence Number为序列号,由主机设定,一般设为由0递增的序列,回送响应消息与回送消息中Sequence Number保持一致
•data为数据,由主机设定,回送响应消息与回送消息中data保持一致
- ///include/uapi/linux/icmp.h
-
- struct icmphdr {
- __u8 type;
- __u8 code;
- __sum16 checksum;
- union {
- struct {
- __be16 id;
- __be16 sequence;
- } echo;
- __be32 gateway;
- struct {
- __be16 __unused;
- __be16 mtu;
- } frag;
- __u8 reserved[4];
- } un;
- };
- #ifndef _LINUX_IP_H
- #define _LINUX_IP_H
-
- #include
- #include
-
- static inline struct iphdr *ip_hdr(const struct sk_buff *skb)
- {
- return (struct iphdr *)skb_network_header(skb);
- }
-
- static inline struct iphdr *inner_ip_hdr(const struct sk_buff *skb)
- {
- return (struct iphdr *)skb_inner_network_header(skb);
- }
-
- static inline struct iphdr *ipip_hdr(const struct sk_buff *skb)
- {
- return (struct iphdr *)skb_transport_header(skb);
- }
- #endif /* _LINUX_IP_H */
-
-
-
- struct iphdr {
- #if defined(__LITTLE_ENDIAN_BITFIELD)
- __u8 ihl:4,
- version:4;
- #elif defined (__BIG_ENDIAN_BITFIELD)
- __u8 version:4,
- ihl:4;
- #else
- #error "Please fix
" - #endif
- __u8 tos;
- __be16 tot_len;
- __be16 id;
- __be16 frag_off;
- __u8 ttl;
- __u8 protocol;
- __sum16 check;
- __be32 saddr;
- __be32 daddr;
- /*The options start here. */
- };
-
- ///include/uapi/linux/in.h
-
- enum {
- IPPROTO_IP = 0, /* Dummy protocol for TCP */
- #define IPPROTO_IP IPPROTO_IP
- IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
- #define IPPROTO_ICMP IPPROTO_ICMP
- IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
- #define IPPROTO_IGMP IPPROTO_IGMP
- IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
- #define IPPROTO_IPIP IPPROTO_IPIP
- IPPROTO_TCP = 6, /* Transmission Control Protocol */
- #define IPPROTO_TCP IPPROTO_TCP
- IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
- #define IPPROTO_EGP IPPROTO_EGP
- IPPROTO_PUP = 12, /* PUP protocol */
- #define IPPROTO_PUP IPPROTO_PUP
- IPPROTO_UDP = 17, /* User Datagram Protocol */
- #define IPPROTO_UDP IPPROTO_UDP
- IPPROTO_IDP = 22, /* XNS IDP protocol */
- #define IPPROTO_IDP IPPROTO_IDP
- IPPROTO_TP = 29, /* SO Transport Protocol Class 4 */
- #define IPPROTO_TP IPPROTO_TP
- IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol */
- #define IPPROTO_DCCP IPPROTO_DCCP
- IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */
- #define IPPROTO_IPV6 IPPROTO_IPV6
- IPPROTO_RSVP = 46, /* RSVP Protocol */
- #define IPPROTO_RSVP IPPROTO_RSVP
- IPPROTO_GRE = 47, /* Cisco GRE tunnels (rfc 1701,1702) */
- #define IPPROTO_GRE IPPROTO_GRE
- IPPROTO_ESP = 50, /* Encapsulation Security Payload protocol */
- #define IPPROTO_ESP IPPROTO_ESP
- IPPROTO_AH = 51, /* Authentication Header protocol */
- #define IPPROTO_AH IPPROTO_AH
- IPPROTO_MTP = 92, /* Multicast Transport Protocol */
- #define IPPROTO_MTP IPPROTO_MTP
- IPPROTO_BEETPH = 94, /* IP option pseudo header for BEET */
- #define IPPROTO_BEETPH IPPROTO_BEETPH
- IPPROTO_ENCAP = 98, /* Encapsulation Header */
- #define IPPROTO_ENCAP IPPROTO_ENCAP
- IPPROTO_PIM = 103, /* Protocol Independent Multicast */
- #define IPPROTO_PIM IPPROTO_PIM
- IPPROTO_COMP = 108, /* Compression Header Protocol */
- #define IPPROTO_COMP IPPROTO_COMP
- IPPROTO_SCTP = 132, /* Stream Control Transport Protocol */
- #define IPPROTO_SCTP IPPROTO_SCTP
- IPPROTO_UDPLITE = 136, /* UDP-Lite (RFC 3828) */
- #define IPPROTO_UDPLITE IPPROTO_UDPLITE
- IPPROTO_MPLS = 137, /* MPLS in IP (RFC 4023) */
- #define IPPROTO_MPLS IPPROTO_MPLS
- IPPROTO_RAW = 255, /* Raw IP packets */
- #define IPPROTO_RAW IPPROTO_RAW
- IPPROTO_MAX
- };
驱动层如何识别ICMP报文?
在.ndo_start_xmit = esp_hard_start_xmit,函数中,将邻居子系统传递下来的skb使用ip_hdr函数即可转换!
- #include
- #include
- #include
-
- struct iphdr *iph;
- struct icmphdr *icmpph = NULL;
-
- iph = ip_hdr(skb);
- if(iph->protocol==IPPROTO_ICMP ) {
- //如果是ICMP报文
- icmpph = (struct icmphdr *) ((u8 *) iph + (iph->ihl << 2));
- printk("ESP type:%d, code:%d\n", icmpph->type, icmpph->code);
- if(icmpph->type==8 && icmpph->code==0) {//ping 请求
- printk(KERN_ERR "ping RQ:%d\n",icmpph->un.echo.sequence);
- }
- }
ref:
不为人知的网络编程(十三):深入操作系统,彻底搞懂127.0.0.1本机网络通信 - 知乎
linux内核协议栈 icmp 报文收发流程_老王不让用的博客-CSDN博客
ping命令全链路分析(4) - 数据包内核态处理 - 知乎
Linux内核:从skb获取udp头,udp_hdr()获取到是错误的udp头_kanguolaikanguolaik的博客-CSDN博客