golang实现ping命令的完整代码

 更新时间:2024年02月18日 10:43:30   作者:NPE~  
这篇文章给大家介绍了如何使用golang实现ping命令,文中给大家介绍了完整的实现代码,并有详细的图文讲解,对大家的学习或工作有一定的帮助,需要的朋友可以参考下

golang实现ping命令(附:完整代码)

代码链接:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ping_demo

1 ping原理:ICMP协议(Type+Code+checksum+ID+sequence)

ping是使用ICMP协议。ICMP协议的组成:

Type(8bits) + Code(8bits) + 校验码(checksum,8bits) + ID(16bits) + 序号(sequence,16bits) + 数据

在这里插入图片描述

这些组成部分的含义:
1)Type: ICMP的类型,标识生成的错误报文
2)Code: 进一步划分ICMP的类型,该字段用来查找产生的原因;例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。

在这里插入图片描述

总结: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)

1.1 Type 类型值,标识ICMP分组类型

1.2 Code 代码值,标识ICMP分组类型的某一种具体分组

1.3 Checksum 校验和,用于检验数据包是否完整或是否被修改

1.4 Identifier 标识符,标识本进程。当同时与多个目的通信时,通过本字段来区分

1.5 Sequence Number 序列号,标识本地到目的的数据包序号,一般从序号1开始

常见ICMP类型

  • 回复应答(ICMP类型0):ping命令用到该类型的数据包以测试TCP/IP连接;
  • 目标不可达 (ICMP类型3):用以知识目标网络、主机或者端口不可达;
  • 源站抑制 (ICMP类型4):当路由器处理IP数据的速度不够快时,会发送此类的消息。它的意思是让发送方降低发送数据的速率。Microsoft Windows NT或Windows 2000主机可以通过降低数据传输率来响应这种类型的消息;
  • 重定向消息 (ICMP类型5):用于将主机重新定向到一个不同的网络路径,该消息告诉路由器对于该数据包可以忽略它内部的路由表项;
  • 回复请求(ICMP类型8):ping命令用该类型的数据包测试TCP/IP连接;
  • 路由器通告 (ICMP类型9):以随机的时间间隔发送该数据包以响应
  • 路由器请求 (ICMP类型10):路由器发送该数据包来请求路由器通告的更新;
  • 超时 (ICMP类型11):指示数据包由于通过了太多的网段,其的生存时间(TTL)已经过期,Tracert命令用此消息来测试本地和远程主机之间的多个路由器;
  • 参数问题 (ICMP类型12):用以指示处理IP数据包头时出错。

2 实现

2.1 定义ICMP结构体

type ICMP struct {
    Type        uint8
    Code        uint8
    Checksum    uint16
    Identifier  uint16
    SequenceNum uint16
}

2.2 计算校验和

官网解释:The checksum is the 16-bit ones’s complement of the one’s
complement sum of the ICMP message starting with the ICMP Type.
For computing the checksum , the checksum field should be zero.
If the total length is odd, the received data is padded with one
octet of zeros for computing the checksum. This checksum may be
replaced in the future.

  • 获取ICMP报文(首部+数据部分)
  • 将ICMP报文中的校验和字段置为0。
  • 将ICMP协议报文中的每两个字节(16位,需要注意大小端问题)两两相加,得到一个累加和。若报文长度为奇数,则最后一个字节(8-bit)作为高8位,再用0填充一个字节(低8-bit)扩展到16-bit,之后再和前面的累加和继续相加得到一个新的累加和。
  • (若有溢出)将累加和的高16位和低16位相加,直到最后只剩下16位。
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)
}

2.3 命令行参数

var (
    icmp  ICMP
    laddr = net.IPAddr{IP: net.ParseIP("ip")}
    num     int
    timeout int64
    size    int
    stop    bool
)

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。
`)
    }
}

2.4 发送ICMP包

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请求 ping请求
	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)
	//写入icmp包头及空数据
	buffer.Write(data)
	data = buffer.Bytes()

	var SuccessTimes int // 成功次数
	var FailTimes int    // 失败次数
	var minTime = 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)
		//设置checksum
		icmp.Checksum = CheckSum(data)
		data[2] = byte(icmp.Checksum >> 8)
		data[3] = byte(icmp.Checksum)

		// 开始时间
		t1 := time.Now()
		conn.SetDeadline(t1.Add(time.Duration(timeout) * time.Millisecond))
		//设置icmp包checksum 校验和
		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
		}
		//time.Now()转换为毫秒
		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 != math.MaxInt32 {
		fmt.Printf("往返行程的估计时间(以毫秒为单位):\n")
		fmt.Printf("    最短 = %dms,最长 = %dms,平均 = %dms\n", minTime, maxTime, totalTime/SuccessTimes)
	}

效果

将编写好的代码编译为yi-ping:

在这里插入图片描述

尝试ping baidu.com:

因为涉及到网络通信,所以需要以sudo管理员方式运行

在这里插入图片描述

尝试设置参数,查看是否生效:

-n参数,设置ping的次数:

在这里插入图片描述

全部代码

Github: https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ping_demo

package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"math"
	"net"
	"os"
	"time"
)

var (
	timeout int64 //ping请求超时时间
	num     int   //发送请求包的个数
	size    int64 //每个包的大小
	stop    bool  //是否一直ping
	icmp    ICMP
)

// ICMP ICMP包头
type ICMP struct {
	Type        uint8
	Code        uint8
	Checksum    uint16
	Identifier  uint16
	SequenceNum uint16
}

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请求 ping请求
	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)
	//写入icmp包头及空数据
	buffer.Write(data)
	data = buffer.Bytes()

	var SuccessTimes int // 成功次数
	var FailTimes int    // 失败次数
	var minTime = 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)
		//设置checksum
		icmp.Checksum = CheckSum(data)
		data[2] = byte(icmp.Checksum >> 8)
		data[3] = byte(icmp.Checksum)

		// 开始时间
		t1 := time.Now()
		conn.SetDeadline(t1.Add(time.Duration(timeout) * time.Millisecond))
		//设置icmp包checksum 校验和
		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
		}
		//time.Now()转换为毫秒
		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 != math.MaxInt32 {
		fmt.Printf("往返行程的估计时间(以毫秒为单位):\n")
		fmt.Printf("    最短 = %dms,最长 = %dms,平均 = %dms\n", minTime, maxTime, totalTime/SuccessTimes)
	}
}

func ParseArgs() {
	flag.Int64Var(&timeout, "w", 10000, "超时时间(毫秒)")
	flag.IntVar(&num, "n", 4, "发送的回显请求数")
	flag.Int64Var(&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。
`)
	}
}

// CheckSum 计算校验和
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 = uint32(uint16(sum>>16) + uint16(sum))
	sum = uint32(uint16(sum>>16) + uint16(sum))
	return uint16(^sum)
}

参考:https://developer.aliyun.com/article/654267

以上就是golang实现ping命令的完整代码的详细内容,更多关于golang实现ping命令的资料请关注脚本之家其它相关文章!

相关文章

  • Go中函数的使用细节与注意事项详解

    Go中函数的使用细节与注意事项详解

    在Go语言中函数可是一等的(first-class)公民,函数类型也是一等的数据类型,下面这篇文章主要给大家介绍了关于Go中函数的使用细节与注意事项的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • 如何使用golang实现traceroute

    如何使用golang实现traceroute

    这篇文章主要介绍了如何使用golang实现traceroute,该工具在linux环境下的命令是traceroute或者tracepath,在windows下命令是tracert,本文给大家详细讲解需要的朋友可以参考下
    2023-04-04
  • Golang中匿名组合实现伪继承的方法

    Golang中匿名组合实现伪继承的方法

    这篇文章主要介绍了Golang中匿名组合实现伪继承的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • Golang初始化MySQL数据库方法浅析

    Golang初始化MySQL数据库方法浅析

    这篇文章主要介绍了Golang初始化MySQL数据库的方法,数据库的建立第一步即要初始化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-05-05
  • Golang的md5 hash计算操作

    Golang的md5 hash计算操作

    这篇文章主要介绍了Golang的md5 hash计算操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • go语言中切片Slice与数组Array对比以及panic: runtime error: index out of range问题解决

    go语言中切片Slice与数组Array对比以及panic: runtime error: index out 

    go语言中数组与其他语言有在显著的不同,包括其不能够进行添加,以及值拷贝的特性,下面这篇文章主要给大家介绍了关于go语言中切片Slice与数组Array对比以及panic: runtime error: index out of range问题解决的相关资料,需要的朋友可以参考下
    2022-07-07
  • Golang单元测试中的技巧分享

    Golang单元测试中的技巧分享

    这篇文章主要为大家详细介绍了Golang进行单元测试时的一些技巧和科技,文中的示例代码讲解详细,具有一定的参考价值,感兴趣的小伙伴可以了解一下
    2023-03-03
  • 浅谈golang fasthttp踩坑经验

    浅谈golang fasthttp踩坑经验

    本文主要介绍了golang fasthttp踩坑经验,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • go 判断两个 slice/struct/map 是否相等的实例

    go 判断两个 slice/struct/map 是否相等的实例

    这篇文章主要介绍了go 判断两个 slice/struct/map 是否相等的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang教程之不可重入函数的实现方法

    Golang教程之不可重入函数的实现方法

    这篇文章主要给大家介绍了关于Golang教程之不可重入函数的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09

最新评论