You are here: Home > Publications > RIPE Labs > Shane Kerr > ScanPcap

ScanPcap

Shane Kerr — 12 May 2015
ScanPcap.py file

Python Source icon ScanPcap.py — Python Source, 5 KB (5974 bytes)

File contents

import psyco
psyco.full()

import sys
import pcap
import cPickle
import time

# http://www.dnspython.org/
import dns.message
import dns.rdataclass
import dns.rdatatype

if len(sys.argv) < 4:
    sys.stderr.write("Syntax: %s MAC-address mapping_pickle pcap_file_name [pcap_file_name ...]\n", sys.argv[0])
    sys.exit(1)

server_mac_str = sys.argv[1]
server_mac = pcap.str2mac(server_mac_str)
if server_mac is None:
    sys.stderr.write('Invalid MAC address "%s"\n' % server_mac_str)
    sys.exit(1)
sys.stderr.write("Scanning packets to/from MAC address %s\n" % server_mac_str.upper())

# get total and working IP addresses per server
# this is a mapping of name servers to (total_ip, good_ip) sequences
mapping_pickle_file = file(sys.argv[2])
ns_ip = cPickle.load(mapping_pickle_file)
sys.stderr.write("Using name server mapping from '%s'\n" % sys.argv[2])

# our various counters
pkt_num = 0
srv_num = 0
bad_num = 0
ns_num = 0
rcode_num = 0
stale_num = 0
ns_without_ip = { }
ip_not_answering = { }

last_time = time.time()
for pcap_file_name in sys.argv[3:]:
    # open our input file
    try:
        dump_file = pcap.pcap_file(pcap_file_name)
    except IOError, e:
        sys.stderr.write("Error opening pcapfile; " + str(e) + "\n")
        sys.exit(1)
    sys.stderr.write("\rScanning file '%s'\n" % pcap_file_name)

    # read and process each packet
    while True:
        # read next packet
        (pkt_time, pkt_len, eth_pkt) = dump_file.next()
        if not eth_pkt: 
            break
        pkt_num = pkt_num + 1

        # check for truncation
        assert(pkt_len == len(eth_pkt))

        # parse Ethernet frame
        (mac_src, mac_dst, eth_ptype, ip_pkt) = pcap.eth_pkt_parse(eth_pkt)

        # only look at packets coming from the server
        if mac_src != server_mac: 
            continue
        srv_num = srv_num + 1

        # verify we have IPv4 or IPv6 packet
        assert((eth_ptype == 0x0800) or (eth_ptype == 0x86DD))
 
        # parse IP packet
        (version, ip_src, ip_dst, ip_proto, ip_data) = pcap.ip_pkt_parse(ip_pkt)

        # double-check our IP version
        assert((version == 4) or (version == 6))

        # confirm we are UDP
        if ip_proto != 0x11:
            sys.stderr.write("\nSkipping ip_proto 0x%02X\n" % ip_proto)
            continue
        assert(ip_proto == 0x11)

        # parse UDP packet
        (port_src, port_dst, udp_data) = pcap.udp_pkt_parse(ip_data)

        # confirm we are coming from the DNS port
        assert(port_src == 53)

        # parse DNS packet
        try:
            msg = dns.message.from_wire(udp_data)
        except:
            bad_num = bad_num + 1
            continue

        # check RCODE
        if msg.rcode() != 0:
           rcode_num = rcode_num + 1
           continue

        # look for NS RRset
        ns = { }
        for rrset in msg.authority:
            rdataset = rrset.to_rdataset()
            if (rdataset.rdclass == dns.rdataclass.IN) and (rdataset.rdtype == dns.rdatatype.NS):
                for rdata in rdataset:
                    name = str(rdata)[:-1].upper()
                    ns[name] = 1
        if not ns:
            continue

        # skip stale versions
        stale = False
        for name_server in ns:
            if name_server not in ns_ip:
                stale = True
                break
        if stale:
            stale_num = stale_num + 1
            continue

        # okay we have enough data to go on
        ns_num = ns_num + 1
 
        # check nameservers to see if any are broken
        num_ns_without_ip = 0
        num_ip = 0
        num_bad_ip = 0
        for name_server in ns:
            if ns_ip[name_server][0] == 0:
                num_ns_without_ip = num_ns_without_ip + 1
            num_ip = num_ip + ns_ip[name_server][0]
            num_bad_ip = num_bad_ip + (ns_ip[name_server][0] - ns_ip[name_server][1])
        idx = (num_ns_without_ip, len(ns))
        ns_without_ip[idx] = ns_without_ip.get(idx, 0) + 1
        idx = (num_bad_ip, num_ip)
        ip_not_answering[idx] = ip_not_answering.get(idx, 0) + 1

        # periodically let us know what's going on
        if time.time() - last_time > 2:
            sys.stderr.write("\r%d packets scanned" % pkt_num)
            last_time = time.time()


# output counts
sys.stdout.write("""
Total packets scanned:     %8d
Replies sent from server:  %8d
Unparsable replies:        %8d
Packets with non-0 rcode:  %8d
Packets with stale NS:     %8d
Packets with NS in RRSET:  %8d

""" % (pkt_num, srv_num, bad_num, rcode_num, stale_num, ns_num))

# estimate NS lookup failures
ns_lookup_failures = 0.0
total_with_failure = 0.0
for key in ns_without_ip.keys():
    (bad_ns, ns) = key
    total_with_failure = total_with_failure + ((bad_ns / float(ns)) * ns_without_ip[key])
    failures = 0.0
    while bad_ns > 0:
        failures = failures + (bad_ns / float(ns))
        bad_ns = bad_ns - 1
        ns = ns - 1
    ns_lookup_failures = ns_lookup_failures + (failures * ns_without_ip[key])
sys.stdout.write("Estimate about %.3f lookups have NS lookup failures\n" % total_with_failure)
sys.stdout.write("Estimate about %.3f NS lookup failures\n" % ns_lookup_failures)

# estimate extra packets to non-answering IP 
ip_timeouts = 0.0
total_with_failure = 0.0
for key in ip_not_answering.keys():
    (bad_ip, ip) = key
#    if ip == 0:
#        total_with_failure = total_with_failure + ip_not_answering[key]
#    else:
    if ip > 0:
        total_with_failure = total_with_failure + ((bad_ip / float(ip)) * ip_not_answering[key])
    extra_packets = 0.0
    while bad_ip > 0:
        extra_packets = extra_packets + (bad_ip / float(ip))
        bad_ip = bad_ip - 1
        ip = ip - 1
    ip_timeouts = ip_timeouts + (extra_packets * ip_not_answering[key])
sys.stdout.write("Estimate about %.3f lookups use non-answering IP\n" % total_with_failure)
sys.stdout.write("Estimate about %.3f queries to non-answering IP\n" % ip_timeouts)