要模拟实现ping命令,就需要对ICMP协议有所了解:

ICMP:Internet控制报文协议,它是TCP/IP协议族中的一个子协议,用于在IP主机,路由之间传递信息的协议。

传输的信息包括:

1.目的不可达消息

2.超时消息

3.重定向消息

4.时间戳请求和时间戳响应消息

5.回显请求和回显响应消息。

ping命令 的机制就是回显请求和回显应答消息,具体是向网络上另一个主机上发送ICMP报文,如果指定的主机得到了这个报文,就将报文原封不动的发送回发送者。

ICMP报文格式:

ICMP通过指定vlan_ICMP通过指定vlan

类型:回显请求报文其中类型为0,代码为0

代码:回显应答报文其中类型为8,代码为0

校验和:包括数据在内整个ICMP协议数据包校验和

标识符:用于文艺标识ICMP报文,Linux中使用进程ID

序列号:报文的序列号

数据:ping中将发送报文的时间戳放入数据字段

通过下面的代码可以打印出IP协议头部的格式:

ICMP通过指定vlan_ICMP通过指定vlan_02

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>


int main() {
  int sfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if(sfd < 0)
  {
    perror("socket");
    return 1;
  }
  
  int opt = 1;
  setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt));

  char buf[1500];
  while(1)
  {
    memset(buf, 0x00, sizeof(buf));
    int ret = read(sfd, buf, 1500);
    if(ret <= 0)
    {
      break;
    }

    struct iphdr* pip = (struct iphdr*)(buf);
    struct in_addr ad;

    ad.s_addr = pip->saddr;
    //printf("protocol: %hhd, %s <-----> ", pip->protocol, inet_ntoa(ad));
    printf("数据报为%s <===>",inet_ntoa(ad));
    ad.s_addr = pip->daddr;
    printf("%s\n", inet_ntoa(ad));

  }
  return 0;
}

打印出信息如下:

ICMP通过指定vlan_ping命令_03

准备工作就绪,先看看系统的ping长得样子:

ICMP通过指定vlan_ping命令_04

首先介绍几个函数:

1.得到主机的时间,精确到毫秒

int gettimeofday(struct timeval *tv, struct timezone *tz);//获取的时间为微秒级的,big存放在tv中。
struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */

};这是一个线程安全的函数。

2.类似DNS的域名解析,将输入的域名解析成ip地址

struct hostent *gethostbyname(const char *name);     //函数返回给定主机名的hostent类型的结构。
 struct hostent {
               char  *h_name;            /* official name of host */
               char **h_aliases;         /* alias list */
               int    h_addrtype;        /* host address type */
               int    h_length;          /* length of address */
               char **h_addr_list;       /* list of addresses */
}

3.校验和:

校验算法可以分成两步来实现。
首先在发送端,有以下三步:
1.把校验和字段置为 0。
2.对需要校验的数据看成以 16bit 为单位的数字组成,依次进行二进制求和。
3.将上一步的求和结果取反,存入校验和字段。
其次在接收端,也有相应的三步:
1.对需要校验的数据看成以 16bit 为单位的数字组成,依次进行二进制求和,包括校验和字段。
2.将上一步的求和结果取反
3.判断最终结果是否为 0。如果为 0,说明校验和正确。如果不为 0,则协议栈会丢掉接收到的数据。

unsigned short chksum(unsigned short* addr, int len)   //校验和
{
  unsigned int ret = 0;
  
  while(len > 1)
  {
    ret += *addr++;
    len -= 2;
  }
  if(len == 1)
  {
    ret += *(unsigned char*)addr;
  }

  ret = (ret >> 16) + (ret & 0xffff);
  ret += (ret >> 16);
  
  return (unsigned short)~ret;
}

万事具备:完整代码链接

代码执行结果:

ICMP通过指定vlan_ICMP通过指定vlan_05

至此,整个ping命令就全部模仿出来了,ping命令基本的结构都有了。