Go實現Ping指令


package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"math"
	"net"
	"os"
	"time"
)

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 字節的數據:\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
選項:
    -t             Ping 指定的主機,直到停止。
                   若要查看統計信息並繼續操作,請鍵入 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 路由隔離艙標識符。
    -p             Ping Hyper-V 網絡虛擬化提供程序地址。
    -4             強制使用 IPv4。
    -6             強制使用 IPv6。
`)
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM