文章目录
- 实时网络数据包捕获 过滤网络流量(BPF过滤器) 读取和解析pcap文件 统计网络接口信息
-
- go get github.com/google/gopacket go get github.com/google/gopacket/pcap
- devices, err := pcap.FindAllDevs() if err != nil { log.Fatal(err) } for _, device := range devices { fmt.Printf(“Device: %sn”, device.Name) fmt.Printf(“Description: %sn”, device.Description) fmt.Printf(“Flags: %dn”, device.Flags) for _, address := range device.Addresses { fmt.Printf(“tIP: %sn”, address.IP) fmt.Printf(“tNetmask: %sn”, address.Netmask) } }
- handle, err := pcap.OpenLive( “eth0”, // 接口名 65536, // 最大包长度 true, // 是否启用混杂模式 pcap.BlockForever, // 超时时间 ) if err != nil { log.Fatal(err) } defer handle.Close()
- err = handle.SetBPFFilter(“tcp and port 80”) if err != nil { log.Fatal(err) }
- packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { // 处理每个数据包 fmt.Println(packet) }
-
- // 解析以太网层 ethernetLayer := packet.Layer(layers.LayerTypeEthernet) if ethernetLayer != nil { ethernetPacket, _ := ethernetLayer.(*layers.Ethernet) fmt.Println(“Source MAC: “, ethernetPacket.SrcMAC) fmt.Println(“Destination MAC: “, ethernetPacket.DstMAC) } // 解析IP层 ipLayer := packet.Layer(layers.LayerTypeIPv4) if ipLayer != nil { ip, _ := ipLayer.(*layers.IPv4) fmt.Println(“Source IP: “, ip.SrcIP) fmt.Println(“Destination IP: “, ip.DstIP) } // 解析TCP层 tcpLayer := packet.Layer(layers.LayerTypeTCP) if tcpLayer != nil { tcp, _ := tcpLayer.(*layers.TCP) fmt.Println(“Source Port: “, tcp.SrcPort) fmt.Println(“Destination Port: “, tcp.DstPort) }
- handle, err := pcap.OpenDead(layers.LinkTypeEthernet, 65536) if err != nil { log.Fatal(err) } defer handle.Close() writer, err := pcap.NewWriter(handle, “output.pcap”) if err != nil { log.Fatal(err) } defer writer.Close() // 创建并写入数据包 // … (创建数据包代码) err = writer.WritePacket(packet.Metadata().CaptureInfo, packet.Data()) if err != nil { log.Fatal(err) }
- handle, err := pcap.OpenOffline(“input.pcap”) if err != nil { log.Fatal(err) } defer handle.Close() packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { fmt.Println(packet) }
- 重用缓冲区:减少内存分配 var buf [4096]byte for { data, ci, err := handle.ReadPacketData() if err != nil { continue } copy(buf[:], data) // 处理数据 } 零拷贝处理:直接操作原始数据 packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default) 并发处理:使用多个goroutine处理数据包 packets := make(chan gopacket.Packet, 1000) go func() { for packet := range packetSource.Packets() { packets <- packet } close(packets) }() for i := 0; i < runtime.NumCPU(); i++ { go func() { for packet := range packets { // 处理数据包 } }() }
- 权限问题:需要root或管理员权限才能捕获网络数据包 接口不可用:确保接口名称正确且处于活动状态 过滤器语法错误:BPF过滤器需要正确语法 内存泄漏:确保及时关闭handle和释放资源
- 首先需要导入必要的包: import ( “github.com/google/gopacket” “github.com/google/gopacket/layers” // 包含DNS层定义 “github.com/google/gopacket/pcap” )
-
- handle, err := pcap.OpenLive(“eth0”, 1600, true, pcap.BlockForever) if err != nil { panic(err) } defer handle.Close() // 设置BPF过滤器捕获DNS流量(UDP和TCP端口53) err = handle.SetBPFFilter(“udp port 53 or tcp port 53”) if err != nil { panic(err) }
- // 捕获发往DNS服务器的请求 err = handle.SetBPFFilter(“dst port 53”) // 或者捕获来自DNS服务器的响应 err = handle.SetBPFFilter(“src port 53”)
- packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { // 检查是否包含DNS层 dnsLayer := packet.Layer(layers.LayerTypeDNS) if dnsLayer != nil { dns, _ := dnsLayer.(*layers.DNS) analyzeDNS(dns) // 自定义解析函数 } }
- func analyzeDNS(dns *layers.DNS) { // 判断是请求还是响应 if dns.QR { fmt.Println(“[DNS Response]”) } else { fmt.Println(“[DNS Query]”) } // 打印基本信息 fmt.Printf(“ID: %d, OpCode: %s, RecursionDesired: %tn”, dns.ID, dns.OpCode, dns.RD) // 解析问题部分(查询的问题) for _, question := range dns.Questions { fmt.Printf(“Query: %s (Type: %s, Class: %s)n”, string(question.Name), question.Type, question.Class) } // 解析回答部分(响应的资源记录) for _, answer := range dns.Answers { fmt.Printf(“Answer: %s -> %s (Type: %s, TTL: %d)n”, string(answer.Name), getAnswerValue(answer), // 自定义函数获取值 answer.Type, answer.TTL) } // 解析权威名称服务器部分 for _, ns := range dns.Authorities { fmt.Printf(“Authority: %s -> %sn”, string(ns.Name), getAnswerValue(ns)) } // 解析附加记录部分 for _, extra := range dns.Additionals { fmt.Printf(“Additional: %s -> %sn”, string(extra.Name), getAnswerValue(extra)) } } // 辅助函数:根据类型获取DNS记录的值 func getAnswerValue(answer layers.DNSResourceRecord) string { switch answer.Type { case layers.DNSTypeA: return answer.IP.String() case layers.DNSTypeAAAA: return answer.IP.String() case layers.DNSTypeCNAME: return string(answer.CNAME) case layers.DNSTypeMX: return fmt.Sprintf(“%s (pref %d)”, string(answer.MX), answer.Preference) case layers.DNSTypeNS: return string(answer.NS) case layers.DNSTypeTXT: return string(answer.TXT) case layers.DNSTypeSOA: return fmt.Sprintf(“MName: %s, RName: %s”, string(answer.SOA.MName), string(answer.SOA.RName)) default: return fmt.Sprintf(“[Unhandled Type %d]”, answer.Type) } }
- DNS通常使用UDP,但大响应可能使用TCP: // 在分析函数中添加TCP处理 tcpLayer := packet.Layer(layers.LayerTypeTCP) if tcpLayer != nil { tcp, _ := tcpLayer.(*layers.TCP) if tcp.SYN { // TCP握手开始 } else if len(tcp.Payload) > 0 { // 尝试解析TCP负载中的DNS dns := &layers.DNS{} err := dns.DecodeFromBytes(tcp.Payload, gopacket.NilDecodeFeedback) if err == nil { analyzeDNS(dns) } } }
- package main import ( “fmt” “log” “time” “github.com/google/gopacket” “github.com/google/gopacket/layers” “github.com/google/gopacket/pcap” ) func main() { // 1. 获取网络设备 devices, err := pcap.FindAllDevs() if err != nil { log.Fatal(err) } // 打印可用设备 fmt.Println(“Available devices:”) for _, dev := range devices { fmt.Printf(“- %s”, dev.Name) if dev.Description != “” { fmt.Printf(” (%s)”, dev.Description) } fmt.Println() } // 2. 打开设备 var deviceName = “eth0” // 根据实际情况修改 var snapshotLen int32 = 1024 var promiscuous = true var timeout = 30 * time.Second handle, err := pcap.OpenLive(deviceName, snapshotLen, promiscuous, timeout) if err != nil { log.Fatal(err) } defer handle.Close() // 3. 设置过滤器 var filter = “udp port 53 or tcp port 53” err = handle.SetBPFFilter(filter) if err != nil { log.Fatal(err) } // 4. 处理数据包 packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { processPacket(packet) } } func processPacket(packet gopacket.Packet) { // 检查DNS层 dnsLayer := packet.Layer(layers.LayerTypeDNS) if dnsLayer != nil { dns, _ := dnsLayer.(*layers.DNS) printDNSInfo(dns) } // 检查TCP DNS tcpLayer := packet.Layer(layers.LayerTypeTCP) if tcpLayer != nil { tcp, _ := tcpLayer.(*layers.TCP) if len(tcp.Payload) > 0 && (tcp.SrcPort == 53 || tcp.DstPort == 53) { dns := &layers.DNS{} err := dns.DecodeFromBytes(tcp.Payload, gopacket.NilDecodeFeedback) if err == nil { printDNSInfo(dns) } } } } func printDNSInfo(dns *layers.DNS) { // 打印基本信息 direction := “Query” if dns.QR { direction = “Response” } fmt.Printf(“n=== %s ===n”, direction) fmt.Printf(“Transaction ID: 0x%xn”, dns.ID) fmt.Printf(“Flags: %s (QR: %t, OpCode: %s, AA: %t, TC: %t, RD: %t, RA: %t, Z: %d, RCode: %s)n”, dns.Flags, dns.QR, dns.OpCode, dns.AA, dns.TC, dns.RD, dns.RA, dns.Z, dns.ResponseCode) // 打印问题部分 if len(dns.Questions) > 0 { fmt.Println(“nQuestions:”) for _, q := range dns.Questions { fmt.Printf(“- %s (Type: %s, Class: %s)n”, string(q.Name), q.Type, q.Class) } } // 打印回答部分 if len(dns.Answers) > 0 { fmt.Println(“nAnswers:”) for _, a := range dns.Answers { fmt.Printf(“- %s: %s (Type: %s, TTL: %d)n”, string(a.Name), getDNSRecordValue(a), a.Type, a.TTL) } } // 打印权威部分 if len(dns.Authorities) > 0 { fmt.Println(“nAuthorities:”) for _, a := range dns.Authorities { fmt.Printf(“- %s: %s (Type: %s, TTL: %d)n”, string(a.Name), getDNSRecordValue(a), a.Type, a.TTL) } } // 打印附加部分 if len(dns.Additionals) > 0 { fmt.Println(“nAdditionals:”) for _, a := range dns.Additionals { fmt.Printf(“- %s: %s (Type: %s, TTL: %d)n”, string(a.Name), getDNSRecordValue(a), a.Type, a.TTL) } } } func getDNSRecordValue(r layers.DNSResourceRecord) string { switch r.Type { case layers.DNSTypeA: return r.IP.String() case layers.DNSTypeAAAA: return r.IP.String() case layers.DNSTypeCNAME: return string(r.CNAME) case layers.DNSTypeMX: return fmt.Sprintf(“%s (pref %d)”, string(r.MX), r.Preference) case layers.DNSTypeNS: return string(r.NS) case layers.DNSTypePTR: return string(r.PTR) case layers.DNSTypeSOA: return fmt.Sprintf(“MName: %s, RName: %s”, string(r.SOA.MName), string(r.SOA.RName)) case layers.DNSTypeTXT: return string(r.TXT) case layers.DNSTypeSRV: return fmt.Sprintf(“Target: %s, Port: %d, Priority: %d, Weight: %d”, string(r.SRV.Name), r.SRV.Port, r.SRV.Priority, r.SRV.Weight) default: return fmt.Sprintf(“[Type %d Data]”, r.Type) } }
- 权限要求:需要root或管理员权限才能捕获网络数据包 性能考虑:高流量环境下可能需要优化处理逻辑 DNS over HTTPS/TLS:这种方法无法解析DoH/DoT加密的DNS流量 EDNS扩展:如果需要解析EDNS扩展信息,需要额外处理OPT记录 通过这种组合使用 pcap 和 layers 包,你可以构建功能强大的DNS监控和分析工具,能够详细解析DNS协议的各种细节。
- 检查 DNS 响应:确保是 DNS 响应 (QR == true) 遍历回答记录:检查 Answers 切片 过滤 A 和 AAAA 记录:获取 IPv4 和 IPv6 地址 解析记录数据:将二进制数据转换为可读格式
- package main import ( “fmt” “github.com/google/gopacket” “github.com/google/gopacket/layers” “github.com/google/gopacket/pcap” “net” ) func main() { // 打开网络接口 handle, err := pcap.OpenLive(“eth0”, 1600, true, pcap.BlockForever) if err != nil { panic(err) } defer handle.Close() // 设置过滤器只捕获 DNS 响应 err = handle.SetBPFFilter(“udp and port 53”) if err != nil { panic(err) } // 开始捕获数据包 packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { // 检查是否包含 DNS 层 dnsLayer := packet.Layer(layers.LayerTypeDNS) if dnsLayer == nil { continue } dns, _ := dnsLayer.(*layers.DNS) // 只处理响应包 if !dns.QR { continue } // 遍历所有回答记录 for _, answer := range dns.Answers { var ip string // 根据记录类型处理 switch answer.Type { case layers.DNSTypeA: // IPv4 地址 if len(answer.Data) == net.IPv4len { ip = net.IP(answer.Data).String() } case layers.DNSTypeAAAA: // IPv6 地址 if len(answer.Data) == net.IPv6len { ip = net.IP(answer.Data).String() } default: continue // 跳过非IP记录 } if ip != “” { fmt.Printf(“域名: %s, IP: %s, TTL: %dn”, string(answer.Name), ip, answer.TTL) } } } }
- 下面是一个更完整的函数,可以处理各种情况: func extractDomainsAndIPs(dns *layers.DNS) map[string][]string { result := make(map[string][]string) // 首先收集所有查询的域名 var domains []string for _, q := range dns.Questions { domains = append(domains, string(q.Name)) } // 处理回答记录 for _, answer := range dns.Answers { domain := string(answer.Name) var ip string switch answer.Type { case layers.DNSTypeA: if len(answer.Data) >= net.IPv4len { ip = net.IP(answer.Data).String() } case layers.DNSTypeAAAA: if len(answer.Data) >= net.IPv6len { ip = net.IP(answer.Data).String() } case layers.DNSTypeCNAME: // 处理CNAME记录 cname := string(answer.Data) result[domain] = append(result[domain], “CNAME: “+cname) continue default: continue } if ip != “” { result[domain] = append(result[domain], ip) } } return result }
- CNAME 记录:如果遇到 CNAME 记录,你可能需要继续查找对应的 A/AAAA 记录 多IP情况:一个域名可能对应多个IP地址 压缩域名:layers.DNS 已经自动处理了DNS名称压缩
- 预分配切片:如果你知道大致数量,可以预分配切片大小 重用缓冲区:在处理大量数据包时重用缓冲区 并行处理:使用 goroutine 池处理数据包
- func processDNSResponse(dns *layers.DNS) { if !dns.QR { return // 不是响应包 } // 创建域名到IP的映射 domainToIPs := make(map[string][]string) cnameMap := make(map[string]string) // 首先处理CNAME记录 for _, answer := range dns.Answers { if answer.Type == layers.DNSTypeCNAME { domain := string(answer.Name) cname := string(answer.Data) cnameMap[domain] = cname } } // 然后处理A和AAAA记录 for _, answer := range dns.Answers { var ip string domain := string(answer.Name) switch answer.Type { case layers.DNSTypeA: if len(answer.Data) >= net.IPv4len { ip = net.IP(answer.Data).String() } case layers.DNSTypeAAAA: if len(answer.Data) >= net.IPv6len { ip = net.IP(answer.Data).String() } default: continue } if ip != “” { // 检查是否有CNAME链 finalDomain := domain for { if cname, exists := cnameMap[finalDomain]; exists { finalDomain = cname } else { break } } domainToIPs[finalDomain] = append(domainToIPs[finalDomain], ip) } } // 打印结果 for domain, ips := range domainToIPs { fmt.Printf(“域名: %sn”, domain) for _, ip := range ips { fmt.Printf(” IP: %sn”, ip) } } } 通过这些方法,你可以有效地从 layers.DNS 数据包中提取域名和对应的IP地址,并处理各种DNS记录类型和特殊情况。
目录
- 详解github.com/google/gopacket/pcap包
- 核心功能
- 基本使用
- 1. 安装
- 2. 获取网络接口列表
- 3. 打开网络接口进行捕获
- 4. 设置BPF过滤器
- 5. 读取数据包
- 高级功能
- 1. 解析数据包
- 2. 写入pcap文件
- 3. 读取pcap文件
- 性能优化
- 常见问题
- 实际应用示例
- 简单的HTTP请求捕获
- 使用github.com/google/gopacket/pcap解析 DNS 请求和响应
- 1. 基本 DNS 解析设置
- 2. 捕获 DNS 流量的配置
- 方法一:捕获所有DNS流量(端口53)
- 方法二:区分请求和响应
- 3. 解析DNS数据包
- 4. 详细DNS解析函数
- 5. 处理TCP DNS流量
- 6. 完整示例:DNS监控工具
- 注意事项
- 从layers.DNS中获取域名和对应 IP 的方法
- 基本方法
- 示例代码
- 更完整的处理函数
- 处理特殊情况
- 性能优化建议
- 完整示例(带CNAME处理)
- 总结
github.com/google/gopacket/pcap 是 Go 语言中一个强大的网络数据包捕获库,它是 gopacket 项目的一部分,提供了对 libpcap(Linux/Unix)和 WinPcap(Windows)的 Go 语言绑定,用于实时网络数据包捕获和分析。
- 实时网络数据包捕获
- 过滤网络流量(BPF过滤器)
- 读取和解析pcap文件
- 统计网络接口信息
go get github.com/google/gopacket
go get github.com/google/gopacket/pcap
devices, err := pcap.FindAllDevs()
if err != nil {
log.Fatal(err)
}
for _, device := range devices {
fmt.Printf("Device: %sn", device.Name)
fmt.Printf("Description: %sn", device.Description)
fmt.Printf("Flags: %dn", device.Flags)
for _, address := range device.Addresses {
fmt.Printf("tIP: %sn", address.IP)
fmt.Printf("tNetmask: %sn", address.Netmask)
}
}
handle, err := pcap.OpenLive(
"eth0", // 接口名
65536, // 最大包长度
true, // 是否启用混杂模式
pcap.BlockForever, // 超时时间
)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
err = handle.SetBPFFilter("tcp and port 80")
if err != nil {
log.Fatal(err)
}
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// 处理每个数据包
fmt.Println(packet)
}
// 解析以太网层
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
if ethernetLayer != nil {
ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)
fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)
fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)
}
// 解析IP层
ipLayer := packet.Layer(layers.LayerTypeIPv4)
if ipLayer != nil {
ip, _ := ipLayer.(*layers.IPv4)
fmt.Println("Source IP: ", ip.SrcIP)
fmt.Println("Destination IP: ", ip.DstIP)
}
// 解析TCP层
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
fmt.Println("Source Port: ", tcp.SrcPort)
fmt.Println("Destination Port: ", tcp.DstPort)
}
handle, err := pcap.OpenDead(layers.LinkTypeEthernet, 65536)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
writer, err := pcap.NewWriter(handle, "output.pcap")
if err != nil {
log.Fatal(err)
}
defer writer.Close()
// 创建并写入数据包
// ... (创建数据包代码)
err = writer.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
if err != nil {
log.Fatal(err)
}
handle, err := pcap.OpenOffline("input.pcap")
if err != nil {
log.Fatal(err)
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
fmt.Println(packet)
}
重用缓冲区:减少内存分配
var buf [4096]byte
for {
data, ci, err := handle.ReadPacketData()
if err != nil {
continue
}
copy(buf[:], data)
// 处理数据
}
零拷贝处理:直接操作原始数据
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default)
并发处理:使用多个goroutine处理数据包
packets := make(chan gopacket.Packet, 1000)
go func() {
for packet := range packetSource.Packets() {
packets <- packet
}
close(packets)
}()
for i := 0; i < runtime.NumCPU(); i++ {
go func() {
for packet := range packets {
// 处理数据包
}
}()
}
- 权限问题:需要root或管理员权限才能捕获网络数据包
- 接口不可用:确保接口名称正确且处于活动状态
- 过滤器语法错误:BPF过滤器需要正确语法
- 内存泄漏:确保及时关闭handle和释放资源
handle, err := pcap.OpenLive("eth0", 1600, true, pcap.BlockForever)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
err = handle.SetBPFFilter("tcp and port 80")
if err != nil {
log.Fatal(err)
}
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
appLayer := packet.ApplicationLayer()
if appLayer != nil {
payload := appLayer.Payload()
if bytes.Contains(payload, []byte("HTTP")) {
fmt.Printf("%sn", payload)
}
}
}
pcap 包是 Go 中网络监控和安全工具开发的基础,结合 gopacket 的其他组件可以构建强大的网络分析工具。
github.com/google/gopacket/pcap 可以捕获和解析 DNS 请求和响应,但需要结合 gopacket/layers 包中的 DNS 层解析功能。
首先需要导入必要的包:
import (
"github.com/google/gopacket"
"github.com/google/gopacket/layers" // 包含DNS层定义
"github.com/google/gopacket/pcap"
)
handle, err := pcap.OpenLive("eth0", 1600, true, pcap.BlockForever)
if err != nil {
panic(err)
}
defer handle.Close()
// 设置BPF过滤器捕获DNS流量(UDP和TCP端口53)
err = handle.SetBPFFilter("udp port 53 or tcp port 53")
if err != nil {
panic(err)
}
// 捕获发往DNS服务器的请求
err = handle.SetBPFFilter("dst port 53")
// 或者捕获来自DNS服务器的响应
err = handle.SetBPFFilter("src port 53")
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// 检查是否包含DNS层
dnsLayer := packet.Layer(layers.LayerTypeDNS)
if dnsLayer != nil {
dns, _ := dnsLayer.(*layers.DNS)
analyzeDNS(dns) // 自定义解析函数
}
}
func analyzeDNS(dns *layers.DNS) {
// 判断是请求还是响应
if dns.QR {
fmt.Println("[DNS Response]")
} else {
fmt.Println("[DNS Query]")
}
// 打印基本信息
fmt.Printf("ID: %d, OpCode: %s, RecursionDesired: %tn",
dns.ID, dns.OpCode, dns.RD)
// 解析问题部分(查询的问题)
for _, question := range dns.Questions {
fmt.Printf("Query: %s (Type: %s, Class: %s)n",
string(question.Name),
question.Type,
question.Class)
}
// 解析回答部分(响应的资源记录)
for _, answer := range dns.Answers {
fmt.Printf("Answer: %s -> %s (Type: %s, TTL: %d)n",
string(answer.Name),
getAnswerValue(answer), // 自定义函数获取值
answer.Type,
answer.TTL)
}
// 解析权威名称服务器部分
for _, ns := range dns.Authorities {
fmt.Printf("Authority: %s -> %sn",
string(ns.Name),
getAnswerValue(ns))
}
// 解析附加记录部分
for _, extra := range dns.Additionals {
fmt.Printf("Additional: %s -> %sn",
string(extra.Name),
getAnswerValue(extra))
}
}
// 辅助函数:根据类型获取DNS记录的值
func getAnswerValue(answer layers.DNSResourceRecord) string {
switch answer.Type {
case layers.DNSTypeA:
return answer.IP.String()
case layers.DNSTypeAAAA:
return answer.IP.String()
case layers.DNSTypeCNAME:
return string(answer.CNAME)
case layers.DNSTypeMX:
return fmt.Sprintf("%s (pref %d)", string(answer.MX), answer.Preference)
case layers.DNSTypeNS:
return string(answer.NS)
case layers.DNSTypeTXT:
return string(answer.TXT)
case layers.DNSTypeSOA:
return fmt.Sprintf("MName: %s, RName: %s", string(answer.SOA.MName), string(answer.SOA.RName))
default:
return fmt.Sprintf("[Unhandled Type %d]", answer.Type)
}
}
DNS通常使用UDP,但大响应可能使用TCP:
// 在分析函数中添加TCP处理
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
if tcp.SYN {
// TCP握手开始
} else if len(tcp.Payload) > 0 {
// 尝试解析TCP负载中的DNS
dns := &layers.DNS{}
err := dns.DecodeFromBytes(tcp.Payload, gopacket.NilDecodeFeedback)
if err == nil {
analyzeDNS(dns)
}
}
}
package main
import (
"fmt"
"log"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)
func main() {
// 1. 获取网络设备
devices, err := pcap.FindAllDevs()
if err != nil {
log.Fatal(err)
}
// 打印可用设备
fmt.Println("Available devices:")
for _, dev := range devices {
fmt.Printf("- %s", dev.Name)
if dev.Description != "" {
fmt.Printf(" (%s)", dev.Description)
}
fmt.Println()
}
// 2. 打开设备
var deviceName = "eth0" // 根据实际情况修改
var snapshotLen int32 = 1024
var promiscuous = true
var timeout = 30 * time.Second
handle, err := pcap.OpenLive(deviceName, snapshotLen, promiscuous, timeout)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// 3. 设置过滤器
var filter = "udp port 53 or tcp port 53"
err = handle.SetBPFFilter(filter)
if err != nil {
log.Fatal(err)
}
// 4. 处理数据包
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
processPacket(packet)
}
}
func processPacket(packet gopacket.Packet) {
// 检查DNS层
dnsLayer := packet.Layer(layers.LayerTypeDNS)
if dnsLayer != nil {
dns, _ := dnsLayer.(*layers.DNS)
printDNSInfo(dns)
}
// 检查TCP DNS
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
if len(tcp.Payload) > 0 && (tcp.SrcPort == 53 || tcp.DstPort == 53) {
dns := &layers.DNS{}
err := dns.DecodeFromBytes(tcp.Payload, gopacket.NilDecodeFeedback)
if err == nil {
printDNSInfo(dns)
}
}
}
}
func printDNSInfo(dns *layers.DNS) {
// 打印基本信息
direction := "Query"
if dns.QR {
direction = "Response"
}
fmt.Printf("n=== %s ===n", direction)
fmt.Printf("Transaction ID: 0x%xn", dns.ID)
fmt.Printf("Flags: %s (QR: %t, OpCode: %s, AA: %t, TC: %t, RD: %t, RA: %t, Z: %d, RCode: %s)n",
dns.Flags, dns.QR, dns.OpCode, dns.AA, dns.TC, dns.RD, dns.RA, dns.Z, dns.ResponseCode)
// 打印问题部分
if len(dns.Questions) > 0 {
fmt.Println("nQuestions:")
for _, q := range dns.Questions {
fmt.Printf("- %s (Type: %s, Class: %s)n",
string(q.Name), q.Type, q.Class)
}
}
// 打印回答部分
if len(dns.Answers) > 0 {
fmt.Println("nAnswers:")
for _, a := range dns.Answers {
fmt.Printf("- %s: %s (Type: %s, TTL: %d)n",
string(a.Name), getDNSRecordValue(a), a.Type, a.TTL)
}
}
// 打印权威部分
if len(dns.Authorities) > 0 {
fmt.Println("nAuthorities:")
for _, a := range dns.Authorities {
fmt.Printf("- %s: %s (Type: %s, TTL: %d)n",
string(a.Name), getDNSRecordValue(a), a.Type, a.TTL)
}
}
// 打印附加部分
if len(dns.Additionals) > 0 {
fmt.Println("nAdditionals:")
for _, a := range dns.Additionals {
fmt.Printf("- %s: %s (Type: %s, TTL: %d)n",
string(a.Name), getDNSRecordValue(a), a.Type, a.TTL)
}
}
}
func getDNSRecordValue(r layers.DNSResourceRecord) string {
switch r.Type {
case layers.DNSTypeA:
return r.IP.String()
case layers.DNSTypeAAAA:
return r.IP.String()
case layers.DNSTypeCNAME:
return string(r.CNAME)
case layers.DNSTypeMX:
return fmt.Sprintf("%s (pref %d)", string(r.MX), r.Preference)
case layers.DNSTypeNS:
return string(r.NS)
case layers.DNSTypePTR:
return string(r.PTR)
case layers.DNSTypeSOA:
return fmt.Sprintf("MName: %s, RName: %s", string(r.SOA.MName), string(r.SOA.RName))
case layers.DNSTypeTXT:
return string(r.TXT)
case layers.DNSTypeSRV:
return fmt.Sprintf("Target: %s, Port: %d, Priority: %d, Weight: %d",
string(r.SRV.Name), r.SRV.Port, r.SRV.Priority, r.SRV.Weight)
default:
return fmt.Sprintf("[Type %d Data]", r.Type)
}
}
- 权限要求:需要root或管理员权限才能捕获网络数据包
- 性能考虑:高流量环境下可能需要优化处理逻辑
- DNS over HTTPS/TLS:这种方法无法解析DoH/DoT加密的DNS流量
- EDNS扩展:如果需要解析EDNS扩展信息,需要额外处理OPT记录
通过这种组合使用 pcap 和 layers 包,你可以构建功能强大的DNS监控和分析工具,能够详细解析DNS协议的各种细节。
要从 layers.DNS 数据包中提取域名和对应的 IP 地址,你需要检查 DNS 响应中的回答部分 (Answers)。以下是详细方法和示例代码:
- 检查 DNS 响应:确保是 DNS 响应 (
QR == true)
- 遍历回答记录:检查
Answers 切片
- 过滤 A 和 AAAA 记录:获取 IPv4 和 IPv6 地址
- 解析记录数据:将二进制数据转换为可读格式
QR == true)Answers 切片
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"net"
)
func main() {
// 打开网络接口
handle, err := pcap.OpenLive("eth0", 1600, true, pcap.BlockForever)
if err != nil {
panic(err)
}
defer handle.Close()
// 设置过滤器只捕获 DNS 响应
err = handle.SetBPFFilter("udp and port 53")
if err != nil {
panic(err)
}
// 开始捕获数据包
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// 检查是否包含 DNS 层
dnsLayer := packet.Layer(layers.LayerTypeDNS)
if dnsLayer == nil {
continue
}
dns, _ := dnsLayer.(*layers.DNS)
// 只处理响应包
if !dns.QR {
continue
}
// 遍历所有回答记录
for _, answer := range dns.Answers {
var ip string
// 根据记录类型处理
switch answer.Type {
case layers.DNSTypeA:
// IPv4 地址
if len(answer.Data) == net.IPv4len {
ip = net.IP(answer.Data).String()
}
case layers.DNSTypeAAAA:
// IPv6 地址
if len(answer.Data) == net.IPv6len {
ip = net.IP(answer.Data).String()
}
default:
continue // 跳过非IP记录
}
if ip != "" {
fmt.Printf("域名: %s, IP: %s, TTL: %dn",
string(answer.Name), ip, answer.TTL)
}
}
}
}
下面是一个更完整的函数,可以处理各种情况:
func extractDomainsAndIPs(dns *layers.DNS) map[string][]string {
result := make(map[string][]string)
// 首先收集所有查询的域名
var domains []string
for _, q := range dns.Questions {
domains = append(domains, string(q.Name))
}
// 处理回答记录
for _, answer := range dns.Answers {
domain := string(answer.Name)
var ip string
switch answer.Type {
case layers.DNSTypeA:
if len(answer.Data) >= net.IPv4len {
ip = net.IP(answer.Data).String()
}
case layers.DNSTypeAAAA:
if len(answer.Data) >= net.IPv6len {
ip = net.IP(answer.Data).String()
}
case layers.DNSTypeCNAME:
// 处理CNAME记录
cname := string(answer.Data)
result[domain] = append(result[domain], "CNAME: "+cname)
continue
default:
continue
}
if ip != "" {
result[domain] = append(result[domain], ip)
}
}
return result
}
- CNAME 记录:如果遇到 CNAME 记录,你可能需要继续查找对应的 A/AAAA 记录
- 多IP情况:一个域名可能对应多个IP地址
- 压缩域名:
layers.DNS 已经自动处理了DNS名称压缩
layers.DNS 已经自动处理了DNS名称压缩
- 预分配切片:如果你知道大致数量,可以预分配切片大小
- 重用缓冲区:在处理大量数据包时重用缓冲区
- 并行处理:使用 goroutine 池处理数据包
func processDNSResponse(dns *layers.DNS) {
if !dns.QR {
return // 不是响应包
}
// 创建域名到IP的映射
domainToIPs := make(map[string][]string)
cnameMap := make(map[string]string)
// 首先处理CNAME记录
for _, answer := range dns.Answers {
if answer.Type == layers.DNSTypeCNAME {
domain := string(answer.Name)
cname := string(answer.Data)
cnameMap[domain] = cname
}
}
// 然后处理A和AAAA记录
for _, answer := range dns.Answers {
var ip string
domain := string(answer.Name)
switch answer.Type {
case layers.DNSTypeA:
if len(answer.Data) >= net.IPv4len {
ip = net.IP(answer.Data).String()
}
case layers.DNSTypeAAAA:
if len(answer.Data) >= net.IPv6len {
ip = net.IP(answer.Data).String()
}
default:
continue
}
if ip != "" {
// 检查是否有CNAME链
finalDomain := domain
for {
if cname, exists := cnameMap[finalDomain]; exists {
finalDomain = cname
} else {
break
}
}
domainToIPs[finalDomain] = append(domainToIPs[finalDomain], ip)
}
}
// 打印结果
for domain, ips := range domainToIPs {
fmt.Printf("域名: %sn", domain)
for _, ip := range ips {
fmt.Printf(" IP: %sn", ip)
}
}
}
通过这些方法,你可以有效地从 layers.DNS 数据包中提取域名和对应的IP地址,并处理各种DNS记录类型和特殊情况。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。
您可能感兴趣的文章:
- 使用Golang实现对网络数据包的捕获与分析
- Golang实现读取ZIP压缩包并显示Gin静态html网站
- golang版本升级如何实现
- golang格式化输出函数printf、sprintf、fprintf解读
- golang如何获取域名ip dns信息