IGMP: proxy device
[vpp.git] / test / util.py
1 """ test framework utilities """
2
3 import socket
4 import sys
5 import os.path
6 from abc import abstractmethod, ABCMeta
7 from cStringIO import StringIO
8 from scapy.utils6 import in6_mactoifaceid
9
10 from scapy.layers.l2 import Ether
11 from scapy.packet import Raw
12 from scapy.layers.inet import IP
13 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, IPv6ExtHdrRouting,\
14     IPv6ExtHdrHopByHop
15 from scapy.utils import hexdump
16 from socket import AF_INET6
17
18
19 def ppp(headline, packet):
20     """ Return string containing the output of scapy packet.show() call. """
21     o = StringIO()
22     old_stdout = sys.stdout
23     sys.stdout = o
24     print(headline)
25     hexdump(packet)
26     print("")
27     packet.show()
28     sys.stdout = old_stdout
29     return o.getvalue()
30
31
32 def ppc(headline, capture, limit=10):
33     """ Return string containing ppp() printout for a capture.
34
35     :param headline: printed as first line of output
36     :param capture: packets to print
37     :param limit: limit the print to # of packets
38     """
39     if not capture:
40         return headline
41     tail = ""
42     if limit < len(capture):
43         tail = "\nPrint limit reached, %s out of %s packets printed" % (
44             limit, len(capture))
45     body = "".join([ppp("Packet #%s:" % count, p)
46                     for count, p in zip(range(0, limit), capture)])
47     return "%s\n%s%s" % (headline, body, tail)
48
49
50 def ip4_range(ip4, s, e):
51     tmp = ip4.rsplit('.', 1)[0]
52     return ("%s.%d" % (tmp, i) for i in range(s, e))
53
54
55 def ip4n_range(ip4n, s, e):
56     ip4 = socket.inet_ntop(socket.AF_INET, ip4n)
57     return (socket.inet_pton(socket.AF_INET, ip)
58             for ip in ip4_range(ip4, s, e))
59
60
61 def mactobinary(mac):
62     """ Convert the : separated format into binary packet data for the API """
63     return mac.replace(':', '').decode('hex')
64
65
66 def mk_ll_addr(mac):
67     euid = in6_mactoifaceid(mac)
68     addr = "fe80::" + euid
69     return addr
70
71
72 def ip6_normalize(ip6):
73     return socket.inet_ntop(socket.AF_INET6,
74                             socket.inet_pton(socket.AF_INET6, ip6))
75
76
77 def get_core_path(tempdir):
78     return "%s/%s" % (tempdir, get_core_pattern())
79
80
81 def is_core_present(tempdir):
82     return os.path.isfile(get_core_path(tempdir))
83
84
85 def get_core_pattern():
86     with open("/proc/sys/kernel/core_pattern", "r") as f:
87         corefmt = f.read().strip()
88     return corefmt
89
90
91 def check_core_path(logger, core_path):
92     corefmt = get_core_pattern()
93     if corefmt.startswith("|"):
94         logger.error(
95             "WARNING: redirecting the core dump through a"
96             " filter may result in truncated dumps.")
97         logger.error(
98             "   You may want to check the filter settings"
99             " or uninstall it and edit the"
100             " /proc/sys/kernel/core_pattern accordingly.")
101         logger.error(
102             "   current core pattern is: %s" % corefmt)
103
104
105 class NumericConstant(object):
106     __metaclass__ = ABCMeta
107
108     desc_dict = {}
109
110     @abstractmethod
111     def __init__(self, value):
112         self._value = value
113
114     def __int__(self):
115         return self._value
116
117     def __long__(self):
118         return self._value
119
120     def __str__(self):
121         if self._value in self.desc_dict:
122             return self.desc_dict[self._value]
123         return ""
124
125
126 class Host(object):
127     """ Generic test host "connected" to VPPs interface. """
128
129     @property
130     def mac(self):
131         """ MAC address """
132         return self._mac
133
134     @property
135     def bin_mac(self):
136         """ MAC address """
137         return mactobinary(self._mac)
138
139     @property
140     def ip4(self):
141         """ IPv4 address - string """
142         return self._ip4
143
144     @property
145     def ip4n(self):
146         """ IPv4 address of remote host - raw, suitable as API parameter."""
147         return socket.inet_pton(socket.AF_INET, self._ip4)
148
149     @property
150     def ip6(self):
151         """ IPv6 address - string """
152         return self._ip6
153
154     @property
155     def ip6n(self):
156         """ IPv6 address of remote host - raw, suitable as API parameter."""
157         return socket.inet_pton(socket.AF_INET6, self._ip6)
158
159     @property
160     def ip6_ll(self):
161         """ IPv6 link-local address - string """
162         return self._ip6_ll
163
164     @property
165     def ip6n_ll(self):
166         """ IPv6 link-local address of remote host -
167         raw, suitable as API parameter."""
168         return socket.inet_pton(socket.AF_INET6, self._ip6_ll)
169
170     def __eq__(self, other):
171         if isinstance(other, Host):
172             return (self.mac == other.mac and
173                     self.ip4 == other.ip4 and
174                     self.ip6 == other.ip6 and
175                     self.ip6_ll == other.ip6_ll)
176         else:
177             return False
178
179     def __ne__(self, other):
180         return not self.__eq__(other)
181
182     def __repr__(self):
183         return "Host { mac:%s ip4:%s ip6:%s ip6_ll:%s }" % (self.mac,
184                                                             self.ip4,
185                                                             self.ip6,
186                                                             self.ip6_ll)
187
188     def __hash__(self):
189         return hash(self.__repr__())
190
191     def __init__(self, mac=None, ip4=None, ip6=None, ip6_ll=None):
192         self._mac = mac
193         self._ip4 = ip4
194         self._ip6 = ip6
195         self._ip6_ll = ip6_ll
196
197
198 class ForeignAddressFactory(object):
199     count = 0
200     prefix_len = 24
201     net_template = '10.10.10.{}'
202     net = net_template.format(0) + '/' + str(prefix_len)
203
204     def get_ip4(self):
205         if self.count > 255:
206             raise Exception("Network host address exhaustion")
207         self.count += 1
208         return self.net_template.format(self.count)
209
210
211 class L4_Conn():
212     """ L4 'connection' tied to two VPP interfaces """
213
214     def __init__(self, testcase, if1, if2, af, l4proto, port1, port2):
215         self.testcase = testcase
216         self.ifs = [None, None]
217         self.ifs[0] = if1
218         self.ifs[1] = if2
219         self.address_family = af
220         self.l4proto = l4proto
221         self.ports = [None, None]
222         self.ports[0] = port1
223         self.ports[1] = port2
224         self
225
226     def pkt(self, side, l4args={}, payload="x"):
227         is_ip6 = 1 if self.address_family == AF_INET6 else 0
228         s0 = side
229         s1 = 1 - side
230         src_if = self.ifs[s0]
231         dst_if = self.ifs[s1]
232         layer_3 = [IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4),
233                    IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)]
234         merged_l4args = {'sport': self.ports[s0], 'dport': self.ports[s1]}
235         merged_l4args.update(l4args)
236         p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
237              layer_3[is_ip6] /
238              self.l4proto(**merged_l4args) /
239              Raw(payload))
240         return p
241
242     def send(self, side, flags=None, payload=""):
243         l4args = {}
244         if flags is not None:
245             l4args['flags'] = flags
246         self.ifs[side].add_stream(self.pkt(side,
247                                            l4args=l4args, payload=payload))
248         self.ifs[1 - side].enable_capture()
249         self.testcase.pg_start()
250
251     def recv(self, side):
252         p = self.ifs[side].wait_for_packet(1)
253         return p
254
255     def send_through(self, side, flags=None, payload=""):
256         self.send(side, flags, payload)
257         p = self.recv(1 - side)
258         return p
259
260     def send_pingpong(self, side, flags1=None, flags2=None):
261         p1 = self.send_through(side, flags1)
262         p2 = self.send_through(1 - side, flags2)
263         return [p1, p2]
264
265
266 class L4_CONN_SIDE:
267     L4_CONN_SIDE_ZERO = 0
268     L4_CONN_SIDE_ONE = 1
269
270
271 class LoggerWrapper(object):
272     def __init__(self, logger=None):
273         self._logger = logger
274
275     def debug(self, *args, **kwargs):
276         if self._logger:
277             self._logger.debug(*args, **kwargs)
278
279     def error(self, *args, **kwargs):
280         if self._logger:
281             self._logger.error(*args, **kwargs)
282
283
284 def fragment_rfc791(packet, fragsize, _logger=None):
285     """
286     Fragment an IPv4 packet per RFC 791
287     :param packet: packet to fragment
288     :param fragsize: size at which to fragment
289     :note: IP options are not supported
290     :returns: list of fragments
291     """
292     logger = LoggerWrapper(_logger)
293     logger.debug(ppp("Fragmenting packet:", packet))
294     packet = packet.__class__(str(packet))  # recalculate all values
295     if len(packet[IP].options) > 0:
296         raise Exception("Not implemented")
297     if len(packet) <= fragsize:
298         return [packet]
299
300     pre_ip_len = len(packet) - len(packet[IP])
301     ip_header_len = packet[IP].ihl * 4
302     hex_packet = str(packet)
303     hex_headers = hex_packet[:(pre_ip_len + ip_header_len)]
304     hex_payload = hex_packet[(pre_ip_len + ip_header_len):]
305
306     pkts = []
307     ihl = packet[IP].ihl
308     otl = len(packet[IP])
309     nfb = (fragsize - pre_ip_len - ihl * 4) / 8
310     fo = packet[IP].frag
311
312     p = packet.__class__(hex_headers + hex_payload[:nfb * 8])
313     p[IP].flags = "MF"
314     p[IP].frag = fo
315     p[IP].len = ihl * 4 + nfb * 8
316     del p[IP].chksum
317     pkts.append(p)
318
319     p = packet.__class__(hex_headers + hex_payload[nfb * 8:])
320     p[IP].len = otl - nfb * 8
321     p[IP].frag = fo + nfb
322     del p[IP].chksum
323
324     more_fragments = fragment_rfc791(p, fragsize, _logger)
325     pkts.extend(more_fragments)
326
327     return pkts
328
329
330 def fragment_rfc8200(packet, identification, fragsize, _logger=None):
331     """
332     Fragment an IPv6 packet per RFC 8200
333     :param packet: packet to fragment
334     :param fragsize: size at which to fragment
335     :note: IP options are not supported
336     :returns: list of fragments
337     """
338     logger = LoggerWrapper(_logger)
339     packet = packet.__class__(str(packet))  # recalculate all values
340     if len(packet) <= fragsize:
341         return [packet]
342     logger.debug(ppp("Fragmenting packet:", packet))
343     pkts = []
344     counter = 0
345     routing_hdr = None
346     hop_by_hop_hdr = None
347     upper_layer = None
348     seen_ipv6 = False
349     ipv6_nr = -1
350     l = packet.getlayer(counter)
351     while l is not None:
352         if l.__class__ is IPv6:
353             if seen_ipv6:
354                 # ignore 2nd IPv6 header and everything below..
355                 break
356             ipv6_nr = counter
357             seen_ipv6 = True
358         elif l.__class__ is IPv6ExtHdrFragment:
359             raise Exception("Already fragmented")
360         elif l.__class__ is IPv6ExtHdrRouting:
361             routing_hdr = counter
362         elif l.__class__ is IPv6ExtHdrHopByHop:
363             hop_by_hop_hdr = counter
364         elif seen_ipv6 and not upper_layer and \
365                 not l.__class__.__name__.startswith('IPv6ExtHdr'):
366             upper_layer = counter
367         counter = counter + 1
368         l = packet.getlayer(counter)
369
370     logger.debug(
371         "Layers seen: IPv6(#%s), Routing(#%s), HopByHop(#%s), upper(#%s)" %
372         (ipv6_nr, routing_hdr, hop_by_hop_hdr, upper_layer))
373
374     if upper_layer is None:
375         raise Exception("Upper layer header not found in IPv6 packet")
376
377     last_per_fragment_hdr = ipv6_nr
378     if routing_hdr is None:
379         if hop_by_hop_hdr is not None:
380             last_per_fragment_hdr = hop_by_hop_hdr
381     else:
382         last_per_fragment_hdr = routing_hdr
383     logger.debug("Last per-fragment hdr is #%s" % (last_per_fragment_hdr))
384
385     per_fragment_headers = packet.copy()
386     per_fragment_headers[last_per_fragment_hdr].remove_payload()
387     logger.debug(ppp("Per-fragment headers:", per_fragment_headers))
388
389     ext_and_upper_layer = packet.getlayer(last_per_fragment_hdr)[1]
390     hex_payload = str(ext_and_upper_layer)
391     logger.debug("Payload length is %s" % len(hex_payload))
392     logger.debug(ppp("Ext and upper layer:", ext_and_upper_layer))
393
394     fragment_ext_hdr = IPv6ExtHdrFragment()
395     logger.debug(ppp("Fragment header:", fragment_ext_hdr))
396
397     if len(per_fragment_headers) + len(fragment_ext_hdr) +\
398             len(ext_and_upper_layer) - len(ext_and_upper_layer.payload)\
399             > fragsize:
400         raise Exception("Cannot fragment this packet - MTU too small "
401                         "(%s, %s, %s, %s, %s)" % (
402                             len(per_fragment_headers), len(fragment_ext_hdr),
403                             len(ext_and_upper_layer),
404                             len(ext_and_upper_layer.payload), fragsize))
405
406     orig_nh = packet[IPv6].nh
407     p = per_fragment_headers
408     del p[IPv6].plen
409     del p[IPv6].nh
410     p = p / fragment_ext_hdr
411     del p[IPv6ExtHdrFragment].nh
412     first_payload_len_nfb = (fragsize - len(p)) / 8
413     p = p / Raw(hex_payload[:first_payload_len_nfb * 8])
414     del p[IPv6].plen
415     p[IPv6ExtHdrFragment].nh = orig_nh
416     p[IPv6ExtHdrFragment].id = identification
417     p[IPv6ExtHdrFragment].offset = 0
418     p[IPv6ExtHdrFragment].m = 1
419     p = p.__class__(str(p))
420     logger.debug(ppp("Fragment %s:" % len(pkts), p))
421     pkts.append(p)
422     offset = first_payload_len_nfb * 8
423     logger.debug("Offset after first fragment: %s" % offset)
424     while len(hex_payload) > offset:
425         p = per_fragment_headers
426         del p[IPv6].plen
427         del p[IPv6].nh
428         p = p / fragment_ext_hdr
429         del p[IPv6ExtHdrFragment].nh
430         l_nfb = (fragsize - len(p)) / 8
431         p = p / Raw(hex_payload[offset:offset + l_nfb * 8])
432         p[IPv6ExtHdrFragment].nh = orig_nh
433         p[IPv6ExtHdrFragment].id = identification
434         p[IPv6ExtHdrFragment].offset = offset / 8
435         p[IPv6ExtHdrFragment].m = 1
436         p = p.__class__(str(p))
437         logger.debug(ppp("Fragment %s:" % len(pkts), p))
438         pkts.append(p)
439         offset = offset + l_nfb * 8
440
441     pkts[-1][IPv6ExtHdrFragment].m = 0  # reset more-flags in last fragment
442
443     return pkts