Go语言实现ping命令

简介: ping是使用ICMP协议ICMP协议的组成:Type(8bits) + Code(8bits) + 校验码(checksum,8bits) + ID(16bits) + 序号(sequence,16bits) + 数据这些组成部分的含义:1)Type ICMP的类型,标识生成的错误报文2)Code 进一步划分ICMP的类型,该字段用来查找产生的原因;例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。

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 = 8
icmp.Code = 0
icmp.Checksum = 0
icmp.Identifier = 1
icmp.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 main

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

参考文章:
1)https://blog.csdn.net/zhj082/article/details/80518322
2)https://blog.csdn.net/simplelovecs/article/details/51146960
3)https://blog.csdn.net/gophers/article/details/21481447
4)https://blog.csdn.net/zhj082/article/details/80518322

目录
相关文章
|
5天前
|
运维 网络协议 前端开发
程序员应该掌握的网络命令telnet、ping和curl
本文分享开发中遇到的服务调用问题,通过运维指导使用telnet排查网络连通性,引出常用网络工具的使用场景:telnet测端口、ping测主机、curl模拟接口请求,助力快速定位问题。
|
8月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
8月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
249 0
|
4月前
|
测试技术 程序员 Go
Go语言测试简明指南:深度解读go test命令
总的来说,go test是 Go 语言中一个强而有力的工具,每个 Go 程序员都应该掌握并把它融入到日常的开发和调试过程中。就像是一个眼镜过滤出的太阳,让我们在宽阔的代码海洋中游泳,而不是淹没。用好它,让我们的代码更健壮,让我们的生产力更强效。
229 23
|
4月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
118 10

热门文章

最新文章