commit b4b488a3fa587b32ad432fc4d63734a5d0b56030 Author: sre Date: Thu Jun 16 18:14:21 2022 +0800 pong: ping tcping udping diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a78226 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.idea +go.sum +main.exe +eth.exe +log.txt +logs +*.log +uploads +gin.log \ No newline at end of file diff --git a/dial/dial.go b/dial/dial.go new file mode 100644 index 0000000..2a0b2b8 --- /dev/null +++ b/dial/dial.go @@ -0,0 +1,46 @@ +package dial + +import ( + "fmt" + "net" + "time" +) + +func PortDial(address, port, protocol string, timeout int) { + ip := digIp(address) + if ip == "" { + fmt.Printf("%v ping: %v: Name or service not known\n", protocol, address) + return + } + fmt.Printf("%v ping %v (%v) via port %v (timeout: %vs)...\n", protocol, address, ip, port, timeout) + dialTarget := ip + ":" + port + for { + startTime := time.Now() + conn, err := net.DialTimeout(protocol, dialTarget, time.Duration(timeout)*time.Second) + endTime := time.Now() + elapsedTime := float64(endTime.Sub(startTime)) / float64(time.Millisecond) + if err != nil { + fmt.Printf("TIMEOUT via %v:%v time=%v ms\n", ip, port, elapsedTime) + time.Sleep(time.Duration(timeout) * time.Second) + + } else { + err := conn.Close() + if err != nil { + return + } + fmt.Printf("OK via %v:%v time=%v ms\n", ip, port, elapsedTime) + time.Sleep(time.Duration(1) * time.Second) + continue + } + + } + +} + +func digIp(address string) string { + ip, err := net.ResolveIPAddr("ip", address) + if err != nil { + return "" + } + return ip.String() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..49d3aa7 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module pong + +go 1.19 + +require ( + golang.org/x/net v0.0.0-20220615171555-694bf12d69de // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect +) diff --git a/goping/goping.go b/goping/goping.go new file mode 100644 index 0000000..ef54a4d --- /dev/null +++ b/goping/goping.go @@ -0,0 +1,161 @@ +package goping + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" + "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, Count 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("\nPING %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 + + for i := Count; i > 0; i-- { + /* + 向目标地址发送二进制报文包 + 如果发送失败就丢包 ++ + */ + 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 + } + fmt.Printf("%d bytes from %s (%s): icmp_seq=%d ttl=53 time=%.3f ms\n", len, raddr.String(), raddr.String(), Count-i+1, dur) + //fmt.Printf("来自 %s 的回复: 大小 = %d byte 时间 = %.3fms\n", raddr.String(), len, dur) + time.Sleep(time.Second) + } + + fmt.Printf("--- %s ping statistics ---\n", raddr.String()) + + fmt.Printf("%d packets transmitted, %f lost, %.2f%% packet loss, time 1000ms\n", Count, dropPack, dropPack/float64(Count)*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) +} diff --git a/goping/ping_test.go b/goping/ping_test.go new file mode 100644 index 0000000..c42fb02 --- /dev/null +++ b/goping/ping_test.go @@ -0,0 +1,24 @@ +package goping + +import ( + "testing" +) + +func TestPing(tt *testing.T) { + //if len(os.Args) < 3 { + // fmt.Printf("Param domain |data package Sizeof|trace times\n Ex: ./Ping www.so.com 100 4\n") + // os.Exit(1) + //} + //PS, err := strconv.Atoi(os.Args[2]) + //if err != nil { + // fmt.Println("you need input correct PackageSizeof(complete int)") + // os.Exit(1) + //} + //Count, err := strconv.Atoi(os.Args[3]) + //if err != nil { + // fmt.Println("you need input correct Counts") + // os.Exit(1) + //} + Ping("www.baidu.com", 48, 5) + //Ping("www.baidu.com", 48, 5) +} diff --git a/pong.go b/pong.go new file mode 100644 index 0000000..82461eb --- /dev/null +++ b/pong.go @@ -0,0 +1,56 @@ +package main + +import ( + "flag" + "fmt" + "os" + "pong/dial" + "pong/goping" + "strconv" +) + +// 定义命令行参数对应的变量,这三个变量都是指针类型 + +var helpText = `usage: pong [-t -k ] +eg: +pong www.baidu.com +pong www.baidu.com 443 +pong www.baidu.com 443 -t 1 +pong www.baidu.com 443 -t 1 -k udp +pong -a www.baidu.com -p 443 -k tcp -t 3 +` + +func main() { + address := flag.String("a", "", "domain or ip address") + port := flag.Int("p", 0, "port") + protocol := flag.String("k", "tcp", "protocol kind") + timeout := flag.Int("t", 1, "timeout") + flag.Parse() + + switch flag.NArg() { //函数返回没有被解析的命令行参数的个数 + // flag.Args() 函数返回没有被解析的命令行参数 + case 0: + if *address != "" && *port != 0 { + dial.PortDial(*address, strconv.Itoa(*port), *protocol, *timeout) + } + fmt.Printf(helpText) + os.Exit(0) + case 1: + if *port != 0 { + dial.PortDial(flag.Arg(0), strconv.Itoa(*port), *protocol, *timeout) + } + goping.Ping(flag.Arg(0), 48, 5) + os.Exit(0) + default: + fmt.Println("ping") + addressDial := flag.Arg(0) + portDial := flag.Arg(1) + if *address != "" { + addressDial = *address + } + if *port != 0 { + portDial = strconv.Itoa(*port) + } + dial.PortDial(addressDial, portDial, *protocol, *timeout) + } +} diff --git a/pong_test.go b/pong_test.go new file mode 100644 index 0000000..2cf2aa5 --- /dev/null +++ b/pong_test.go @@ -0,0 +1,34 @@ +package main + +//func TestPing(tt *testing.T) { +// p := fastping.NewPinger() +// ra, err := net.ResolveIPAddr("ip4:icmp", os.Args[1]) +// if err != nil { +// fmt.Println(err) +// os.Exit(1) +// } +// p.AddIPAddr(ra) +// p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) { +// fmt.Printf("IP Addr: %s receive, RTT: %v\n", addr.String(), rtt) +// } +// p.OnIdle = func() { +// fmt.Println("finish") +// } +// err = p.Run() +// if err != nil { +// fmt.Println(err) +// } +//} + +//func TestPing(tt *testing.T) { +// cmd := exec.Command("ping", "www.google.com", "-c", "4", "-W", "5") +// fmt.Println("NetWorkStatus Start:", time.Now().Unix()) +// err := cmd.Run() +// fmt.Println("NetWorkStatus End :", time.Now().Unix()) +// if err != nil { +// fmt.Println(err.Error()) +// } else { +// fmt.Println("Net Status , OK") +// } +// +//} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..f74f19b --- /dev/null +++ b/readme.md @@ -0,0 +1,6 @@ +## extend tool for ping +pong www.baidu.com +pong www.baidu.com 443 +pong www.baidu.com 443 -t 1 +pong www.baidu.com 443 -t 1 -k udp +pong -a www.baidu.com -p 443 -k tcp -t 3 \ No newline at end of file