def write(self, out=sys.stdout): """Output text-based details about this ARP packet to the specified file or stream. :param out: An object with a `write(str)` method. """ if self.time is not None: out.write("ARP observed at %s:\n" % (datetime.fromtimestamp(self.time))) if self.vid is not None: out.write(" 802.1q VLAN ID (VID): %s (0x%03x)\n" % (self.vid, self.vid)) if self.src_mac is not None: out.write(" Ethernet source: %s\n" % format_eui(self.src_mac)) if self.dst_mac is not None: out.write(" Ethernet destination: %s\n" % format_eui(self.dst_mac)) out.write(" Hardware type: 0x%04x\n" % self.hardware_type) out.write(" Protocol type: 0x%04x\n" % self.protocol_type) out.write("Hardware address length: %d\n" % self.hardware_length) out.write("Protocol address length: %d\n" % self.protocol_length) out.write(" Operation: %s\n" % (ARP_OPERATION(self.operation))) out.write("Sender hardware address: %s\n" % (format_eui(self.source_eui))) out.write("Sender protocol address: %s\n" % self.source_ip) out.write("Target hardware address: %s\n" % (format_eui(self.target_eui))) out.write("Target protocol address: %s\n" % self.target_ip) out.write("\n")
def test__prints_bindings_in_json_format(self): bindings = {} ip = IPAddress("192.168.0.1") mac1 = EUI("00:01:02:03:04:05") mac2 = EUI("02:03:04:05:06:07") # Need to test with three bindings so that we ensure we cover JSON # output for NEW, MOVED, and REFRESHED. Two packets is sufficient # to test all three. (Though it would be three packets in real life, # it's better to test it this way, since some packets *do* have two # bindings.) arp1 = FakeARP([(ip, mac1), (ip, mac2)]) arp2 = FakeARP([(ip, mac2)], time=SEEN_AGAIN_THRESHOLD) out = io.StringIO() update_and_print_bindings(bindings, arp1, out) update_and_print_bindings(bindings, arp2, out) self.assertThat( bindings, Equals({(None, ip): { "mac": mac2, "time": SEEN_AGAIN_THRESHOLD }}), ) output = io.StringIO(out.getvalue()) lines = output.readlines() self.assertThat(lines, HasLength(3)) line1 = json.loads(lines[0]) self.assertThat( line1, Equals({ "ip": str(ip), "mac": format_eui(mac1), "time": 0, "event": "NEW", "vid": None, }), ) line2 = json.loads(lines[1]) self.assertThat( line2, Equals({ "ip": str(ip), "mac": format_eui(mac2), "previous_mac": format_eui(mac1), "time": 0, "event": "MOVED", "vid": None, }), ) line3 = json.loads(lines[2]) self.assertThat( line3, Equals({ "ip": str(ip), "mac": format_eui(mac2), "time": SEEN_AGAIN_THRESHOLD, "event": "REFRESHED", "vid": None, }), )
def update_bindings_and_get_event(bindings, vid, ip, mac, time): """Update the specified bindings dictionary and returns a dictionary if the information resulted in an update to the bindings. (otherwise, returns None.) If an event is returned, it will be a dictionary with the following fields: ip - The IP address of the binding. mac - The MAC address the IP was bound to. previous_mac - (if the IP moved between MACs) The previous MAC that was using the IP address. time - The time (in seconds since the epoch) the binding was observed. event - An event type; either "NEW", "MOVED", or "REFRESHED". """ if (vid, ip) in bindings: binding = bindings[(vid, ip)] if binding["mac"] != mac: # Another MAC claimed ownership of this IP address. Update the # MAC and emit a "MOVED" event. previous_mac = binding["mac"] binding["mac"] = mac binding["time"] = time return dict( ip=str(ip), mac=format_eui(mac), time=time, event="MOVED", previous_mac=format_eui(previous_mac), vid=vid, ) elif time - binding["time"] >= SEEN_AGAIN_THRESHOLD: binding["time"] = time return dict( ip=str(ip), mac=format_eui(mac), time=time, event="REFRESHED", vid=vid, ) else: # The IP was found in the bindings dict, but within the # SEEN_AGAIN_THRESHOLD. Don't update the record; the time field # records the last time we emitted an event for this IP address. return None else: # We haven't seen this IP before, so add a binding for it and # emit a "NEW" event. bindings[(vid, ip)] = {"mac": mac, "time": time} return dict(ip=str(ip), mac=format_eui(mac), time=time, event="NEW", vid=vid)
def observe_beaconing_packets(input=sys.stdin.buffer, out=sys.stdout): """Read stdin and look for tcpdump binary beaconing output. :param input: Stream to read PCAP data from. :type input: a file or stream supporting `read(int)` :param out: Stream to write to. :type input: a file or stream supporting `write(str)` and `flush()`. """ err = sys.stderr try: pcap = PCAP(input) if pcap.global_header.data_link_type != 1: # Not an Ethernet interface. Need to exit here, because our # assumptions about the link layer header won't be correct. return 4 for pcap_header, packet_bytes in pcap: try: packet = decode_ethernet_udp_packet(packet_bytes, pcap_header) beacon = BeaconingPacket(packet.payload) if not beacon.valid: continue output_json = { "source_mac": format_eui(packet.l2.src_eui), "destination_mac": format_eui(packet.l2.dst_eui), "source_ip": str(packet.l3.src_ip), "destination_ip": str(packet.l3.dst_ip), "source_port": packet.l4.packet.src_port, "destination_port": packet.l4.packet.dst_port, "time": pcap_header.timestamp_seconds } if packet.l2.vid is not None: output_json["vid"] = packet.l2.vid if beacon.data is not None: output_json.update(beacon_to_json(beacon.data)) out.write(json.dumps(output_json)) out.write('\n') out.flush() except PacketProcessingError as e: err.write(e.error) err.write("\n") err.flush() except EOFError: # Capture aborted before it could even begin. Note that this does not # occur if the end-of-stream occurs normally. (In that case, the # program will just exit.) return 3 except PCAPError: # Capture aborted due to an I/O error. return 2 return None
def observe_dhcp_packets(input=sys.stdin.buffer, out=sys.stdout): """Read stdin and look for tcpdump binary DHCP output. :param input: Stream to read PCAP data from. :type input: a file or stream supporting `read(int)` :param out: Stream to write to. :type input: a file or stream supporting `write(str)` and `flush()`. """ try: pcap = PCAP(input) if pcap.global_header.data_link_type != 1: # Not an Ethernet interface. Need to exit here, because our # assumptions about the link layer header won't be correct. return 4 for pcap_header, packet_bytes in pcap: out.write(str(datetime.now())) out.write("\n") try: packet = decode_ethernet_udp_packet(packet_bytes, pcap_header) dhcp = DHCP(packet.payload) if not dhcp.is_valid(): out.write(dhcp.invalid_reason) out.write( " Source MAC address: %s\n" % format_eui(packet.l2.src_eui) ) out.write( "Destination MAC address: %s\n" % format_eui(packet.l2.dst_eui) ) if packet.l2.vid is not None: out.write(" Seen on 802.1Q VID: %s\n" % packet.l2.vid) out.write(" Source IP address: %s\n" % packet.l3.src_ip) out.write(" Destination IP address: %s\n" % packet.l3.dst_ip) dhcp.write(out=out) out.flush() except PacketProcessingError as e: out.write(e.error) out.write("\n\n") out.flush() except EOFError: # Capture aborted before it could even begin. Note that this does not # occur if the end-of-stream occurs normally. (In that case, the # program will just exit.) return 3 except PCAPError: # Capture aborted due to an I/O error. return 2 return None
def test__refreshed_binding(self): bindings = {} ip = IPAddress("192.168.0.1") mac = EUI("00:01:02:03:04:05") vid = None update_bindings_and_get_event(bindings, vid, ip, mac, 0) event = update_bindings_and_get_event(bindings, vid, ip, mac, SEEN_AGAIN_THRESHOLD) self.assertThat( bindings, Equals({(vid, ip): { "mac": mac, "time": SEEN_AGAIN_THRESHOLD }}), ) self.assertThat( event, Equals( dict( event="REFRESHED", ip=str(ip), mac=format_eui(mac), time=SEEN_AGAIN_THRESHOLD, vid=vid, )), )
def test__moved_binding(self): bindings = {} ip = IPAddress("192.168.0.1") mac1 = EUI("00:01:02:03:04:05") mac2 = EUI("02:03:04:05:06:07") vid = None update_bindings_and_get_event( bindings, vid, ip, mac1, 0) event = update_bindings_and_get_event( bindings, vid, ip, mac2, 1) self.assertThat(bindings, Equals({ (vid, ip): {"mac": mac2, "time": 1} })) self.assertThat(event, Equals(dict( event="MOVED", ip=str(ip), mac=format_eui(mac2), time=1, previous_mac=format_eui(mac1), vid=vid )))
def test__new_bindings_with_vid(self): bindings = {} ip = IPAddress("192.168.0.1") mac = EUI("00:01:02:03:04:05") vid = None event = update_bindings_and_get_event(bindings, vid, ip, mac, 0) self.assertThat(bindings, Equals({(vid, ip): {"mac": mac, "time": 0}})) self.assertThat( event, Equals( dict( event="NEW", ip=str(ip), mac=format_eui(mac), time=0, vid=vid, )), ) vid = 4095 event = update_bindings_and_get_event(bindings, vid, ip, mac, 0) self.assertThat( bindings, Equals({ (None, ip): { "mac": mac, "time": 0 }, (4095, ip): { "mac": mac, "time": 0 }, }), ) self.assertThat( event, Equals( dict( event="NEW", ip=str(ip), mac=format_eui(mac), time=0, vid=vid, )), )