Packet Capture, Analysis, and Injection with Go by John Leon at GopherCon 2016
https://www.youtube.com/watch?v=APDnbmTKjgM
代码:https://github.com/gophercon/2016-talks/tree/master/JohnLeon-PacketCapturingWithGo 博文:http://www.devdungeon.com/content/packet-capture-injection-and-analysis-gopacket
抓包是分析网络上的流量。
有线网络和无线网络不同
有线网络会由交换机根据 MAC 地址决定是否将包转发给你,和你无关的包是不会转发给你,除非用的不是交换机而是古董的 Hub。而无线网络就很开放了,所有的包都无法控制发给谁,因此你是可以听到所有的包的。当然,需要设置为混淆模式,不然本地网络设备会过滤掉不是给自己的包。
抓包不会影响其它通讯,它只是被动监听,不是中间人的干扰。不过可以利用抓包来做一些事情。比如去年参加 DefCon 的时候,John 身边的几个俄罗斯的与会者,就写了个东西抓包监听。凡是听到 HTTP 请求,就抢先一步模拟 HTTP 响应,让访问者重定向到某个NSFW(色情网站)上去了。
子包:
github.com/google/gopacket
github.com/google/gopacket/pcap
github.com/google/gopacket/layers
:解析包用的最多的就是这个包github.com/google/gopacket/pcapgo
类型:
Decoder
Flow
Layer
Packet
PacketSource
Payload
import ( "fmt" "github.com/google/gopacket/pcap" ) func main() { // 获取 libpcap 的版本 version := pcap.Version() fmt.Println(version) // 获取网卡列表 var devices []pcap.Interface devices, _ := pcap.FindAllDevs() } |
---|
pcap.Interface
的定义是
type Interface struct { Name string Description string Address []InterfaceAddress } |
---|
InterfaceAddress
的定义是:
type InterfaceAddress struct { IP net.IP Netmask net.IPMask } |
---|
这是在线捕获分析
handle, _ := pcap.OpenLive( "eth0", // device int32(65535), // snapshot length false, // promiscuous mode? -1 * time.Second, // timeout 负数表示不缓存,直接输出 ) defer handle.Close() |
---|
对于一些抓到的包进行离线分析,可以用文件。
handle, _ := pcap.OpenOffline("dump.pcap") defer handle.Close() |
---|
packetSource := gopacket.NewPacketSource( handle, handle.LinkType() ) |
---|
packet, _ := packetSource.NextPacket() fmt.Println(packet) |
---|
for packet := range packetSource.Packets() { fmt.Println(packet) } |
---|
默认是将所有捕获的包返回回来,而很多时候我们需要关注某个特定类型的包,这时候就需要设置过滤器。这里可以用 Berkeley Packet Filter 的语法:
handle.SetBPFFilter("tcp and port 80") |
---|
在 C 开发中,你必须独立的撰写 BPF,编译,然后再 attach 进来。而 Go 超方便,一行代码就好了。
dumpFile, _ := os.Create("dump.pcap") defer dumpFile.Close() // 准备好写入的 Writer packetWriter := pcapgo.NewWriter(dumpFile) packetWriter.WriteFileHeader( 65535, // Snapshot length layers.LinkTypeEthernet, ) // 写入包 for packet := range packetSource.Packets() { packetWriter.WritePacket( packet.Metadata().CaptureInfo, packet.Data(), ) } |
---|
for _, layer := range packet.Layers() { fmt.Println(layer.LayerType()) } |
---|
包的分层就像俄罗斯套娃,上一层的 payload 是下一层完整的包,下一层解析完得出的 payload,是更下一层的包。
ipLayer := packet.Layer(layers.LayerTypeIPv4) if ipLayer != nil { ip, _ := ipLayer.(*layers.IPv4) fmt.Println(ip.SrcIP, ip.DstIP) fmt.Println(ip.Protocol) } |
---|
tcpLayer := packet.Layer(layers.LayerTypeTCP) if tcpLayer != nil { tcp, _ := tcpLayer.(*layers.TCP) fmt.Println(tcp.SrcPort) fmt.Println(tcp.DstPort) } |
---|
// 解析 ethernet 层 ethernetPacket := gopacket.NewPacket( packet, layers.LayerTypeEthernet, gopacket.Default) // 复制一份包 // 解析 IP 层 ipPacket := gopacket.NewPacket( packet, layers.LayerTypeIPv6, gopacket.NoCopy) // 不复制,所以不要修改 // 解析 TCP 层 tcpPacket := gopacket.NewPacket( packet, layers.LayerTypeTCP, gopacket.Lazy) // 等修改的时候再复制(不是thread safe) ) |
---|
之前说的都是每次创建一个新的包,除此以外,也可以创建一个,以后每次复用。
// 创建所有所需的变量 var eth layers.Ethernet var ip4 layers.IPv4 var tcp layers.TCP parser := gopacket.NewDecodingLayerParser( layers.LayerTypeEthernet, ð, &ip4, &tcp) decodedLayers := []gopacket.LayerType{} // 解析 for packet := range packetSource.Packets() { parser.DecodeLayers(packet, &decodedLayers) for _, layerType := range decodedLayers { fmt.Println(layerType) } } |
---|
这样做的好处是速度很快,因为复用了内存空间。缺点是只能检测定义的包,所以是个权衡。
packet.LinkLayer()
// 以太网packet.NetworkLayer()
// 网络层,通常也就是 IP 层packet.TransportLayer()
// 传输层,比如 TCP/UDPpacket.ApplicationLayer()
// 应用层,比如 HTTP 层。packet.ErrorLayer()
// ……出错了如果有特殊的协议,无论是未公开的私有协议,还是包里没有提供支持的协议,可以自己定义包的结构以及解析方式。
// 注册自定义的层 var MyLayerType = gopacket.RegisterLayerType( 12345, // 唯一的 ID "MyLayerType", // 唯一的名字 gopacket.DecodeFunc(decodeMyLayer), // 解析函数(稍后定义) ) // 定义层的内容 type MyLayer struct { Header []byte payload []byte } // 定义解析函数 func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error { p.AddLayer(&MyLayer{data[:4], data[4:]}) return p.NextDecoder(layers.LayerTypeEthernet) } // 定义一些解析包所需的接口 func (m MyLayer) LayerType() LayerType { return MyLayerType } func (m MyLayer) LayerContents() []byte { return m.Header } func (m MyLayer) LayerPayload() []byte { return m.payload } // 然后就可以像其它内置层一样解析这个自定义的包了 decodedPacket := gopacket.NewPacket( data, MyLayerType, gopacket.Default, ) |
---|
buffer = gopacket.NewSerializeBuffer() options := gopacket.SerializeOptions{} gopacket.SerializeLayers(buffer, options, &layers.Ethernet{}, &layers.IPv4{}, &layers.TCP{}, gopacket.Payload([]byte{65, 66, 67}), ) |
---|
handle.WritePacketData(buffer.Bytes()) |
---|
可以指定一个特定的流的数据,当发现后,会通知你找到了这样的流,当然具体的包还需要你去提取。
someFlow := gopacket.NewFlow( layers.NewUDPPortEndpoint(1000), layers.NewUDPPortEndpoint(500)) t := packet.NetworkLayer() // Check nil if t.TransportFlow() == someFlow { fmt.Println("UDP 1000 -> 500 found.") } |
---|