美文网首页
falcon-ping机制实现

falcon-ping机制实现

作者: 皮皮虾_132a | 来源:发表于2019-03-14 12:01 被阅读0次

    ping监控用途与场景

    用于检测主机是否能通,存活等

    业内常用工具

    一般都是linux下使用fping 命令,能快速识别出主机是否宕机,zabbix 就是集成的该工具,但是用第三方语言(py go等)调用该命令性能会较差,而且对资源消耗巨大,甚至会出现大量的僵尸进程(资源回收不过来),包括zabbix也并不是直接使用命令,而是集成了一些库文件。

    go fast-ping模块

    GITHUB地址https://github.com/tatsushid/go-fastping
    基于icmp包实现的一个快速ping模块,目前版本的ping监控使用的是该工具,但经过对压测数据的分析,以及源码的阅读,发现存在以下问题:

    如果要ping 多个IP 会共用一个icmp连接

    这样也是该模块能“fast”的原因,虽然加快了速度,但是准确度也大打折扣,一个icmp连接如果要处理多个ping数据,一方面要频繁的进行上下文切换,对结果的准确性会产生很大的影响,另一方面 icmp没有类似tcp三次握手机制,本身就不可靠。比如他处理方法为:先同时发出n个ip的icmp请求(go协程),然后再挂起一个接收循环等待响应,响应一个处理,但由于是并发机制,再加上使用的是同一个通道,又没有三次握手保证数据的可靠性,当该通道突然同时接到大量数据时,难免会出现丢包!

    超时机制

    通过阅读源码发现,该模块内置了一个计数器(默认1s),计数器置位后,就会强行关闭通道。所以之前的做法是对所有的主机ping 30次,第一个1s内,ping完一部分,下一个1s,继续随机ping(因为map是无序的,所以每次读取都有可能是不同的ip),等于是30s内随机碰撞,来实现快速的ping,经过测试,发现一秒内,能ping 400台主机左右。

    go-fastping核心代码

    image.png

    可以看到复用了一个conn

    优化方案

    修改这种共用通道的机制

    改成单IP独享一个通道,毕竟icmp,不如TCP可靠,每个通道一个协程负责处理(每开辟500个协程 sleep 1秒,等待资源释放一部分),发现不通的,再次发起一次ping数据,确认是否真的异常

    代码

    放弃了go-fastping 重写了一个ping.go
    ping.go

    type pkg struct {
        conn     net.PacketConn
        ipv4conn *ipv4.PacketConn
        msg      icmp.Message
        netmsg   []byte
        id       int
        seq      int
        maxrtt   time.Duration
        dest     net.Addr
    }
    
    type ICMP struct {
        Addr    net.Addr
        RTT     time.Duration
        MaxRTT  time.Duration
        MinRTT  time.Duration
        AvgRTT  time.Duration
        Final   bool
        Timeout bool
        Down    bool
        Error   error
    }
    
    func (t *pkg) Send(ttl int) ICMP {
        var hop ICMP
        var err error
        t.conn, hop.Error = net.ListenPacket("ip4:icmp", "0.0.0.0")
    
        if nil != err {
            return hop
        }
        defer t.conn.Close()
        t.ipv4conn = ipv4.NewPacketConn(t.conn)
        defer t.ipv4conn.Close()
        hop.Error = t.conn.SetReadDeadline(time.Now().Add(t.maxrtt))
        if nil != hop.Error {
            return hop
        }
        if nil != t.ipv4conn {
            hop.Error = t.ipv4conn.SetTTL(ttl)
        }
        if nil != hop.Error {
            return hop
        }
        sendOn := time.Now()
        if nil != t.ipv4conn {
            _, hop.Error = t.conn.WriteTo(t.netmsg, t.dest)
        }
        if nil != hop.Error {
            return hop
        }
        buf := make([]byte, 1500)
        for {
            var readLen int
            readLen, hop.Addr, hop.Error = t.conn.ReadFrom(buf)
            if nerr, ok := hop.Error.(net.Error); ok && nerr.Timeout() {
                hop.Timeout = true
                return hop
            }
            if nil != hop.Error {
                return hop
            }
            var result *icmp.Message
            if nil != t.ipv4conn {
                result, hop.Error = icmp.ParseMessage(1, buf[:readLen])
            }
            if nil != hop.Error {
                return hop
            }
            switch result.Type {
            case ipv4.ICMPTypeEchoReply:
                if rply, ok := result.Body.(*icmp.Echo); ok {
                    if t.id == rply.ID && t.seq == rply.Seq {
                        hop.Final = true
                        hop.RTT = time.Since(sendOn)
                        return hop
                    }
    
                }
            case ipv4.ICMPTypeTimeExceeded:
                if rply, ok := result.Body.(*icmp.TimeExceeded); ok {
                    if len(rply.Data) > 24 {
                        if uint16(t.id) == binary.BigEndian.Uint16(rply.Data[24:26]) {
                            hop.RTT = time.Since(sendOn)
                            return hop
                        }
                    }
                }
            case ipv4.ICMPTypeDestinationUnreachable:
                if rply, ok := result.Body.(*icmp.Echo); ok {
                    if t.id == rply.ID && t.seq == rply.Seq {
                        hop.Down = true
                        hop.RTT = time.Since(sendOn)
                        return hop
                    }
    
                }
            }
        }
    
    }
    
    func RunPing(Addr string, maxrtt time.Duration, maxttl int, seq int) (float64, error) {
        var res pkg
        var err error
        res.dest, err = net.ResolveIPAddr("ip", Addr)
        if err != nil {
            return 0, errors.New("Unable to resolve destination host")
        }
        res.maxrtt = maxrtt
        //res.id = rand.Int() % 0x7fff
        res.id = rand.Intn(65535)
        res.seq = seq
        res.msg = icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ID: res.id, Seq: res.seq}}
        res.netmsg, err = res.msg.Marshal(nil)
        if nil != err {
            return 0, err
        }
        pingRsult := res.Send(maxttl)
    
        return float64(pingRsult.RTT.Nanoseconds()) / 1e6, pingRsult.Error
    }
    

    调用方法

    _, err := RunPing("127.0.0.1", 6 * time.Second,64, i)
    

    测试结果

    与 fping程序ping的结果对比后,完全能保持一致。

    仓储地址

    https://github.com/peng19940915/ping-falcon

    其他

    MAXTTL:为icmp报文在网络中达到目标主机要经过多少跳,该参数主要目的是防止报文在网络内无限传递下去,我们在这里设置的为64跳,超过64跳认为已经不通了

    相关文章

      网友评论

          本文标题:falcon-ping机制实现

          本文链接:https://www.haomeiwen.com/subject/xgqqmqtx.html