Fix small issues in ACL api
[vpp.git] / test / test_acl_plugin_conns.py
1 #!/usr/bin/env python
2 """ ACL plugin extended stateful tests """
3
4 import unittest
5 from framework import VppTestCase, VppTestRunner, running_extended_tests
6 from scapy.layers.l2 import Ether
7 from scapy.packet import Raw
8 from scapy.layers.inet import IP, UDP, TCP
9 from scapy.packet import Packet
10 from socket import inet_pton, AF_INET, AF_INET6
11 from scapy.layers.inet6 import IPv6, ICMPv6Unknown, ICMPv6EchoRequest
12 from scapy.layers.inet6 import ICMPv6EchoReply, IPv6ExtHdrRouting
13 from scapy.layers.inet6 import IPv6ExtHdrFragment
14 from pprint import pprint
15 from random import randint
16
17
18 def to_acl_rule(self, is_permit, wildcard_sport=False):
19     p = self
20     rule_family = AF_INET6 if p.haslayer(IPv6) else AF_INET
21     rule_prefix_len = 128 if p.haslayer(IPv6) else 32
22     rule_l3_layer = IPv6 if p.haslayer(IPv6) else IP
23     rule_l4_sport = p.sport
24     rule_l4_dport = p.dport
25     if p.haslayer(IPv6):
26         rule_l4_proto = p[IPv6].nh
27     else:
28         rule_l4_proto = p[IP].proto
29
30     if wildcard_sport:
31         rule_l4_sport_first = 0
32         rule_l4_sport_last = 65535
33     else:
34         rule_l4_sport_first = rule_l4_sport
35         rule_l4_sport_last = rule_l4_sport
36
37     new_rule = {
38           'is_permit': is_permit,
39           'is_ipv6': p.haslayer(IPv6),
40           'src_ip_addr': inet_pton(rule_family,
41                                    p[rule_l3_layer].src),
42           'src_ip_prefix_len': rule_prefix_len,
43           'dst_ip_addr': inet_pton(rule_family,
44                                    p[rule_l3_layer].dst),
45           'dst_ip_prefix_len': rule_prefix_len,
46           'srcport_or_icmptype_first': rule_l4_sport_first,
47           'srcport_or_icmptype_last': rule_l4_sport_last,
48           'dstport_or_icmpcode_first': rule_l4_dport,
49           'dstport_or_icmpcode_last': rule_l4_dport,
50           'proto': rule_l4_proto,
51          }
52     return new_rule
53
54 Packet.to_acl_rule = to_acl_rule
55
56
57 class IterateWithSleep():
58     def __init__(self, testcase, n_iters, description, sleep_sec):
59         self.curr = 0
60         self.testcase = testcase
61         self.n_iters = n_iters
62         self.sleep_sec = sleep_sec
63         self.description = description
64
65     def __iter__(self):
66         for x in range(0, self.n_iters):
67             yield x
68             self.testcase.sleep(self.sleep_sec)
69
70
71 class Conn():
72     def __init__(self, testcase, if1, if2, af, l4proto, port1, port2):
73         self.testcase = testcase
74         self.ifs = [None, None]
75         self.ifs[0] = if1
76         self.ifs[1] = if2
77         self.address_family = af
78         self.l4proto = l4proto
79         self.ports = [None, None]
80         self.ports[0] = port1
81         self.ports[1] = port2
82         self
83
84     def pkt(self, side, flags=None):
85         is_ip6 = 1 if self.address_family == AF_INET6 else 0
86         s0 = side
87         s1 = 1-side
88         src_if = self.ifs[s0]
89         dst_if = self.ifs[s1]
90         layer_3 = [IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4),
91                    IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)]
92         payload = "x"
93         l4args = {'sport': self.ports[s0], 'dport': self.ports[s1]}
94         if flags is not None:
95             l4args['flags'] = flags
96         p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
97              layer_3[is_ip6] /
98              self.l4proto(**l4args) /
99              Raw(payload))
100         return p
101
102     def apply_acls(self, reflect_side, acl_side):
103         pkts = []
104         pkts.append(self.pkt(0))
105         pkts.append(self.pkt(1))
106         pkt = pkts[reflect_side]
107
108         r = []
109         r.append(pkt.to_acl_rule(2, wildcard_sport=True))
110         r.append(self.wildcard_rule(0))
111         res = self.testcase.api_acl_add_replace(0xffffffff, r)
112         self.testcase.assert_equal(res.retval, 0, "error adding ACL")
113         reflect_acl_index = res.acl_index
114
115         r = []
116         r.append(self.wildcard_rule(0))
117         res = self.testcase.api_acl_add_replace(0xffffffff, r)
118         self.testcase.assert_equal(res.retval, 0, "error adding deny ACL")
119         deny_acl_index = res.acl_index
120
121         if reflect_side == acl_side:
122             self.testcase.api_acl_interface_set_acl_list(
123                    self.ifs[acl_side].sw_if_index, 2, 1,
124                    [reflect_acl_index,
125                     deny_acl_index])
126             self.testcase.api_acl_interface_set_acl_list(
127                    self.ifs[1-acl_side].sw_if_index, 0, 0, [])
128         else:
129             self.testcase.api_acl_interface_set_acl_list(
130                    self.ifs[acl_side].sw_if_index, 2, 1,
131                    [deny_acl_index,
132                     reflect_acl_index])
133             self.testcase.api_acl_interface_set_acl_list(
134                    self.ifs[1-acl_side].sw_if_index, 0, 0, [])
135
136     def wildcard_rule(self, is_permit):
137         any_addr = ["0.0.0.0", "::"]
138         rule_family = self.address_family
139         is_ip6 = 1 if rule_family == AF_INET6 else 0
140         new_rule = {
141               'is_permit': is_permit,
142               'is_ipv6': is_ip6,
143               'src_ip_addr': inet_pton(rule_family, any_addr[is_ip6]),
144               'src_ip_prefix_len': 0,
145               'dst_ip_addr': inet_pton(rule_family, any_addr[is_ip6]),
146               'dst_ip_prefix_len': 0,
147               'srcport_or_icmptype_first': 0,
148               'srcport_or_icmptype_last': 65535,
149               'dstport_or_icmpcode_first': 0,
150               'dstport_or_icmpcode_last': 65535,
151               'proto': 0,
152              }
153         return new_rule
154
155     def send(self, side, flags=None):
156         self.ifs[side].add_stream(self.pkt(side, flags))
157         self.ifs[1-side].enable_capture()
158         self.testcase.pg_start()
159
160     def recv(self, side):
161         p = self.ifs[side].wait_for_packet(1)
162         return p
163
164     def send_through(self, side, flags=None):
165         self.send(side, flags)
166         p = self.recv(1-side)
167         return p
168
169     def send_pingpong(self, side, flags1=None, flags2=None):
170         p1 = self.send_through(side, flags1)
171         p2 = self.send_through(1-side, flags2)
172         return [p1, p2]
173
174
175 @unittest.skipUnless(running_extended_tests(), "part of extended tests")
176 class ACLPluginConnTestCase(VppTestCase):
177     """ ACL plugin connection-oriented extended testcases """
178
179     @classmethod
180     def setUpClass(self):
181         super(ACLPluginConnTestCase, self).setUpClass()
182         # create pg0 and pg1
183         self.create_pg_interfaces(range(2))
184         for i in self.pg_interfaces:
185             i.admin_up()
186             i.config_ip4()
187             i.config_ip6()
188             i.resolve_arp()
189             i.resolve_ndp()
190
191     def tearDown(self):
192         """Run standard test teardown and log various show commands
193         """
194         super(ACLPluginConnTestCase, self).tearDown()
195         if not self.vpp_dead:
196             self.logger.info(self.vapi.cli("show ip arp"))
197             self.logger.info(self.vapi.cli("show ip6 neighbors"))
198             self.logger.info(self.vapi.cli("show acl-plugin sessions"))
199             self.logger.info(self.vapi.cli("show acl-plugin acl"))
200             self.logger.info(self.vapi.cli("show acl-plugin interface"))
201             self.logger.info(self.vapi.cli("show acl-plugin tables"))
202
203     def api_acl_add_replace(self, acl_index, r, count=-1, tag="",
204                             expected_retval=0):
205         """Add/replace an ACL
206
207         :param int acl_index: ACL index to replace, 4294967295 to create new.
208         :param acl_rule r: ACL rules array.
209         :param str tag: symbolic tag (description) for this ACL.
210         :param int count: number of rules.
211         """
212         if (count < 0):
213             count = len(r)
214         return self.vapi.api(self.vapi.papi.acl_add_replace,
215                              {'acl_index': acl_index,
216                               'r': r,
217                               'count': count,
218                               'tag': tag
219                               }, expected_retval=expected_retval)
220
221     def api_acl_interface_set_acl_list(self, sw_if_index, count, n_input, acls,
222                                        expected_retval=0):
223         return self.vapi.api(self.vapi.papi.acl_interface_set_acl_list,
224                              {'sw_if_index': sw_if_index,
225                               'count': count,
226                               'n_input': n_input,
227                               'acls': acls
228                               }, expected_retval=expected_retval)
229
230     def api_acl_dump(self, acl_index, expected_retval=0):
231         return self.vapi.api(self.vapi.papi.acl_dump,
232                              {'acl_index': acl_index},
233                              expected_retval=expected_retval)
234
235     def run_basic_conn_test(self, af, acl_side):
236         """ Basic conn timeout test """
237         conn1 = Conn(self, self.pg0, self.pg1, af, UDP, 42001, 4242)
238         conn1.apply_acls(0, acl_side)
239         conn1.send_through(0)
240         # the return packets should pass
241         conn1.send_through(1)
242         # send some packets on conn1, ensure it doesn't go away
243         for i in IterateWithSleep(self, 20, "Keep conn active", 0.3):
244             conn1.send_through(1)
245         # allow the conn to time out
246         for i in IterateWithSleep(self, 30, "Wait for timeout", 0.1):
247             pass
248         # now try to send a packet on the reflected side
249         try:
250             p2 = conn1.send_through(1).command()
251         except:
252             # If we asserted while waiting, it's good.
253             # the conn should have timed out.
254             p2 = None
255         self.assert_equal(p2, None, "packet on long-idle conn")
256
257     def run_active_conn_test(self, af, acl_side):
258         """ Idle connection behind active connection test """
259         base = 10000 + 1000*acl_side
260         conn1 = Conn(self, self.pg0, self.pg1, af, UDP, base + 1, 2323)
261         conn2 = Conn(self, self.pg0, self.pg1, af, UDP, base + 2, 2323)
262         conn3 = Conn(self, self.pg0, self.pg1, af, UDP, base + 3, 2323)
263         conn1.apply_acls(0, acl_side)
264         conn1.send(0)
265         conn1.recv(1)
266         # create and check that the conn2/3 work
267         self.sleep(0.1)
268         conn2.send_pingpong(0)
269         self.sleep(0.1)
270         conn3.send_pingpong(0)
271         # send some packets on conn1, keep conn2/3 idle
272         for i in IterateWithSleep(self, 20, "Keep conn active", 0.2):
273             conn1.send_through(1)
274         try:
275             p2 = conn2.send_through(1).command()
276         except:
277             # If we asserted while waiting, it's good.
278             # the conn should have timed out.
279             p2 = None
280         # We should have not received the packet on a long-idle
281         # connection, because it should have timed out
282         # If it didn't - it is a problem
283         self.assert_equal(p2, None, "packet on long-idle conn")
284
285     def run_clear_conn_test(self, af, acl_side):
286         """ Clear the connections via CLI """
287         conn1 = Conn(self, self.pg0, self.pg1, af, UDP, 42001, 4242)
288         conn1.apply_acls(0, acl_side)
289         conn1.send_through(0)
290         # the return packets should pass
291         conn1.send_through(1)
292         # send some packets on conn1, ensure it doesn't go away
293         for i in IterateWithSleep(self, 20, "Keep conn active", 0.3):
294             conn1.send_through(1)
295         # clear all connections
296         self.vapi.ppcli("clear acl-plugin sessions")
297         # now try to send a packet on the reflected side
298         try:
299             p2 = conn1.send_through(1).command()
300         except:
301             # If we asserted while waiting, it's good.
302             # the conn should have timed out.
303             p2 = None
304         self.assert_equal(p2, None, "packet on supposedly deleted conn")
305
306     def run_tcp_transient_setup_conn_test(self, af, acl_side):
307         conn1 = Conn(self, self.pg0, self.pg1, af, TCP, 53001, 5151)
308         conn1.apply_acls(0, acl_side)
309         conn1.send_through(0, 'S')
310         # the return packets should pass
311         conn1.send_through(1, 'SA')
312         # allow the conn to time out
313         for i in IterateWithSleep(self, 30, "Wait for timeout", 0.1):
314             pass
315         # ensure conn times out
316         try:
317             p2 = conn1.send_through(1).command()
318         except:
319             # If we asserted while waiting, it's good.
320             # the conn should have timed out.
321             p2 = None
322         self.assert_equal(p2, None, "packet on supposedly deleted conn")
323
324     def run_tcp_established_conn_test(self, af, acl_side):
325         conn1 = Conn(self, self.pg0, self.pg1, af, TCP, 53002, 5052)
326         conn1.apply_acls(0, acl_side)
327         conn1.send_through(0, 'S')
328         # the return packets should pass
329         conn1.send_through(1, 'SA')
330         # complete the threeway handshake
331         # (NB: sequence numbers not tracked, so not set!)
332         conn1.send_through(0, 'A')
333         # allow the conn to time out if it's in embryonic timer
334         for i in IterateWithSleep(self, 30, "Wait for transient timeout", 0.1):
335             pass
336         # Try to send the packet from the "forbidden" side - it must pass
337         conn1.send_through(1, 'A')
338         # ensure conn times out for real
339         for i in IterateWithSleep(self, 130, "Wait for timeout", 0.1):
340             pass
341         try:
342             p2 = conn1.send_through(1).command()
343         except:
344             # If we asserted while waiting, it's good.
345             # the conn should have timed out.
346             p2 = None
347         self.assert_equal(p2, None, "packet on supposedly deleted conn")
348
349     def run_tcp_transient_teardown_conn_test(self, af, acl_side):
350         conn1 = Conn(self, self.pg0, self.pg1, af, TCP, 53002, 5052)
351         conn1.apply_acls(0, acl_side)
352         conn1.send_through(0, 'S')
353         # the return packets should pass
354         conn1.send_through(1, 'SA')
355         # complete the threeway handshake
356         # (NB: sequence numbers not tracked, so not set!)
357         conn1.send_through(0, 'A')
358         # allow the conn to time out if it's in embryonic timer
359         for i in IterateWithSleep(self, 30, "Wait for transient timeout", 0.1):
360             pass
361         # Try to send the packet from the "forbidden" side - it must pass
362         conn1.send_through(1, 'A')
363         # Send the FIN to bounce the session out of established
364         conn1.send_through(1, 'FA')
365         # If conn landed on transient timer it will time out here
366         for i in IterateWithSleep(self, 30, "Wait for transient timeout", 0.1):
367             pass
368         # Now it should have timed out already
369         try:
370             p2 = conn1.send_through(1).command()
371         except:
372             # If we asserted while waiting, it's good.
373             # the conn should have timed out.
374             p2 = None
375         self.assert_equal(p2, None, "packet on supposedly deleted conn")
376
377     def test_0000_conn_prepare_test(self):
378         """ Prepare the settings """
379         self.vapi.ppcli("set acl-plugin session timeout udp idle 1")
380
381     def test_0001_basic_conn_test(self):
382         """ IPv4: Basic conn timeout test reflect on ingress """
383         self.run_basic_conn_test(AF_INET, 0)
384
385     def test_0002_basic_conn_test(self):
386         """ IPv4: Basic conn timeout test reflect on egress """
387         self.run_basic_conn_test(AF_INET, 1)
388
389     def test_0005_clear_conn_test(self):
390         """ IPv4: reflect egress, clear conn """
391         self.run_clear_conn_test(AF_INET, 1)
392
393     def test_0006_clear_conn_test(self):
394         """ IPv4: reflect ingress, clear conn """
395         self.run_clear_conn_test(AF_INET, 0)
396
397     def test_0011_active_conn_test(self):
398         """ IPv4: Idle conn behind active conn, reflect on ingress """
399         self.run_active_conn_test(AF_INET, 0)
400
401     def test_0012_active_conn_test(self):
402         """ IPv4: Idle conn behind active conn, reflect on egress """
403         self.run_active_conn_test(AF_INET, 1)
404
405     def test_1001_basic_conn_test(self):
406         """ IPv6: Basic conn timeout test reflect on ingress """
407         self.run_basic_conn_test(AF_INET6, 0)
408
409     def test_1002_basic_conn_test(self):
410         """ IPv6: Basic conn timeout test reflect on egress """
411         self.run_basic_conn_test(AF_INET6, 1)
412
413     def test_1005_clear_conn_test(self):
414         """ IPv6: reflect egress, clear conn """
415         self.run_clear_conn_test(AF_INET6, 1)
416
417     def test_1006_clear_conn_test(self):
418         """ IPv6: reflect ingress, clear conn """
419         self.run_clear_conn_test(AF_INET6, 0)
420
421     def test_1011_active_conn_test(self):
422         """ IPv6: Idle conn behind active conn, reflect on ingress """
423         self.run_active_conn_test(AF_INET6, 0)
424
425     def test_1012_active_conn_test(self):
426         """ IPv6: Idle conn behind active conn, reflect on egress """
427         self.run_active_conn_test(AF_INET6, 1)
428
429     def test_2000_prepare_for_tcp_test(self):
430         """ Prepare for TCP session tests """
431         # ensure the session hangs on if it gets treated as UDP
432         self.vapi.ppcli("set acl-plugin session timeout udp idle 200")
433         # let the TCP connection time out at 5 seconds
434         self.vapi.ppcli("set acl-plugin session timeout tcp idle 10")
435         self.vapi.ppcli("set acl-plugin session timeout tcp transient 1")
436
437     def test_2001_tcp_transient_conn_test(self):
438         """ IPv4: transient TCP session (incomplete 3WHS), ref. on ingress """
439         self.run_tcp_transient_setup_conn_test(AF_INET, 0)
440
441     def test_2002_tcp_transient_conn_test(self):
442         """ IPv4: transient TCP session (incomplete 3WHS), ref. on egress """
443         self.run_tcp_transient_setup_conn_test(AF_INET, 1)
444
445     def test_2003_tcp_transient_conn_test(self):
446         """ IPv4: established TCP session (complete 3WHS), ref. on ingress """
447         self.run_tcp_established_conn_test(AF_INET, 0)
448
449     def test_2004_tcp_transient_conn_test(self):
450         """ IPv4: established TCP session (complete 3WHS), ref. on egress """
451         self.run_tcp_established_conn_test(AF_INET, 1)
452
453     def test_2005_tcp_transient_teardown_conn_test(self):
454         """ IPv4: transient TCP session (3WHS,ACK,FINACK), ref. on ingress """
455         self.run_tcp_transient_teardown_conn_test(AF_INET, 0)
456
457     def test_2006_tcp_transient_teardown_conn_test(self):
458         """ IPv4: transient TCP session (3WHS,ACK,FINACK), ref. on egress """
459         self.run_tcp_transient_teardown_conn_test(AF_INET, 1)
460
461     def test_3001_tcp_transient_conn_test(self):
462         """ IPv6: transient TCP session (incomplete 3WHS), ref. on ingress """
463         self.run_tcp_transient_setup_conn_test(AF_INET6, 0)
464
465     def test_3002_tcp_transient_conn_test(self):
466         """ IPv6: transient TCP session (incomplete 3WHS), ref. on egress """
467         self.run_tcp_transient_setup_conn_test(AF_INET6, 1)
468
469     def test_3003_tcp_transient_conn_test(self):
470         """ IPv6: established TCP session (complete 3WHS), ref. on ingress """
471         self.run_tcp_established_conn_test(AF_INET6, 0)
472
473     def test_3004_tcp_transient_conn_test(self):
474         """ IPv6: established TCP session (complete 3WHS), ref. on egress """
475         self.run_tcp_established_conn_test(AF_INET6, 1)
476
477     def test_3005_tcp_transient_teardown_conn_test(self):
478         """ IPv6: transient TCP session (3WHS,ACK,FINACK), ref. on ingress """
479         self.run_tcp_transient_teardown_conn_test(AF_INET6, 0)
480
481     def test_3006_tcp_transient_teardown_conn_test(self):
482         """ IPv6: transient TCP session (3WHS,ACK,FINACK), ref. on egress """
483         self.run_tcp_transient_teardown_conn_test(AF_INET6, 1)