Go語言實現(xiàn)ping命令

ping是使用ICMP協(xié)議

ICMP協(xié)議的組成:Type(8bits) + Code(8bits) + 校驗碼(checksum,8bits) + ID(16bits) + 序號(sequence,16bits) + 數(shù)據(jù)

這些組成部分的含義:
1)Type ICMP的類型,標識生成的錯誤報文
2)Code 進一步劃分ICMP的類型,該字段用來查找產生的原因;例如,ICMP的目標不可達類型可以把這個位設為1至15等來表示不同的意思。
3)CheckSum 校驗碼部分,這個字段包含從ICMP報頭和數(shù)據(jù)部分計算得來的,用于檢查錯誤的,其中此校驗碼字段的值視為0.
4)ID 這個字段包含了ID值,在Echo Reply類型的消息中要返回這個字段。
5)Sequence 這個字段包含一個序號

ping命令的實現(xiàn)是使用ICMP中類型值為8(reply)和0(request)

現(xiàn)在開始編寫代碼:
一、解析參數(shù)

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, "要發(fā)送的請求數(shù)")
    flag.IntVar(&size, "l", 32, "要發(fā)送緩沖區(qū)大小")
    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頭部和數(shù)據(jù)內容,ICMP校驗和計算過程如下:
1)將ICMP頭部內容中的校驗內容(Checksum)的值設為0
2)將拼接好(Type+Code+Checksum+Id+Seq+傳輸Data)的ICMP包按Type開始每兩個字節(jié)一組(其中Checksum的兩個字節(jié)都看成0),進行加和處理,如果字節(jié)個數(shù)為奇數(shù)個,則直接加上這個字節(jié)內容。說明:這個加和過程的內容放在一個4字節(jié)上,如果溢出4字節(jié),則將溢出的直接拋棄
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)
}

五、發(fā)送ICMP包
六、打印結果

完整實現(xiàn)代碼:
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 字節(jié)的數(shù)據(jù):\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    // 成功次數(shù)
    var FailTimes int       // 失敗次數(shù)
    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 的回復: 字節(jié)=%d 時間=%dms TTL=%d\n", desIp, len(buf[28:n]), et, buf[8])
        SuccessTimes++
        time.Sleep(1 * time.Second)
    }
    fmt.Printf("\n%s 的 Ping 統(tǒng)計信息:\n", desIp)
    fmt.Printf("    數(shù)據(jù)包: 已發(fā)送 = %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, "要發(fā)送的請求數(shù)")
    flag.IntVar(&size, "l", 32, "要發(fā)送緩沖區(qū)大小")
    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 指定的主機,直到停止。
                   若要查看統(tǒng)計信息并繼續(xù)操作,請鍵入 Ctrl+Break;
                   若要停止,請鍵入 Ctrl+C。
    -a             將地址解析為主機名。
    -n count       要發(fā)送的回顯請求數(shù)。
    -l size        發(fā)送緩沖區(qū)大小。
    -f             在數(shù)據(jù)包中設置“不分段”標記(僅適用于 IPv4)。
    -i TTL         生存時間。
    -v TOS         服務類型(僅適用于 IPv4。該設置已被棄用,
                   對 IP 標頭中的服務類型字段沒有任何
                   影響)。
    -r count       記錄計數(shù)躍點的路由(僅適用于 IPv4)。
    -s count       計數(shù)躍點的時間戳(僅適用于 IPv4)。
    -j host-list   與主機列表一起使用的松散源路由(僅適用于 IPv4)。
    -k host-list    與主機列表一起使用的嚴格源路由(僅適用于 IPv4)。
    -w timeout     等待每次回復的超時時間(毫秒)。
    -R             同樣使用路由標頭測試反向路由(僅適用于 IPv6)。
                   根據(jù) RFC 5095,已棄用此路由標頭。
                   如果使用此標頭,某些系統(tǒng)可能丟棄
                   回顯請求。
    -S srcaddr     要使用的源地址。
    -c compartment 路由隔離艙標識符。
    -p             Ping Hyper-V 網(wǎng)絡虛擬化提供程序地址。
    -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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,862評論 2 59
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,076評論 25 708
  • 簡介 用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 5,987評論 1 13
  • 有點累,不知道為何?一個人到底是如何做到明明很在乎卻假裝不在乎,明明很怕失去,卻又不堅定地付出一切,卻還有所保留,...
    87596a809b1f閱讀 257評論 0 0
  • 游戲有的時候是精神和靈魂的寄托 剛接觸這款游戲是因為一個人,很喜歡他,但是和他相處又很拘謹,所以我玩了這款游戲,只...
    懶阿珍閱讀 298評論 0 0