I have started to learn golang recently (January). I am trying to reproduce a tool we have internally that is written in Python in Go.
So I have the tool working completely for UDP decoding of DNS, however I have been struggling for a week trying to get TCP based DNS decoding. My objective is to log DNS source, destination, query and answers for every packet that hits our DNS servers. In a similar way to what dnstap does, however we have an internal solution currently using Python to suit our in-house custom logging and event correlation system.
func Listen(h *pcap.Handle, c *Config, logger chan<- *dnslog) {
qType := decodeQuery()
OpCode := decodeOpCode()
parser := gopacket.NewDecodingLayerParser(
layers.LayerTypeEthernet,
ð,
&ip4,
&ip6,
&tcp,
&udp,
&dns,
)
decoded := make([]gopacket.LayerType, 0, 10)
for {
data, _, err := h.ZeroCopyReadPacketData()
if err != nil {
log.Println("Error reading packet data ", err)
continue
}
dnslog := &dnslog{}
err = parser.DecodeLayers(data, &decoded)
for _, layer := range decoded {
switch layer {
case layers.LayerTypeIPv4:
dnslog.Dst = ip4.DstIP.String()
dnslog.Src = ip4.SrcIP.String()
case layers.LayerTypeIPv6:
dnslog.Dst = ip6.DstIP.String()
dnslog.Src = ip6.SrcIP.String()
case layers.LayerTypeTCP:
dnslog.Srcport = fmt.Sprintf("%d", tcp.SrcPort)
dnslog.Dstport = fmt.Sprintf("%d", tcp.DstPort)
case layers.LayerTypeUDP:
dnslog.Srcport = fmt.Sprintf("%d", udp.SrcPort)
dnslog.Dstport = fmt.Sprintf("%d", udp.DstPort)
case layers.LayerTypeDNS:
dnslog.Truncated = dns.TC
for _, q := range dns.Questions {
dnslog.OpCode = OpCode[uint8(dns.OpCode)]
dnslog.QueryCount = dns.QDCount
dnslog.AnswerCount = dns.ANCount
}
}
}
logger <- dnslog
}
I have attempted to force the NextLayerType in layers/tcp.go to the DNS Layertype etc in an attempt to find what I am missing. So far no luck. Any advice would be ace. What we see with UDP is the following output. (pprint json encoded output)
[{
"src": "172.10.56.23",
"src_port": "52464",
"dst": "172.10.16.120",
"dst_port": "53",
"bytes": 63,
"transport": "UDP",
"reply_code": "Query",
"query_count": 1,
"answer_count": 0,
"question": ["helposx.apple.com"],
"query_type": "A",
"answer": null,
"truncated": false
}, {
"src": "172.10.16.120",
"src_port": "53",
"dst": "172.10.56.23",
"dst_port": "52464",
"bytes": 156,
"transport": "UDP",
"reply_code": "Query",
"query_count": 1,
"answer_count": 3,
"question": ["helposx.apple.com"],
"query_type": "A",
"answer": [{
"response-name": "helposx.apple.com",
"response-query_type": "CNAME",
"response-ttl": 4607,
"response-bytes": 31,
"response-cname": "helposx.apple.com.edgekey.net",
"response-soa": {},
"response-srv": {},
"response-mx": {}
}, {
"response-name": "helposx.apple.com.edgekey.net",
"response-query_type": "CNAME",
"response-ttl": 33,
"response-bytes": 22,
"response-cname": "e3167.e9.akamaiedge.net",
"response-soa": {},
"response-srv": {},
"response-mx": {}
}, {
"response-name": "e3167.e9.akamaiedge.net",
"response-query_type": "A",
"response-ttl": 13,
"response-bytes": 4,
"response-ip": "104.98.20.77",
"response-soa": {},
"response-srv": {},
"response-mx": {}
}],
"truncated": false
}]
If I now do exactly the same query using dig +tcp (forces TCP) I get the following output.
[{
"src": "172.10.56.23",
"src_port": "57188",
"dst": "172.10.16.120",
"dst_port": "53",
"bytes": 64,
"transport": "TCP",
"reply_code": "",
"query_count": 0,
"answer_count": 0,
"question": null,
"query_type": "",
"answer": null,
"truncated": false
}, {
"src": "172.10.16.120",
"src_port": "53",
"dst": "172.10.56.23",
"dst_port": "57188",
"bytes": 60,
"transport": "TCP",
"reply_code": "",
"query_count": 0,
"answer_count": 0,
"question": null,
"query_type": "",
"answer": null,
"truncated": false
}, {
"src": "172.10.56.23",
"src_port": "57188",
"dst": "172.10.16.120",
"dst_port": "53",
"bytes": 52,
"transport": "TCP",
"reply_code": "",
"query_count": 0,
"answer_count": 0,
"question": null,
"query_type": "",
"answer": null,
"truncated": false
}, {
"src": "172.10.56.23",
"src_port": "57188",
"dst": "172.10.16.120",
"dst_port": "53",
"bytes": 86,
"transport": "TCP",
"reply_code": "",
"query_count": 0,
"answer_count": 0,
"question": null,
"query_type": "",
"answer": null,
"truncated": false
}, {
"src": "172.10.16.120",
"src_port": "53",
"dst": "172.10.56.23",
"dst_port": "57188",
"bytes": 102,
"transport": "TCP",
"reply_code": "",
"query_count": 0,
"answer_count": 0,
"question": null,
"query_type": "",
"answer": null,
"truncated": false
}, {
"src": "172.10.56.23",
"src_port": "57188",
"dst": "172.10.16.120",
"dst_port": "53",
"bytes": 52,
"transport": "TCP",
"reply_code": "",
"query_count": 0,
"answer_count": 0,
"question": null,
"query_type": "",
"answer": null,
"truncated": false
}]
From looking at the same packets in wireshark I can see that those various packets are the TCP handshake, then a response. Which is not decoded.
When I add in a fmt.Println(layer) after the for _, layer := range decoded line I get the following.
Ethernet IPv4 TCP << JSON OUTPUT ABOVE.
VS
Ethernet IPv4 UDP DNS
As you can see there is never a next decoder for the TCP based DNS. It just stops at TCP. I am unsure as to what the solution is. Reading the upstream library looks like it should work. However it does not and I am confused to where I should be looking. Being new to Go it is sending me in loops.