[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)
}
参考
本文来自博客园,作者:花酒锄作田,转载请注明原文链接:https://www.cnblogs.com/XY-Heruo/p/18788375