Revert "vpp-device: GENEVE tunnel test, l3 mode"
[csit.git] / GPL / traffic_scripts / PacketVerifier.py
1 # Copyright (c) 2020 Cisco and/or its affiliates.
2 #
3 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
4 #
5 # Licensed under the Apache License 2.0 or
6 # GNU General Public License v2.0 or later;  you may not use this file
7 # except in compliance with one of these Licenses. You
8 # may obtain a copy of the Licenses at:
9 #
10 #     http://www.apache.org/licenses/LICENSE-2.0
11 #     https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 #
13 # Note: If this file is linked with Scapy, which is GPLv2+, your use of it
14 # must be under GPLv2+.  If at any point in the future it is no longer linked
15 # with Scapy (or other GPLv2+ licensed software), you are free to choose Apache 2.
16 #
17 # Unless required by applicable law or agreed to in writing, software
18 # distributed under the License is distributed on an "AS IS" BASIS,
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 # See the License for the specific language governing permissions and
21 # limitations under the License.
22
23 """PacketVerifier module.
24
25   Example. ::
26
27     | >>> from scapy.all import *
28     | >>> from PacketVerifier import *
29     | >>> rxq = RxQueue('eth1')
30     | >>> txq = TxQueue('eth1')
31     | >>> src_mac = "AA:BB:CC:DD:EE:FF"
32     | >>> dst_mac = "52:54:00:ca:5d:0b"
33     | >>> src_ip = "11.11.11.10"
34     | >>> dst_ip = "11.11.11.11"
35     | >>> sent_packets = []
36     | >>> pkt_send = Ether(src=src_mac, dst=dst_mac) /
37     | ... IP(src=src_ip, dst=dst_ip) /
38     | ... ICMP()
39     | >>> sent_packets.append(pkt_send)
40     | >>> txq.send(pkt_send)
41     | >>> pkt_send = Ether(src=src_mac, dst=dst_mac) /
42     | ... ARP(hwsrc=src_mac, psrc=src_ip, hwdst=dst_mac, pdst=dst_ip, op=2)
43     | >>> sent_packets.append(pkt_send)
44     | >>> txq.send(pkt_send)
45     | >>> rxq.recv(100, sent_packets).show()
46     | ###[ Ethernet ]###
47     |   dst       = aa:bb:cc:dd:ee:ff
48     |   src       = 52:54:00:ca:5d:0b
49     |   type      = 0x800
50     | ###[ IP ]###
51     |   version   = 4L
52     |   ihl       = 5L
53     |   tos       = 0x0
54     |   len       = 28
55     |   id        = 43183
56     |   flags     =
57     |   frag      = 0L
58     |   ttl       = 64
59     |   proto     = icmp
60     |   chksum    = 0xa607
61     |   src       = 11.11.11.11
62     |   dst       = 11.11.11.10
63     |   options
64     | ###[ ICMP ]###
65     |   type      = echo-reply
66     |   code      = 0
67     |   chksum    = 0xffff
68     |   id        = 0x0
69     |   seq       = 0x0
70     | ###[ Padding ]###
71     |   load = 'RT\x00\xca]\x0b\xaa\xbb\xcc\xdd\xee\xff\x08\x06\x00\x01\x08\x00'
72
73   Example end.
74 """
75
76 import os
77 import select
78
79 from scapy.all import ETH_P_IP, ETH_P_IPV6, ETH_P_ALL, ETH_P_ARP
80 from scapy.config import conf
81 from scapy.layers.inet6 import IPv6
82 from scapy.layers.l2 import Ether, ARP
83 from scapy.packet import Raw, Padding
84
85 # Enable libpcap's L2listen
86 conf.use_pcap = True
87
88 __all__ = [
89     u"RxQueue", u"TxQueue", u"Interface", u"create_gratuitous_arp_request",
90     u"auto_pad", u"checksum_equal"
91 ]
92
93 # TODO: http://stackoverflow.com/questions/320232/
94 # ensuring-subprocesses-are-dead-on-exiting-python-program
95
96
97 class PacketVerifier:
98     """Base class for TX and RX queue objects for packet verifier."""
99     def __init__(self, interface_name):
100         os.system(
101             f"sudo echo 1 > /proc/sys/net/ipv6/conf/{interface_name}/"
102             f"disable_ipv6"
103         )
104         os.system(f"sudo ip link set {interface_name} up promisc on")
105         self._ifname = interface_name
106
107
108 def extract_one_packet(buf):
109     """Extract one packet from the incoming buf buffer.
110
111     Takes string as input and looks for first whole packet in it.
112     If it finds one, it returns substring from the buf parameter.
113
114     :param buf: String representation of incoming packet buffer.
115     :type buf: str
116     :returns: String representation of first packet in buf.
117     :rtype: str
118     """
119     pkt_len = 0
120
121     if len(buf) < 60:
122         return None
123
124     try:
125         ether_type = Ether(buf[0:14]).type
126     except AttributeError:
127         raise RuntimeError(f"No EtherType in packet {buf!r}")
128
129     if ether_type == ETH_P_IP:
130         # 14 is Ethernet fame header size.
131         # 4 bytes is just enough to look for length in ip header.
132         # ip total length contains just the IP packet length so add the Ether
133         #     header.
134         pkt_len = Ether(buf[0:14+4]).len + 14
135         if len(buf) < 60:
136             return None
137     elif ether_type == ETH_P_IPV6:
138         if not Ether(buf[0:14+6]).haslayer(IPv6):
139             raise RuntimeError(f"Invalid IPv6 packet {buf!r}")
140         # ... to add to the above, 40 bytes is the length of IPV6 header.
141         #   The ipv6.len only contains length of the payload and not the header
142         pkt_len = Ether(buf)[u"IPv6"].plen + 14 + 40
143         if len(buf) < 60:
144             return None
145     elif ether_type == ETH_P_ARP:
146         pkt = Ether(buf[:20])
147         if not pkt.haslayer(ARP):
148             raise RuntimeError(u"Incomplete ARP packet")
149         # len(eth) + arp(2 hw addr type + 2 proto addr type
150         #                + 1b len + 1b len + 2b operation)
151
152         pkt_len = 14 + 8
153         pkt_len += 2 * pkt.getlayer(ARP).hwlen
154         pkt_len += 2 * pkt.getlayer(ARP).plen
155
156         del pkt
157     elif ether_type == 32821:  # RARP (Reverse ARP)
158         pkt = Ether(buf[:20])
159         pkt.type = ETH_P_ARP  # Change to ARP so it works with scapy
160         pkt = Ether(pkt)
161         if not pkt.haslayer(ARP):
162             pkt.show()
163             raise RuntimeError(u"Incomplete RARP packet")
164
165         # len(eth) + arp(2 hw addr type + 2 proto addr type
166         #                + 1b len + 1b len + 2b operation)
167         pkt_len = 14 + 8
168         pkt_len += 2 * pkt.getlayer(ARP).hwlen
169         pkt_len += 2 * pkt.getlayer(ARP).plen
170
171         del pkt
172     else:
173         raise RuntimeError(f"Unknown protocol {ether_type}")
174
175     if pkt_len < 60:
176         pkt_len = 60
177
178     if len(buf) < pkt_len:
179         return None
180
181     return buf[0:pkt_len]
182
183
184 def packet_reader(interface_name, queue):
185     """Sub-process routine that reads packets and puts them to queue.
186
187     This function is meant to be run in separate subprocess and is in tight
188     loop reading raw packets from interface passed as parameter.
189
190     :param interface_name: Name of interface to read packets from.
191     :param queue: Queue in which this function will push incoming packets.
192     :type interface_name: str
193     :type queue: multiprocessing.Queue
194     """
195     sock = conf.L2listen(iface=interface_name, type=ETH_P_ALL)
196
197     while True:
198         pkt = sock.recv(0x7fff)
199         queue.put(pkt)
200
201
202 class RxQueue(PacketVerifier):
203     """Receive queue object.
204
205     This object creates raw socket, reads packets from it and provides
206     function to access them.
207
208     :param interface_name: Which interface to bind to.
209     :type interface_name: str
210     """
211     def __init__(self, interface_name):
212         PacketVerifier.__init__(self, interface_name)
213         self._sock = conf.L2listen(iface=interface_name, type=ETH_P_ALL)
214
215     def recv(self, timeout=3, ignore=None, verbose=True):
216         """Read next received packet.
217
218         Returns scapy's Ether() object created from next packet in the queue.
219         Queue is being filled in parallel in subprocess. If no packet
220         arrives in given timeout None is returned.
221
222         If the list of packets to ignore is given, they are logged
223         but otherwise ignored upon arrival, not adding to the timeout.
224         Each time a packet is ignored, it is removed from the ignored list.
225
226         :param timeout: How many seconds to wait for next packet.
227         :param ignore: List of packets that should be ignored.
228         :param verbose: Used to suppress detailed logging of received packets.
229         :type timeout: int
230         :type ignore: list
231         :type verbose: bool
232
233         :returns: Ether() initialized object from packet data.
234         :rtype: scapy.Ether
235         """
236         time_end = time.monotonic() + timeout
237         ignore = ignore if ignore else list()
238         # Auto pad all packets in ignore list
239         ignore = [str(auto_pad(ig_pkt)) for ig_pkt in ignore]
240         while 1:
241             time_now = time.monotonic()
242             if time_now >= time_end:
243                 return None
244             timedelta = time_end - time_now
245             rlist, _, _ = select.select([self._sock], [], [], timedelta)
246             if self._sock not in rlist:
247                 # Might have been an interrupt.
248                 continue
249             pkt = self._sock.recv(0x7fff)
250             pkt_pad = str(auto_pad(pkt))
251             print(f"Received packet on {self._ifname} of len {len(pkt)}")
252             if verbose:
253                 if hasattr(pkt, u"show2"):
254                     pkt.show2()
255                 else:
256                     # Never happens in practice, but Pylint does not know that.
257                     print(f"Unexpected instance: {pkt!r}")
258                 print()
259             if pkt_pad in ignore:
260                 ignore.remove(pkt_pad)
261                 print(u"Received packet ignored.")
262                 continue
263             return pkt
264
265
266 class TxQueue(PacketVerifier):
267     """Transmission queue object.
268
269     This object is used to send packets over RAW socket on a interface.
270
271     :param interface_name: Which interface to send packets from.
272     :type interface_name: str
273     """
274     def __init__(self, interface_name):
275         PacketVerifier.__init__(self, interface_name)
276         self._sock = conf.L2socket(iface=interface_name, type=ETH_P_ALL)
277
278     def send(self, pkt, verbose=True):
279         """Send packet out of the bound interface.
280
281         :param pkt: Packet to send.
282         :param verbose: Used to suppress detailed logging of sent packets.
283         :type pkt: string or scapy Packet derivative.
284         :type verbose: bool
285         """
286         pkt = auto_pad(pkt)
287         print(f"Sending packet out of {self._ifname} of len {len(pkt)}")
288         if verbose:
289             pkt.show2()
290             print()
291
292         self._sock.send(pkt)
293
294
295 class Interface:
296     """Class for network interfaces. Contains methods for sending and receiving
297      packets."""
298     def __init__(self, if_name):
299         """Initialize the interface class.
300
301         :param if_name: Name of the interface.
302         :type if_name: str
303         """
304         self.if_name = if_name
305         self.sent_packets = []
306         self.rxq = RxQueue(if_name)
307         self.txq = TxQueue(if_name)
308
309     def send_pkt(self, pkt):
310         """Send the provided packet out the interface."""
311         self.sent_packets.append(pkt)
312         self.txq.send(pkt)
313
314     def recv_pkt(self, timeout=3):
315         """Read one packet from the interface's receive queue.
316
317         :param timeout: Timeout value in seconds.
318         :type timeout: int
319         :returns: Ether() initialized object from packet data.
320         :rtype: scapy.Ether
321         """
322         return self.rxq.recv(timeout, self.sent_packets)
323
324
325 def create_gratuitous_arp_request(src_mac, src_ip):
326     """Creates scapy representation of gratuitous ARP request."""
327     return (Ether(src=src_mac, dst=u"ff:ff:ff:ff:ff:ff") /
328             ARP(psrc=src_ip, hwsrc=src_mac, pdst=src_ip)
329             )
330
331
332 def auto_pad(packet):
333     """Pads zeroes at the end of the packet if the total packet length is less
334     then 64 bytes in case of IPv4 or 78 bytes in case of IPv6.
335     """
336     min_len = 78 if packet.haslayer(IPv6) else 64
337     pad_layer = Raw if packet.haslayer(Raw) \
338         else Padding if packet.haslayer(Padding) else None
339     if pad_layer:
340         packet[pad_layer].load += (b"\0" * (min_len - len(packet)))
341     return packet
342
343
344 def checksum_equal(chksum1, chksum2):
345     """Compares two checksums in one's complement notation.
346
347     Checksums to be compared are calculated as 16 bit one's complement of the
348     one's complement sum of 16 bit words of some buffer.
349     In one's complement notation 0x0000 (positive zero) and 0xFFFF
350     (negative zero) are equivalent.
351
352     :param chksum1: First checksum.
353     :param chksum2: Second checksum.
354     :type chksum1: uint16
355     :type chksum2: uint16
356
357     :returns: True if checksums are equivalent, False otherwise.
358     :rtype: boolean
359     """
360     if chksum1 == 0xFFFF:
361         chksum1 = 0
362     if chksum2 == 0xFFFF:
363         chksum2 = 0
364     return chksum1 == chksum2