[Go]命令行工具-检测网络连通性

前言

实现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 cmd

import (
	"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 int

func init() {
	rootCmd.AddCommand(pingCmd)
	pingCmd.Flags().IntVarP(&count, "count", "c", 4, "ping次数")
}

type icmpStruct struct {
	Type        uint8
	Code        uint8
	CheckSum    uint16
	Identifier  uint16
	SequenceNum 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.Buffer
	binary.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 int
	var totalTime int

	for 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 += et
		fmt.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 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)
}
  • cmd/tcp.go
package cmd

import (
	"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 cmd

import (
	"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 string
	port   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)
}

参考

posted @ 2025-03-23 21:49  花酒锄作田  阅读(72)  评论(0)    收藏  举报