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的類型荷辕,該字段用來查找產(chǎn)生的原因;例如件豌,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結(jié)構(gòu)體

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ù)內(nèi)容富岳,ICMP校驗和計算過程如下:
1)將ICMP頭部內(nèi)容中的校驗內(nèi)容(Checksum)的值設為0
2)將拼接好(Type+Code+Checksum+Id+Seq+傳輸Data)的ICMP包按Type開始每兩個字節(jié)一組(其中Checksum的兩個字節(jié)都看成0)蛔糯,進行加和處理,如果字節(jié)個數(shù)為奇數(shù)個窖式,則直接加上這個字節(jié)內(nèi)容蚁飒。說明:這個加和過程的內(nèi)容放在一個4字節(jié)上,如果溢出4字節(jié)萝喘,則將溢出的直接拋棄
3)將高16位與低16位內(nèi)容加和淮逻,直到高16為0
4)將步驟三得出的結(jié)果取反,得到的結(jié)果就是ICMP校驗和的值

驗證校驗和的方式也是一樣阁簸,驗證時先計算驗證和爬早,然后和驗證和中內(nèi)容進行比較是否一樣

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包
六启妹、打印結(jié)果

完整實現(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位匈棘,得到的結(jié)果進行重復以該方式進行計算,直到高16位為0
    /*
        sum的最大情況是:ffffffff
        第一次高16位+低16位:ffff + ffff = 1fffe
        第二次高16位+低16位:0001 + fffe = ffff
        即推出一個結(jié)論析命,只要第一次高16位+低16位的結(jié)果主卫,再進行之前的計算結(jié)果用到高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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市海诲,隨后出現(xiàn)的幾起案子繁莹,更是在濱河造成了極大的恐慌,老刑警劉巖特幔,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咨演,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚯斯,警方通過查閱死者的電腦和手機薄风,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拍嵌,“玉大人遭赂,你說我怎么就攤上這事『崃荆” “怎么了撇他?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狈蚤。 經(jīng)常有香客問我困肩,道長,這世上最難降的妖魔是什么脆侮? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任锌畸,我火速辦了婚禮,結(jié)果婚禮上靖避,老公的妹妹穿的比我還像新娘潭枣。我一直安慰自己,他們只是感情好幻捏,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布盆犁。 她就那樣靜靜地躺著,像睡著了一般粘咖。 火紅的嫁衣襯著肌膚如雪蚣抗。 梳的紋絲不亂的頭發(fā)上侈百,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天瓮下,我揣著相機與錄音,去河邊找鬼钝域。 笑死讽坏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的例证。 我是一名探鬼主播路呜,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胀葱?” 一聲冷哼從身側(cè)響起漠秋,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抵屿,沒想到半個月后庆锦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡轧葛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年搂抒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尿扯。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡求晶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衷笋,到底是詐尸還是另有隱情芳杏,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布辟宗,位于F島的核電站蚜锨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏慢蜓。R本人自食惡果不足惜亚再,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晨抡。 院中可真熱鬧氛悬,春花似錦、人聲如沸耘柱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽调煎。三九已至镜遣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間士袄,已是汗流浹背悲关。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留娄柳,地道東北人寓辱。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像赤拒,于是被迫代替她去往敵國和親秫筏。 傳聞我的和親對象是個殘疾皇子诱鞠,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

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