You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
6.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package goping
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/fatih/color"
"net"
"os"
"os/signal"
"time"
)
// https://www.cnblogs.com/wlw-x/p/14169607.html
// https://www.cnblogs.com/wlw-x/p/14169607.html
//类型1个字节。8表示回显请求报文0表示回显响应报文。
//代码1个字节。回显请求报文、回显响应报文 时均为0。
//校验和2个字节。非重点略过。
//标识符2个字节。发送ICMP报文的客户端进程的id服务端会回传给客户端。因为同一个客户端可能同时运行多个ping程序这样客户端收到回西显报文可以知道是响应给哪个客户端进程的。
//序列号2个字节。从0开始客户端每次发送新的回显请求时+1。服务端原样会传。
//数据6个字节。客户端记录回显请求的发送时间服务端记录回西显响应的发送时间
// 分析:
//
// ping 命令在执行后显示出被测试系统主机名和相应的IP地址、返回给当前主机的ICMP报文顺序号、ttl生存时间和往返时间rtt(单位是毫秒)
//
// 要想真正了解ping 命令的实现原理首先要了解ping命令所以使用到的TCP/IP协议ICMP(Internet Control Message网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制使他们在遇到时尽可能的包错误报告发送给源发方。ICMP协议是IP层的一个协议但是由于差错报告在发送报文给报文源发方时可能也要经过若干子网因此涉及到路由选择等问题所以ICMP报文需要通过IP协议来发送。ICMP数据包的数据发送前需要进性两级封装
//
// 1. 首先添加ICMP报头形成ICMP报文
// 2. 在添加IP报头形成IP数据报
//
// 由于IP层协议是一种点对点的协议而非端对端的协议提供无连接的数据报服务没有端对端的概念因此很少使用bind() 和 connect() 函数若有使用也只是用于设置IP地址。
// 我们在go中定义 ICMP 的报文格式如下结构
const (
MAX_PG = 2000
)
// 封装 icmp 报头
type ICMP struct {
Type uint8
Code uint8
Checksum uint16
Identifier uint16
SequenceNum uint16
}
var (
originBytes []byte
)
func init() {
originBytes = make([]byte, MAX_PG)
}
func CheckSum(data []byte) (rt uint16) {
var (
sum uint32
length int = len(data)
index int
)
for length > 1 {
sum += uint32(data[index])<<8 + uint32(data[index+1])
index += 2
length -= 2
}
if length > 0 {
sum += uint32(data[index]) << 8
}
rt = uint16(sum) + uint16(sum>>16)
return ^rt
}
func Ping(domain string, PS int) {
var (
icmp ICMP
laddr = net.IPAddr{IP: net.ParseIP("0.0.0.0")} // 得到本机的IP地址结构
raddr, _ = net.ResolveIPAddr("ip", domain) // 解析域名得到 IP 地址结构
max_lan, min_lan, avg_lan float64
)
// 返回一个 ip socket
conn, err := net.DialIP("ip4:icmp", &laddr, raddr)
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
// 初始化 icmp 报文
icmp = ICMP{8, 0, 0, 0, 0}
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp)
//fmt.Println(buffer.Bytes())
binary.Write(&buffer, binary.BigEndian, originBytes[0:PS])
b := buffer.Bytes()
binary.BigEndian.PutUint16(b[2:], CheckSum(b))
//fmt.Println(b)
fmt.Printf("PING %s (%s) %d(%d) bytes of data.\n", domain, raddr.String(), PS, PS+28)
//fmt.Printf("\n正在 Ping %s 具有 %d(%d) 字节的数据:\n", raddr.String(), PS, PS+28)
recv := make([]byte, 1024)
ret_list := []float64{}
dropPack := 0.0 /*统计丢包的次数,用于计算丢包率*/
max_lan = 3000.0
min_lan = 0.0
avg_lan = 0.0
icmp_seq := 1
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
red := color.New(color.FgRed).SprintFunc()
green := color.New(color.FgGreen).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
for {
/*
向目标地址发送二进制报文包
如果发送失败就丢包 ++
*/
if _, err := conn.Write(buffer.Bytes()); err != nil {
fmt.Printf("%s\n", red("timeout"))
dropPack++
time.Sleep(time.Second)
fmt.Println("666666666666666666666666666")
continue
}
// 否则记录当前得时间
t_start := time.Now()
conn.SetReadDeadline((time.Now().Add(time.Second * 3)))
len, err := conn.Read(recv)
/*
查目标地址是否返回失败
如果返回失败则丢包 ++
*/
if err != nil {
dropPack++
time.Sleep(time.Second)
continue
fmt.Println("777777777777777777777777777777777777777777")
}
t_end := time.Now()
dur := float64(t_end.Sub(t_start).Nanoseconds()) / 1e6
ret_list = append(ret_list, dur)
if dur < max_lan {
max_lan = dur
fmt.Println("88888888888888888888888")
}
if dur > min_lan {
min_lan = dur
fmt.Println("999999999999999999999999999999999")
}
fmt.Println("33333333333333333333333333333333333333333333333")
select {
case s := <-c:
fmt.Println("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
foot(raddr.String(), icmp_seq, dropPack, min_lan, avg_lan, max_lan, ret_list)
os.Exit(0)
fmt.Println("Got signal:", s)
default:
fmt.Println("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
if dur <= 50 {
fmt.Printf("%d bytes from %s (%s): icmp_seq=%d ttl=53 time=%.3f %s\n", len, raddr.String(), raddr.String(), icmp_seq, dur, green("ms"))
} else if dur <= 500 {
fmt.Printf("%d bytes from %s (%s): icmp_seq=%d ttl=53 time=%.3f %s\n", len, raddr.String(), raddr.String(), icmp_seq, dur, yellow("ms"))
} else {
fmt.Printf("%d bytes from %s (%s): icmp_seq=%d ttl=53 time=%.3f %s\n", len, raddr.String(), raddr.String(), icmp_seq, dur, red("ms"))
//fmt.Printf("来自 %s 的回复: 大小 = %d byte 时间 = %.3fms\n", raddr.String(), len, dur)
}
icmp_seq++
time.Sleep(time.Second)
}
}
}
func foot(address string, icmp_seq int, dropPack, min_lan, avg_lan, max_lan float64, ret_list []float64) {
red := color.New(color.FgRed).SprintFunc()
green := color.New(color.FgGreen).SprintFunc()
icmp_seq -= 1 //最后一个seq没统计过来
fmt.Printf("\n--- %s ping statistics ---\n", address)
if dropPack >= 1 {
fmt.Printf("%d packets transmitted, %d %s, %.2f%% packet loss, time 1000ms\n", icmp_seq, int(dropPack), red("lost"), dropPack/float64(icmp_seq)*100)
} else {
fmt.Printf("%d packets transmitted, %d %s, %.2f%% packet loss, time 1000ms\n", icmp_seq, int(dropPack), green("lost"), dropPack/float64(icmp_seq)*100)
}
if len(ret_list) == 0 {
avg_lan = 3000.0
} else {
sum := 0.0
for _, n := range ret_list {
sum += n
}
avg_lan = sum / float64(len(ret_list))
}
fmt.Printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f ms\n", min_lan, avg_lan, max_lan)
}