ping是使用ICMP协议
ICMP协议的组成:Type(8bits) + Code(8bits) + 校验码(checksum,8bits) + ID(16bits) + 序号(sequence,16bits) + 数据
这些组成部分的含义:
1)Type ICMP的类型,标识生成的错误报文
2)Code 进一步划分ICMP的类型,该字段用来查找产生的原因;例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。
3)CheckSum 校验码部分,这个字段包含从ICMP报头和数据部分计算得来的,用于检查错误的,其中此校验码字段的值视为0.
4)ID 这个字段包含了ID值,在Echo Reply类型的消息中要返回这个字段。
5)Sequence 这个字段包含一个序号
ping命令的实现是使用ICMP中类型值为8(reply)和0(request)
现在开始编写代码:
一、解析参数
var ( icmp ICMP laddr = net.IPAddr{IP: net.ParseIP("ip")} //raddr, _ = net.ResolveIPAddr("ip", os.Args[1]) num int timeout int64 size int stop bool)func ParseArgs() { flag.Int64Var(&timeout, "w", 1000, "等待每次回复的超时时间(毫秒)") flag.IntVar(&num, "n", 4, "要发送的请求数") flag.IntVar(&size, "l", 32, "要发送缓冲区大小") flag.BoolVar(&stop, "t", false, "Ping 指定的主机,直到停止") flag.Parse() }
二、定义ICMP结构体
type ICMP struct { Type uint8 Code uint8 Checksum uint16 Identifier uint16 SequenceNum uint16 }
三、为ICMP变量设置值
//icmp头部填充icmp.Type = 8icmp.Code = 0icmp.Checksum = 0icmp.Identifier = 1icmp.SequenceNum = 1
四、计算ICMP校验和
这边讲解下校验和的计算,ICMP的校验和IP的校验不同,ICMP的校验是校验ICMP头部和数据内容,ICMP校验和计算过程如下:
1)将ICMP头部内容中的校验内容(Checksum)的值设为0
2)将拼接好(Type+Code+Checksum+Id+Seq+传输Data)的ICMP包按Type开始每两个字节一组(其中Checksum的两个字节都看成0),进行加和处理,如果字节个数为奇数个,则直接加上这个字节内容。说明:这个加和过程的内容放在一个4字节上,如果溢出4字节,则将溢出的直接抛弃
3)将高16位与低16位内容加和,直到高16为0
4)将步骤三得出的结果取反,得到的结果就是ICMP校验和的值
验证校验和的方式也是一样,验证时先计算验证和,然后和验证和中内容进行比较是否一样
func CheckSum(data []byte) uint16 { var sum uint32 var length = len(data) var index int for length > 1 { // 溢出部分直接去除 sum += uint32(data[index])<<8 + uint32(data[index+1]) index += 2 length -= 2 } if length == 1 { sum += uint32(data[index]) } sum = uint16(sum >> 16) + uint16(sum) sum = uint16(sum >> 16) + uint16(sum) return uint16(^sum) }
五、发送ICMP包
六、打印结果
完整实现代码:
github下载链接:https://github.com/laijinhang/ping
package mainimport ( "bytes" "encoding/binary" "flag" "fmt" "log" "net" "os" "time" "math")type ICMP struct { Type uint8 Code uint8 Checksum uint16 Identifier uint16 SequenceNum uint16 } var ( icmp ICMP laddr = net.IPAddr{IP: net.ParseIP("ip")} num int timeout int64 size int stop bool )func main() { ParseArgs() args := os.Args if len(args) < 2 { Usage() } desIp := args[len(args) - 1] conn, err := net.DialTimeout("ip:icmp", desIp, time.Duration(timeout) * time.Millisecond) if err != nil { log.Fatal(err) } defer conn.Close() //icmp头部填充 icmp.Type = 8 icmp.Code = 0 icmp.Checksum = 0 icmp.Identifier = 1 icmp.SequenceNum = 1 fmt.Printf("\n正在 ping %s 具有 %d 字节的数据:\n", desIp, size) var buffer bytes.Buffer binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式写入 data := make([]byte, size) // buffer.Write(data) data = buffer.Bytes() var SuccessTimes int // 成功次数 var FailTimes int // 失败次数 var minTime int = int(math.MaxInt32) var maxTime int var totalTime int for i := 0;i < num;i++ { icmp.SequenceNum = uint16(1) // 检验和设为0 data[2] = byte(0) data[3] = byte(0) data[6] = byte(icmp.SequenceNum >> 8) data[7] = byte(icmp.SequenceNum) icmp.Checksum = CheckSum(data) data[2] = byte(icmp.Checksum >> 8) data[3] = byte(icmp.Checksum) // 开始时间 t1 := time.Now() conn.SetDeadline(t1.Add(time.Duration(time.Duration(timeout) * time.Millisecond))) n, err := conn.Write(data) if err != nil { log.Fatal(err) } buf := make([]byte, 65535) n, err = conn.Read(buf) if err != nil { fmt.Println("请求超时。") FailTimes++ continue } et := int(time.Since(t1) / 1000000) if minTime > et { minTime = et } if maxTime <et { maxTime = et } totalTime += et fmt.Printf("来自 %s 的回复: 字节=%d 时间=%dms TTL=%d\n", desIp, len(buf[28:n]), et, buf[8]) SuccessTimes++ time.Sleep(1 * time.Second) } fmt.Printf("\n%s 的 Ping 统计信息:\n", desIp) fmt.Printf(" 数据包: 已发送 = %d,已接收 = %d,丢失 = %d (%.2f%% 丢失),\n", SuccessTimes + FailTimes, SuccessTimes, FailTimes, float64(FailTimes * 100) / float64(SuccessTimes + FailTimes)) if maxTime != 0 && minTime != int(math.MaxInt32) { fmt.Printf("往返行程的估计时间(以毫秒为单位):\n") fmt.Printf(" 最短 = %dms,最长 = %dms,平均 = %dms\n", minTime, maxTime, totalTime / SuccessTimes) } }func CheckSum(data []byte) uint16 { var sum uint32 var length = len(data) var index int for length > 1 { // 溢出部分直接去除 sum += uint32(data[index]) << 8 + uint32(data[index+1]) index += 2 length -= 2 } if length == 1 { sum += uint32(data[index]) } // CheckSum的值是16位,计算是将高16位加低16位,得到的结果进行重复以该方式进行计算,直到高16位为0 /* sum的最大情况是:ffffffff 第一次高16位+低16位:ffff + ffff = 1fffe 第二次高16位+低16位:0001 + fffe = ffff 即推出一个结论,只要第一次高16位+低16位的结果,再进行之前的计算结果用到高16位+低16位,即可处理溢出情况 */ sum = uint32(sum >> 16) + uint32(sum) sum = uint32(sum >> 16) + uint32(sum) return uint16(^sum) }func ParseArgs() { flag.Int64Var(&timeout, "w", 1500, "等待每次回复的超时时间(毫秒)") flag.IntVar(&num, "n", 4, "要发送的请求数") flag.IntVar(&size, "l", 32, "要发送缓冲区大小") flag.BoolVar(&stop, "t", false, "Ping 指定的主机,直到停止") flag.Parse() }func Usage() { argNum := len(os.Args) if argNum < 2 { fmt.Print( ` 用法: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS] [-r count] [-s count] [[-j host-list] | [-k host-list]] [-w timeout] [-R] [-S srcaddr] [-c compartment] [-p] [-4] [-6] target_name 选项: -t Ping 指定的主机,直到停止。 若要查看统计信息并继续操作,请键入 Ctrl+Break; 若要停止,请键入 Ctrl+C。 -a 将地址解析为主机名。 -n count 要发送的回显请求数。 -l size 发送缓冲区大小。 -f 在数据包中设置“不分段”标记(仅适用于 IPv4)。 -i TTL 生存时间。 -v TOS 服务类型(仅适用于 IPv4。该设置已被弃用, 对 IP 标头中的服务类型字段没有任何 影响)。 -r count 记录计数跃点的路由(仅适用于 IPv4)。 -s count 计数跃点的时间戳(仅适用于 IPv4)。 -j host-list 与主机列表一起使用的松散源路由(仅适用于 IPv4)。 -k host-list 与主机列表一起使用的严格源路由(仅适用于 IPv4)。 -w timeout 等待每次回复的超时时间(毫秒)。 -R 同样使用路由标头测试反向路由(仅适用于 IPv6)。 根据 RFC 5095,已弃用此路由标头。 如果使用此标头,某些系统可能丢弃 回显请求。 -S srcaddr 要使用的源地址。 -c compartment 路由隔离舱标识符。 -p Ping Hyper-V 网络虚拟化提供程序地址。 -4 强制使用 IPv4。 -6 强制使用 IPv6。 `) } }
作者:laijh
链接:https://www.jianshu.com/p/48ffe3c58dcb
共同学习,写下你的评论
评论加载中...
作者其他优质文章