ping 是一个经常被用来检查主机间连通性的工具, 它基于 ICMP 协议实现, 基本原理很简单: 本机给远程机器发送 ICMP 报文, 远程主机接收到 ICMP 报文后便会回复一个类似的 ICMP 报文; 当本机接收到回复后变认为远程主机是可连接的, 否则便认为这个主机是不可达的.
为了了解 golang 的网络编程, 我用 go 实现了一个 ping 命令, 本文会介绍如何实现 ping 命令.
Demo
这里有完整的示例代码, 可以直接执行实现下面的效果 (注意需要 sudo 权限):
1 | 复制代码➜ ping git:(master) sudo go run goping.go baidu.com |
如何实现
ICMP 报文
首先我们需要定义出 ICMP 报文头的结构:
1 | 复制代码type ICMP struct { |
其中 Type
表明的是 ICMP 的类型, Code
则用来进一步划分 ICMP 的类型, ping 使用的是 echo 类型的 ICMP, 这两个值需要分别设置为 8 和 0.
CheckSum
是报文头的校验值, 以防止在网络传输过程中的数据错误. 会先把这个字段设置为 0 来计算校验值, 计算完成后再把校验值赋值到这个字段.
ID 是用来标识一个 ICMP, 可以设置为 0; 而 SequenceNum
则是序列号, 可以在发送 ICMP 报文的时候依次累加.
这篇文章对 ICMP 的结构有更详细的介绍.
基于上面的描述, 我们可以实现下面这个基于序列号生成 ICMP 报文头的函数:
1 | 复制代码func getICMP(seq uint16) ICMP { |
其中 CheckSum()
是用来计算校验值的函数. 在网络中传输的数据需要是大端字节序的.
发送及接收 ICMP 报文
首先, 我们使用 net.DialIP("ip4:icmp", nil, destAddr)
来创建一个 ICMP 报文.
接着我们使用下面的代码填充 ICMP 报文并发送:
1 | 复制代码binary.Write(&buffer, binary.BigEndian, icmp) |
发送完之后, 我们使用下面的命令接收请求:
1 | 复制代码recv := make([]byte, 1024) |
同时我们还需要统计发送到接收之间所耗费的时间.
完整的代码如下所示:
1 | 复制代码func sendICMPRequest(icmp ICMP, destAddr *net.IPAddr) error { |
ping 命令的完整代码
1 | 复制代码package main |
References
本文转载自: 掘金