blob: 5ce84070a878054a1edf157c35e8f699780a7df7 [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 Vinciguerra3bce8eb2018-11-24 21:46:05 -08005import six
Klement Sekera7bb873a2016-11-18 07:38:42 +01006import sys
juraj.linkes40dd73b2018-09-21 13:55:16 +02007import os.path
Neale Ranns2bc94022018-02-25 12:27:18 -08008from scapy.utils6 import in6_mactoifaceid
Klement Sekera7bb873a2016-11-18 07:38:42 +01009
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +020010from scapy.layers.l2 import Ether
11from scapy.packet import Raw
Klement Sekera75e7d132017-09-20 08:26:30 +020012from scapy.layers.inet import IP
13from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, IPv6ExtHdrRouting,\
14 IPv6ExtHdrHopByHop
Klement Sekera611864f2018-09-26 11:19:00 +020015from scapy.utils import hexdump
Klement Sekera75e7d132017-09-20 08:26:30 +020016from socket import AF_INET6
Ole Troan7f991832018-12-06 17:35:12 +010017from io import BytesIO
Ole Troan8006c6a2018-12-17 12:02:26 +010018from vpp_papi import mac_pton
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +020019
Klement Sekera7bb873a2016-11-18 07:38:42 +010020
21def ppp(headline, packet):
22 """ Return string containing the output of scapy packet.show() call. """
Paul Vinciguerrab951ad82019-01-08 21:36:39 -080023 return '%s\n%s\n\n%s\n' % (headline,
24 hexdump(packet, dump=True),
25 packet.show(dump=True))
Klement Sekera7bb873a2016-11-18 07:38:42 +010026
Damjan Marionf56b77a2016-10-03 19:44:57 +020027
Klement Sekera9225dee2016-12-12 08:36:58 +010028def ppc(headline, capture, limit=10):
29 """ Return string containing ppp() printout for a capture.
30
31 :param headline: printed as first line of output
32 :param capture: packets to print
33 :param limit: limit the print to # of packets
34 """
35 if not capture:
36 return headline
Klement Sekeradab231a2016-12-21 08:50:14 +010037 tail = ""
Klement Sekera9225dee2016-12-12 08:36:58 +010038 if limit < len(capture):
Klement Sekeradab231a2016-12-21 08:50:14 +010039 tail = "\nPrint limit reached, %s out of %s packets printed" % (
Klement Sekera75e7d132017-09-20 08:26:30 +020040 limit, len(capture))
Klement Sekeradab231a2016-12-21 08:50:14 +010041 body = "".join([ppp("Packet #%s:" % count, p)
42 for count, p in zip(range(0, limit), capture)])
43 return "%s\n%s%s" % (headline, body, tail)
Klement Sekera9225dee2016-12-12 08:36:58 +010044
45
Eyal Barid81da8c2017-01-11 13:39:54 +020046def ip4_range(ip4, s, e):
47 tmp = ip4.rsplit('.', 1)[0]
48 return ("%s.%d" % (tmp, i) for i in range(s, e))
49
50
51def ip4n_range(ip4n, s, e):
52 ip4 = socket.inet_ntop(socket.AF_INET, ip4n)
Klement Sekera72715ee2017-01-17 10:37:05 +010053 return (socket.inet_pton(socket.AF_INET, ip)
54 for ip in ip4_range(ip4, s, e))
Eyal Barid81da8c2017-01-11 13:39:54 +020055
56
Neale Ranns2a3ea492017-04-19 05:24:40 -070057def mk_ll_addr(mac):
58 euid = in6_mactoifaceid(mac)
59 addr = "fe80::" + euid
60 return addr
61
62
Juraj Sloboda4b9669d2018-01-15 10:39:21 +010063def ip6_normalize(ip6):
64 return socket.inet_ntop(socket.AF_INET6,
65 socket.inet_pton(socket.AF_INET6, ip6))
66
67
juraj.linkes40dd73b2018-09-21 13:55:16 +020068def get_core_path(tempdir):
69 return "%s/%s" % (tempdir, get_core_pattern())
70
71
72def is_core_present(tempdir):
73 return os.path.isfile(get_core_path(tempdir))
74
75
76def get_core_pattern():
Andrew Yourtchenko57612eb2018-03-28 15:32:10 +020077 with open("/proc/sys/kernel/core_pattern", "r") as f:
juraj.linkes40dd73b2018-09-21 13:55:16 +020078 corefmt = f.read().strip()
79 return corefmt
80
81
82def check_core_path(logger, core_path):
83 corefmt = get_core_pattern()
84 if corefmt.startswith("|"):
85 logger.error(
86 "WARNING: redirecting the core dump through a"
87 " filter may result in truncated dumps.")
88 logger.error(
89 " You may want to check the filter settings"
90 " or uninstall it and edit the"
91 " /proc/sys/kernel/core_pattern accordingly.")
92 logger.error(
93 " current core pattern is: %s" % corefmt)
Andrew Yourtchenko57612eb2018-03-28 15:32:10 +020094
95
Klement Sekera0e3c0de2016-09-29 14:43:44 +020096class NumericConstant(object):
Klement Sekera0e3c0de2016-09-29 14:43:44 +020097
98 desc_dict = {}
99
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200100 def __init__(self, value):
101 self._value = value
102
103 def __int__(self):
104 return self._value
105
106 def __long__(self):
107 return self._value
108
109 def __str__(self):
110 if self._value in self.desc_dict:
111 return self.desc_dict[self._value]
112 return ""
113
114
Matej Klotton0178d522016-11-04 11:11:44 +0100115class Host(object):
116 """ Generic test host "connected" to VPPs interface. """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200117
Klement Sekeraf62ae122016-10-11 11:47:09 +0200118 @property
119 def mac(self):
120 """ MAC address """
121 return self._mac
Damjan Marionf56b77a2016-10-03 19:44:57 +0200122
Klement Sekeraf62ae122016-10-11 11:47:09 +0200123 @property
Eyal Baric86e5922017-07-02 18:33:16 +0300124 def bin_mac(self):
125 """ MAC address """
Ole Troan8006c6a2018-12-17 12:02:26 +0100126 return mac_pton(self._mac)
Eyal Baric86e5922017-07-02 18:33:16 +0300127
128 @property
Klement Sekeraf62ae122016-10-11 11:47:09 +0200129 def ip4(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100130 """ IPv4 address - string """
Klement Sekeraf62ae122016-10-11 11:47:09 +0200131 return self._ip4
Damjan Marionf56b77a2016-10-03 19:44:57 +0200132
Klement Sekeraf62ae122016-10-11 11:47:09 +0200133 @property
Matej Klotton0178d522016-11-04 11:11:44 +0100134 def ip4n(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100135 """ IPv4 address of remote host - raw, suitable as API parameter."""
Matej Klotton0178d522016-11-04 11:11:44 +0100136 return socket.inet_pton(socket.AF_INET, self._ip4)
137
138 @property
Klement Sekeraf62ae122016-10-11 11:47:09 +0200139 def ip6(self):
Klement Sekera46a87ad2017-01-02 08:22:23 +0100140 """ IPv6 address - string """
Klement Sekeraf62ae122016-10-11 11:47:09 +0200141 return self._ip6
Damjan Marionf56b77a2016-10-03 19:44:57 +0200142
Klement Sekera46a87ad2017-01-02 08:22:23 +0100143 @property
144 def ip6n(self):
145 """ IPv6 address of remote host - raw, suitable as API parameter."""
146 return socket.inet_pton(socket.AF_INET6, self._ip6)
147
Neale Ranns2a3ea492017-04-19 05:24:40 -0700148 @property
149 def ip6_ll(self):
150 """ IPv6 link-local address - string """
151 return self._ip6_ll
152
153 @property
154 def ip6n_ll(self):
155 """ IPv6 link-local address of remote host -
156 raw, suitable as API parameter."""
157 return socket.inet_pton(socket.AF_INET6, self._ip6_ll)
158
Eyal Baric86e5922017-07-02 18:33:16 +0300159 def __eq__(self, other):
160 if isinstance(other, Host):
161 return (self.mac == other.mac and
162 self.ip4 == other.ip4 and
163 self.ip6 == other.ip6 and
164 self.ip6_ll == other.ip6_ll)
165 else:
166 return False
167
168 def __ne__(self, other):
169 return not self.__eq__(other)
170
171 def __repr__(self):
172 return "Host { mac:%s ip4:%s ip6:%s ip6_ll:%s }" % (self.mac,
173 self.ip4,
174 self.ip6,
175 self.ip6_ll)
176
177 def __hash__(self):
178 return hash(self.__repr__())
179
Neale Ranns2a3ea492017-04-19 05:24:40 -0700180 def __init__(self, mac=None, ip4=None, ip6=None, ip6_ll=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200181 self._mac = mac
182 self._ip4 = ip4
183 self._ip6 = ip6
Neale Ranns2a3ea492017-04-19 05:24:40 -0700184 self._ip6_ll = ip6_ll
Filip Tehlar770e89e2017-01-31 10:39:16 +0100185
186
187class ForeignAddressFactory(object):
188 count = 0
189 prefix_len = 24
190 net_template = '10.10.10.{}'
191 net = net_template.format(0) + '/' + str(prefix_len)
192
193 def get_ip4(self):
194 if self.count > 255:
195 raise Exception("Network host address exhaustion")
196 self.count += 1
197 return self.net_template.format(self.count)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200198
199
200class L4_Conn():
201 """ L4 'connection' tied to two VPP interfaces """
Klement Sekera75e7d132017-09-20 08:26:30 +0200202
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200203 def __init__(self, testcase, if1, if2, af, l4proto, port1, port2):
204 self.testcase = testcase
205 self.ifs = [None, None]
206 self.ifs[0] = if1
207 self.ifs[1] = if2
208 self.address_family = af
209 self.l4proto = l4proto
210 self.ports = [None, None]
211 self.ports[0] = port1
212 self.ports[1] = port2
213 self
214
215 def pkt(self, side, l4args={}, payload="x"):
216 is_ip6 = 1 if self.address_family == AF_INET6 else 0
217 s0 = side
Klement Sekera75e7d132017-09-20 08:26:30 +0200218 s1 = 1 - side
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200219 src_if = self.ifs[s0]
220 dst_if = self.ifs[s1]
221 layer_3 = [IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4),
222 IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)]
223 merged_l4args = {'sport': self.ports[s0], 'dport': self.ports[s1]}
224 merged_l4args.update(l4args)
225 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
226 layer_3[is_ip6] /
227 self.l4proto(**merged_l4args) /
228 Raw(payload))
229 return p
230
231 def send(self, side, flags=None, payload=""):
232 l4args = {}
233 if flags is not None:
234 l4args['flags'] = flags
235 self.ifs[side].add_stream(self.pkt(side,
236 l4args=l4args, payload=payload))
Klement Sekera75e7d132017-09-20 08:26:30 +0200237 self.ifs[1 - side].enable_capture()
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200238 self.testcase.pg_start()
239
240 def recv(self, side):
241 p = self.ifs[side].wait_for_packet(1)
242 return p
243
244 def send_through(self, side, flags=None, payload=""):
245 self.send(side, flags, payload)
Klement Sekera75e7d132017-09-20 08:26:30 +0200246 p = self.recv(1 - side)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200247 return p
248
249 def send_pingpong(self, side, flags1=None, flags2=None):
250 p1 = self.send_through(side, flags1)
Klement Sekera75e7d132017-09-20 08:26:30 +0200251 p2 = self.send_through(1 - side, flags2)
Andrew Yourtchenko92dc12a2017-09-07 13:22:24 +0200252 return [p1, p2]
253
254
255class L4_CONN_SIDE:
256 L4_CONN_SIDE_ZERO = 0
257 L4_CONN_SIDE_ONE = 1
Klement Sekera75e7d132017-09-20 08:26:30 +0200258
259
260class LoggerWrapper(object):
261 def __init__(self, logger=None):
262 self._logger = logger
263
264 def debug(self, *args, **kwargs):
265 if self._logger:
266 self._logger.debug(*args, **kwargs)
267
268 def error(self, *args, **kwargs):
269 if self._logger:
270 self._logger.error(*args, **kwargs)
271
272
273def fragment_rfc791(packet, fragsize, _logger=None):
274 """
275 Fragment an IPv4 packet per RFC 791
276 :param packet: packet to fragment
277 :param fragsize: size at which to fragment
278 :note: IP options are not supported
279 :returns: list of fragments
280 """
281 logger = LoggerWrapper(_logger)
282 logger.debug(ppp("Fragmenting packet:", packet))
283 packet = packet.__class__(str(packet)) # recalculate all values
284 if len(packet[IP].options) > 0:
285 raise Exception("Not implemented")
286 if len(packet) <= fragsize:
287 return [packet]
288
289 pre_ip_len = len(packet) - len(packet[IP])
290 ip_header_len = packet[IP].ihl * 4
291 hex_packet = str(packet)
292 hex_headers = hex_packet[:(pre_ip_len + ip_header_len)]
293 hex_payload = hex_packet[(pre_ip_len + ip_header_len):]
294
295 pkts = []
296 ihl = packet[IP].ihl
297 otl = len(packet[IP])
298 nfb = (fragsize - pre_ip_len - ihl * 4) / 8
299 fo = packet[IP].frag
300
301 p = packet.__class__(hex_headers + hex_payload[:nfb * 8])
302 p[IP].flags = "MF"
303 p[IP].frag = fo
304 p[IP].len = ihl * 4 + nfb * 8
305 del p[IP].chksum
306 pkts.append(p)
307
308 p = packet.__class__(hex_headers + hex_payload[nfb * 8:])
309 p[IP].len = otl - nfb * 8
310 p[IP].frag = fo + nfb
311 del p[IP].chksum
312
313 more_fragments = fragment_rfc791(p, fragsize, _logger)
314 pkts.extend(more_fragments)
315
316 return pkts
317
318
319def fragment_rfc8200(packet, identification, fragsize, _logger=None):
320 """
321 Fragment an IPv6 packet per RFC 8200
322 :param packet: packet to fragment
323 :param fragsize: size at which to fragment
324 :note: IP options are not supported
325 :returns: list of fragments
326 """
327 logger = LoggerWrapper(_logger)
328 packet = packet.__class__(str(packet)) # recalculate all values
329 if len(packet) <= fragsize:
330 return [packet]
331 logger.debug(ppp("Fragmenting packet:", packet))
332 pkts = []
333 counter = 0
334 routing_hdr = None
335 hop_by_hop_hdr = None
336 upper_layer = None
337 seen_ipv6 = False
338 ipv6_nr = -1
339 l = packet.getlayer(counter)
340 while l is not None:
341 if l.__class__ is IPv6:
342 if seen_ipv6:
343 # ignore 2nd IPv6 header and everything below..
344 break
345 ipv6_nr = counter
346 seen_ipv6 = True
347 elif l.__class__ is IPv6ExtHdrFragment:
348 raise Exception("Already fragmented")
349 elif l.__class__ is IPv6ExtHdrRouting:
350 routing_hdr = counter
351 elif l.__class__ is IPv6ExtHdrHopByHop:
352 hop_by_hop_hdr = counter
353 elif seen_ipv6 and not upper_layer and \
354 not l.__class__.__name__.startswith('IPv6ExtHdr'):
355 upper_layer = counter
356 counter = counter + 1
357 l = packet.getlayer(counter)
358
359 logger.debug(
360 "Layers seen: IPv6(#%s), Routing(#%s), HopByHop(#%s), upper(#%s)" %
361 (ipv6_nr, routing_hdr, hop_by_hop_hdr, upper_layer))
362
363 if upper_layer is None:
364 raise Exception("Upper layer header not found in IPv6 packet")
365
366 last_per_fragment_hdr = ipv6_nr
367 if routing_hdr is None:
368 if hop_by_hop_hdr is not None:
369 last_per_fragment_hdr = hop_by_hop_hdr
370 else:
371 last_per_fragment_hdr = routing_hdr
372 logger.debug("Last per-fragment hdr is #%s" % (last_per_fragment_hdr))
373
374 per_fragment_headers = packet.copy()
375 per_fragment_headers[last_per_fragment_hdr].remove_payload()
376 logger.debug(ppp("Per-fragment headers:", per_fragment_headers))
377
378 ext_and_upper_layer = packet.getlayer(last_per_fragment_hdr)[1]
379 hex_payload = str(ext_and_upper_layer)
380 logger.debug("Payload length is %s" % len(hex_payload))
381 logger.debug(ppp("Ext and upper layer:", ext_and_upper_layer))
382
383 fragment_ext_hdr = IPv6ExtHdrFragment()
384 logger.debug(ppp("Fragment header:", fragment_ext_hdr))
385
386 if len(per_fragment_headers) + len(fragment_ext_hdr) +\
387 len(ext_and_upper_layer) - len(ext_and_upper_layer.payload)\
388 > fragsize:
389 raise Exception("Cannot fragment this packet - MTU too small "
390 "(%s, %s, %s, %s, %s)" % (
391 len(per_fragment_headers), len(fragment_ext_hdr),
392 len(ext_and_upper_layer),
393 len(ext_and_upper_layer.payload), fragsize))
394
395 orig_nh = packet[IPv6].nh
396 p = per_fragment_headers
397 del p[IPv6].plen
398 del p[IPv6].nh
399 p = p / fragment_ext_hdr
400 del p[IPv6ExtHdrFragment].nh
401 first_payload_len_nfb = (fragsize - len(p)) / 8
402 p = p / Raw(hex_payload[:first_payload_len_nfb * 8])
403 del p[IPv6].plen
404 p[IPv6ExtHdrFragment].nh = orig_nh
405 p[IPv6ExtHdrFragment].id = identification
406 p[IPv6ExtHdrFragment].offset = 0
407 p[IPv6ExtHdrFragment].m = 1
408 p = p.__class__(str(p))
409 logger.debug(ppp("Fragment %s:" % len(pkts), p))
410 pkts.append(p)
411 offset = first_payload_len_nfb * 8
412 logger.debug("Offset after first fragment: %s" % offset)
413 while len(hex_payload) > offset:
414 p = per_fragment_headers
415 del p[IPv6].plen
416 del p[IPv6].nh
417 p = p / fragment_ext_hdr
418 del p[IPv6ExtHdrFragment].nh
419 l_nfb = (fragsize - len(p)) / 8
420 p = p / Raw(hex_payload[offset:offset + l_nfb * 8])
421 p[IPv6ExtHdrFragment].nh = orig_nh
422 p[IPv6ExtHdrFragment].id = identification
423 p[IPv6ExtHdrFragment].offset = offset / 8
424 p[IPv6ExtHdrFragment].m = 1
425 p = p.__class__(str(p))
426 logger.debug(ppp("Fragment %s:" % len(pkts), p))
427 pkts.append(p)
428 offset = offset + l_nfb * 8
429
430 pkts[-1][IPv6ExtHdrFragment].m = 0 # reset more-flags in last fragment
431
432 return pkts
Ole Troan7f991832018-12-06 17:35:12 +0100433
434
435def reassemble4_core(listoffragments, return_ip):
436 buffer = BytesIO()
437 first = listoffragments[0]
438 buffer.seek(20)
439 for pkt in listoffragments:
440 buffer.seek(pkt[IP].frag*8)
441 buffer.write(bytes(pkt[IP].payload))
442 first.len = len(buffer.getvalue()) + 20
443 first.flags = 0
444 del(first.chksum)
445 if return_ip:
446 header = bytes(first[IP])[:20]
447 return first[IP].__class__(header + buffer.getvalue())
448 else:
449 header = bytes(first[Ether])[:34]
450 return first[Ether].__class__(header + buffer.getvalue())
451
452
453def reassemble4_ether(listoffragments):
454 return reassemble4_core(listoffragments, False)
455
456
457def reassemble4(listoffragments):
458 return reassemble4_core(listoffragments, True)