from ctypes import *
from ctypes.util import find_library
import array
import pcap
import time
import sys

c = CDLL(find_library("c"))
c.memcmp.argtypes = [c_void_p, c_void_p, c_size_t]

def sbo32(pInt):
    a = array.array("L", (pInt,))
    a.byteswap()
    return a[0]

def sbo16(pInt):
    a = array.array("H", (pInt,))
    a.byteswap()
    return a[0]

class ARP(LittleEndianStructure):
    _fields_ = [("htype",   c_uint16),
                ("ptype",   c_uint16),
                ("hlen",    c_uint8),
                ("plen",    c_uint8),
                ("oper",    c_uint16),
                ("sha",     c_uint8 * 6),
                ("spa",     c_uint8 * 4),
                ("tha",     c_uint8 * 6),
                ("tpa",     c_uint8 * 4)]

class Eth(LittleEndianStructure):
    _fields_ = [("dst",     c_uint8 * 6),
                ("src",     c_uint8 * 6),
                ("type",    c_uint16)]
   
   
eth_len = sizeof(Eth)
arp_len = sizeof(ARP)
eth_brd = (c_uint8 * 6)(0xFF)
eth_arp = sbo16(0x0806)
eth_arp_op_req = sbo16(0x01)
eth_arp_op_rep = sbo16(0x02)

def printeth(header, eth):
    print ("Got packet, length was %d type: 0x%X src: %s dst: %s " % (
        header.contents.len, eth.contents.type,
        eth.contents.src, eth.contents.dst))


offset_eth_p = None
offset_arp_p = None
header_p = None

def getoffsets(header, buf):
    global header_p
    global offset_eth_p
    global offset_arp_p
    header_p = header
    print ("Packet buffer now at 0x%X" % int(buf))
    offset_eth_p = cast(buf, POINTER(Eth))
    offset_arp_p = cast(buf + eth_len, POINTER(ARP))

def main(handle):
    # Grab a packet
    pb_last, header = pcap.getbuffer(handle)
    pb_curr = pb_last
    getoffsets(header, pb_curr)
    count = 0

    while True:
        if pb_curr:
            # libpcap writes to a different buffer position sometimes
            # depending on how many packets are queued.
            if pb_curr != pb_last:
                pb_last = pb_curr
                getoffsets(header, pb_curr)

            count = count + 1
            offset_eth = offset_eth_p.contents
            offset_arp = offset_arp_p.contents

            # Basic sanity check
            if header_p.len < eth_len:
                print ("Packet truncated, expected minimum of %d bytes, only got %d bytes" % (eth_len, header_p.len))
            else:
                # Typecast pointers into the offset_eth header
                if eth_arp != offset_eth.type:
                    print("Incorrect ethertype, expected 0x%X, got 0x%X" % 
                        (sbo16(eth_arp), offset_eth.type))
                else:
                    # Request
                    if offset_arp.oper == eth_arp_op_req:
                        xx = cast(offset_eth.dst, c_void_p)
                        yy = cast(eth_brd, c_void_p)
                        if c.memcmp(xx, yy, 6) != 0:
                            # ARP request was not broadcast (a little weird)
                            pass
                            #print "DIRECTED REQUEST"

                        # Ethernet SRC and hardware SRC address must always be identical
                        #if offset_eth.contents.src != offset_arp.contents.sha:
                        xx = cast(offset_eth.src, c_void_p)
                        yy = cast(offset_arp.sha, c_void_p)
                        if c.memcmp(xx, yy, 6) != 0:
                            print "SOURCE MISMATCH REQUEST"
                        else:
                            #print "ok"
                            pass
                    # Reply
                    elif offset_arp.oper == eth_arp_op_rep:

                        # I'm here I'm here! (Mostly VRRP/CARP maybe HSRP?)
                        if offset_eth.dst != eth_brd:
                            #print "GRATUITOUS REPLY"
                            pass

                        # Ethernet SRC and hardware SRC address must always be identical
                        #if offset_eth.contents.src != offset_arp.contents.sha:
                        xx = cast(offset_eth.src, c_void_p)
                        yy = cast(offset_arp.sha, c_void_p)
                        if c.memcmp(xx, yy, 6) != 0:
                            print "SOURCE MISMATCH REPLY"
                        else:
                            #print "ok"
                            pass
                    else:
                        print "INVALID ARP OPERATOR"
        else:
            break
        pb_curr = pcap.next(handle, header)
    return count

if __name__ == "__main__":
    handle = None
    net = None
    filename = "long.pcap"
    
    # Get default capture device
    dev = pcap.lookup_dev()
    
    if dev is None:
        handle = pcap.open_offline(filename)
        if not handle:
            print >> sys.stderr, "Couldn't open capture device: %s" % pcap.get_error()
            sys.exit(2)
    else:
        print "Will listen on %s" % dev
        
        # Attempt to open the capture device
        handle = pcap.open_live(dev, 65335, 1, 50)
    
    net = pcap.lookup_net(dev)
    if net is None:
        print >> sys.stderr, "Couldn't get IP interface for device: %s" % pcap.get_error()
        sys.exit(2)
        
    # Build ARP Filter
    pcap_filter = pcap.pcap_compile(handle, "arp", 0, net)
    pcap.setfilter(handle, pcap_filter)
    
    print "Capture started"
    x = time.time()
    count = main(handle)
    fin = time.time()
    
    print("elapsed time  : %.2fms" % ((fin - x) * 1000))
    print("Total packets : %d" % count)
    print("packets/s     : %0.2f\n" % (count / ((fin - x))))
    pcap.close(handle)
