golang實(shí)現(xiàn)ping命令的完整代碼
golang實(shí)現(xiàn)ping命令(附:完整代碼)
代碼鏈接:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ping_demo
1 ping原理:ICMP協(xié)議(Type+Code+checksum+ID+sequence)
ping是使用ICMP協(xié)議。ICMP協(xié)議的組成:
Type(8bits) + Code(8bits) + 校驗(yàn)碼(checksum,8bits) + ID(16bits) + 序號(sequence,16bits) + 數(shù)據(jù)
這些組成部分的含義:
1)Type: ICMP的類型,標(biāo)識(shí)生成的錯(cuò)誤報(bào)文
2)Code: 進(jìn)一步劃分ICMP的類型,該字段用來查找產(chǎn)生的原因;例如,ICMP的目標(biāo)不可達(dá)類型可以把這個(gè)位設(shè)為1至15等來表示不同的意思。
總結(jié):ICMP協(xié)議的組成:Type(8bits) + Code(8bits) + 校驗(yàn)碼(checksum,8bits) + ID(16bits) + 序號(sequence,16bits) + 數(shù)據(jù)
這些組成部分的含義:
1)Type ICMP的類型,標(biāo)識(shí)生成的錯(cuò)誤報(bào)文
2)Code 進(jìn)一步劃分ICMP的類型,該字段用來查找產(chǎn)生的原因;例如,ICMP的目標(biāo)不可達(dá)類型可以把這個(gè)位設(shè)為1至15等來表示不同的意思。
3)CheckSum 校驗(yàn)碼部分,這個(gè)字段包含從ICMP報(bào)頭和數(shù)據(jù)部分計(jì)算得來的,用于檢查錯(cuò)誤的,其中此校驗(yàn)碼字段的值視為0.
4)ID 這個(gè)字段包含了ID值,在Echo Reply類型的消息中要返回這個(gè)字段。
5)Sequence 這個(gè)字段包含一個(gè)序號
ping命令的實(shí)現(xiàn)是使用ICMP中類型值為8(reply)和0(request)
1.1 Type 類型值,標(biāo)識(shí)ICMP分組類型
1.2 Code 代碼值,標(biāo)識(shí)ICMP分組類型的某一種具體分組
1.3 Checksum 校驗(yàn)和,用于檢驗(yàn)數(shù)據(jù)包是否完整或是否被修改
1.4 Identifier 標(biāo)識(shí)符,標(biāo)識(shí)本進(jìn)程。當(dāng)同時(shí)與多個(gè)目的通信時(shí),通過本字段來區(qū)分
1.5 Sequence Number 序列號,標(biāo)識(shí)本地到目的的數(shù)據(jù)包序號,一般從序號1開始
常見ICMP類型
- 回復(fù)應(yīng)答(ICMP類型0):ping命令用到該類型的數(shù)據(jù)包以測試TCP/IP連接;
- 目標(biāo)不可達(dá) (ICMP類型3):用以知識(shí)目標(biāo)網(wǎng)絡(luò)、主機(jī)或者端口不可達(dá);
- 源站抑制 (ICMP類型4):當(dāng)路由器處理IP數(shù)據(jù)的速度不夠快時(shí),會(huì)發(fā)送此類的消息。它的意思是讓發(fā)送方降低發(fā)送數(shù)據(jù)的速率。Microsoft Windows NT或Windows 2000主機(jī)可以通過降低數(shù)據(jù)傳輸率來響應(yīng)這種類型的消息;
- 重定向消息 (ICMP類型5):用于將主機(jī)重新定向到一個(gè)不同的網(wǎng)絡(luò)路徑,該消息告訴路由器對于該數(shù)據(jù)包可以忽略它內(nèi)部的路由表項(xiàng);
- 回復(fù)請求(ICMP類型8):ping命令用該類型的數(shù)據(jù)包測試TCP/IP連接;
- 路由器通告 (ICMP類型9):以隨機(jī)的時(shí)間間隔發(fā)送該數(shù)據(jù)包以響應(yīng)
- 路由器請求 (ICMP類型10):路由器發(fā)送該數(shù)據(jù)包來請求路由器通告的更新;
- 超時(shí) (ICMP類型11):指示數(shù)據(jù)包由于通過了太多的網(wǎng)段,其的生存時(shí)間(TTL)已經(jīng)過期,Tracert命令用此消息來測試本地和遠(yuǎn)程主機(jī)之間的多個(gè)路由器;
- 參數(shù)問題 (ICMP類型12):用以指示處理IP數(shù)據(jù)包頭時(shí)出錯(cuò)。
2 實(shí)現(xiàn)
2.1 定義ICMP結(jié)構(gòu)體
type ICMP struct { Type uint8 Code uint8 Checksum uint16 Identifier uint16 SequenceNum uint16 }
2.2 計(jì)算校驗(yàn)和
官網(wǎng)解釋: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報(bào)文(首部+數(shù)據(jù)部分)
- 將ICMP報(bào)文中的校驗(yàn)和字段置為0。
- 將ICMP協(xié)議報(bào)文中的每兩個(gè)字節(jié)(16位,需要注意大小端問題)兩兩相加,得到一個(gè)累加和。若報(bào)文長度為奇數(shù),則最后一個(gè)字節(jié)(8-bit)作為高8位,再用0填充一個(gè)字節(jié)(低8-bit)擴(kuò)展到16-bit,之后再和前面的累加和繼續(xù)相加得到一個(gè)新的累加和。
- (若有溢出)將累加和的高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 命令行參數(shù)
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, "等待每次回復(fù)的超時(shí)時(shí)間(毫秒)") flag.IntVar(&num, "n", 4, "要發(fā)送的請求數(shù)") flag.IntVar(&size, "l", 32, "要發(fā)送緩沖區(qū)大小") flag.BoolVar(&stop, "t", false, "Ping 指定的主機(jī),直到停止") 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 選項(xiàng): -t Ping 指定的主機(jī),直到停止。 若要查看統(tǒng)計(jì)信息并繼續(xù)操作,請鍵入 Ctrl+Break; 若要停止,請鍵入 Ctrl+C。 -a 將地址解析為主機(jī)名。 -n count 要發(fā)送的回顯請求數(shù)。 -l size 發(fā)送緩沖區(qū)大小。 -f 在數(shù)據(jù)包中設(shè)置“不分段”標(biāo)記(僅適用于 IPv4)。 -i TTL 生存時(shí)間。 -v TOS 服務(wù)類型(僅適用于 IPv4。該設(shè)置已被棄用, 對 IP 標(biāo)頭中的服務(wù)類型字段沒有任何 影響)。 -r count 記錄計(jì)數(shù)躍點(diǎn)的路由(僅適用于 IPv4)。 -s count 計(jì)數(shù)躍點(diǎn)的時(shí)間戳(僅適用于 IPv4)。 -j host-list 與主機(jī)列表一起使用的松散源路由(僅適用于 IPv4)。 -k host-list 與主機(jī)列表一起使用的嚴(yán)格源路由(僅適用于 IPv4)。 -w timeout 等待每次回復(fù)的超時(shí)時(shí)間(毫秒)。 -R 同樣使用路由標(biāo)頭測試反向路由(僅適用于 IPv6)。 根據(jù) RFC 5095,已棄用此路由標(biāo)頭。 如果使用此標(biāo)頭,某些系統(tǒng)可能丟棄 回顯請求。 -S srcaddr 要使用的源地址。 -c compartment 路由隔離艙標(biāo)識(shí)符。 -p Ping Hyper-V 網(wǎng)絡(luò)虛擬化提供程序地址。 -4 強(qiáng)制使用 IPv4。 -6 強(qiáng)制使用 IPv6。 `) } }
2.4 發(fā)送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 字節(jié)的數(shù)據(jù):\n", desIp, size) var buffer bytes.Buffer binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式寫入(低位對應(yīng)高地址) data := make([]byte, size) //寫入icmp包頭及空數(shù)據(jù) buffer.Write(data) data = buffer.Bytes() var SuccessTimes int // 成功次數(shù) var FailTimes int // 失敗次數(shù) var minTime = math.MaxInt32 var maxTime int var totalTime int for i := 0; i < num; i++ { icmp.SequenceNum = uint16(1) // 檢驗(yàn)和設(shè)為0 data[2] = byte(0) data[3] = byte(0) data[6] = byte(icmp.SequenceNum >> 8) data[7] = byte(icmp.SequenceNum) //設(shè)置checksum icmp.Checksum = CheckSum(data) data[2] = byte(icmp.Checksum >> 8) data[3] = byte(icmp.Checksum) // 開始時(shí)間 t1 := time.Now() conn.SetDeadline(t1.Add(time.Duration(timeout) * time.Millisecond)) //設(shè)置icmp包c(diǎn)hecksum 校驗(yàn)和 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("請求超時(shí)。") FailTimes++ continue } //time.Now()轉(zhuǎn)換為毫秒 et := int(time.Since(t1) / 1000000) if minTime > et { minTime = et } if maxTime < et { maxTime = et } totalTime += et fmt.Printf("來自 %s 的回復(fù): 字節(jié)=%d 時(shí)間=%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)計(jì)信息:\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 != math.MaxInt32 { fmt.Printf("往返行程的估計(jì)時(shí)間(以毫秒為單位):\n") fmt.Printf(" 最短 = %dms,最長 = %dms,平均 = %dms\n", minTime, maxTime, totalTime/SuccessTimes) }
效果
將編寫好的代碼編譯為yi-ping:
嘗試ping baidu.com:
因?yàn)樯婕暗骄W(wǎng)絡(luò)通信,所以需要以sudo管理員方式運(yùn)行
嘗試設(shè)置參數(shù),查看是否生效:
-n參數(shù),設(shè)置ping的次數(shù):
全部代碼
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請求超時(shí)時(shí)間 num int //發(fā)送請求包的個(gè)數(shù) size int64 //每個(gè)包的大小 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 字節(jié)的數(shù)據(jù):\n", desIp, size) var buffer bytes.Buffer binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式寫入(低位對應(yīng)高地址) data := make([]byte, size) //寫入icmp包頭及空數(shù)據(jù) buffer.Write(data) data = buffer.Bytes() var SuccessTimes int // 成功次數(shù) var FailTimes int // 失敗次數(shù) var minTime = math.MaxInt32 var maxTime int var totalTime int for i := 0; i < num; i++ { icmp.SequenceNum = uint16(1) // 檢驗(yàn)和設(shè)為0 data[2] = byte(0) data[3] = byte(0) data[6] = byte(icmp.SequenceNum >> 8) data[7] = byte(icmp.SequenceNum) //設(shè)置checksum icmp.Checksum = CheckSum(data) data[2] = byte(icmp.Checksum >> 8) data[3] = byte(icmp.Checksum) // 開始時(shí)間 t1 := time.Now() conn.SetDeadline(t1.Add(time.Duration(timeout) * time.Millisecond)) //設(shè)置icmp包c(diǎn)hecksum 校驗(yàn)和 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("請求超時(shí)。") FailTimes++ continue } //time.Now()轉(zhuǎn)換為毫秒 et := int(time.Since(t1) / 1000000) if minTime > et { minTime = et } if maxTime < et { maxTime = et } totalTime += et fmt.Printf("來自 %s 的回復(fù): 字節(jié)=%d 時(shí)間=%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)計(jì)信息:\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 != math.MaxInt32 { fmt.Printf("往返行程的估計(jì)時(shí)間(以毫秒為單位):\n") fmt.Printf(" 最短 = %dms,最長 = %dms,平均 = %dms\n", minTime, maxTime, totalTime/SuccessTimes) } } func ParseArgs() { flag.Int64Var(&timeout, "w", 10000, "超時(shí)時(shí)間(毫秒)") flag.IntVar(&num, "n", 4, "發(fā)送的回顯請求數(shù)") flag.Int64Var(&size, "l", 32, "發(fā)送緩沖區(qū)大小") flag.BoolVar(&stop, "t", false, "Ping 指定的主機(jī),直到停止") 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 選項(xiàng): -t Ping 指定的主機(jī),直到停止。 若要查看統(tǒng)計(jì)信息并繼續(xù)操作,請鍵入 Ctrl+Break; 若要停止,請鍵入 Ctrl+C。 -a 將地址解析為主機(jī)名。 -n count 要發(fā)送的回顯請求數(shù)。 -l size 發(fā)送緩沖區(qū)大小。 -f 在數(shù)據(jù)包中設(shè)置“不分段”標(biāo)記(僅適用于 IPv4)。 -i TTL 生存時(shí)間。 -v TOS 服務(wù)類型(僅適用于 IPv4。該設(shè)置已被棄用, 對 IP 標(biāo)頭中的服務(wù)類型字段沒有任何 影響)。 -r count 記錄計(jì)數(shù)躍點(diǎn)的路由(僅適用于 IPv4)。 -s count 計(jì)數(shù)躍點(diǎn)的時(shí)間戳(僅適用于 IPv4)。 -j host-list 與主機(jī)列表一起使用的松散源路由(僅適用于 IPv4)。 -k host-list 與主機(jī)列表一起使用的嚴(yán)格源路由(僅適用于 IPv4)。 -w timeout 等待每次回復(fù)的超時(shí)時(shí)間(毫秒)。 -R 同樣使用路由標(biāo)頭測試反向路由(僅適用于 IPv6)。 根據(jù) RFC 5095,已棄用此路由標(biāo)頭。 如果使用此標(biāo)頭,某些系統(tǒng)可能丟棄 回顯請求。 -S srcaddr 要使用的源地址。 -c compartment 路由隔離艙標(biāo)識(shí)符。 -p Ping Hyper-V 網(wǎng)絡(luò)虛擬化提供程序地址。 -4 強(qiáng)制使用 IPv4。 -6 強(qiáng)制使用 IPv6。 `) } } // CheckSum 計(jì)算校驗(yàn)和 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實(shí)現(xiàn)ping命令的完整代碼的詳細(xì)內(nèi)容,更多關(guān)于golang實(shí)現(xiàn)ping命令的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang time包做時(shí)間轉(zhuǎn)換操作
這篇文章主要介紹了golang time包做時(shí)間轉(zhuǎn)換操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12golang網(wǎng)絡(luò)通信超時(shí)設(shè)置方式
這篇文章主要介紹了golang網(wǎng)絡(luò)通信超時(shí)設(shè)置方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12詳解Go語言如何使用標(biāo)準(zhǔn)庫sort對切片進(jìn)行排序
Sort?標(biāo)準(zhǔn)庫提供了對基本數(shù)據(jù)類型的切片和自定義類型的切片進(jìn)行排序的函數(shù)。今天主要分享的內(nèi)容是使用?Go?標(biāo)準(zhǔn)庫?sort?對切片進(jìn)行排序,感興趣的可以了解一下2022-12-12Golang中List的實(shí)現(xiàn)方法示例詳解
最近決定復(fù)習(xí)下Go,所以下面這篇文章主要給大家介紹了關(guān)于Golang中List的實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09多階段構(gòu)建優(yōu)化Go?程序Docker鏡像
這篇文章主要為大家介紹了多階段構(gòu)建優(yōu)化Go?程序Docker鏡像,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08詳解如何使用go-acme/lego實(shí)現(xiàn)自動(dòng)簽發(fā)證書
這篇文章主要為大家詳細(xì)介紹了如何使用?go-acme/lego?的客戶端或庫完成證書的自動(dòng)簽發(fā),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03