Exemple #1
0
 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")
Exemple #2
0
 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,
         }),
     )
Exemple #3
0
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)
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
 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,
             )),
     )
Exemple #7
0
 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
     )))
Exemple #8
0
 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,
             )),
     )