Go語言實現ping命令
ping是使用ICMP協議
ICMP協議的組成:Type(8bits) + Code(8bits) + 校驗碼(checksum,8bits) + ID(16bits) + 序號(sequence,16bits) + 資料
這些組成部分的含義:
1)TypeICMP的型別,標識生成的錯誤報文
2)Code 進一步劃分ICMP的型別,該欄位用來查詢產生的原因;例如,ICMP的目標不可達型別可以把這個位設為1至15等來表示不同的意思。
3)CheckSum 校驗碼部分,這個欄位包含從ICMP報頭和資料部分計算得來的,用於檢查錯誤的,其中此校驗碼欄位的值視為0.
4)ID 這個欄位包含了ID值,在Echo Reply型別的訊息中要返回這個欄位。
5)Sequence 這個欄位包含一個序號
ping命令的實現是使用ICMP中型別值為8(reply)和0(request)
現在開始編寫程式碼:
一、解析引數
var ( icmpICMP laddr = net.IPAddr{IP: net.ParseIP("ip")} //raddr, _ = net.ResolveIPAddr("ip", os.Args[1]) numint timeout int64 sizeint stopbool ) 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 { Typeuint8 Codeuint8 Checksumuint16 Identifieruint16 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下載連結:ofollow,noindex">https://github.com/laijinhang/ping
package main import ( "bytes" "encoding/binary" "flag" "fmt" "log" "net" "os" "time" "math" ) type ICMP struct { Typeuint8 Codeuint8 Checksumuint16 Identifieruint16 SequenceNum uint16 } var ( icmpICMP laddr = net.IPAddr{IP: net.ParseIP("ip")} numint timeout int64 sizeint stopbool ) 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]) } // 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, "要傳送的請求數") 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 選項: -tPing 指定的主機,直到停止。 若要檢視統計資訊並繼續操作,請鍵入 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 路由隔離艙識別符號。 -pPing Hyper-V 網路虛擬化提供程式地址。 -4強制使用 IPv4。 -6強制使用 IPv6。 `) } }
參考文章:
1)https://blog.csdn.net/zhj082/article/details/80518322
2)https://blog.csdn.net/simplelovecs/article/details/51146960