美文网首页GolangGo知识库Go
Go语言实现ping命令

Go语言实现ping命令

作者: ljh123 | 来源:发表于2018-09-27 19:32 被阅读6次

    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])
        }
            sum = uint16(sum >> 16) + uint16(sum)
            sum = uint16(sum >> 16) + uint16(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

    相关文章

      网友评论

      • fnoop:checkSum()函数的最后的`sum = uint16(sum>>16) + uint16(sum)`部分会报错,具体内容为`cannot use uint16(sum >> 16) + uint16(sum) (type uint16) as type uint32 in assignment`
        ljh123:@fnoop 确实有这错误,现在改了
        fnoop:@fnoop 改为unit32后正常的
        fnoop:unit16的计算结果不能赋给sum,sum定义为unit32的

      本文标题:Go语言实现ping命令

      本文链接:https://www.haomeiwen.com/subject/iqwsoftx.html