简介
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 (
downSteamDataSize = 0 // 单位时间内下行的总字节数
upSteamDataSize = 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() {
// 只获取以太网帧,因为网速要排除ARP等无关数据包
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
if ethernetLayer != nil {
ethernet := ethernetLayer.(*layers.Ethernet)
// 如果封包的目的MAC是本机则表示是下行的数据包,否则为上行
if ethernet.DstMAC.String() == macAddr {
downSteamDataSize += len(packet.Data()) // 统计下行封包总大小
} else {
upSteamDataSize += 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(downSteamDataSize)/1024/1, float32(upSteamDataSize)/1024/1))
downSteamDataSize = 0
upSteamDataSize = 0
time.Sleep(1 * time.Second)
}
}
输出
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
。