前言
icmp 也是我们经常会碰到的协议
比如 我们通常使用 ping 来测试 两台主机之间是否可以正常通信
ping 是基于 icmp 协议的
发送 icmp 数据包
测试用例如下, 封装了一个 icmp 报文, 并发送
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<pthread.h>
#include<poll.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<signal.h>
#include<sys/sem.h>
#include<poll.h>
#include<pthread.h>
#include<sys/select.h>
#include<sys/un.h>
#define SA (struct sockaddr*)
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>
unsigned short
csum (unsigned short *buf, int nwords)
{
unsigned long sum;
for (sum = 0; nwords > 0; nwords--)
sum += *buf++;
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return ~sum;
}
int
main (int argc, char *argv[])
{
if (argc != 2)
{
printf ("need destination for tracert\n");
exit (0);
}
int sfd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
char buf[4096] = { 0 };
struct ip *ip_hdr = (struct ip *) buf;
int hop = 0;
int one = 1;
const int *val = &one;
if (setsockopt (sfd, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
printf ("Cannot set HDRINCL!\n");
struct sockaddr_in addr;
addr.sin_port = htons (7);
addr.sin_family = AF_INET;
inet_pton (AF_INET, argv[1], &(addr.sin_addr));
while (1)
{
ip_hdr->ip_hl = 5;
ip_hdr->ip_v = 4;
ip_hdr->ip_tos = 0;
ip_hdr->ip_len = 20 + 8;
ip_hdr->ip_id = 10000;
ip_hdr->ip_off = 0;
ip_hdr->ip_ttl = hop;
ip_hdr->ip_p = IPPROTO_ICMP;
inet_pton (AF_INET, "192.168.1.2", &(ip_hdr->ip_src));
inet_pton (AF_INET, argv[1], &(ip_hdr->ip_dst));
ip_hdr->ip_sum = csum ((unsigned short *) buf, 9);
struct icmphdr *icmphd = (struct icmphdr *) (buf + 20);
icmphd->type = ICMP_ECHO;
icmphd->code = 0;
icmphd->checksum = 0;
icmphd->un.echo.id = 0;
icmphd->un.echo.sequence = hop + 1;
icmphd->checksum = csum ((unsigned short *) (buf + 20), 4);
sendto (sfd, buf, 28, 0, SA & addr, sizeof addr);
char buff[4096] = { 0 };
struct sockaddr_in addr2;
socklen_t len = sizeof (struct sockaddr_in);
recvfrom (sfd, buff, 28, 0, SA & addr2, &len);
struct icmphdr *icmphd2 = (struct icmphdr *) (buff + 20);
if (icmphd2->type != 0)
printf ("hop limit:%d Address:%s\n", hop, inet_ntoa (addr2.sin_addr));
else
{
printf ("Reached destination:%s with hop limit:%d\n",
inet_ntoa (addr2.sin_addr), hop);
exit (0);
}
hop++;
}
return 0;
}
如何查看 skb 的数据
skb 有一个 header, 表示的是数据块的头部
skb 有一个 data, 表示的是 eth数据内容 的开始的地方, 通常来说对应于 ip包 头部
skb 有一个 tail, 表示的是当前 eth 包末尾的位置
skb 有一个 mac_len 表示的是 eth头 的长度
skb 有一个 len 表示的是 eth数据内容 的长度
skb 有一个 mac_header 表示的是 header + mac_header 为 eth头 的开始的位置
skb 有一个 network_header 表示的是 header + network _header 为 ip头 的开始的位置
skb 有一个 transport_header 表示的是 header + transport _header 为 传输层头 的开始的位置
内存中 skb 对应的 icmp 报文数据剖析
(gdb) x /10gx 0xffff88007fa8f000
0xffff88007fa8f000: 0x000000000000f200 0x0008000000000000
0xffff88007fa8f010: 0x000027101c000045 0x0201a8c066270100
0xffff88007fa8f020: 0xfff600080201a8c0 0x0000000000010000
0xffff88007fa8f030: 0x0000000000200000 0x0000000600000001
0xffff88007fa8f040: 0x0000000000025bc0 0x0000000000225bc0
# eth
destMac : 000000000000
srcMac : 000000000000
ethType : 0800
# ip
version+headerLen : 45
diffSerField : 00
totalLen : 001c
identification : 2710
flags : 0000
timeToLive : 00
protocol : 01
header checksum : 2766
srcIp : c0 a8 01 02
dstIp : c0 a8 01 02
# icmp
type : 08
code : 00
checksum : f6ff
identifier be : 0000
sequence be : 0001
icmp 信息的发送
参见 测试代码
icmp 信息的接受
首先是软中断, 然后 转给对应的设备 使用 net_rx_action 处理
到 ip 层的处理, protocol 为 1, 表示 icmp, 然后使用 icmp_protocol->handler 来处理当前请求
然后是 icmp 协议层的处理, 请求类型为 ECHO, 使用 icmp_echo 进行处理
封装 icmp 响应, 并返回给客户端
接下来就是 ip 层 以及 以太网层 的处理了
完