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