如何手写一个ping命令?

241 阅读5分钟

工作中最常用的命令应该属 ping命令次数最多了,那么本篇文章的主题是通过 SOCK_RAW 套接字类型,实现简单ping命令功能。

⭐ socket套接字相关的文章:

socket套接字你搞清楚了吗?

如何使用socket系统调用创建TCP三次握手呢?

手写一个UDP端口探测程序

ping命令是用来测试网络连通性的,其核心使用的ICMP协议实现。

SOCK_RAW是一种原始套接字类型,允许程序在用户空间中直接访问网络层协议(IP协议),绕过传输层(TCP/UDP)的封装,实现对网络数据包的完全控制。
原始套接字接收或发送不包括链路层的原始数据报。

SOCK_RAW基本特性

  1. 底层访问:可以直接读写IP层及以上的数据包
  2. 协议定制:可以自定义协议头部(IP头、传输层头等)
  3. 数据包嗅探:可以接收所有经过网卡的数据包(需root特权)
  4. 无自动处理:内核不会自动添加/解析协议头部

ping命令简易功能,研发思路:

  1. 我们执行 ping 192.168.42.173 命令,然后通过tcpdump -i 网卡名称 icmp 抓包;
  2. 拿到数据包格式内容后,按照IP协议、ICMP协议格式进行组包;
  3. 最后我们通过SOCK_RAW套接字类型,进行封包,创建消息请求;

下面要了解两种网络协议 IP协议和 ICMP协议。

IP协议(Internet Protocol)是TCP/IP协议族中最核心的协议,提供不可靠的、无连接的、尽力而为的数据报传输服务。

报文格式

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段说明

字段长度含义
Version4比特IP版本
IHL4比特首部长度,如果不带Option字段,则为20,最长为60。以4字节为一个单位。
Type of Service8比特服务类型。只有在有QoS差分服务要求时这个字段才起作用。
Total Length16比特总长度,整个IP数据报的长度,包括首部和数据之和,单位为字节,最长65535,总长度必须不超过最大传输单元MTU。
Identification16比特标识,主机每发一个报文,加1,分片重组时会用到该字段。
Flags3比特标志位,表示能不能分片。
Fragment Offset13比特片偏移,以8个字节为偏移单位。
Time to Live8比特生存时间:可经过的最多路由数,即数据包在网络中可通过的路由器数的最大值。
Protocol8比特下一层协议。
Header Checksum16比特首部检验和,只检验数据包的首部,不检验数据部分。
Source Address32比特源IP地址。
Destination Address32比特目的IP地址。
Options可变选项字段,选项字段长度可变,从1字节到40字节不等,取决于所选项的功能。
Padding可变填充字段,全填0。

ICMP(Internet Control Message Protocol)因特网控制报文协议。它是IPv4协议族中的一个子协议,用于IP主机、路由器之间传递控制消息。控制消息是在网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然不传输用户数据,但是对于用户数据的传递起着重要的作用。

ICMPIP协议来完成任务,所以ICMP报文中要封装IP头部。

ICMP Echo Request/Reply消息格式

用于检测IP网络连通性的Ping/Tracert,是通过发送ICMP Echo消息实现的。

报文格式

+0------7-------15---------------31
|  Type | Code  |    Checksum    |
+--------------------------------+
|   Identifier  | Sequence Number|
+--------------------------------+
|             Data               |
+--------------------------------+

字段说明

字段长度含义
Type1字节消息类型。
+ 0:回显应答报文
+ 8:请求回显报文
Code1字节消息代码,此处值为0。
Checksum2字节检验和。
Identifier2字节标识符,发送端标示此发送的报文。
Sequence Number2字节序列号,每发送一次顺序号就加1。
Data可变选项数据。

让我们一起来进入代码的世界~

首先,先封装ping数据包,分为IP报文和ICMP报文两部分。

这里需要注意的是,自定义封装的ping数据包总长度为60字节。 分别为以太帧14字节、IP头部20字节、ICMP报文26字节。

其中 Ethernet II 以太帧占用14字节,因为 SOCK_RAW 套接字类型只能处理IP层及以上协议,这里代码中只包含IPICMP协议的填充。

packet变量申请了46字节的长度,包含20字节IP头部、8字节ICMP头部、18字节ICMP数据。以太帧要求数据字段的最小长度必须为46字节,这里符合要求。

最后,将封装的ping数据包通过系统调用,创建 SOCK_RAW 套接字类型的socket网络请求。

代码中使用了 syscall.SetsockoptInt() 函数,用于设置套接字的选项。
syscall.IPPROTO_IP 代表是在IP层中。
syscall.IP_HDRINCL 作用是需要手动填充IP层首部。(如果不选择则由内核自动填充)

完成发送后,目标服务端会将收到的 ping请求,立马返回ping所携带的数据响应请求。

下面代码用于接收ping响应报文, buf 变量接收长度为46,是因为我们发送时定义的报文大小是46,所以服务端也会将按照这个大小返回响应。

完整代码,请在github仓库查阅:github.com/hltfaith/go…


执行ping命令看下效果

最后进行抓包,看下是否是我们预期自定义的内容

技术文章持续更新,请大家多多关注呀~~
搜索微信公众号,关注我【 帽儿山的枪手 】