golang實現ping命令的完整代碼
golang實現ping命令(附:完整代碼)
代碼鏈接:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ping_demo
1 ping原理:ICMP協議(Type+Code+checksum+ID+sequence)
ping是使用ICMP協議。ICMP協議的組成:
Type(8bits) + Code(8bits) + 校驗碼(checksum,8bits) + ID(16bits) + 序號(sequence,16bits) + 數據

這些組成部分的含義:
1)Type: ICMP的類型,標識生成的錯誤報文
2)Code: 進一步劃分ICMP的類型,該字段用來查找產生的原因;例如,ICMP的目標不可達類型可以把這個位設為1至15等來表示不同的意思。

總結:ICMP協議的組成:Type(8bits) + Code(8bits) + 校驗碼(checksum,8bits) + ID(16bits) + 序號(sequence,16bits) + 數據
這些組成部分的含義:
1)Type ICMP的類型,標識生成的錯誤報文
2)Code 進一步劃分ICMP的類型,該字段用來查找產生的原因;例如,ICMP的目標不可達類型可以把這個位設為1至15等來表示不同的意思。
3)CheckSum 校驗碼部分,這個字段包含從ICMP報頭和數據部分計算得來的,用于檢查錯誤的,其中此校驗碼字段的值視為0.
4)ID 這個字段包含了ID值,在Echo Reply類型的消息中要返回這個字段。
5)Sequence 這個字段包含一個序號
ping命令的實現是使用ICMP中類型值為8(reply)和0(request)
1.1 Type 類型值,標識ICMP分組類型
1.2 Code 代碼值,標識ICMP分組類型的某一種具體分組
1.3 Checksum 校驗和,用于檢驗數據包是否完整或是否被修改
1.4 Identifier 標識符,標識本進程。當同時與多個目的通信時,通過本字段來區(qū)分
1.5 Sequence Number 序列號,標識本地到目的的數據包序號,一般從序號1開始
常見ICMP類型
- 回復應答(ICMP類型0):ping命令用到該類型的數據包以測試TCP/IP連接;
- 目標不可達 (ICMP類型3):用以知識目標網絡、主機或者端口不可達;
- 源站抑制 (ICMP類型4):當路由器處理IP數據的速度不夠快時,會發(fā)送此類的消息。它的意思是讓發(fā)送方降低發(fā)送數據的速率。Microsoft Windows NT或Windows 2000主機可以通過降低數據傳輸率來響應這種類型的消息;
- 重定向消息 (ICMP類型5):用于將主機重新定向到一個不同的網絡路徑,該消息告訴路由器對于該數據包可以忽略它內部的路由表項;
- 回復請求(ICMP類型8):ping命令用該類型的數據包測試TCP/IP連接;
- 路由器通告 (ICMP類型9):以隨機的時間間隔發(fā)送該數據包以響應
- 路由器請求 (ICMP類型10):路由器發(fā)送該數據包來請求路由器通告的更新;
- 超時 (ICMP類型11):指示數據包由于通過了太多的網段,其的生存時間(TTL)已經過期,Tracert命令用此消息來測試本地和遠程主機之間的多個路由器;
- 參數問題 (ICMP類型12):用以指示處理IP數據包頭時出錯。
2 實現
2.1 定義ICMP結構體
type ICMP struct {
Type uint8
Code uint8
Checksum uint16
Identifier uint16
SequenceNum uint16
}
2.2 計算校驗和
官網解釋: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報文(首部+數據部分)
- 將ICMP報文中的校驗和字段置為0。
- 將ICMP協議報文中的每兩個字節(jié)(16位,需要注意大小端問題)兩兩相加,得到一個累加和。若報文長度為奇數,則最后一個字節(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 命令行參數
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, "等待每次回復的超時時間(毫秒)")
flag.IntVar(&num, "n", 4, "要發(fā)送的請求數")
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 指定的主機,直到停止。
若要查看統計信息并繼續(xù)操作,請鍵入 Ctrl+Break;
若要停止,請鍵入 Ctrl+C。
-a 將地址解析為主機名。
-n count 要發(fā)送的回顯請求數。
-l size 發(fā)送緩沖區(qū)大小。
-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 路由隔離艙標識符。
-p Ping Hyper-V 網絡虛擬化提供程序地址。
-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é)的數據:\n", desIp, size)
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式寫入(低位對應高地址)
data := make([]byte, size)
//寫入icmp包頭及空數據
buffer.Write(data)
data = buffer.Bytes()
var SuccessTimes int // 成功次數
var FailTimes int // 失敗次數
var minTime = 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)
//設置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))
//設置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()轉換為毫秒
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 統計信息:\n", desIp)
fmt.Printf(" 數據包: 已發(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:
因為涉及到網絡通信,所以需要以sudo管理員方式運行

嘗試設置參數,查看是否生效:
-n參數,設置ping的次數:

全部代碼
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ā)送請求包的個數
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é)的數據:\n", desIp, size)
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式寫入(低位對應高地址)
data := make([]byte, size)
//寫入icmp包頭及空數據
buffer.Write(data)
data = buffer.Bytes()
var SuccessTimes int // 成功次數
var FailTimes int // 失敗次數
var minTime = 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)
//設置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))
//設置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()轉換為毫秒
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 統計信息:\n", desIp)
fmt.Printf(" 數據包: 已發(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ā)送的回顯請求數")
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 指定的主機,直到停止。
若要查看統計信息并繼續(xù)操作,請鍵入 Ctrl+Break;
若要停止,請鍵入 Ctrl+C。
-a 將地址解析為主機名。
-n count 要發(fā)送的回顯請求數。
-l size 發(fā)送緩沖區(qū)大小。
-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 路由隔離艙標識符。
-p Ping Hyper-V 網絡虛擬化提供程序地址。
-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實現ping命令的完整代碼的詳細內容,更多關于golang實現ping命令的資料請關注腳本之家其它相關文章!
相關文章
詳解如何使用go-acme/lego實現自動簽發(fā)證書
這篇文章主要為大家詳細介紹了如何使用?go-acme/lego?的客戶端或庫完成證書的自動簽發(fā),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-03-03

