package goping import ( "bytes" "encoding/binary" "fmt" "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) for { /* 向目标地址发送二进制报文包 如果发送失败就丢包 ++ */ if _, err := conn.Write(buffer.Bytes()); err != nil { dropPack++ time.Sleep(time.Second) 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 } 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 } if dur > min_lan { min_lan = dur } select { case s := <-c: signal.Notify(c, os.Interrupt, os.Kill) fmt.Println("Got signal:", s) foot(raddr.String(), icmp_seq, dropPack,min_lan,avg_lan,max_lan,ret_list) os.Exit(0) default: fmt.Printf("%d bytes from %s (%s): icmp_seq=%d ttl=53 time=%.3f ms\n", len, raddr.String(), raddr.String(), icmp_seq, dur) //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{}) { fmt.Printf("--- %s ping statistics ---\n", address) fmt.Printf("%d packets transmitted, %f lost, %.2f%% packet loss, time 1000ms\n", icmp_seq, dropPack, 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", min_lan, avg_lan, max_lan) }