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