blob: c7e4693e99864d135b10e88b7300dac48acb684c [file] [log] [blame]
Klement Sekeraacb9b8e2017-02-14 02:55:31 +01001""" test framework utilities """
2
Paul Vinciguerra3bce8eb2018-11-24 21:46:05 -08003import abc
Paul Vinciguerra2f156312020-05-02 22:34:40 -04004import ipaddress
Paul Vinciguerra9beabd82019-12-01 22:24:28 -05005import logging
Matej Klotton0178d522016-11-04 11:11:44 +01006import socket
Paul Vinciguerrae8fece82019-02-28 15:34:00 -08007from socket import AF_INET6
Paul Vinciguerra3bce8eb2018-11-24 21:46:05 -08008import six
Klement Sekera7bb873a2016-11-18 07:38:42 +01009import sys
juraj.linkes40dd73b2018-09-21 13:55:16 +020010import os.path
Klement Sekera7bb873a2016-11-18 07:38:42 +010011
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -070012import scapy.compat
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +020013from scapy.layers.l2 import Ether
Klement Sekera75e7d132017-09-20 08:26:30 +020014from scapy.layers.inet import IP
15from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, IPv6ExtHdrRouting,\
16 IPv6ExtHdrHopByHop
Paul Vinciguerrae8fece82019-02-28 15:34:00 -080017from scapy.packet import Raw
Klement Sekera611864f2018-09-26 11:19:00 +020018from scapy.utils import hexdump
Paul Vinciguerrae8fece82019-02-28 15:34:00 -080019from scapy.utils6 import in6_mactoifaceid
20
Ole Troan7f991832018-12-06 17:35:12 +010021from io import BytesIO
Ole Troan8006c6a2018-12-17 12:02:26 +010022from vpp_papi import mac_pton
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +020023
Paul Vinciguerra9beabd82019-12-01 22:24:28 -050024# Set up an empty logger for the testcase that can be overridden as necessary
25null_logger = logging.getLogger('VppTestCase.util')
26null_logger.addHandler(logging.NullHandler())
27
Klement Sekera7bb873a2016-11-18 07:38:42 +010028
29def ppp(headline, packet):
30 """ Return string containing the output of scapy packet.show() call. """
Paul Vinciguerrab951ad82019-01-08 21:36:39 -080031 return '%s\n%s\n\n%s\n' % (headline,
32 hexdump(packet, dump=True),
33 packet.show(dump=True))
Klement Sekera7bb873a2016-11-18 07:38:42 +010034
Damjan Marionf56b77a2016-10-03 19:44:57 +020035
Klement Sekera9225dee2016-12-12 08:36:58 +010036def ppc(headline, capture, limit=10):
37 """ Return string containing ppp() printout for a capture.
38
39 :param headline: printed as first line of output
40 :param capture: packets to print
41 :param limit: limit the print to # of packets
42 """
43 if not capture:
44 return headline
Klement Sekeradab231a2016-12-21 08:50:14 +010045 tail = ""
Klement Sekera9225dee2016-12-12 08:36:58 +010046 if limit < len(capture):
Klement Sekeradab231a2016-12-21 08:50:14 +010047 tail = "\nPrint limit reached, %s out of %s packets printed" % (
Klement Sekera75e7d132017-09-20 08:26:30 +020048 limit, len(capture))
Klement Sekeradab231a2016-12-21 08:50:14 +010049 body = "".join([ppp("Packet #%s:" % count, p)
50 for count, p in zip(range(0, limit), capture)])
51 return "%s\n%s%s" % (headline, body, tail)
Klement Sekera9225dee2016-12-12 08:36:58 +010052
53
Eyal Barid81da8c2017-01-11 13:39:54 +020054def ip4_range(ip4, s, e):
55 tmp = ip4.rsplit('.', 1)[0]
56 return ("%s.%d" % (tmp, i) for i in range(s, e))
57
58
Paul Vinciguerra2f156312020-05-02 22:34:40 -040059def mcast_ip_to_mac(ip):
60 ip = ipaddress.ip_address(ip)
61 if not ip.is_multicast:
62 raise ValueError("Must be multicast address.")
63 ip_as_int = int(ip)
64 if ip.version == 4:
65 mcast_mac = "01:00:5e:%02x:%02x:%02x" % ((ip_as_int >> 16) & 0x7f,
66 (ip_as_int >> 8) & 0xff,
67 ip_as_int & 0xff)
68 else:
69 mcast_mac = "33:33:%02x:%02x:%02x:%02x" % ((ip_as_int >> 24) & 0xff,
70 (ip_as_int >> 16) & 0xff,
71 (ip_as_int >> 8) & 0xff,
72 ip_as_int & 0xff)
73 return mcast_mac
Eyal Barid81da8c2017-01-11 13:39:54 +020074
75
Paul Vinciguerrae8fece82019-02-28 15:34:00 -080076# wrapper around scapy library function.
Neale Ranns2a3ea492017-04-19 05:24:40 -070077def mk_ll_addr(mac):
Ole Troan6ed154f2019-10-15 19:31:55 +020078 euid = in6_mactoifaceid(str(mac))
Neale Ranns2a3ea492017-04-19 05:24:40 -070079 addr = "fe80::" + euid
80 return addr
81
82
Juraj Sloboda4b9669d2018-01-15 10:39:21 +010083def ip6_normalize(ip6):
84 return socket.inet_ntop(socket.AF_INET6,
85 socket.inet_pton(socket.AF_INET6, ip6))
86
87
juraj.linkes40dd73b2018-09-21 13:55:16 +020088def get_core_path(tempdir):
89 return "%s/%s" % (tempdir, get_core_pattern())
90
91
92def is_core_present(tempdir):
93 return os.path.isfile(get_core_path(tempdir))
94
95
96def get_core_pattern():
Andrew Yourtchenko57612eb2018-03-28 15:32:10 +020097 with open("/proc/sys/kernel/core_pattern", "r") as f:
juraj.linkes40dd73b2018-09-21 13:55:16 +020098 corefmt = f.read().strip()
99 return corefmt
100
101
102def check_core_path(logger, core_path):
103 corefmt = get_core_pattern()
104 if corefmt.startswith("|"):
105 logger.error(
106 "WARNING: redirecting the core dump through a"
107 " filter may result in truncated dumps.")
108 logger.error(
109 " You may want to check the filter settings"
110 " or uninstall it and edit the"
111 " /proc/sys/kernel/core_pattern accordingly.")
112 logger.error(
113 " current core pattern is: %s" % corefmt)
Andrew Yourtchenko57612eb2018-03-28 15:32:10 +0200114
115
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200116class NumericConstant(object):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200117
118 desc_dict = {}
119
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200120 def __init__(self, value):
121 self._value = value
122
123 def __int__(self):
124 return self._value
125
126 def __long__(self):
127 return self._value
128
129 def __str__(self):
130 if self._value in self.desc_dict:
131 return self.desc_dict[self._value]
132 return ""
133
134
Matej Klotton0178d522016-11-04 11:11:44 +0100135class Host(object):
136 """ Generic test host "connected" to VPPs interface. """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200137
Klement Sekeraf62ae122016-10-11 11:47:09 +0200138 @property
139 def mac(self):
140 """ MAC address """
141 return self._mac
Damjan Marionf56b77a2016-10-03 19:44:57 +0200142
Klement Sekeraf62ae122016-10-11 11:47:09 +0200143 @property
Eyal Baric86e5922017-07-02 18:33:16 +0300144 def bin_mac(self):
145 """ MAC address """
Ole Troan8006c6a2018-12-17 12:02:26 +0100146 return mac_pton(self._mac)
Eyal Baric86e5922017-07-02 18:33:16 +0300147
148 @property
Klement Sekeraf62ae122016-10-11 11:47:09 +0200149 def ip4(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100150 """ IPv4 address - string """
Klement Sekeraf62ae122016-10-11 11:47:09 +0200151 return self._ip4
Damjan Marionf56b77a2016-10-03 19:44:57 +0200152
Klement Sekeraf62ae122016-10-11 11:47:09 +0200153 @property
Matej Klotton0178d522016-11-04 11:11:44 +0100154 def ip4n(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100155 """ IPv4 address of remote host - raw, suitable as API parameter."""
Matej Klotton0178d522016-11-04 11:11:44 +0100156 return socket.inet_pton(socket.AF_INET, self._ip4)
157
158 @property
Klement Sekeraf62ae122016-10-11 11:47:09 +0200159 def ip6(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100160 """ IPv6 address - string """
Klement Sekeraf62ae122016-10-11 11:47:09 +0200161 return self._ip6
Damjan Marionf56b77a2016-10-03 19:44:57 +0200162
Klement Sekera46a87ad2017-01-02 08:22:23 +0100163 @property
164 def ip6n(self):
165 """ IPv6 address of remote host - raw, suitable as API parameter."""
166 return socket.inet_pton(socket.AF_INET6, self._ip6)
167
Neale Ranns2a3ea492017-04-19 05:24:40 -0700168 @property
169 def ip6_ll(self):
170 """ IPv6 link-local address - string """
171 return self._ip6_ll
172
173 @property
174 def ip6n_ll(self):
175 """ IPv6 link-local address of remote host -
176 raw, suitable as API parameter."""
177 return socket.inet_pton(socket.AF_INET6, self._ip6_ll)
178
Eyal Baric86e5922017-07-02 18:33:16 +0300179 def __eq__(self, other):
180 if isinstance(other, Host):
181 return (self.mac == other.mac and
182 self.ip4 == other.ip4 and
183 self.ip6 == other.ip6 and
184 self.ip6_ll == other.ip6_ll)
185 else:
186 return False
187
188 def __ne__(self, other):
189 return not self.__eq__(other)
190
191 def __repr__(self):
192 return "Host { mac:%s ip4:%s ip6:%s ip6_ll:%s }" % (self.mac,
193 self.ip4,
194 self.ip6,
195 self.ip6_ll)
196
197 def __hash__(self):
198 return hash(self.__repr__())
199
Neale Ranns2a3ea492017-04-19 05:24:40 -0700200 def __init__(self, mac=None, ip4=None, ip6=None, ip6_ll=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200201 self._mac = mac
202 self._ip4 = ip4
203 self._ip6 = ip6
Neale Ranns2a3ea492017-04-19 05:24:40 -0700204 self._ip6_ll = ip6_ll
Filip Tehlar770e89e2017-01-31 10:39:16 +0100205
206
207class ForeignAddressFactory(object):
208 count = 0
209 prefix_len = 24
210 net_template = '10.10.10.{}'
211 net = net_template.format(0) + '/' + str(prefix_len)
212
213 def get_ip4(self):
214 if self.count > 255:
215 raise Exception("Network host address exhaustion")
216 self.count += 1
217 return self.net_template.format(self.count)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200218
219
220class L4_Conn():
221 """ L4 'connection' tied to two VPP interfaces """
Klement Sekera75e7d132017-09-20 08:26:30 +0200222
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200223 def __init__(self, testcase, if1, if2, af, l4proto, port1, port2):
224 self.testcase = testcase
225 self.ifs = [None, None]
226 self.ifs[0] = if1
227 self.ifs[1] = if2
228 self.address_family = af
229 self.l4proto = l4proto
230 self.ports = [None, None]
231 self.ports[0] = port1
232 self.ports[1] = port2
233 self
234
235 def pkt(self, side, l4args={}, payload="x"):
236 is_ip6 = 1 if self.address_family == AF_INET6 else 0
237 s0 = side
Klement Sekera75e7d132017-09-20 08:26:30 +0200238 s1 = 1 - side
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200239 src_if = self.ifs[s0]
240 dst_if = self.ifs[s1]
241 layer_3 = [IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4),
242 IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)]
243 merged_l4args = {'sport': self.ports[s0], 'dport': self.ports[s1]}
244 merged_l4args.update(l4args)
245 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
246 layer_3[is_ip6] /
247 self.l4proto(**merged_l4args) /
248 Raw(payload))
249 return p
250
251 def send(self, side, flags=None, payload=""):
252 l4args = {}
253 if flags is not None:
254 l4args['flags'] = flags
255 self.ifs[side].add_stream(self.pkt(side,
256 l4args=l4args, payload=payload))
Klement Sekera75e7d132017-09-20 08:26:30 +0200257 self.ifs[1 - side].enable_capture()
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200258 self.testcase.pg_start()
259
260 def recv(self, side):
261 p = self.ifs[side].wait_for_packet(1)
262 return p
263
264 def send_through(self, side, flags=None, payload=""):
265 self.send(side, flags, payload)
Klement Sekera75e7d132017-09-20 08:26:30 +0200266 p = self.recv(1 - side)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200267 return p
268
269 def send_pingpong(self, side, flags1=None, flags2=None):
270 p1 = self.send_through(side, flags1)
Klement Sekera75e7d132017-09-20 08:26:30 +0200271 p2 = self.send_through(1 - side, flags2)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200272 return [p1, p2]
273
274
275class L4_CONN_SIDE:
276 L4_CONN_SIDE_ZERO = 0
277 L4_CONN_SIDE_ONE = 1
Klement Sekera75e7d132017-09-20 08:26:30 +0200278
279
Paul Vinciguerra9beabd82019-12-01 22:24:28 -0500280def fragment_rfc791(packet, fragsize, logger=null_logger):
Klement Sekera75e7d132017-09-20 08:26:30 +0200281 """
282 Fragment an IPv4 packet per RFC 791
283 :param packet: packet to fragment
284 :param fragsize: size at which to fragment
285 :note: IP options are not supported
286 :returns: list of fragments
287 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200288 logger.debug(ppp("Fragmenting packet:", packet))
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700289 packet = packet.__class__(scapy.compat.raw(packet)) # recalc. all values
Klement Sekera75e7d132017-09-20 08:26:30 +0200290 if len(packet[IP].options) > 0:
291 raise Exception("Not implemented")
292 if len(packet) <= fragsize:
293 return [packet]
294
295 pre_ip_len = len(packet) - len(packet[IP])
296 ip_header_len = packet[IP].ihl * 4
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700297 hex_packet = scapy.compat.raw(packet)
Klement Sekera75e7d132017-09-20 08:26:30 +0200298 hex_headers = hex_packet[:(pre_ip_len + ip_header_len)]
299 hex_payload = hex_packet[(pre_ip_len + ip_header_len):]
300
301 pkts = []
302 ihl = packet[IP].ihl
303 otl = len(packet[IP])
snaramre3030bea2019-10-17 16:39:03 +0000304 nfb = int((fragsize - pre_ip_len - ihl * 4) / 8)
Klement Sekera75e7d132017-09-20 08:26:30 +0200305 fo = packet[IP].frag
306
307 p = packet.__class__(hex_headers + hex_payload[:nfb * 8])
308 p[IP].flags = "MF"
309 p[IP].frag = fo
310 p[IP].len = ihl * 4 + nfb * 8
311 del p[IP].chksum
312 pkts.append(p)
313
314 p = packet.__class__(hex_headers + hex_payload[nfb * 8:])
315 p[IP].len = otl - nfb * 8
316 p[IP].frag = fo + nfb
317 del p[IP].chksum
318
Paul Vinciguerra9beabd82019-12-01 22:24:28 -0500319 more_fragments = fragment_rfc791(p, fragsize, logger)
Klement Sekera75e7d132017-09-20 08:26:30 +0200320 pkts.extend(more_fragments)
321
322 return pkts
323
324
Paul Vinciguerra9beabd82019-12-01 22:24:28 -0500325def fragment_rfc8200(packet, identification, fragsize, logger=null_logger):
Klement Sekera75e7d132017-09-20 08:26:30 +0200326 """
327 Fragment an IPv6 packet per RFC 8200
328 :param packet: packet to fragment
329 :param fragsize: size at which to fragment
330 :note: IP options are not supported
331 :returns: list of fragments
332 """
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700333 packet = packet.__class__(scapy.compat.raw(packet)) # recalc. all values
Klement Sekera75e7d132017-09-20 08:26:30 +0200334 if len(packet) <= fragsize:
335 return [packet]
336 logger.debug(ppp("Fragmenting packet:", packet))
337 pkts = []
338 counter = 0
339 routing_hdr = None
340 hop_by_hop_hdr = None
341 upper_layer = None
342 seen_ipv6 = False
343 ipv6_nr = -1
344 l = packet.getlayer(counter)
345 while l is not None:
346 if l.__class__ is IPv6:
347 if seen_ipv6:
348 # ignore 2nd IPv6 header and everything below..
349 break
350 ipv6_nr = counter
351 seen_ipv6 = True
352 elif l.__class__ is IPv6ExtHdrFragment:
353 raise Exception("Already fragmented")
354 elif l.__class__ is IPv6ExtHdrRouting:
355 routing_hdr = counter
356 elif l.__class__ is IPv6ExtHdrHopByHop:
357 hop_by_hop_hdr = counter
358 elif seen_ipv6 and not upper_layer and \
359 not l.__class__.__name__.startswith('IPv6ExtHdr'):
360 upper_layer = counter
361 counter = counter + 1
362 l = packet.getlayer(counter)
363
364 logger.debug(
365 "Layers seen: IPv6(#%s), Routing(#%s), HopByHop(#%s), upper(#%s)" %
366 (ipv6_nr, routing_hdr, hop_by_hop_hdr, upper_layer))
367
368 if upper_layer is None:
369 raise Exception("Upper layer header not found in IPv6 packet")
370
371 last_per_fragment_hdr = ipv6_nr
372 if routing_hdr is None:
373 if hop_by_hop_hdr is not None:
374 last_per_fragment_hdr = hop_by_hop_hdr
375 else:
376 last_per_fragment_hdr = routing_hdr
377 logger.debug("Last per-fragment hdr is #%s" % (last_per_fragment_hdr))
378
379 per_fragment_headers = packet.copy()
380 per_fragment_headers[last_per_fragment_hdr].remove_payload()
381 logger.debug(ppp("Per-fragment headers:", per_fragment_headers))
382
383 ext_and_upper_layer = packet.getlayer(last_per_fragment_hdr)[1]
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700384 hex_payload = scapy.compat.raw(ext_and_upper_layer)
Klement Sekera75e7d132017-09-20 08:26:30 +0200385 logger.debug("Payload length is %s" % len(hex_payload))
386 logger.debug(ppp("Ext and upper layer:", ext_and_upper_layer))
387
388 fragment_ext_hdr = IPv6ExtHdrFragment()
389 logger.debug(ppp("Fragment header:", fragment_ext_hdr))
390
Neale Ranns14046982019-07-29 14:49:52 +0000391 len_ext_and_upper_layer_payload = len(ext_and_upper_layer.payload)
392 if not len_ext_and_upper_layer_payload and \
393 hasattr(ext_and_upper_layer, "data"):
394 len_ext_and_upper_layer_payload = len(ext_and_upper_layer.data)
395
Klement Sekera75e7d132017-09-20 08:26:30 +0200396 if len(per_fragment_headers) + len(fragment_ext_hdr) +\
Neale Ranns14046982019-07-29 14:49:52 +0000397 len(ext_and_upper_layer) - len_ext_and_upper_layer_payload\
Klement Sekera75e7d132017-09-20 08:26:30 +0200398 > fragsize:
399 raise Exception("Cannot fragment this packet - MTU too small "
400 "(%s, %s, %s, %s, %s)" % (
401 len(per_fragment_headers), len(fragment_ext_hdr),
402 len(ext_and_upper_layer),
Neale Ranns14046982019-07-29 14:49:52 +0000403 len_ext_and_upper_layer_payload, fragsize))
Klement Sekera75e7d132017-09-20 08:26:30 +0200404
405 orig_nh = packet[IPv6].nh
406 p = per_fragment_headers
407 del p[IPv6].plen
408 del p[IPv6].nh
409 p = p / fragment_ext_hdr
410 del p[IPv6ExtHdrFragment].nh
snaramre3030bea2019-10-17 16:39:03 +0000411 first_payload_len_nfb = int((fragsize - len(p)) / 8)
Klement Sekera75e7d132017-09-20 08:26:30 +0200412 p = p / Raw(hex_payload[:first_payload_len_nfb * 8])
413 del p[IPv6].plen
414 p[IPv6ExtHdrFragment].nh = orig_nh
415 p[IPv6ExtHdrFragment].id = identification
416 p[IPv6ExtHdrFragment].offset = 0
417 p[IPv6ExtHdrFragment].m = 1
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700418 p = p.__class__(scapy.compat.raw(p))
Klement Sekera75e7d132017-09-20 08:26:30 +0200419 logger.debug(ppp("Fragment %s:" % len(pkts), p))
420 pkts.append(p)
421 offset = first_payload_len_nfb * 8
422 logger.debug("Offset after first fragment: %s" % offset)
423 while len(hex_payload) > offset:
424 p = per_fragment_headers
425 del p[IPv6].plen
426 del p[IPv6].nh
427 p = p / fragment_ext_hdr
428 del p[IPv6ExtHdrFragment].nh
snaramre3030bea2019-10-17 16:39:03 +0000429 l_nfb = int((fragsize - len(p)) / 8)
Klement Sekera75e7d132017-09-20 08:26:30 +0200430 p = p / Raw(hex_payload[offset:offset + l_nfb * 8])
431 p[IPv6ExtHdrFragment].nh = orig_nh
432 p[IPv6ExtHdrFragment].id = identification
snaramre3030bea2019-10-17 16:39:03 +0000433 p[IPv6ExtHdrFragment].offset = int(offset / 8)
Klement Sekera75e7d132017-09-20 08:26:30 +0200434 p[IPv6ExtHdrFragment].m = 1
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700435 p = p.__class__(scapy.compat.raw(p))
Klement Sekera75e7d132017-09-20 08:26:30 +0200436 logger.debug(ppp("Fragment %s:" % len(pkts), p))
437 pkts.append(p)
438 offset = offset + l_nfb * 8
439
440 pkts[-1][IPv6ExtHdrFragment].m = 0 # reset more-flags in last fragment
441
442 return pkts
Ole Troan7f991832018-12-06 17:35:12 +0100443
444
445def reassemble4_core(listoffragments, return_ip):
446 buffer = BytesIO()
447 first = listoffragments[0]
448 buffer.seek(20)
449 for pkt in listoffragments:
450 buffer.seek(pkt[IP].frag*8)
451 buffer.write(bytes(pkt[IP].payload))
452 first.len = len(buffer.getvalue()) + 20
453 first.flags = 0
454 del(first.chksum)
455 if return_ip:
456 header = bytes(first[IP])[:20]
457 return first[IP].__class__(header + buffer.getvalue())
458 else:
459 header = bytes(first[Ether])[:34]
460 return first[Ether].__class__(header + buffer.getvalue())
461
462
463def reassemble4_ether(listoffragments):
464 return reassemble4_core(listoffragments, False)
465
466
467def reassemble4(listoffragments):
468 return reassemble4_core(listoffragments, True)