简介
gopacket是经过cgo封装的libpcap的接口,这样便于我们在go语言中使用libpcap。
前提
libpcap是Linux平台的抓包框架,它也有Windows移植版,比如winpcap,但在2013年已停止维护。所以本文采用的是winpcap官方推荐的npcap。
npcap的安装
- 首先下载npcap以及npcap-sdk;
- 安装
npcap; - 解压
npcap-sdk到C:\WpdPack。(gopacket的cgo实现会在这个位置寻找pcap.h)
代码
package main
import (
"errors"
"flag"
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"net"
"os"
"time"
)
var (
downStreamDataSize = 0 // 单位时间内下行的总字节数
upStreamDataSize = 0 // 单位时间内上行的总字节数
deviceName = flag.String("i", "eth0", "network interface device name") // 要监控的网卡名称
)
func main() {
flag.Parse()
// Find all devices
// 获取所有网卡
devices, err := pcap.FindAllDevs()
if err != nil {
log.Fatal(err)
}
// Find exact device
// 根据网卡名称从所有网卡中取到精确的网卡
var device pcap.Interface
for _, d := range devices {
if d.Name == *deviceName {
device = d
}
}
// 根据网卡的ipv4地址获取网卡的mac地址,用于后面判断数据包的方向
macAddr, err := findMacAddrByIp(findDeviceIpv4(device))
if err != nil {
panic(err)
}
fmt.Printf("Chosen device's IPv4: %s\n", findDeviceIpv4(device))
fmt.Printf("Chosen device's MAC: %s\n", macAddr)
// 获取网卡handler,可用于读取或写入数据包
handle, err := pcap.OpenLive(*deviceName, 1024 /*每个数据包读取的最大值*/, true /*是否开启混杂模式*/, 30*time.Second /*读包超时时长*/)
if err != nil {
panic(err)
}
defer handle.Close()
// 开启子线程,每一秒计算一次该秒内的数据包大小平均值,并将下载、上传总量置零
go monitor()
// 开始抓包
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// 只获取以太网帧
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
if ethernetLayer != nil {
ethernet := ethernetLayer.(*layers.Ethernet)
// 如果封包的目的MAC是本机则表示是下行的数据包,否则为上行
if ethernet.DstMAC.String() == macAddr {
downStreamDataSize += len(packet.Data()) // 统计下行封包总大小
} else {
upStreamDataSize += len(packet.Data()) // 统计上行封包总大小
}
}
}
}
// 获取网卡的IPv4地址
func findDeviceIpv4(device pcap.Interface) string {
for _, addr := range device.Addresses {
if ipv4 := addr.IP.To4(); ipv4 != nil {
return ipv4.String()
}
}
panic("device has no IPv4")
}
// 根据网卡的IPv4地址获取MAC地址
// 有此方法是因为gopacket内部未封装获取MAC地址的方法,所以这里通过找到IPv4地址相同的网卡来寻找MAC地址
func findMacAddrByIp(ip string) (string, error) {
interfaces, err := net.Interfaces()
if err != nil {
panic(interfaces)
}
for _, i := range interfaces {
addrs, err := i.Addrs()
if err != nil {
panic(err)
}
for _, addr := range addrs {
if a, ok := addr.(*net.IPNet); ok {
if ip == a.IP.String() {
return i.HardwareAddr.String(), nil
}
}
}
}
return "", errors.New(fmt.Sprintf("no device has given ip: %s", ip))
}
// 每一秒计算一次该秒内的数据包大小平均值,并将下载、上传总量置零
func monitor() {
for {
os.Stdout.WriteString(fmt.Sprintf("\rDown:%.2fkb/s \t Up:%.2fkb/s", float32(downStreamDataSize)/1024/1, float32(upStreamDataSize)/1024/1))
downStreamDataSize = 0
upStreamDataSize = 0
time.Sleep(1 * time.Second)
}
}
运行
运行的时候需要指定网卡名称,如: ./speedTest -i etc0。
但是在Windows平台有些不一样,因为gopacket接口返回的deviceName是Windows平台特有Transport Name,通过getmac命令即可获得MAC与Transport Name的对应关系了:
C:\Users\hanzi>getmac
物理地址 传输名称
=================== ==========================================================
00-50-56-C0-00-08 \Device\Tcpip_{0849F909-8F06-475C-B0D3-81F5902DA026}
00-FF-D8-BD-B9-CF 媒体已断开连接
00-50-56-C0-00-01 \Device\Tcpip_{55A0F561-C6BF-4E15-9E97-D378BDB2B856}
36-15-2F-36-64-AE \Device\Tcpip_{E1264F2B-388D-4992-A9DE-040165E3A4EE}
4C-CC-6A-B7-E6-D6 \Device\Tcpip_{DF0ECEC3-C9D5-4D38-946F-3E5CD67CCA2E}
02-00-4C-4F-4F-50 \Device\Tcpip_{0D1DF6B1-ADA4-485B-B0A6-B95C2EC619A5}
在笔者电脑上运行./speedTest -i \Device\NPF_{DF0ECEC3-C9D5-4D38-946F-3E5CD67CCA2E}, 输出:
Chosen device's IPv4: 192.168.0.8
Chosen device's MAC: 4c:cc:6a:b7:e6:d6
Down:48.26kb/s Up:3.20kb/s
以上代码的正确运行需要网卡有IPv4,但可以很容易的扩展到IPv6(响应工信部号召) 。
本文介绍如何使用Go语言和gopacket库进行网络流量监控,包括安装配置npcap,通过抓包获取上行和下行数据包大小,以及计算每秒平均流量。
1865

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



