前文prometheus client_java实现进程的CPU、内存、IO、流量的可观测是通过nethogs命令来监控进程的流量数据。本文使用gopacket实现进程的流量监控和访问次数。
gopacket是google出品的golang三方库,是个抓取网络数据包的库
抓包工具包括:
Windows平台下有Wireshark抓包工具,其底层抓包库是npcap(以前是winpcap);
Linux平台下有Tcpdump,其抓包库是libpcap;
gopacket库是libpcap和npcap的go封装,提供了更方便的go语言操作接口。
gopacket包含许多有用的附加功能的子包,包括:
layers:包含了内置在gopacket中用于解码数据包协议的逻辑。
pcap:使用libpcap从网络上读取数据包的C绑定。
pfring:使用PF_RING从网络上读取数据包的C绑定。
afpacket: Linux的AF_PACKET的C绑定,用于从网络上读取数据包。
tcppassemassembly: TCP流重组
官方地址:https://github.com/google/gopacket
API文档:https://godoc.org/github.com/google/gopacket
google官方bugfix慢,有个第三方分支版本
源码地址:https://github.com/gopacket/gopacket
文档地址:https://pkg.go.dev/github.com/gopacket/gopacket
1、环境准备
环境准备
安装第三方库 libpcap或npcap
windows 平台
如果是在windows平台下,需要确保安装了npcap或winpcap
npcap下载地址:https://nmap.org/npcap/
linux平台
如果实在linux平台下确保安装了libpcap库
# Linux (Debian/Ubuntu)
sudo apt-get update
sudo apt-get install libpcap-dev
# Linux (Fedora/RHEL/CentOS)
sudo dnf install libpcap-devel
# macOS 平台,使用 Homebrew 安装:
brew install libpcap
如果报错
# github.com/google/gopacket/pcap
/usr/local/go/go/pkg/mod/github.com/google/gopacket@v1.1.19/pcap/pcap_unix.go:34:10: fatal error: pcap.h: No such file or directory
34 | #include <pcap.h>
| ^~~~~~~~
compilation terminated.
那就是没安装上面的第三方库
在Linux (Fedora/RHEL/CentOS)下可能会出现libpcap-devel没有安装的问题,可以通过如下命令安装:
# 安装
vim /etc/yum.repos.d/xxxxxx.repo
[PowerTools]
name=CentOS-$releasever - PowerTools - mirrors.aliyun.com
#failovermethod=priority
baseurl=http://mirrors.aliyun.com/centos/$releasever/PowerTools/$basearch/os/
http://mirrors.aliyuncs.com/centos/$releasever/PowerTools/$basearch/os/
http://mirrors.cloud.aliyuncs.com/centos/$releasever/PowerTools/$basearch/os/
gpgcheck=1
enabled=1
gpgkey=http://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-Official
# 执行如下命令安装
dnf config-manager --set-enabled PowerTools
yum install libpcap
yum install libpcap-devel
缺少gcc导致错误
# fixed by set CGO_ENABLED=1; and istall gcc with libcap-dev
dnf install gcc
2、实现抓包统计流量
实现原理:
1、在解析gopacket数据包时,从数据包中解析出ipLayer和tcpLayer两个层,从ipLayer获取源和目标的ip地址,从tcpLayer获取源和目的端口号。这样可以获取地址:源端口_目标地址:目标端口从而判断数据包是流入还是流出。
2、TCP包的标志位:CWR, ECE, URG, ACK, PSH, RST, SYN, FIN,
(1)当ACK、PSH为true时,表示数据包在传输,统计包的大小。
(2)当ACK、FIN为true时,表示数据包在传输完成,统计包的大小,结束当前统计。
# 标志位说明
CWR:#拥塞窗口减少标志被发送主机设置,用来表明它接收到了设置ECE标志的TCP包。拥塞窗口是被TCP维护的一个内部变量,用来管理发送窗口大小。
ECE:#ECE 响应标志被用来在TCP3次握手时表明一个TCP端是具备ECE功能的,并且表明接收到的TCP包的IP头部的ECN被设置为11。
URG:#此标志表示TCP包的紧急指针域(后面马上就要说到)有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据。
ACK:#此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0。
PSH:#这个标志位表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队。
RST:#这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。
SYN:#表示同步序号,用来建立连接。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0;连接被相应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。扫描者发送一个只有SYN的数据包,如果对方主机响应了一个数据包回来 ,就表明这台主机存在这个端口;但是由于这种扫描方式只是进行TCP三次握手的第一次握手,因此这种扫描的成功表示被扫描的机器不很安全,一台安全的主机将会强制要求一个连接严格的进行TCP的三次握手。
FIN:#表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描。当一个FIN标志的TCP数据包发送到一台计算机的特定端口,如果这台计算机响应了这个数据,并且反馈回来一个RST标志的TCP包,就表明这台计算机上没有打开这个端口,但是这台计算机是存在的;如果这台计算机没有反馈回来任何数据包,这就表明,这台被扫描的计算机存在这个端口。
3、对于http请求判断
(1)请求时http头信息,包括:GET /xxxx HTTP/1.1
(2)响应时包含:HTTP/1.1 200 OK
可以通过信息是否包含http协议来判断是否是http请求和响应完成。
4、判断当前在数据传输的进程,可以通过gopsutil实现,
(1)使用gopsutil获取服务器端监听的程序,通过数据交换的端口是否相同来判断是否是同一个进程。
(2)对于wget、curl之类工具进行上传或下载的,本地没有监听端口,但是有数据交换的端口,可以通过gopsutil的process.Processes()获取所有进程,然后遍历使用Process.Connections()遍历每个进程的链接,查找是否存在相同的端口来判断是否为相同进程。
主要的实现代码:
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gopacket/gopacket"
"github.com/gopacket/gopacket/layers"
"github.com/gopacket/gopacket/pcap"
"github.com/prometheus/client_golang/prometheus"
"strconv"
"strings"
"sync"
"time"
)
// 获取所有网卡的IP,需要用户有权限读取proc/pid目录
func findDeviceIp() {
// 得到所有的(网络)设备
devices, err := pcap.FindAllDevs()
if err != nil {
logger.Fatal(err)
return
}
// 打印设备信息
for _, device := range devices {
var ip = ""
for _, address := range device.Addresses {
ip = address.IP.String()
if common.IsMatch(ip, "[\\d\\.]+") {
break
}
}
if ip != "" {
deviceIpMap[device.Name] = ip
}
logger.Info("===device Name: ", device.Name, ", ", ip)
}
jsonBytes, err := json.MarshalIndent(deviceIpMap2, "", " ")
logger.Info("======deviceIpMap: ", string(jsonBytes))
}
// 对指定的网卡进行监听,进行抓包,并把包放到队列通道中
func listenDevs() {
//device = "eth0"
networkDevice := common.ConfigInfo.NetworkDevice
snapshot_len := int32(1024 * 1024)
promiscuous := true
//timeout := 2 * time.Second
timeout := 200 * time.Millisecond
devices := strings.Split(networkDevice, ",")
for _, device := range devices {
logger.Info("OpenLive======================", device)
_, exists := deviceIpMap[device]
if exists {
go openLive(device, snapshot_len, promiscuous, timeout)
} else {
logger.Info("=======device not exists:", device)
}
}
}
func openLive(devi string, snaplen int32, promisc bool, timeout time.Duration) {
handle, err := pcap.OpenLive(devi, snaplen, promisc, timeout)
if err != nil {
logger.Fatal(err)
}
defer handle.Close()
// 设置过滤器 tcp port 8080 and tcp port 9311
//if err := handle.SetBPFFilter("tcp and port 9311"); err != nil {
// logger.Info("set bpf filter failed: %v", err)
// return
//}
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
// Set up assembly
for packet := range packetSource.Packets() {
handlePacket(packet)
}
}
func handlePacket(packet gopacket.Packet, chanCount uint64) {
payload := ""
// Let's see if the packet is IP (even though the ether type told us)
// 判断数据包是否为IP数据包,可解析出源ip、目的ip、协议号等
ipLayer4 := packet.Layer(layers.LayerTypeIPv4)
if ipLayer4 == nil {
return
}
ip, _ := ipLayer4.(*layers.IPv4)
// 判断数据包是否为TCP数据包,可解析源端口、目的端口、seq序列号、tcp标志位等
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer == nil {
return
}
tcp, _ := tcpLayer.(*layers.TCP)
payload = string(tcp.Payload)
// 接收到数据包
if tcp.ACK && tcp.PSH && len(tcp.Payload) > 0 {
payload = strings.Split(payload, "\n")[0]
dataDelivery(ip, tcp, packet)
}
// 传输结束
if tcp.ACK && tcp.FIN {
httpDelivery(ip, tcp, packet)
}
// 判断layer是否存在错误
if err := packet.ErrorLayer(); err != nil {
logger.Info(packet.Metadata().Timestamp, "========Error decoding some part of the packet:", err)
}
}
// 统计数据包大小,传输流量
func dataDelivery(ip *layers.IPv4, tcp *layers.TCP, packet gopacket.Packet) {
localIP := ip.SrcIP.String()
localPort, _ := strconv.Atoi(tcp.TransportFlow().Src().String())
remoteIP := ip.DstIP.String()
remotePort, _ := strconv.Atoi(tcp.TransportFlow().Dst().String())
byteCount := len(tcp.Payload)
//logger.Info(packet.Metadata().Timestamp, ",handlePacket=====localIP:", localIP, ",localPort:", uint16(localPort), ",remoteIP:", remoteIP, ",remotePort:", uint16(remotePort), ",DstPort:", tcp.DstPort, ",byteCount:", byteCount) // , "Payload:", payload
if byteCount > 0 {
payloadPacket := models.PayloadPacket{localIP, localPort, remoteIP, remotePort, "TCP", byteCount}
payloadChans[0] <- payloadPacket
}
// 获取http请求
http, ok := httpMap.Get(ip.NetworkFlow().Reverse(), tcp.TransportFlow().Reverse())
if ok {
// 响应
localIP, remoteIP = remoteIP, localIP
localPort, remotePort = remotePort, localPort
if http.response == nil {
http.response = &statsStream{
net: ip.NetworkFlow(),
transport: tcp.TransportFlow(),
start: &packet.Metadata().Timestamp,
buffer: bytes.Buffer{},
}
}
http.response.bytes += int64(len(tcp.Payload))
} else {
// 请求
http, ok = httpMap.Get(ip.NetworkFlow(), tcp.TransportFlow())
if ok == false {
s := &statsStream{
net: ip.NetworkFlow(),
transport: tcp.TransportFlow(),
start: &packet.Metadata().Timestamp,
buffer: bytes.Buffer{},
}
http = &RequestResponse{nowTime: time.Now().UnixMilli()}
http.request = s
}
//http.request.buffer.Write(tcp.Payload)
http.request.bytes += int64(len(tcp.Payload))
httpMap.Put(ip.NetworkFlow(), tcp.TransportFlow(), http)
}
}
func httpDelivery(ip *layers.IPv4, tcp *layers.TCP, packet gopacket.Packet) {
http, ok := httpMap.Get(ip.NetworkFlow().Reverse(), tcp.TransportFlow().Reverse())
if ok {
// 响应结束
if http.response == nil {
return
}
http.response.end = &packet.Metadata().Timestamp
http.response.bytes += int64(len(tcp.Payload))
http.response.sawEnd = true
} else {
// 请求结束
http, ok = httpMap.Get(ip.NetworkFlow(), tcp.TransportFlow())
if ok {
if http.request == nil {
return
}
http.request.end = &packet.Metadata().Timestamp
http.request.bytes += int64(len(tcp.Payload))
http.request.sawStart = true
} else {
return
}
}
if http.request != nil && http.response != nil && http.request.sawStart && http.response.sawEnd {
requestResponseChans <- *http
httpMap.Remove(http.request.net, http.request.transport.Reverse())
}
}
3、运行效果


1万+

被折叠的 条评论
为什么被折叠?



