前言
实现ping、tcp端口检测、HTTP状态码检测。使用效果:
$ ./iptest tcp 192.168.0.106 9999
dial 192.168.0.106:9999 success, elapsed: 109.574125ms$ sudo ./iptest ping baidu.com
[sudo] password for test:
来自 baidu.com 的回复: 字节=64 时间=84ms TTL=49
来自 baidu.com 的回复: 字节=64 时间=91ms TTL=49
来自 baidu.com 的回复: 字节=64 时间=105ms TTL=49
来自 baidu.com 的回复: 字节=64 时间=113ms TTL=49$ ./iptest http 192.168.0.106 -p 9999 -s http
Connected to http://192.168.0.106:9999, response code: 307
准备
go mod init iptest
cobra-cli init
cobra-cli add ping
cobra-cli add tcp
cobra-cli add http
示例代码
cmd/ping.go
package cmdimport ("bytes""encoding/binary""fmt""math""net""time""github.com/spf13/cobra"
)// pingCmd represents the ping command
var pingCmd = &cobra.Command{Use: "ping",Short: "向主机发起ping测试",Long: `向主机发起ICMP协议的ping请求. Linux系统下运行需要root权限`,Example: `./iptest ping baidu.com`,Run: func(cmd *cobra.Command, args []string) {// fmt.Println("ping called")if len(args) > 0 {host := args[0]pingMain(host, count)} else {fmt.Println("未检测到参数传入")}},
}var count intfunc init() {rootCmd.AddCommand(pingCmd)pingCmd.Flags().IntVarP(&count, "count", "c", 4, "ping次数")
}type icmpStruct struct {Type uint8Code uint8CheckSum uint16Identifier uint16SequenceNum uint16
}func pingMain(host string, count int) {// laddr := net.IPAddr{IP: net.ParseIP("ip")}conn, err := net.DialTimeout("ip:icmp", host, 2*time.Second)if err != nil {fmt.Printf("Connecting to %s failed\n", host)return}defer conn.Close()icmp := icmpStruct{Type: 8,Code: 0,CheckSum: 0,Identifier: 1,SequenceNum: 1,}var buffer bytes.Bufferbinary.Write(&buffer, binary.BigEndian, icmp)data := make([]byte, 64)buffer.Write(data)data = buffer.Bytes()var SuccessTimes int // 成功次数var FailTimes int // 失败次数var minTime int = int(math.MaxInt32)var maxTime intvar totalTime intfor i := 0; i < count; i++ {icmp.SequenceNum = uint16(1)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(2 * time.Second))_, err := conn.Write(data)if err != nil {// log.Fatal(err)fmt.Println(err)return}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 += etfmt.Printf("来自 %s 的回复: 字节=%d 时间=%dms TTL=%d\n", host, len(buf[28:n]), et, buf[8])SuccessTimes++time.Sleep(1 * time.Second)}
}/*
计算校验和
1. 将ICMP头部内容中的校验内容的值置为0
2.
*/
func checkSum(data []byte) uint16 {var sum uint32var length = len(data)var index intfor length > 1 { // 溢出部分直接去除sum += uint32(data[index])<<8 + uint32(data[index+1])index += 2length -= 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)
}
cmd/tcp.go
package cmdimport ("fmt""net""os""strconv""time""github.com/spf13/cobra"
)// tcpCmd represents the tcp command
var tcpCmd = &cobra.Command{Use: "tcp",Short: "检测tcp端口是否连通",Long: `检测远端tcp端口是否连通`,Example: `./iptest tcp 192.168.0.106 9999`,Run: func(cmd *cobra.Command, args []string) {if len(args) > 0 {host := args[0]portStr := args[1]port, err := strconv.Atoi(portStr)if err != nil {fmt.Printf("%s 非有效数值\n", portStr)return}tcpMain(host, port)} else {fmt.Println("未检测到参数传入")}},
}func init() {rootCmd.AddCommand(tcpCmd)
}func tcpMain(host string, port int) {start := time.Now()conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 2*time.Second)if err != nil {if os.IsTimeout(err) {fmt.Printf("Connecting to %s:%d timeout\n", host, port)return} else {fmt.Printf("Connecting to %s:%d failed\n", host, port)return}}defer conn.Close()elapsed := time.Since(start)if conn != nil {fmt.Printf("dial %s:%d success, elapsed: %v\n", host, port, elapsed)} else {fmt.Printf("dial %s:%d failed\n", host, port)}
}
cmd/http.go
package cmdimport ("fmt""net/http""os""time""github.com/spf13/cobra"
)// httpCmd represents the http command
var httpCmd = &cobra.Command{Use: "http",Short: "检测远端http是否连通",Long: `通过发起HEAD请求, 根据状态码判断是否连通`,Example: `# 检测 https://example.com:443./iptest http example.com# 检测 http://example.com:80./iptest http -s http -p 80 example.com`,Run: func(cmd *cobra.Command, args []string) {if len(args) > 0 {host := args[0]httpMain(host, port)} else {fmt.Println("未检测到参数传入")}},
}
var (schema stringport int
)func init() {rootCmd.AddCommand(httpCmd)httpCmd.Flags().StringVarP(&schema, "schema", "s", "https", "schema")httpCmd.Flags().IntVarP(&port, "port", "p", 443, "port")
}func httpMain(host string, port int) {if schema != "http" && schema != "https" {fmt.Println("schema must be http or https")return}var dest string = fmt.Sprintf("%s://%s:%d", schema, host, port)client := &http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error {return http.ErrUseLastResponse},Timeout: time.Second * 2,}resp, err := client.Head(dest)if err != nil {if os.IsTimeout(err) {fmt.Printf("Connecting to %s://%s:%d timeout\n", schema, host, port)return} else {fmt.Printf("Connecting to %s://%s:%d failed\n", schema, host, port)return}}fmt.Printf("Connected to %s://%s:%d, response code: %d\n", schema, host, port, resp.StatusCode)
}
参考
- 博客园 - 使用go实现一个ping程序
- 简书 - Go语言实现ping命令