Python3: resources and libraries
[csit.git] / resources / libraries / python / PacketVerifier.py
index 45fab6a..397ce76 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Cisco and/or its affiliates.
+# Copyright (c) 2019 Cisco and/or its affiliates.
 # 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:
 # 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:
 
 """PacketVerifier module.
 
 
 """PacketVerifier module.
 
-    :Example:
-
-    >>> from scapy.all import *
-    >>> from PacketVerifier import *
-    >>> rxq = RxQueue('eth1')
-    >>> txq = TxQueue('eth1')
-    >>> src_mac = "AA:BB:CC:DD:EE:FF"
-    >>> dst_mac = "52:54:00:ca:5d:0b"
-    >>> src_ip = "11.11.11.10"
-    >>> dst_ip = "11.11.11.11"
-    >>> sent_packets = []
-    >>> pkt_send = Ether(src=src_mac, dst=dst_mac) /
-    ... IP(src=src_ip, dst=dst_ip) /
-    ... ICMP()
-    >>> sent_packets.append(pkt_send)
-    >>> txq.send(pkt_send)
-    >>> pkt_send = Ether(src=src_mac, dst=dst_mac) /
-    ... ARP(hwsrc=src_mac, psrc=src_ip, hwdst=dst_mac, pdst=dst_ip, op=2)
-    >>> sent_packets.append(pkt_send)
-    >>> txq.send(pkt_send)
-    >>> rxq.recv(100, sent_packets).show()
-    ###[ Ethernet ]###
-      dst       = aa:bb:cc:dd:ee:ff
-      src       = 52:54:00:ca:5d:0b
-      type      = 0x800
-    ###[ IP ]###
-      version   = 4L
-      ihl       = 5L
-      tos       = 0x0
-      len       = 28
-      id        = 43183
-      flags     =
-      frag      = 0L
-      ttl       = 64
-      proto     = icmp
-      chksum    = 0xa607
-      src       = 11.11.11.11
-      dst       = 11.11.11.10
-      \options   \
-    ###[ ICMP ]###
-      type      = echo-reply
-      code      = 0
-      chksum    = 0xffff
-      id        = 0x0
-      seq       = 0x0
-    ###[ Padding ]###
-      load = 'RT\x00\xca]\x0b\xaa\xbb\xcc\xdd\xee\xff\x08\x06\x00\x01\x08\x00'
+  Example. ::
+
+    | >>> from scapy.all import *
+    | >>> from PacketVerifier import *
+    | >>> rxq = RxQueue('eth1')
+    | >>> txq = TxQueue('eth1')
+    | >>> src_mac = "AA:BB:CC:DD:EE:FF"
+    | >>> dst_mac = "52:54:00:ca:5d:0b"
+    | >>> src_ip = "11.11.11.10"
+    | >>> dst_ip = "11.11.11.11"
+    | >>> sent_packets = []
+    | >>> pkt_send = Ether(src=src_mac, dst=dst_mac) /
+    | ... IP(src=src_ip, dst=dst_ip) /
+    | ... ICMP()
+    | >>> sent_packets.append(pkt_send)
+    | >>> txq.send(pkt_send)
+    | >>> pkt_send = Ether(src=src_mac, dst=dst_mac) /
+    | ... ARP(hwsrc=src_mac, psrc=src_ip, hwdst=dst_mac, pdst=dst_ip, op=2)
+    | >>> sent_packets.append(pkt_send)
+    | >>> txq.send(pkt_send)
+    | >>> rxq.recv(100, sent_packets).show()
+    | ###[ Ethernet ]###
+    |   dst       = aa:bb:cc:dd:ee:ff
+    |   src       = 52:54:00:ca:5d:0b
+    |   type      = 0x800
+    | ###[ IP ]###
+    |   version   = 4L
+    |   ihl       = 5L
+    |   tos       = 0x0
+    |   len       = 28
+    |   id        = 43183
+    |   flags     =
+    |   frag      = 0L
+    |   ttl       = 64
+    |   proto     = icmp
+    |   chksum    = 0xa607
+    |   src       = 11.11.11.11
+    |   dst       = 11.11.11.10
+    |   options
+    | ###[ ICMP ]###
+    |   type      = echo-reply
+    |   code      = 0
+    |   chksum    = 0xffff
+    |   id        = 0x0
+    |   seq       = 0x0
+    | ###[ Padding ]###
+    |   load = 'RT\x00\xca]\x0b\xaa\xbb\xcc\xdd\xee\xff\x08\x06\x00\x01\x08\x00'
+
+  Example end.
 """
 
 """
 
-
-import socket
-import select
 import os
 import os
-import time
-from multiprocessing import Queue, Process
+import select
+
 from scapy.all import ETH_P_IP, ETH_P_IPV6, ETH_P_ALL, ETH_P_ARP
 from scapy.all import ETH_P_IP, ETH_P_IPV6, ETH_P_ALL, ETH_P_ARP
-from scapy.all import Ether, ARP, Packet
+from scapy.config import conf
 from scapy.layers.inet6 import IPv6
 from scapy.layers.inet6 import IPv6
+from scapy.layers.l2 import Ether, ARP
+from scapy.packet import Raw
+
+# Enable libpcap's L2listen
+conf.use_pcap = True
+import scapy.arch.pcapdnet  # pylint: disable=C0413, unused-import
+
+__all__ = [
+    u"RxQueue", u"TxQueue", u"Interface", u"create_gratuitous_arp_request",
+    u"auto_pad", u"checksum_equal"
+]
 
 
-__all__ = ['RxQueue', 'TxQueue', 'Interface', 'create_gratuitous_arp_request',
-           'auto_pad', 'checksum_equal']
+# TODO: http://stackoverflow.com/questions/320232/
+# ensuring-subprocesses-are-dead-on-exiting-python-program
 
 
-# TODO: http://stackoverflow.com/questions/320232/ensuring-subprocesses-are-dead-on-exiting-python-program
 
 
-class PacketVerifier(object):
+class PacketVerifier:
     """Base class for TX and RX queue objects for packet verifier."""
     def __init__(self, interface_name):
     """Base class for TX and RX queue objects for packet verifier."""
     def __init__(self, interface_name):
-        os.system('sudo echo 1 > /proc/sys/net/ipv6/conf/{0}/disable_ipv6'
-                  .format(interface_name))
-        os.system('sudo ip link set {0} up promisc on'.format(interface_name))
-        self._sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
-                                   ETH_P_ALL)
-        self._sock.bind((interface_name, ETH_P_ALL))
+        os.system(
+            f"sudo echo 1 > /proc/sys/net/ipv6/conf/{interface_name}/"
+            f"disable_ipv6"
+        )
+        os.system(f"sudo ip link set {interface_name} up promisc on")
         self._ifname = interface_name
 
 
         self._ifname = interface_name
 
 
@@ -95,26 +103,20 @@ def extract_one_packet(buf):
     Takes string as input and looks for first whole packet in it.
     If it finds one, it returns substring from the buf parameter.
 
     Takes string as input and looks for first whole packet in it.
     If it finds one, it returns substring from the buf parameter.
 
-    :param buf: string representation of incoming packet buffer.
-    :type buf: string
-    :return: String representation of first packet in buf.
-    :rtype: string
+    :param buf: String representation of incoming packet buffer.
+    :type buf: str
+    :returns: String representation of first packet in buf.
+    :rtype: str
     """
     pkt_len = 0
 
     if len(buf) < 60:
         return None
 
     """
     pkt_len = 0
 
     if len(buf) < 60:
         return None
 
-    # print
-    # print buf.__repr__()
-    # print Ether(buf).__repr__()
-    # print len(Ether(buf))
-    # print
     try:
         ether_type = Ether(buf[0:14]).type
     except AttributeError:
     try:
         ether_type = Ether(buf[0:14]).type
     except AttributeError:
-        raise RuntimeError(
-            'No EtherType in packet {0}'.format(buf.__repr__()))
+        raise RuntimeError(f"No EtherType in packet {buf!r}")
 
     if ether_type == ETH_P_IP:
         # 14 is Ethernet fame header size.
 
     if ether_type == ETH_P_IP:
         # 14 is Ethernet fame header size.
@@ -126,17 +128,16 @@ def extract_one_packet(buf):
             return None
     elif ether_type == ETH_P_IPV6:
         if not Ether(buf[0:14+6]).haslayer(IPv6):
             return None
     elif ether_type == ETH_P_IPV6:
         if not Ether(buf[0:14+6]).haslayer(IPv6):
-            raise RuntimeError(
-                'Invalid IPv6 packet {0}'.format(buf.__repr__()))
+            raise RuntimeError(f"Invalid IPv6 packet {buf!r}")
         # ... to add to the above, 40 bytes is the length of IPV6 header.
         #   The ipv6.len only contains length of the payload and not the header
         # ... to add to the above, 40 bytes is the length of IPV6 header.
         #   The ipv6.len only contains length of the payload and not the header
-        pkt_len = Ether(buf)['IPv6'].plen + 14 + 40
+        pkt_len = Ether(buf)[u"IPv6"].plen + 14 + 40
         if len(buf) < 60:
             return None
     elif ether_type == ETH_P_ARP:
         pkt = Ether(buf[:20])
         if not pkt.haslayer(ARP):
         if len(buf) < 60:
             return None
     elif ether_type == ETH_P_ARP:
         pkt = Ether(buf[:20])
         if not pkt.haslayer(ARP):
-            raise RuntimeError('Incomplete ARP packet')
+            raise RuntimeError(u"Incomplete ARP packet")
         # len(eth) + arp(2 hw addr type + 2 proto addr type
         #                + 1b len + 1b len + 2b operation)
 
         # len(eth) + arp(2 hw addr type + 2 proto addr type
         #                + 1b len + 1b len + 2b operation)
 
@@ -148,10 +149,10 @@ def extract_one_packet(buf):
     elif ether_type == 32821:  # RARP (Reverse ARP)
         pkt = Ether(buf[:20])
         pkt.type = ETH_P_ARP  # Change to ARP so it works with scapy
     elif ether_type == 32821:  # RARP (Reverse ARP)
         pkt = Ether(buf[:20])
         pkt.type = ETH_P_ARP  # Change to ARP so it works with scapy
-        pkt = Ether(str(pkt))
+        pkt = Ether(pkt)
         if not pkt.haslayer(ARP):
             pkt.show()
         if not pkt.haslayer(ARP):
             pkt.show()
-            raise RuntimeError('Incomplete RARP packet')
+            raise RuntimeError(u"Incomplete RARP packet")
 
         # len(eth) + arp(2 hw addr type + 2 proto addr type
         #                + 1b len + 1b len + 2b operation)
 
         # len(eth) + arp(2 hw addr type + 2 proto addr type
         #                + 1b len + 1b len + 2b operation)
@@ -161,7 +162,7 @@ def extract_one_packet(buf):
 
         del pkt
     else:
 
         del pkt
     else:
-        raise RuntimeError('Unknown protocol {0}'.format(ether_type))
+        raise RuntimeError(f"Unknown protocol {ether_type}")
 
     if pkt_len < 60:
         pkt_len = 60
 
     if pkt_len < 60:
         pkt_len = 60
@@ -178,14 +179,12 @@ def packet_reader(interface_name, queue):
     This function is meant to be run in separate subprocess and is in tight
     loop reading raw packets from interface passed as parameter.
 
     This function is meant to be run in separate subprocess and is in tight
     loop reading raw packets from interface passed as parameter.
 
-    :param interace_name: Name of interface to read packets from.
+    :param interface_name: Name of interface to read packets from.
     :param queue: Queue in which this function will push incoming packets.
     :param queue: Queue in which this function will push incoming packets.
-    :type interface_name: string
+    :type interface_name: str
     :type queue: multiprocessing.Queue
     :type queue: multiprocessing.Queue
-    :return: None
     """
     """
-    sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ETH_P_ALL)
-    sock.bind((interface_name, ETH_P_ALL))
+    sock = conf.L2listen(iface=interface_name, type=ETH_P_ALL)
 
     while True:
         pkt = sock.recv(0x7fff)
 
     while True:
         pkt = sock.recv(0x7fff)
@@ -199,20 +198,13 @@ class RxQueue(PacketVerifier):
     function to access them.
 
     :param interface_name: Which interface to bind to.
     function to access them.
 
     :param interface_name: Which interface to bind to.
-    :type interface_name: string
+    :type interface_name: str
     """
     """
-
     def __init__(self, interface_name):
         PacketVerifier.__init__(self, interface_name)
     def __init__(self, interface_name):
         PacketVerifier.__init__(self, interface_name)
+        self._sock = conf.L2listen(iface=interface_name, type=ETH_P_ALL)
 
 
-        #self._queue = Queue()
-        #self._proc = Process(target=packet_reader, args=(interface_name,
-        #                                                 self._queue))
-        #self._proc.daemon = True
-        #self._proc.start()
-        #time.sleep(2)
-
-    def recv(self, timeout=3, ignore=None):
+    def recv(self, timeout=3, ignore=None, verbose=True):
         """Read next received packet.
 
         Returns scapy's Ether() object created from next packet in the queue.
         """Read next received packet.
 
         Returns scapy's Ether() object created from next packet in the queue.
@@ -220,35 +212,36 @@ class RxQueue(PacketVerifier):
         arrives in given timeout queue.Empty exception will be risen.
 
         :param timeout: How many seconds to wait for next packet.
         arrives in given timeout queue.Empty exception will be risen.
 
         :param timeout: How many seconds to wait for next packet.
+        :param ignore: List of packets that should be ignored.
+        :param verbose: Used to suppress detailed logging of received packets.
         :type timeout: int
         :type timeout: int
+        :type ignore: list
+        :type verbose: bool
 
 
-        :return: Ether() initialized object from packet data.
+        :returns: Ether() initialized object from packet data.
         :rtype: scapy.Ether
         """
         :rtype: scapy.Ether
         """
-
-        #pkt = self._queue.get(True, timeout=timeout)
-        (rlist, _, _) = select.select([self._sock], [], [], timeout)
-        if self._sock not in rlist:
-            return None
-
-        pkt = self._sock.recv(0x7fff)
-        pkt_pad = auto_pad(pkt)
-        print 'Received packet on {0} of len {1}'.format(self._ifname, len(pkt))
-        Ether(pkt).show2()
-        print
-
+        ignore_list = list()
         if ignore is not None:
         if ignore is not None:
-            for i, ig_pkt in enumerate(ignore):
-                # Auto pad all packets in ignore list
-                ignore[i] = auto_pad(ig_pkt)
             for ig_pkt in ignore:
             for ig_pkt in ignore:
-                if ig_pkt == pkt_pad:
-                    # Found the packet in ignore list, get another one
-                    # TODO: subtract timeout - time_spent in here
-                    ignore.remove(ig_pkt)
-                    return self.recv(timeout, ignore)
-
-        return Ether(pkt)
+                # Auto pad all packets in ignore list
+                ignore_list.append(str(auto_pad(ig_pkt)))
+        while True:
+            rlist, _, _ = select.select([self._sock], [], [], timeout)
+            if self._sock not in rlist:
+                return None
+
+            pkt = self._sock.recv(0x7fff)
+            pkt_pad = str(auto_pad(pkt))
+            print(f"Received packet on {self._ifname} of len {len(pkt)}")
+            if verbose:
+                pkt.show2()  # pylint: disable=no-member
+                print()
+            if pkt_pad in ignore_list:
+                ignore_list.remove(pkt_pad)
+                print(u"Received packet ignored.")
+                continue
+            return pkt
 
 
 class TxQueue(PacketVerifier):
 
 
 class TxQueue(PacketVerifier):
@@ -257,53 +250,72 @@ class TxQueue(PacketVerifier):
     This object is used to send packets over RAW socket on a interface.
 
     :param interface_name: Which interface to send packets from.
     This object is used to send packets over RAW socket on a interface.
 
     :param interface_name: Which interface to send packets from.
-    :type interface_name: string
+    :type interface_name: str
     """
     def __init__(self, interface_name):
         PacketVerifier.__init__(self, interface_name)
     """
     def __init__(self, interface_name):
         PacketVerifier.__init__(self, interface_name)
+        self._sock = conf.L2socket(iface=interface_name, type=ETH_P_ALL)
 
 
-    def send(self, pkt):
+    def send(self, pkt, verbose=True):
         """Send packet out of the bound interface.
 
         :param pkt: Packet to send.
         """Send packet out of the bound interface.
 
         :param pkt: Packet to send.
+        :param verbose: Used to suppress detailed logging of sent packets.
         :type pkt: string or scapy Packet derivative.
         :type pkt: string or scapy Packet derivative.
+        :type verbose: bool
         """
         """
-        print 'Sending packet out of {0} of len {1}'.format(self._ifname,
-                                                            len(pkt))
-        Ether(str(pkt)).show2()
-        print
+        pkt = auto_pad(pkt)
+        print(f"Sending packet out of {self._ifname} of len {len(pkt)}")
+        if verbose:
+            pkt.show2()
+            print()
 
 
-        pkt = auto_pad(str(pkt))
         self._sock.send(pkt)
 
 
         self._sock.send(pkt)
 
 
-class Interface(object):
+class Interface:
+    """Class for network interfaces. Contains methods for sending and receiving
+     packets."""
     def __init__(self, if_name):
     def __init__(self, if_name):
+        """Initialize the interface class.
+
+        :param if_name: Name of the interface.
+        :type if_name: str
+        """
         self.if_name = if_name
         self.sent_packets = []
         self.rxq = RxQueue(if_name)
         self.txq = TxQueue(if_name)
 
     def send_pkt(self, pkt):
         self.if_name = if_name
         self.sent_packets = []
         self.rxq = RxQueue(if_name)
         self.txq = TxQueue(if_name)
 
     def send_pkt(self, pkt):
+        """Send the provided packet out the interface."""
         self.sent_packets.append(pkt)
         self.txq.send(pkt)
 
     def recv_pkt(self, timeout=3):
         self.sent_packets.append(pkt)
         self.txq.send(pkt)
 
     def recv_pkt(self, timeout=3):
+        """Read one packet from the interface's receive queue.
+
+        :param timeout: Timeout value in seconds.
+        :type timeout: int
+        :returns: Ether() initialized object from packet data.
+        :rtype: scapy.Ether
+        """
         return self.rxq.recv(timeout, self.sent_packets)
 
 
 def create_gratuitous_arp_request(src_mac, src_ip):
         return self.rxq.recv(timeout, self.sent_packets)
 
 
 def create_gratuitous_arp_request(src_mac, src_ip):
-    """Creates scapy representation of gratuitous ARP request"""
-    return (Ether(src=src_mac, dst='ff:ff:ff:ff:ff:ff') /
-            ARP(psrc=src_ip, hwsrc=src_mac, pdst=src_ip))
+    """Creates scapy representation of gratuitous ARP request."""
+    return (Ether(src=src_mac, dst=u"ff:ff:ff:ff:ff:ff") /
+            ARP(psrc=src_ip, hwsrc=src_mac, pdst=src_ip)
+            )
 
 
 def auto_pad(packet):
     """Pads zeroes at the end of the packet if the total len < 60 bytes."""
 
 
 def auto_pad(packet):
     """Pads zeroes at the end of the packet if the total len < 60 bytes."""
-    padded = str(packet)
-    if len(padded) < 60:
-        padded += ('\0' * (60 - len(padded)))
-    return padded
+    padded = str(packet)
+    if len(packet) < 60:
+        packet[Raw].load += (b"\0" * (60 - len(packet)))
+    return packet
 
 
 def checksum_equal(chksum1, chksum2):
 
 
 def checksum_equal(chksum1, chksum2):
@@ -319,7 +331,7 @@ def checksum_equal(chksum1, chksum2):
     :type chksum1: uint16
     :type chksum2: uint16
 
     :type chksum1: uint16
     :type chksum2: uint16
 
-    :return: True if checksums are equivalent, False otherwise.
+    :returns: True if checksums are equivalent, False otherwise.
     :rtype: boolean
     """
     if chksum1 == 0xFFFF:
     :rtype: boolean
     """
     if chksum1 == 0xFFFF: