blob: ed6c40959f1877fcf4bf7330df390d62091bd93e [file] [log] [blame]
Klement Sekeraacb9b8e2017-02-14 02:55:31 +01001""" test framework utilities """
2
Paul Vinciguerra3bce8eb2018-11-24 21:46:05 -08003import abc
Matej Klotton0178d522016-11-04 11:11:44 +01004import socket
Paul Vinciguerrae8fece82019-02-28 15:34:00 -08005from socket import AF_INET6
Paul Vinciguerra3bce8eb2018-11-24 21:46:05 -08006import six
Klement Sekera7bb873a2016-11-18 07:38:42 +01007import sys
juraj.linkes40dd73b2018-09-21 13:55:16 +02008import os.path
Klement Sekera7bb873a2016-11-18 07:38:42 +01009
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -070010import scapy.compat
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +020011from scapy.layers.l2 import Ether
Klement Sekera75e7d132017-09-20 08:26:30 +020012from scapy.layers.inet import IP
13from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, IPv6ExtHdrRouting,\
14 IPv6ExtHdrHopByHop
Paul Vinciguerrae8fece82019-02-28 15:34:00 -080015from scapy.packet import Raw
Klement Sekera611864f2018-09-26 11:19:00 +020016from scapy.utils import hexdump
Paul Vinciguerrae8fece82019-02-28 15:34:00 -080017from scapy.utils6 import in6_mactoifaceid
18
Ole Troan7f991832018-12-06 17:35:12 +010019from io import BytesIO
Ole Troan8006c6a2018-12-17 12:02:26 +010020from vpp_papi import mac_pton
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +020021
Klement Sekera7bb873a2016-11-18 07:38:42 +010022
23def ppp(headline, packet):
24 """ Return string containing the output of scapy packet.show() call. """
Paul Vinciguerrab951ad82019-01-08 21:36:39 -080025 return '%s\n%s\n\n%s\n' % (headline,
26 hexdump(packet, dump=True),
27 packet.show(dump=True))
Klement Sekera7bb873a2016-11-18 07:38:42 +010028
Damjan Marionf56b77a2016-10-03 19:44:57 +020029
Klement Sekera9225dee2016-12-12 08:36:58 +010030def ppc(headline, capture, limit=10):
31 """ Return string containing ppp() printout for a capture.
32
33 :param headline: printed as first line of output
34 :param capture: packets to print
35 :param limit: limit the print to # of packets
36 """
37 if not capture:
38 return headline
Klement Sekeradab231a2016-12-21 08:50:14 +010039 tail = ""
Klement Sekera9225dee2016-12-12 08:36:58 +010040 if limit < len(capture):
Klement Sekeradab231a2016-12-21 08:50:14 +010041 tail = "\nPrint limit reached, %s out of %s packets printed" % (
Klement Sekera75e7d132017-09-20 08:26:30 +020042 limit, len(capture))
Klement Sekeradab231a2016-12-21 08:50:14 +010043 body = "".join([ppp("Packet #%s:" % count, p)
44 for count, p in zip(range(0, limit), capture)])
45 return "%s\n%s%s" % (headline, body, tail)
Klement Sekera9225dee2016-12-12 08:36:58 +010046
47
Eyal Barid81da8c2017-01-11 13:39:54 +020048def ip4_range(ip4, s, e):
49 tmp = ip4.rsplit('.', 1)[0]
50 return ("%s.%d" % (tmp, i) for i in range(s, e))
51
52
53def ip4n_range(ip4n, s, e):
54 ip4 = socket.inet_ntop(socket.AF_INET, ip4n)
Klement Sekera72715ee2017-01-17 10:37:05 +010055 return (socket.inet_pton(socket.AF_INET, ip)
56 for ip in ip4_range(ip4, s, e))
Eyal Barid81da8c2017-01-11 13:39:54 +020057
58
Paul Vinciguerrae8fece82019-02-28 15:34:00 -080059# wrapper around scapy library function.
Neale Ranns2a3ea492017-04-19 05:24:40 -070060def mk_ll_addr(mac):
Ole Troan6ed154f2019-10-15 19:31:55 +020061 euid = in6_mactoifaceid(str(mac))
Neale Ranns2a3ea492017-04-19 05:24:40 -070062 addr = "fe80::" + euid
63 return addr
64
65
Juraj Sloboda4b9669d2018-01-15 10:39:21 +010066def ip6_normalize(ip6):
67 return socket.inet_ntop(socket.AF_INET6,
68 socket.inet_pton(socket.AF_INET6, ip6))
69
70
juraj.linkes40dd73b2018-09-21 13:55:16 +020071def get_core_path(tempdir):
72 return "%s/%s" % (tempdir, get_core_pattern())
73
74
75def is_core_present(tempdir):
76 return os.path.isfile(get_core_path(tempdir))
77
78
79def get_core_pattern():
Andrew Yourtchenko57612eb2018-03-28 15:32:10 +020080 with open("/proc/sys/kernel/core_pattern", "r") as f:
juraj.linkes40dd73b2018-09-21 13:55:16 +020081 corefmt = f.read().strip()
82 return corefmt
83
84
85def check_core_path(logger, core_path):
86 corefmt = get_core_pattern()
87 if corefmt.startswith("|"):
88 logger.error(
89 "WARNING: redirecting the core dump through a"
90 " filter may result in truncated dumps.")
91 logger.error(
92 " You may want to check the filter settings"
93 " or uninstall it and edit the"
94 " /proc/sys/kernel/core_pattern accordingly.")
95 logger.error(
96 " current core pattern is: %s" % corefmt)
Andrew Yourtchenko57612eb2018-03-28 15:32:10 +020097
98
Klement Sekera0e3c0de2016-09-29 14:43:44 +020099class NumericConstant(object):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200100
101 desc_dict = {}
102
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200103 def __init__(self, value):
104 self._value = value
105
106 def __int__(self):
107 return self._value
108
109 def __long__(self):
110 return self._value
111
112 def __str__(self):
113 if self._value in self.desc_dict:
114 return self.desc_dict[self._value]
115 return ""
116
117
Matej Klotton0178d522016-11-04 11:11:44 +0100118class Host(object):
119 """ Generic test host "connected" to VPPs interface. """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200120
Klement Sekeraf62ae122016-10-11 11:47:09 +0200121 @property
122 def mac(self):
123 """ MAC address """
124 return self._mac
Damjan Marionf56b77a2016-10-03 19:44:57 +0200125
Klement Sekeraf62ae122016-10-11 11:47:09 +0200126 @property
Eyal Baric86e5922017-07-02 18:33:16 +0300127 def bin_mac(self):
128 """ MAC address """
Ole Troan8006c6a2018-12-17 12:02:26 +0100129 return mac_pton(self._mac)
Eyal Baric86e5922017-07-02 18:33:16 +0300130
131 @property
Klement Sekeraf62ae122016-10-11 11:47:09 +0200132 def ip4(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100133 """ IPv4 address - string """
Klement Sekeraf62ae122016-10-11 11:47:09 +0200134 return self._ip4
Damjan Marionf56b77a2016-10-03 19:44:57 +0200135
Klement Sekeraf62ae122016-10-11 11:47:09 +0200136 @property
Matej Klotton0178d522016-11-04 11:11:44 +0100137 def ip4n(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100138 """ IPv4 address of remote host - raw, suitable as API parameter."""
Matej Klotton0178d522016-11-04 11:11:44 +0100139 return socket.inet_pton(socket.AF_INET, self._ip4)
140
141 @property
Klement Sekeraf62ae122016-10-11 11:47:09 +0200142 def ip6(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100143 """ IPv6 address - string """
Klement Sekeraf62ae122016-10-11 11:47:09 +0200144 return self._ip6
Damjan Marionf56b77a2016-10-03 19:44:57 +0200145
Klement Sekera46a87ad2017-01-02 08:22:23 +0100146 @property
147 def ip6n(self):
148 """ IPv6 address of remote host - raw, suitable as API parameter."""
149 return socket.inet_pton(socket.AF_INET6, self._ip6)
150
Neale Ranns2a3ea492017-04-19 05:24:40 -0700151 @property
152 def ip6_ll(self):
153 """ IPv6 link-local address - string """
154 return self._ip6_ll
155
156 @property
157 def ip6n_ll(self):
158 """ IPv6 link-local address of remote host -
159 raw, suitable as API parameter."""
160 return socket.inet_pton(socket.AF_INET6, self._ip6_ll)
161
Eyal Baric86e5922017-07-02 18:33:16 +0300162 def __eq__(self, other):
163 if isinstance(other, Host):
164 return (self.mac == other.mac and
165 self.ip4 == other.ip4 and
166 self.ip6 == other.ip6 and
167 self.ip6_ll == other.ip6_ll)
168 else:
169 return False
170
171 def __ne__(self, other):
172 return not self.__eq__(other)
173
174 def __repr__(self):
175 return "Host { mac:%s ip4:%s ip6:%s ip6_ll:%s }" % (self.mac,
176 self.ip4,
177 self.ip6,
178 self.ip6_ll)
179
180 def __hash__(self):
181 return hash(self.__repr__())
182
Neale Ranns2a3ea492017-04-19 05:24:40 -0700183 def __init__(self, mac=None, ip4=None, ip6=None, ip6_ll=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200184 self._mac = mac
185 self._ip4 = ip4
186 self._ip6 = ip6
Neale Ranns2a3ea492017-04-19 05:24:40 -0700187 self._ip6_ll = ip6_ll
Filip Tehlar770e89e2017-01-31 10:39:16 +0100188
189
190class ForeignAddressFactory(object):
191 count = 0
192 prefix_len = 24
193 net_template = '10.10.10.{}'
194 net = net_template.format(0) + '/' + str(prefix_len)
195
196 def get_ip4(self):
197 if self.count > 255:
198 raise Exception("Network host address exhaustion")
199 self.count += 1
200 return self.net_template.format(self.count)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200201
202
203class L4_Conn():
204 """ L4 'connection' tied to two VPP interfaces """
Klement Sekera75e7d132017-09-20 08:26:30 +0200205
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200206 def __init__(self, testcase, if1, if2, af, l4proto, port1, port2):
207 self.testcase = testcase
208 self.ifs = [None, None]
209 self.ifs[0] = if1
210 self.ifs[1] = if2
211 self.address_family = af
212 self.l4proto = l4proto
213 self.ports = [None, None]
214 self.ports[0] = port1
215 self.ports[1] = port2
216 self
217
218 def pkt(self, side, l4args={}, payload="x"):
219 is_ip6 = 1 if self.address_family == AF_INET6 else 0
220 s0 = side
Klement Sekera75e7d132017-09-20 08:26:30 +0200221 s1 = 1 - side
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200222 src_if = self.ifs[s0]
223 dst_if = self.ifs[s1]
224 layer_3 = [IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4),
225 IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)]
226 merged_l4args = {'sport': self.ports[s0], 'dport': self.ports[s1]}
227 merged_l4args.update(l4args)
228 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
229 layer_3[is_ip6] /
230 self.l4proto(**merged_l4args) /
231 Raw(payload))
232 return p
233
234 def send(self, side, flags=None, payload=""):
235 l4args = {}
236 if flags is not None:
237 l4args['flags'] = flags
238 self.ifs[side].add_stream(self.pkt(side,
239 l4args=l4args, payload=payload))
Klement Sekera75e7d132017-09-20 08:26:30 +0200240 self.ifs[1 - side].enable_capture()
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200241 self.testcase.pg_start()
242
243 def recv(self, side):
244 p = self.ifs[side].wait_for_packet(1)
245 return p
246
247 def send_through(self, side, flags=None, payload=""):
248 self.send(side, flags, payload)
Klement Sekera75e7d132017-09-20 08:26:30 +0200249 p = self.recv(1 - side)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200250 return p
251
252 def send_pingpong(self, side, flags1=None, flags2=None):
253 p1 = self.send_through(side, flags1)
Klement Sekera75e7d132017-09-20 08:26:30 +0200254 p2 = self.send_through(1 - side, flags2)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200255 return [p1, p2]
256
257
258class L4_CONN_SIDE:
259 L4_CONN_SIDE_ZERO = 0
260 L4_CONN_SIDE_ONE = 1
Klement Sekera75e7d132017-09-20 08:26:30 +0200261
262
263class LoggerWrapper(object):
264 def __init__(self, logger=None):
265 self._logger = logger
266
267 def debug(self, *args, **kwargs):
268 if self._logger:
269 self._logger.debug(*args, **kwargs)
270
271 def error(self, *args, **kwargs):
272 if self._logger:
273 self._logger.error(*args, **kwargs)
274
275
276def fragment_rfc791(packet, fragsize, _logger=None):
277 """
278 Fragment an IPv4 packet per RFC 791
279 :param packet: packet to fragment
280 :param fragsize: size at which to fragment
281 :note: IP options are not supported
282 :returns: list of fragments
283 """
284 logger = LoggerWrapper(_logger)
285 logger.debug(ppp("Fragmenting packet:", packet))
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700286 packet = packet.__class__(scapy.compat.raw(packet)) # recalc. all values
Klement Sekera75e7d132017-09-20 08:26:30 +0200287 if len(packet[IP].options) > 0:
288 raise Exception("Not implemented")
289 if len(packet) <= fragsize:
290 return [packet]
291
292 pre_ip_len = len(packet) - len(packet[IP])
293 ip_header_len = packet[IP].ihl * 4
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700294 hex_packet = scapy.compat.raw(packet)
Klement Sekera75e7d132017-09-20 08:26:30 +0200295 hex_headers = hex_packet[:(pre_ip_len + ip_header_len)]
296 hex_payload = hex_packet[(pre_ip_len + ip_header_len):]
297
298 pkts = []
299 ihl = packet[IP].ihl
300 otl = len(packet[IP])
301 nfb = (fragsize - pre_ip_len - ihl * 4) / 8
302 fo = packet[IP].frag
303
304 p = packet.__class__(hex_headers + hex_payload[:nfb * 8])
305 p[IP].flags = "MF"
306 p[IP].frag = fo
307 p[IP].len = ihl * 4 + nfb * 8
308 del p[IP].chksum
309 pkts.append(p)
310
311 p = packet.__class__(hex_headers + hex_payload[nfb * 8:])
312 p[IP].len = otl - nfb * 8
313 p[IP].frag = fo + nfb
314 del p[IP].chksum
315
316 more_fragments = fragment_rfc791(p, fragsize, _logger)
317 pkts.extend(more_fragments)
318
319 return pkts
320
321
322def fragment_rfc8200(packet, identification, fragsize, _logger=None):
323 """
324 Fragment an IPv6 packet per RFC 8200
325 :param packet: packet to fragment
326 :param fragsize: size at which to fragment
327 :note: IP options are not supported
328 :returns: list of fragments
329 """
330 logger = LoggerWrapper(_logger)
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700331 packet = packet.__class__(scapy.compat.raw(packet)) # recalc. all values
Klement Sekera75e7d132017-09-20 08:26:30 +0200332 if len(packet) <= fragsize:
333 return [packet]
334 logger.debug(ppp("Fragmenting packet:", packet))
335 pkts = []
336 counter = 0
337 routing_hdr = None
338 hop_by_hop_hdr = None
339 upper_layer = None
340 seen_ipv6 = False
341 ipv6_nr = -1
342 l = packet.getlayer(counter)
343 while l is not None:
344 if l.__class__ is IPv6:
345 if seen_ipv6:
346 # ignore 2nd IPv6 header and everything below..
347 break
348 ipv6_nr = counter
349 seen_ipv6 = True
350 elif l.__class__ is IPv6ExtHdrFragment:
351 raise Exception("Already fragmented")
352 elif l.__class__ is IPv6ExtHdrRouting:
353 routing_hdr = counter
354 elif l.__class__ is IPv6ExtHdrHopByHop:
355 hop_by_hop_hdr = counter
356 elif seen_ipv6 and not upper_layer and \
357 not l.__class__.__name__.startswith('IPv6ExtHdr'):
358 upper_layer = counter
359 counter = counter + 1
360 l = packet.getlayer(counter)
361
362 logger.debug(
363 "Layers seen: IPv6(#%s), Routing(#%s), HopByHop(#%s), upper(#%s)" %
364 (ipv6_nr, routing_hdr, hop_by_hop_hdr, upper_layer))
365
366 if upper_layer is None:
367 raise Exception("Upper layer header not found in IPv6 packet")
368
369 last_per_fragment_hdr = ipv6_nr
370 if routing_hdr is None:
371 if hop_by_hop_hdr is not None:
372 last_per_fragment_hdr = hop_by_hop_hdr
373 else:
374 last_per_fragment_hdr = routing_hdr
375 logger.debug("Last per-fragment hdr is #%s" % (last_per_fragment_hdr))
376
377 per_fragment_headers = packet.copy()
378 per_fragment_headers[last_per_fragment_hdr].remove_payload()
379 logger.debug(ppp("Per-fragment headers:", per_fragment_headers))
380
381 ext_and_upper_layer = packet.getlayer(last_per_fragment_hdr)[1]
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700382 hex_payload = scapy.compat.raw(ext_and_upper_layer)
Klement Sekera75e7d132017-09-20 08:26:30 +0200383 logger.debug("Payload length is %s" % len(hex_payload))
384 logger.debug(ppp("Ext and upper layer:", ext_and_upper_layer))
385
386 fragment_ext_hdr = IPv6ExtHdrFragment()
387 logger.debug(ppp("Fragment header:", fragment_ext_hdr))
388
Neale Ranns14046982019-07-29 14:49:52 +0000389 len_ext_and_upper_layer_payload = len(ext_and_upper_layer.payload)
390 if not len_ext_and_upper_layer_payload and \
391 hasattr(ext_and_upper_layer, "data"):
392 len_ext_and_upper_layer_payload = len(ext_and_upper_layer.data)
393
Klement Sekera75e7d132017-09-20 08:26:30 +0200394 if len(per_fragment_headers) + len(fragment_ext_hdr) +\
Neale Ranns14046982019-07-29 14:49:52 +0000395 len(ext_and_upper_layer) - len_ext_and_upper_layer_payload\
Klement Sekera75e7d132017-09-20 08:26:30 +0200396 > fragsize:
397 raise Exception("Cannot fragment this packet - MTU too small "
398 "(%s, %s, %s, %s, %s)" % (
399 len(per_fragment_headers), len(fragment_ext_hdr),
400 len(ext_and_upper_layer),
Neale Ranns14046982019-07-29 14:49:52 +0000401 len_ext_and_upper_layer_payload, fragsize))
Klement Sekera75e7d132017-09-20 08:26:30 +0200402
403 orig_nh = packet[IPv6].nh
404 p = per_fragment_headers
405 del p[IPv6].plen
406 del p[IPv6].nh
407 p = p / fragment_ext_hdr
408 del p[IPv6ExtHdrFragment].nh
409 first_payload_len_nfb = (fragsize - len(p)) / 8
410 p = p / Raw(hex_payload[:first_payload_len_nfb * 8])
411 del p[IPv6].plen
412 p[IPv6ExtHdrFragment].nh = orig_nh
413 p[IPv6ExtHdrFragment].id = identification
414 p[IPv6ExtHdrFragment].offset = 0
415 p[IPv6ExtHdrFragment].m = 1
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700416 p = p.__class__(scapy.compat.raw(p))
Klement Sekera75e7d132017-09-20 08:26:30 +0200417 logger.debug(ppp("Fragment %s:" % len(pkts), p))
418 pkts.append(p)
419 offset = first_payload_len_nfb * 8
420 logger.debug("Offset after first fragment: %s" % offset)
421 while len(hex_payload) > offset:
422 p = per_fragment_headers
423 del p[IPv6].plen
424 del p[IPv6].nh
425 p = p / fragment_ext_hdr
426 del p[IPv6ExtHdrFragment].nh
427 l_nfb = (fragsize - len(p)) / 8
428 p = p / Raw(hex_payload[offset:offset + l_nfb * 8])
429 p[IPv6ExtHdrFragment].nh = orig_nh
430 p[IPv6ExtHdrFragment].id = identification
431 p[IPv6ExtHdrFragment].offset = offset / 8
432 p[IPv6ExtHdrFragment].m = 1
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700433 p = p.__class__(scapy.compat.raw(p))
Klement Sekera75e7d132017-09-20 08:26:30 +0200434 logger.debug(ppp("Fragment %s:" % len(pkts), p))
435 pkts.append(p)
436 offset = offset + l_nfb * 8
437
438 pkts[-1][IPv6ExtHdrFragment].m = 0 # reset more-flags in last fragment
439
440 return pkts
Ole Troan7f991832018-12-06 17:35:12 +0100441
442
443def reassemble4_core(listoffragments, return_ip):
444 buffer = BytesIO()
445 first = listoffragments[0]
446 buffer.seek(20)
447 for pkt in listoffragments:
448 buffer.seek(pkt[IP].frag*8)
449 buffer.write(bytes(pkt[IP].payload))
450 first.len = len(buffer.getvalue()) + 20
451 first.flags = 0
452 del(first.chksum)
453 if return_ip:
454 header = bytes(first[IP])[:20]
455 return first[IP].__class__(header + buffer.getvalue())
456 else:
457 header = bytes(first[Ether])[:34]
458 return first[Ether].__class__(header + buffer.getvalue())
459
460
461def reassemble4_ether(listoffragments):
462 return reassemble4_core(listoffragments, False)
463
464
465def reassemble4(listoffragments):
466 return reassemble4_core(listoffragments, True)