Automatic dependency discovery and mapping

An idea from PUBG Hack

最近周末和朋友经常吃鸡(一款PC游戏),游戏过程中经常被外挂虐,但是大部分外挂都是读内存,听朋友说游戏过程中传输的UDP包是没有加密的,于是用golang写了个demo抓取游戏过程中的本机的UDP数据包,不过最终只拿到了自己的位置,了解了之后,也没有再继续研究下去。后来想了想sniffer这个东西能不能用在监控系统上呢

Automatic dependency discovery

  1. 当一台服务器出现宕机,你能立刻知道这台及其可能会波及哪些服务么
  2. 当你复杂的业务出现异常时,你并没有做任何调整,你如何快速确定在你调用的众多接口中,是哪一个出了问题

很多公司都在做服务间的依赖关系来解决这些问题,OpenTracing 做为一种主动上报关联的标准(作用不仅于此),在一定程度上主动解决了服务依赖的问题,但是主动去发现服务依赖存在一定缺陷,首先要依赖人准确毫无遗漏的在代码中提前植入相应log或则tag,涉及到部门之间的合作,语言的不同,将OpenTracing推到所有的业务中存在一定阻力,当然这里仍然不排除OpenTracing是个非常好的标准。

有没有一种非常好的方法无侵入的,单方面的把这个问题解决掉呢,嗯,可以尝试在系统层面sniffer抓包。我们基于底层系统进行四层协议抽样抓包,统计去重就可以找到所有与这台机器交互过后的目的IP和源IP。说到抓包就不得不提及 libpcap 这个库了,tcpdumpwireshark 等软件都是依赖于此库,当然我们也不例外,另外由于我们不是用c直接写的agent,所以还要调用google的一个golang的lib, https://github.com/google/gopacket 下面是一个简单的demo:


package main

import (
    "fmt"
    "log"
    "github.com/google/gopacket/pcap"
)

func main() {
    // Find all devices
    devices, err := pcap.FindAllDevs()
    if err != nil {
        log.Fatal(err)
    }

    // Print device information
    fmt.Println("Devices found:")
    for _, device := range devices {
        fmt.Println("\nName: ", device.Name)
        fmt.Println("Description: ", device.Description)
        fmt.Println("Devices addresses: ", device.Description)
        for _, address := range device.Addresses {
            fmt.Println("- IP address: ", address.IP)
            fmt.Println("- Subnet mask: ", address.Netmask)
        }
    }
}
Open Device for Live Capture
package main

import (
    "fmt"
    "github.com/google/gopacket"
    "github.com/google/gopacket/pcap"
    "log"
    "time"
)

var (
    device       string = "eth0"
    snapshot_len int32  = 1024
    promiscuous  bool   = false
    err          error
    timeout      time.Duration = 30 * time.Second
    handle       *pcap.Handle
)

func main() {
    // Open device
    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
    if err != nil {log.Fatal(err) }
    defer handle.Close()

    // Use the handle as a packet source to process all packets
    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
    for packet := range packetSource.Packets() {
        // Process packet here
        fmt.Println(packet)
    }
}

Data

考虑搭配抓包操作带来的性能消耗是非常大的,尤其会大量占用内存和io资源,所以寻找一个好的抽样策略非常重要,我们最终选择每5分钟随机上报30条不重复的数据,如果1min内并没有采集够(服务器可能没有什么请求),无论多少,直接上报。

基于时间序列的存储,最终上报的数据格式如下:

name: pcap.ipv4
time                DstIP        SrcIP        host                       interface ip          value
----                -----        -----        ----                       --------- --          -----
1515134229000000000 10.10.1.220  10.10.2.142  game_test_webdb142v2_taiji eth0      10.10.2.142 1
1515134229000000000 10.10.40.131 10.10.2.142  game_test_webdb142v2_taiji eth0      10.10.2.142 2
1515134229000000000 10.10.40.250 10.10.2.142  game_test_webdb142v2_taiji eth0      10.10.2.142 3
1515134229000000000 10.10.2.131  10.10.2.142  game_test_webdb142v2_taiji eth0      10.10.2.142 1
1515134229000000000 10.10.2.142  10.10.1.220  game_test_webdb142v2_taiji eth0      10.10.2.142 1
1515134229000000000 10.10.2.142  10.10.40.131 game_test_webdb142v2_taiji eth0      10.10.2.142 1
1515134229000000000 10.10.2.142  10.10.40.250 game_test_webdb142v2_taiji eth0      10.10.2.142 2
1515134229000000000 10.10.2.142  10.10.2.131  game_test_webdb142v2_taiji eth0      10.10.2.142 2
1515134229000000000 10.10.2.142  10.10.2.145  game_test_webdb142v2_taiji eth0      10.10.2.142 1
1515134229000000000 10.10.2.142  10.10.1.244  game_test_webdb142v2_taiji eth0      10.10.2.142 1

mapping

经过两个多星期的不断测试和调优,在FE的努力下,最终上线了, 离圆心越近说明交互越多,权重越大。

改进和展望

其实通过sniffer可以做的东西非常多,后面可能直接把端口号抓住来,关联进程,进而可以确定服务。另外可以把本服务器所有的DNS请求记录下来,可以知道该服务器依赖了多少域名提供服务,方便后期排查问题。总之,可以做的还很多,你有什么想法没?