导语:

在生产实际中,我们需要检测服务器之间的网络连通性。有时候要ping整个网段的需求,普通的用法就是把整个网段的IP地址列出来,进行ping,这样不利于解放我们运维人员的双手。

脚本实现功能

拨测IP+port

拨测网段

脚本思路

```html/xml
1、编写一个根据网段识别出其可用IP的函数
2、编写一个拨测IP和拨测IP+端口及规范IP地址的判断的函数
3、读取存放IP网段、IP及IP+端口的文本内容的函数,并进行判断存放的是其中的哪一种
4、将读取的内容按照属性进行函数带入
5、网段地址实现高可用并发ping


# 完整脚本详解
```go
package main

import (
    "bufio"
    "bytes"
    "encoding/binary"
    "fmt"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "io"
    "regexp"
    "runtime"
    "sync"

    //      "io/ioutil"
    "log"
    "net"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"

)
//定义exporter监控名称及描述
var (
         node_IP_section_ping = prometheus.NewGaugeVec(prometheus.GaugeOpts{
            Name:        "node_IP_section_ping",
            Help:        "node_IP_section_ping_guest_seconds.",

        }, []string{"Destination_address"})

        node_IP_port = prometheus.NewGaugeVec(prometheus.GaugeOpts{
                Name:        "node_ip_port_ping",
                Help:        "node_ip_port_ping_guest_seconds.",
            },
                []string{
        "Destination_address"})

)

func init() {
    // Metrics have to be registered to be exposed:
    prometheus.MustRegister(node_IP_section_ping)
    prometheus.MustRegister(node_IP_port)

}

var icmp ICMP

type ICMP struct {
    Type        uint8
    Code        uint8
    Checksum    uint16
    Identifier  uint16
    SequenceNum uint16
}

//ping IP地址的写法
func isping(ip string) bool {
    //开始填充数据包
    icmp.Type = 8 //8->echo message  0->reply message
    icmp.Code = 0
    icmp.Checksum = 0
    icmp.Identifier = 0
    icmp.SequenceNum = 0

    recvBuf := make([]byte, 32)
    var buffer bytes.Buffer

    //先在buffer中写入icmp数据报求去校验和
    binary.Write(&buffer, binary.BigEndian, icmp)
    icmp.Checksum = CheckSum(buffer.Bytes())
    //然后清空buffer并把求完校验和的icmp数据报写入其中准备发送
    buffer.Reset()
    binary.Write(&buffer, binary.BigEndian, icmp)

    Time, _ := time.ParseDuration("2s")
    conn, err := net.DialTimeout("ip4:icmp", ip, Time)
    if err != nil {
        return false
    }
    _, err = conn.Write(buffer.Bytes())
    if err != nil {
        //log.Println("conn.Write error:", err)
        return false
    }
    conn.SetReadDeadline(time.Now().Add(time.Second * 2))
    num, err := conn.Read(recvBuf)
    if err != nil {
        //log.Println("conn.Read error:", err)
        return false
    }

    conn.SetReadDeadline(time.Time{})

    if string(recvBuf[0:num]) != "" {
        return true
    }
    return false

}

func CheckSum(data []byte) 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])
    }
    sum += (sum >> 16)

    return uint16(^sum)
}
//根据网段识别出所有IP地址,“cidr” 代表网段
func getCidrIpRange(cidr string)  {
    var ips = []string{}
    var ip_data  string
    ip := strings.Split(cidr, "/")[0]
    ipSegs := strings.Split(ip, ".")
    maskLen, _ := strconv.Atoi(strings.Split(cidr, "/")[1])
    seg3MinIp, seg3MaxIp := getIpSeg3Range(ipSegs, maskLen)
    seg4MinIp, seg4MaxIp := getIpSeg4Range(ipSegs, maskLen)
    seg4MaxIp = seg4MaxIp - 1
    ipPrefix := ipSegs[0] + "." + ipSegs[1] + "."
    ip_port3, _ := strconv.Atoi(ipSegs[2])
    ip_port4, _ := strconv.Atoi(ipSegs[3])
    if maskLen ==31{if ip_port4%2==0{
        ips=append(ips,string(ipPrefix + strconv.Itoa(ip_port3) + "." + strconv.Itoa(ip_port4)))
        ips=append(ips,string(ipPrefix + strconv.Itoa(ip_port3) + "." + strconv.Itoa(ip_port4+1)))
    }else{
        ips=append(ips,string(ipPrefix + strconv.Itoa(ip_port3) + "." + strconv.Itoa(ip_port4)))
        ips=append(ips,string(ipPrefix + strconv.Itoa(ip_port3) + "." + strconv.Itoa(ip_port4-1)))
    }
    }
    if maskLen ==32{
        ips=append(ips,string(ipPrefix + strconv.Itoa(ip_port3) + "." + strconv.Itoa(ip_port4)))
    }
    if seg3MaxIp-seg3MinIp > 0 {
        for i := seg3MinIp; i <= seg3MaxIp; i++ {
            for j := 0; j <= 255; j++ {
                ips=append(ips,string(ipPrefix + strconv.Itoa(seg3MinIp) + "." + strconv.Itoa(j)))
                if seg3MinIp == seg3MaxIp {
                    ips=append(ips,string(ipPrefix + strconv.Itoa(seg3MinIp) + "." + strconv.Itoa(j-1)))
                }

            }
            seg3MinIp = seg3MinIp + 1
        }
    } else {
        for i := seg4MinIp; i <= seg4MaxIp; i++ {

            ips=append(ips,string(ipPrefix + strconv.Itoa(seg3MinIp) + "." + strconv.Itoa(i)))
        }
    }
       // fmt.Println(ips)
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := &sync.WaitGroup{}
    ch := make(chan string, len(ips))
    //fmt.Println(ips)
    for i:= 0; i < len(ips); i++ {
        wg.Add(1)
        go func(i int) {
//将识别出来的IP地址,带入ping IP的函数中
            ch <- net_checkIP(ips[i])
            wg.Done()
        }(i)
    }
    wg.Wait()

        for {
        data := <-ch 
//管道中无数据,则结束
       if len(data) == 0 {
            break
        }
        ip_data=strings.Split(data, "/")[0]
        value_data, _ := strconv.Atoi(strings.Split(data, "/")[1])
 //未读通道中的元素为0时,运行此次后,结束运行
        if ;len(ch) ==0{

         node_IP_section_ping.WithLabelValues(ip_data).Set(float64(value_data))
                 break
    }

           node_IP_section_ping.WithLabelValues(ip_data).Set(float64(value_data))

}
}

//得到第三段IP的区间(第一片段.第二片段.第三片段.第四片段)
func getIpSeg3Range(ipSegs []string, maskLen int) (int, int) {
    if maskLen > 24 {
        segIp, _ := strconv.Atoi(ipSegs[2])
        return segIp, segIp
    }
    ipSeg, _ := strconv.Atoi(ipSegs[2])
    return getIpSegRange(uint8(ipSeg), uint8(24 - maskLen))
}

//得到第四段IP的区间(第一片段.第二片段.第三片段.第四片段)
func getIpSeg4Range(ipSegs []string, maskLen int) (int, int) {
    ipSeg, _ := strconv.Atoi(ipSegs[3])
    segMinIp, segMaxIp := getIpSegRange(uint8(ipSeg), uint8(32 - maskLen))
    return segMinIp + 1, segMaxIp
}

//根据用户输入的基础IP地址和CIDR掩码计算一个IP片段的区间
func getIpSegRange(userSegIp, offset uint8) (int, int) {
    var ipSegMax uint8 = 255
    netSegIp := ipSegMax << offset
    segMinIp := netSegIp & userSegIp
    segMaxIp := userSegIp & (255 << offset) | ^(255 << offset)
    return int(segMinIp), int(segMaxIp)
}

//ping IP的函数,通为1 不通为0
func checkIP(IP string) int {
    bool := isping(IP)
    if bool==true{
        return 1
    }
        return 0

}
//为了高并发能对应IP与数据的一致性,将其进行以“/”拼接
func net_checkIP(IP string) string  {
    return IP+"/"+strconv.Itoa(checkIP(IP))

        }

//ping IP+port的函数
func checkPort(ip_port string) int {
    conn, err := net.DialTimeout("tcp", ip_port, 3*time.Second)
    if err != nil {
        return 0
    } else {
        if conn != nil {
            return 1
            conn.Close()
        } else {
            return 0
        }
    }
    return 1
}
//为了确保文件中输入的IP是正确的 如256.256.256.256要自动过滤掉,减少因人为填写的失误到导致服务器性能资源的浪费
func IsIpaddress(ip string) int {
    regstr := `\d+\.\d+\.\d+\.\d+`               //两个及两个以上空格的正则表达式
    reg, _ := regexp.Compile(regstr)             //编译正则表达式

    IP_line := reg.Find([]byte(ip))
    //:= reg.FindStringIndex(string(s2)) //在字符串中搜索
    if m, _ := regexp.MatchString("^(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)$", string(IP_line)); !m {
        return 404
    } else {
        return 200
    }

}

//读取存放IP段、IP及IP+port的文件
func readfile(filename string) {

    // 测试读取文件
    var ip_code int
    var value float64
    f, err := os.Open(filename)
    if err != nil {
        fmt.Printf("error : %s", err)
    }

    fread := bufio.NewReader(f)
    for {
        line, _, err := fread.ReadLine()
        if err == io.EOF {
            break
        }
        ip_code = IsIpaddress(string(line))
   //如果IP不规范,直接过滤掉
        if int(ip_code) == 404 {
            continue
        }
//如果读取的行中带有“/”,代表读取的是IP网段
        if find := strings.Contains(string(line), "/"); find {
            getCidrIpRange(string(line))
        }else{  // Metrics have to be registered to be exposed:

            //如果读取的是IP+prot
        if find := strings.Contains(string(line), ":"); find {

            value = float64(checkPort(string(line)))
            //如果读取的是IP
        } else {
            value = float64(checkIP(string(line)))

        } 
                node_IP_port.WithLabelValues(string(line)).Set(value)

    }

    }
}

func main() {
//为了保持数据的更新,需要写一个循环体,不断读取数据
      go func() {
        for{
    readfile("a.txt")
//2秒循环一次
        time.Sleep(2 * time.Second)
            }
    }()

    http.Handle("/metrics", promhttp.Handler())

    log.Fatal(http.ListenAndServe(":8080", nil))

}

脚本实现效果展示

存放文本的内容

image.png

执行脚本

[root@localhost go]# go run ping.go 

打开页面查看效果

//192.168.194.249代表运行exporter主机的IP  8080是开放的端口,可以随意改 ,只要不占用已有端口号即可
http://192.168.194.249:8080/metrics 

image.png