最近开发的telemetry采集系统上线了。听起来高大上,简单来说就是一个grpc/udp服务端,用户的机器(路由器、交换机)将它们的各种统计数据上报采集、整理后交后端的各类AI分析系统分析。目前华为/思科的大部分设备支持。

上线之后,各类用户开始找来要求帮忙定位问题,一般是上报的数据在后端系统中不存在等等。

在一通抓包分析后,百分之99都是用户自己的问题。但频繁的抓包定位问题,严重的压缩了我摸鱼的时间。而且,这套系统采用多实例方式部署在腾X云多个容器中,一个个的登录抓包,真的很烦。

这让我萌生了一个需求:

  1. 主动给采集器下发抓包任务。
  2. 将抓包的信息返回。
  3. 将抓包的文件暂存,以备进一步定位问题。

方法1

使用fabric等ssh运维工具,编写脚本自动化登录机器后执行tcpdump,然后进一步处理。

很可惜的是,并没有容器母机ssh的权限。只能通过一个web命令行观察容器。这条路玩不转。

方法2

  1. 在采集器中添加一个接口,用以下发tcpdump命令
  2. 采集器执行tcpdump命令,并获取返回的信息(比如captured xxx pacs),保存相关文件。
  3. 将获取的抓包信息以某种方式反发给命令下发人。

使用tcpdump定时抓取并保存信息

首先需要解决tcpdump定时的问题,以免tcpdump无限期的执行抓包,经过一通谷歌,命令如下:

timeout 30 tcpdump -i eth0 host 9.123.123.111 and port 6651 -w /tmp/log.cap

timeout 30 指抓取30秒,超时后tcpdump会直接退出

-i 指定抓取的端口

host xxx 源IP

port xxx 源端口

编写tcpdump函数

下面到了我最喜欢的写代码阶段,为了简单,直接使用os/exec库。不要笑,很多大厂的很多系统其实都是包命令行工具,解决问题最重要。

// TcpDump 执行tcpdump命令,并返回抓到的包数
func TcpDump(sudo bool, timeout int, eth string, host string, port int) (caps int, err error) {
portStr := ""
if port != 0 {
portStr = fmt.Sprintf("and port %v", port)
} tcpdumpCmd := fmt.Sprintf("timeout %v tcpdump -i %v host %v %v -w /tmp/log.cap",
timeout, eth, host, portStr)
if sudo {
tcpdumpCmd = "sudo " + tcpdumpCmd
}
logrus.Infof("call %v", tcpdumpCmd)
cmd := exec.Command("sh", "-c", tcpdumpCmd)
var outb, errb bytes.Buffer
cmd.Stderr = &errb
err = cmd.Run()
if err != nil {
if !errors.Is(err, &exec.ExitError{}) {
logrus.Infof("out:%s ; %s", outb.Bytes(), errb.Bytes())
return getPacs(errb.String()), nil
}
return
}
return 0,fmt.Errorf("unknown error")
} func getPacs(input string) int {
end := strings.Index(input, "packets captured")
pos := end
for {
pos -= 1
if pos <= 0 {
return 0
}
if input[pos] == '\n' {
break
}
}
// logrus.Infof("captured:%s", input[pos+1:end-1])
v, err := strconv.Atoi(input[pos+1 : end-1])
if err != nil {
return 0
}
return v
}

这里要注意几点:

  1. 执行cmd := exec.Command("sh", "-c", tcpdumpCmd)后,tcpdump的返回信息类似:
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes\n56 packets captured\n56 packets received by filter\n0 packets dropped by kernel\n

是在stderr中的。而不是stdout。

  1. getPacs函数简单的从xx packets received中提取出了抓包数。但是如果是中文的服务器系统(不会吧,不会吧),就不太好使了。

编写api

现在函数已经有了,只要再写一个http api,就能很方便的把它暴露出去。

import "github.com/gogf/gf/v2/encoding/gjson"

// ErrJson,写入一个error json,形如:
//{
// "err": code,
// "err_msg": msg
//}
func ErrJson(w http.ResponseWriter, errCode int, errStr string) error {
w.Header().Set("Content-Type", "application/json")
js := make(map[string]interface{})
js["err"] = errCode
js["err_msg"] = errStr
jsBts, _ := json.Marshal(js)
_, err := w.Write(jsBts)
return err
} /* TcpDumpHandler
req:{
"sudo": true,
"eth": "eth0",
"host": "10.99.17.135",
"port": 0
}
rsp:{
"err": 0,
"caps": 14
}
*/
func TcpDumpHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
ret, err := ioutil.ReadAll(r.Body)
if err != nil {
ErrJson(w, 1, "数据错误")
return
}
js := gjson.New(ret)
sudo := js.Get("sudo").Bool()
eth := js.Get("eth").String()
if eth == "" {
ErrJson(w, 1, "数据错误, eth不存在")
return
}
host := js.Get("host").String()
if host == "" {
ErrJson(w, 1, "数据错误, host不存在")
return
}
port := js.Get("port").Int()
timeout := js.Get("timeout").Int()
if timeout == 0 {
ErrJson(w, 1, "数据错误, timeout为0或不存在")
return
}
go func() {
chatKey := config.GlobalConfigObj.Global.ChatKey
botKey := config.GlobalConfigObj.Global.BotKey // 这里直接利用了公司的一个消息系统,如果贵公司没有这样的系统,就变通一下
msgSender := msg.NewNiuBiMsg(chatKey, botKey) caps, err := TcpDump(sudo, timeout, eth, host, port)
if err != nil {
return
}
if caps > 0 {
// 这里直接利用了公司的一个消息系统,向企业IM发一条消息
msgSender.Send(fmt.Sprintf("tcpdump agent_ip:%v host:%v eth:%v port:%v, captured:%v",
config.GlobalLocalConfig.LocalIP, host, eth, port, caps))
bts, err := ioutil.ReadFile("/tmp/log.cap")
if err != nil {
return
}
b64Caps := base64.StdEncoding.EncodeToString(bts)
// 把抓包的文件通过这个消息系统也发到企业IM中
msgSender.File(fmt.Sprintf("pacs_%v.cap", config.GlobalLocalConfig.LocalIP), b64Caps)
}
}()
}

然后起一个http svr

func runHttp() {
mux := http.NewServeMux()
server :=
http.Server{
Addr: fmt.Sprintf(":%d", 3527),
Handler: mux,
ReadTimeout: 3 * time.Second,
WriteTimeout: 5 * time.Second,
}
// 开始添加路由
mux.HandleFunc("/tcpdump", tcpdumpsvc.TcpDumpHandler)
logrus.Infof("run http:%v", 3527)
logrus.Info(server.ListenAndServe())
}

到这一步,这个系统就基本完成了。使用这个命令就能调用接口。

curl --header "Content-Type: application/json" --request GET --data '{"sudo":false,"eth":"eth0","host":"100.xxx.xxx.10","port":0,"timeout":5}' http://0.0.0.0:3527/tcpdump

这个系统有几个硬伤。

  1. 依赖了公司的消息系统完成抓包数据回发的功能。假如各位大佬的公司没有这样的系统msgSender.Send,可行的方法有:

    1. scp到一个特定的文件夹。
    2. 使用电子邮件。
    3. 和领导申请自己开发一套,你看,需求就来了。
  2. tcpdump可能会生成极大的抓包文件,此时使用bts, err := ioutil.ReadFile("/tmp/log.cap"),可能会直接让系统OOM。所以设置timeout和抓包的大小(比如在tcpdump命令中使用-c)是很重要的。换句话说,这个api不是公有的,别让不了解的人去调用。

不过这都是小问题。现在用户找上门来,我只需要启动脚本,从服务发现api拉到所有的实例IP,然后依次调用tcpdump api,等待IM的反馈即可。又能快乐的摸鱼啦。

go程序添加远程调用tcpdump功能的更多相关文章

  1. netty实现远程调用RPC功能

    netty实现远程调用RPC功能 依赖 服务端功能模块编写 客户端功能模块编写 netty实现远程调用RPC功能 PRC的功能一句话说白了,就是远程调用其他电脑的api 依赖 <dependen ...

  2. Windows 10 IoT Serials 5 - 如何为树莓派应用程序添加语音识别与交互功能

    都说语音是人机交互的重要手段,虽然个人觉得在大庭广众之下,对着手机发号施令会显得有些尴尬.但是在资源受限的物联网应用场景下(无法外接鼠标键盘显示器),如果能够通过语音来控制设备,与设备进行交互,那还是 ...

  3. 为我们的SSR程序添加热更新功能

    前沿 通过上一篇文章 通过vue-cli3构建一个SSR应用程序 我们知道了什么是SSR,以及如何通过vue-cli3构建一个SSR应用程序.但是最后遗留了一些问题没有处理,就是没有添加开发时的热更新 ...

  4. 【Java EE 学习 78 中】【数据采集系统第十天】【Spring远程调用】

    一.远程调用概述 1.远程调用的定义 在一个程序中就像调用本地中的方法一样调用另外一个远程程序中的方法,但是整个过程对本地完全透明,这就是远程调用.spring已经能够非常成熟的完成该项功能了. 2. ...

  5. ABAP RFC远程调用

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  6. Hessian怎样实现远程调用

    1.Spring中除了提供HTTP调用器方式的远程调用,还对第三方的远程调用实现提供了支持,其中提供了对Hessian的支持. Hessian是由Caocho公司发布的一个轻量级的二进制协议远程调用实 ...

  7. 图像处理控件ImageGear for .NET教程如何为应用程序 添加DICOM功能(2)

    在前面的一些关于图像处理控件ImageGear for .NET文章<图像处理控件ImageGear for .NET教程: 添加DICOM功能(1)>中讲解了如何对应用程序添加DICOM ...

  8. 011.Adding Search to an ASP.NET Core MVC app --【给程序添加搜索功能】

    Adding Search to an ASP.NET Core MVC app 给程序添加搜索功能 2017-3-7 7 分钟阅读时长 作者 本文内容 1.Adding Search by genr ...

  9. HOOK大法实现不修改程序代码给程序添加功能

    [文章标题]: HOOK大法实现不修改程序代码给程序添加功能[文章作者]: 0x18c0[软件名称]: Scylla[使用工具]: OD.Stub_PE.ResHacker[版权声明]: 本文原创于0 ...

随机推荐

  1. Java学习——数组的基础知识

    数组的特点.分类:一维.二维数组的使用:数组的声明和初始化.调用数组的指定位置的元素.获取数组的长度.遍历数组.数组元素的默认初始化值

  2. Bootstarp框架用法

    Bootstrap框架 Bootstrap框架 2.X 3.X 4.X # 推荐使用3.X版本 使用框架调整页面样式一般都是操作标签的class属性即可 bootstrap需要依赖于jQuery才能正 ...

  3. Oracle双字段约束

    Oracle里有unique约束,意思是该字段唯一. 但如果是两个字段呢? 比如说一个会员等级表 ID NAME POINT DISCOUNT PRIVILEGE MID 1019 普通会员 0 10 ...

  4. Linux中权限对于文件和目录的区别

    Linux系统中的权限对于文件和目录来说,是有一定区别的 下面先列举下普通文件对应的权限 1)可读r:表示具有读取.浏览文件内容的权限,例如,可以对文件执行 cat.more.less.head.ta ...

  5. JS判断移动端还是PC端(改造自腾讯网) 仅用于宣传动画,下载页等

    JS判断移动端还是PC端(改造自腾讯网 http://www.qq.com/) 本脚本仅用于宣传动画,下载页( ipad 也算pc端)等,  ionic 用 ionic.platform 即可( io ...

  6. Java语言学习day03--6月30日

    今日内容介绍 1.变量 2.运算符 ###01变量概述     * A: 什么是变量?         * a: 变量是一个内存中的小盒子(小容器),容器是什么?生活中也有很多容器,例如水杯是容器,用 ...

  7. 2021.11.10 P5231 [JSOI2012]玄武密码(AC自动机)

    2021.11.10 P5231 [JSOI2012]玄武密码(AC自动机) https://www.luogu.com.cn/problem/P5231 题意: 给出字符串S和若干T,求S与每个T的 ...

  8. switch语法

    1. js 代码 // 1. switch 语句也是多分支语句 也可以实现多选1 // 2. 语法结构 switch 转换.开关 case 小例子或者选项的意思 // switch (表达式) { / ...

  9. i2c调试工具分享

    i2c-tools简介 在嵌入式开发仲,有时候需要确认硬件是否正常连接,设备是否正常工作,设备的地址是多少等等,这里我们就需要使用一个用于测试I2C总线的工具--i2c-tools. i2c-tool ...

  10. [原创][开源]C# Winform DPI自适应方案,SunnyUI三步搞定

    SunnyUI.Net, 基于 C# .Net WinForm 开源控件库.工具类库.扩展类库.多页面开发框架 Blog: https://www.cnblogs.com/yhuse Gitee: h ...