135 lines
4.2 KiB
Python
Executable file
135 lines
4.2 KiB
Python
Executable file
#!/usr/bin/env python
|
|
#
|
|
# Copyright 2019-2024 OARC, Inc.
|
|
# Copyright 2017-2018 Akamai Technologies
|
|
# Copyright 2006-2016 Nominum, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import dns.message
|
|
import dns.rrset
|
|
import dns.flags
|
|
import dns.name
|
|
import pcapy
|
|
import socket
|
|
import sys
|
|
import struct
|
|
from optparse import OptionParser
|
|
|
|
|
|
__author__ = "Nominum, Inc."
|
|
__version__ = "1.0.2.0"
|
|
__date__ = "2007-05-14"
|
|
|
|
IPHeader = '!BBHHHBBHLL'
|
|
|
|
IPHDRLEN = 20
|
|
UDPHDRLEN = 8
|
|
LINKTYPE_C_HDLC = 104
|
|
LINKTYPE_ETHERNET = 1
|
|
qtypecount = {}
|
|
|
|
|
|
def main(argv):
|
|
parser = OptionParser(usage="%prog [options]",
|
|
version = "%prog " + __version__ )
|
|
parser.add_option("-i", "--input", dest="fin",
|
|
help="name of tcpdump file to parse", metavar="FILE")
|
|
parser.add_option("-o", "--output", dest="fout",
|
|
help="file in which to save parsed DNS queries",
|
|
metavar="FILE")
|
|
parser.add_option("-r", "--recursion", dest="recurse", action="store_true",
|
|
default=False,
|
|
help="Keep queries whose RD flag is 0 (default: discard)")
|
|
parser.add_option("-R", "--responses", dest="responses",
|
|
action="store_true", default=False,
|
|
help="Parse query responses instead of queries")
|
|
(opts, args) = parser.parse_args()
|
|
|
|
if opts.fin:
|
|
pcap = pcapy.open_offline(opts.fin)
|
|
else:
|
|
pcap = pcapy.open_offline('-')
|
|
linktype = pcap.datalink()
|
|
if linktype == LINKTYPE_C_HDLC:
|
|
IPHDRSTART = 4
|
|
else:
|
|
IPHDRSTART = 14
|
|
if opts.fout:
|
|
outfile = open(opts.fout, "w")
|
|
else:
|
|
outfile = sys.stdout
|
|
while True:
|
|
try:
|
|
packet = pcap.next()
|
|
except Exception:
|
|
break
|
|
|
|
if packet[0] is None:
|
|
break
|
|
packet = packet[1]
|
|
# Toss the stuff before the IP header
|
|
packet = packet[IPHDRSTART:]
|
|
|
|
# Grab the rest of the packet so we can parse proto
|
|
iphdr = packet[0:IPHDRLEN]
|
|
if len(iphdr) < IPHDRLEN:
|
|
continue
|
|
(vhl, tos, tlen, ipid, fragoff, ttl, proto, cksum, srcip, dstip) = \
|
|
struct.unpack(IPHeader, iphdr)
|
|
|
|
# Toss the IP header, we're done with it. We need to account
|
|
# for any IP header options.
|
|
ihl = (vhl & 0xF) * 4
|
|
packet = packet[ihl:]
|
|
|
|
if proto == socket.IPPROTO_UDP: # UDP, 8-byte header
|
|
packet = packet[UDPHDRLEN:]
|
|
else:
|
|
continue
|
|
|
|
try:
|
|
msg = dns.message.from_wire(packet)
|
|
except Exception:
|
|
continue
|
|
if not opts.recurse and not dns.flags.RD:
|
|
continue
|
|
if opts.responses:
|
|
querytest = msg.flags & dns.flags.QR
|
|
else:
|
|
querytest = not (msg.flags & dns.flags.QR)
|
|
if querytest:
|
|
for query in msg.question: # handle multiple queries per packet
|
|
fqdn = query.name.to_text()
|
|
qtype = dns.rdatatype.to_text(query.rdtype)
|
|
outfile.write("%s %s\n" % (fqdn, qtype))
|
|
# add qtype to dict if not present, otherwise increment
|
|
qtypecount[query.rdtype] = qtypecount.get(query.rdtype, 0) + 1
|
|
|
|
if outfile is not sys.stdout:
|
|
outfile.close()
|
|
sum = 0
|
|
print("Statistics:")
|
|
qtypes = list(qtypecount.keys())
|
|
qtypes.sort()
|
|
for qtype in qtypes:
|
|
qtype_str = dns.rdatatype.to_text(qtype)
|
|
count = qtypecount[qtype]
|
|
print(" %10s:\t%d" % (qtype_str, count))
|
|
sum += count
|
|
print("-------------------------")
|
|
print(" TOTAL:\t%d" % sum)
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:])
|