tests: changes for scapy 2.4.3 migration
[vpp.git] / src / plugins / dhcp / test / test_dhcp.py
1 #!/usr/bin/env python3
2
3 import unittest
4 import socket
5 import struct
6 import six
7
8 from framework import VppTestCase, VppTestRunner, running_extended_tests
9 from vpp_neighbor import VppNeighbor
10 from vpp_ip_route import find_route, VppIpTable
11 from util import mk_ll_addr
12 import scapy.compat
13 from scapy.layers.l2 import Ether, getmacbyip, ARP, Dot1Q
14 from scapy.layers.inet import IP, UDP, ICMP
15 from scapy.layers.inet6 import IPv6, in6_getnsmac
16 from scapy.utils6 import in6_mactoifaceid
17 from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
18 from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
19     DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \
20     DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request
21 from socket import AF_INET, AF_INET6, inet_pton, inet_ntop
22 from scapy.utils6 import in6_ptop
23 from vpp_papi import mac_pton, VppEnum
24 from vpp_sub_interface import VppDot1QSubint
25 from vpp_qos import VppQosEgressMap, VppQosMark
26 from vpp_dhcp import VppDHCPClient, VppDHCPProxy
27
28
29 DHCP4_CLIENT_PORT = 68
30 DHCP4_SERVER_PORT = 67
31 DHCP6_CLIENT_PORT = 547
32 DHCP6_SERVER_PORT = 546
33
34
35 class TestDHCP(VppTestCase):
36     """ DHCP Test Case """
37
38     @classmethod
39     def setUpClass(cls):
40         super(TestDHCP, cls).setUpClass()
41
42     @classmethod
43     def tearDownClass(cls):
44         super(TestDHCP, cls).tearDownClass()
45
46     def setUp(self):
47         super(TestDHCP, self).setUp()
48
49         # create 6 pg interfaces for pg0 to pg5
50         self.create_pg_interfaces(range(6))
51         self.tables = []
52
53         # pg0 to 2 are IP configured in VRF 0, 1 and 2.
54         # pg3 to 5 are non IP-configured in VRF 0, 1 and 2.
55         table_id = 0
56         for table_id in range(1, 4):
57             tbl4 = VppIpTable(self, table_id)
58             tbl4.add_vpp_config()
59             self.tables.append(tbl4)
60             tbl6 = VppIpTable(self, table_id, is_ip6=1)
61             tbl6.add_vpp_config()
62             self.tables.append(tbl6)
63
64         table_id = 0
65         for i in self.pg_interfaces[:3]:
66             i.admin_up()
67             i.set_table_ip4(table_id)
68             i.set_table_ip6(table_id)
69             i.config_ip4()
70             i.resolve_arp()
71             i.config_ip6()
72             i.resolve_ndp()
73             table_id += 1
74
75         table_id = 0
76         for i in self.pg_interfaces[3:]:
77             i.admin_up()
78             i.set_table_ip4(table_id)
79             i.set_table_ip6(table_id)
80             table_id += 1
81
82     def tearDown(self):
83         for i in self.pg_interfaces[:3]:
84             i.unconfig_ip4()
85             i.unconfig_ip6()
86
87         for i in self.pg_interfaces:
88             i.set_table_ip4(0)
89             i.set_table_ip6(0)
90             i.admin_down()
91         super(TestDHCP, self).tearDown()
92
93     def verify_dhcp_has_option(self, pkt, option, value):
94         dhcp = pkt[DHCP]
95         found = False
96
97         for i in dhcp.options:
98             if isinstance(i, tuple):
99                 if i[0] == option:
100                     self.assertEqual(i[1], value)
101                     found = True
102
103         self.assertTrue(found)
104
105     def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui):
106         dhcp = pkt[DHCP]
107         found = 0
108         data = []
109         id_len = len(vpn_id)
110
111         for i in dhcp.options:
112             if isinstance(i, tuple):
113                 if i[0] == "relay_agent_Information":
114                     #
115                     # There are two sb-options present - each of length 6.
116                     #
117                     data = i[1]
118                     if oui != 0:
119                         self.assertEqual(len(data), 24)
120                     elif len(vpn_id) > 0:
121                         self.assertEqual(len(data), len(vpn_id) + 17)
122                     else:
123                         self.assertEqual(len(data), 12)
124
125                     #
126                     # First sub-option is ID 1, len 4, then encoded
127                     #  sw_if_index. This test uses low valued indicies
128                     # so [2:4] are 0.
129                     # The ID space is VPP internal - so no matching value
130                     # scapy
131                     #
132                     self.assertEqual(six.byte2int(data[0:1]), 1)
133                     self.assertEqual(six.byte2int(data[1:2]), 4)
134                     self.assertEqual(six.byte2int(data[2:3]), 0)
135                     self.assertEqual(six.byte2int(data[3:4]), 0)
136                     self.assertEqual(six.byte2int(data[4:5]), 0)
137                     self.assertEqual(six.byte2int(data[5:6]),
138                                      intf._sw_if_index)
139
140                     #
141                     # next sub-option is the IP address of the client side
142                     # interface.
143                     # sub-option ID=5, length (of a v4 address)=4
144                     #
145                     claddr = socket.inet_pton(AF_INET, ip_addr)
146
147                     self.assertEqual(six.byte2int(data[6:7]), 5)
148                     self.assertEqual(six.byte2int(data[7:8]), 4)
149                     self.assertEqual(data[8], claddr[0])
150                     self.assertEqual(data[9], claddr[1])
151                     self.assertEqual(data[10], claddr[2])
152                     self.assertEqual(data[11], claddr[3])
153
154                     if oui != 0:
155                         # sub-option 151 encodes vss_type 1,
156                         # the 3 byte oui and the 4 byte fib_id
157                         self.assertEqual(id_len, 0)
158                         self.assertEqual(six.byte2int(data[12:13]), 151)
159                         self.assertEqual(six.byte2int(data[13:14]), 8)
160                         self.assertEqual(six.byte2int(data[14:15]), 1)
161                         self.assertEqual(six.byte2int(data[15:16]), 0)
162                         self.assertEqual(six.byte2int(data[16:17]), 0)
163                         self.assertEqual(six.byte2int(data[17:18]), oui)
164                         self.assertEqual(six.byte2int(data[18:19]), 0)
165                         self.assertEqual(six.byte2int(data[19:20]), 0)
166                         self.assertEqual(six.byte2int(data[20:21]), 0)
167                         self.assertEqual(six.byte2int(data[21:22]), fib_id)
168
169                         # VSS control sub-option
170                         self.assertEqual(six.byte2int(data[22:23]), 152)
171                         self.assertEqual(six.byte2int(data[23:24]), 0)
172
173                     if id_len > 0:
174                         # sub-option 151 encode vss_type of 0
175                         # followerd by vpn_id in ascii
176                         self.assertEqual(oui, 0)
177                         self.assertEqual(six.byte2int(data[12:13]), 151)
178                         self.assertEqual(six.byte2int(data[13:14]), id_len + 1)
179                         self.assertEqual(six.byte2int(data[14:15]), 0)
180                         self.assertEqual(data[15:15 + id_len].decode('ascii'),
181                                          vpn_id)
182
183                         # VSS control sub-option
184                         self.assertEqual(six.byte2int(data[15 + len(vpn_id):
185                                                            16 + len(vpn_id)]),
186                                          152)
187                         self.assertEqual(six.byte2int(data[16 + len(vpn_id):
188                                                            17 + len(vpn_id)]),
189                                          0)
190
191                     found = 1
192         self.assertTrue(found)
193
194         return data
195
196     def verify_dhcp_msg_type(self, pkt, name):
197         dhcp = pkt[DHCP]
198         found = False
199         for o in dhcp.options:
200             if isinstance(o, tuple):
201                 if o[0] == "message-type" \
202                    and DHCPTypes[o[1]] == name:
203                     found = True
204         self.assertTrue(found)
205
206     def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0):
207         ether = pkt[Ether]
208         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
209         self.assertEqual(ether.src, intf.local_mac)
210
211         ip = pkt[IP]
212         self.assertEqual(ip.dst, "255.255.255.255")
213         self.assertEqual(ip.src, intf.local_ip4)
214
215         udp = pkt[UDP]
216         self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
217         self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
218
219         self.verify_dhcp_msg_type(pkt, "offer")
220         data = self.validate_relay_options(pkt, intf, intf.local_ip4,
221                                            vpn_id, fib_id, oui)
222
223     def verify_orig_dhcp_pkt(self, pkt, intf, dscp, l2_bc=True):
224         ether = pkt[Ether]
225         if l2_bc:
226             self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
227         else:
228             self.assertEqual(ether.dst, intf.remote_mac)
229         self.assertEqual(ether.src, intf.local_mac)
230
231         ip = pkt[IP]
232
233         if (l2_bc):
234             self.assertEqual(ip.dst, "255.255.255.255")
235             self.assertEqual(ip.src, "0.0.0.0")
236         else:
237             self.assertEqual(ip.dst, intf.remote_ip4)
238             self.assertEqual(ip.src, intf.local_ip4)
239         self.assertEqual(ip.tos, dscp)
240
241         udp = pkt[UDP]
242         self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
243         self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
244
245     def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None,
246                                   broadcast=True, dscp=0):
247         self.verify_orig_dhcp_pkt(pkt, intf, dscp)
248
249         self.verify_dhcp_msg_type(pkt, "discover")
250         self.verify_dhcp_has_option(pkt, "hostname",
251                                     hostname.encode('ascii'))
252         if client_id:
253             client_id = '\x00' + client_id
254             self.verify_dhcp_has_option(pkt, "client_id",
255                                         client_id.encode('ascii'))
256         bootp = pkt[BOOTP]
257         self.assertEqual(bootp.ciaddr, "0.0.0.0")
258         self.assertEqual(bootp.giaddr, "0.0.0.0")
259         if broadcast:
260             self.assertEqual(bootp.flags, 0x8000)
261         else:
262             self.assertEqual(bootp.flags, 0x0000)
263
264     def verify_orig_dhcp_request(self, pkt, intf, hostname, ip,
265                                  broadcast=True,
266                                  l2_bc=True,
267                                  dscp=0):
268         self.verify_orig_dhcp_pkt(pkt, intf, dscp, l2_bc=l2_bc)
269
270         self.verify_dhcp_msg_type(pkt, "request")
271         self.verify_dhcp_has_option(pkt, "hostname",
272                                     hostname.encode('ascii'))
273         self.verify_dhcp_has_option(pkt, "requested_addr", ip)
274         bootp = pkt[BOOTP]
275
276         if l2_bc:
277             self.assertEqual(bootp.ciaddr, "0.0.0.0")
278         else:
279             self.assertEqual(bootp.ciaddr, intf.local_ip4)
280         self.assertEqual(bootp.giaddr, "0.0.0.0")
281
282         if broadcast:
283             self.assertEqual(bootp.flags, 0x8000)
284         else:
285             self.assertEqual(bootp.flags, 0x0000)
286
287     def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None,
288                                      fib_id=0, oui=0,
289                                      vpn_id="",
290                                      dst_mac=None, dst_ip=None):
291         if not dst_mac:
292             dst_mac = intf.remote_mac
293         if not dst_ip:
294             dst_ip = intf.remote_ip4
295
296         ether = pkt[Ether]
297         self.assertEqual(ether.dst, dst_mac)
298         self.assertEqual(ether.src, intf.local_mac)
299
300         ip = pkt[IP]
301         self.assertEqual(ip.dst, dst_ip)
302         self.assertEqual(ip.src, intf.local_ip4)
303
304         udp = pkt[UDP]
305         self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
306         self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
307
308         dhcp = pkt[DHCP]
309
310         is_discover = False
311         for o in dhcp.options:
312             if isinstance(o, tuple):
313                 if o[0] == "message-type" \
314                    and DHCPTypes[o[1]] == "discover":
315                     is_discover = True
316         self.assertTrue(is_discover)
317
318         data = self.validate_relay_options(pkt, src_intf,
319                                            src_intf.local_ip4,
320                                            vpn_id,
321                                            fib_id, oui)
322         return data
323
324     def verify_dhcp6_solicit(self, pkt, intf,
325                              peer_ip, peer_mac,
326                              vpn_id="",
327                              fib_id=0,
328                              oui=0,
329                              dst_mac=None,
330                              dst_ip=None):
331         if not dst_mac:
332             dst_mac = intf.remote_mac
333         if not dst_ip:
334             dst_ip = in6_ptop(intf.remote_ip6)
335
336         ether = pkt[Ether]
337         self.assertEqual(ether.dst, dst_mac)
338         self.assertEqual(ether.src, intf.local_mac)
339
340         ip = pkt[IPv6]
341         self.assertEqual(in6_ptop(ip.dst), dst_ip)
342         self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
343
344         udp = pkt[UDP]
345         self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
346         self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
347
348         relay = pkt[DHCP6_RelayForward]
349         self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
350         oid = pkt[DHCP6OptIfaceId]
351         cll = pkt[DHCP6OptClientLinkLayerAddr]
352         self.assertEqual(cll.optlen, 8)
353         self.assertEqual(cll.lltype, 1)
354         self.assertEqual(cll.clladdr, peer_mac)
355
356         id_len = len(vpn_id)
357
358         if fib_id != 0:
359             self.assertEqual(id_len, 0)
360             vss = pkt[DHCP6OptVSS]
361             self.assertEqual(vss.optlen, 8)
362             self.assertEqual(vss.type, 1)
363             # the OUI and FIB-id are really 3 and 4 bytes resp.
364             # but the tested range is small
365             self.assertEqual(six.byte2int(vss.data[0:1]), 0)
366             self.assertEqual(six.byte2int(vss.data[1:2]), 0)
367             self.assertEqual(six.byte2int(vss.data[2:3]), oui)
368             self.assertEqual(six.byte2int(vss.data[3:4]), 0)
369             self.assertEqual(six.byte2int(vss.data[4:5]), 0)
370             self.assertEqual(six.byte2int(vss.data[5:6]), 0)
371             self.assertEqual(six.byte2int(vss.data[6:7]), fib_id)
372
373         if id_len > 0:
374             self.assertEqual(oui, 0)
375             vss = pkt[DHCP6OptVSS]
376             self.assertEqual(vss.optlen, id_len + 1)
377             self.assertEqual(vss.type, 0)
378             self.assertEqual(vss.data[0:id_len].decode('ascii'),
379                              vpn_id)
380
381         # the relay message should be an encoded Solicit
382         msg = pkt[DHCP6OptRelayMsg]
383         sol = DHCP6_Solicit()
384         self.assertEqual(msg.optlen, len(sol))
385         self.assertEqual(sol, msg[1])
386
387     def verify_dhcp6_advert(self, pkt, intf, peer):
388         ether = pkt[Ether]
389         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
390         self.assertEqual(ether.src, intf.local_mac)
391
392         ip = pkt[IPv6]
393         self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
394         self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
395
396         udp = pkt[UDP]
397         self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
398         self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
399
400         # not sure why this is not decoding
401         # adv = pkt[DHCP6_Advertise]
402
403     def wait_for_no_route(self, address, length,
404                           n_tries=50, s_time=1):
405         while (n_tries):
406             if not find_route(self, address, length):
407                 return True
408             n_tries = n_tries - 1
409             self.sleep(s_time)
410
411         return False
412
413     def test_dhcp_proxy(self):
414         """ DHCPv4 Proxy """
415
416         #
417         # Verify no response to DHCP request without DHCP config
418         #
419         p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff",
420                              src=self.pg3.remote_mac) /
421                        IP(src="0.0.0.0", dst="255.255.255.255") /
422                        UDP(sport=DHCP4_CLIENT_PORT,
423                            dport=DHCP4_SERVER_PORT) /
424                        BOOTP(op=1) /
425                        DHCP(options=[('message-type', 'discover'), ('end')]))
426         pkts_disc_vrf0 = [p_disc_vrf0]
427         p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
428                              src=self.pg4.remote_mac) /
429                        IP(src="0.0.0.0", dst="255.255.255.255") /
430                        UDP(sport=DHCP4_CLIENT_PORT,
431                            dport=DHCP4_SERVER_PORT) /
432                        BOOTP(op=1) /
433                        DHCP(options=[('message-type', 'discover'), ('end')]))
434         pkts_disc_vrf1 = [p_disc_vrf1]
435         p_disc_vrf2 = (Ether(dst="ff:ff:ff:ff:ff:ff",
436                              src=self.pg5.remote_mac) /
437                        IP(src="0.0.0.0", dst="255.255.255.255") /
438                        UDP(sport=DHCP4_CLIENT_PORT,
439                            dport=DHCP4_SERVER_PORT) /
440                        BOOTP(op=1) /
441                        DHCP(options=[('message-type', 'discover'), ('end')]))
442         pkts_disc_vrf2 = [p_disc_vrf2]
443
444         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
445                                         "DHCP with no configuration")
446         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
447                                         "DHCP with no configuration")
448         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
449                                         "DHCP with no configuration")
450
451         #
452         # Enable DHCP proxy in VRF 0
453         #
454         server_addr = self.pg0.remote_ip4
455         src_addr = self.pg0.local_ip4
456
457         Proxy = VppDHCPProxy(self, server_addr, src_addr, rx_vrf_id=0)
458         Proxy.add_vpp_config()
459
460         #
461         # Discover packets from the client are dropped because there is no
462         # IP address configured on the client facing interface
463         #
464         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
465                                         "Discover DHCP no relay address")
466
467         #
468         # Inject a response from the server
469         #  dropped, because there is no IP addrees on the
470         #  client interfce to fill in the option.
471         #
472         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
473              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
474              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
475              BOOTP(op=1) /
476              DHCP(options=[('message-type', 'offer'), ('end')]))
477         pkts = [p]
478
479         self.send_and_assert_no_replies(self.pg3, pkts,
480                                         "Offer DHCP no relay address")
481
482         #
483         # configure an IP address on the client facing interface
484         #
485         self.pg3.config_ip4()
486
487         #
488         # Try again with a discover packet
489         # Rx'd packet should be to the server address and from the configured
490         # source address
491         # UDP source ports are unchanged
492         # we've no option 82 config so that should be absent
493         #
494         self.pg3.add_stream(pkts_disc_vrf0)
495         self.pg_enable_capture(self.pg_interfaces)
496         self.pg_start()
497
498         rx = self.pg0.get_capture(1)
499         rx = rx[0]
500
501         option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
502                                                       src_intf=self.pg3)
503
504         #
505         # Create an DHCP offer reply from the server with a correctly formatted
506         # option 82. i.e. send back what we just captured
507         # The offer, sent mcast to the client, still has option 82.
508         #
509         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
510              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
511              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
512              BOOTP(op=1) /
513              DHCP(options=[('message-type', 'offer'),
514                            ('relay_agent_Information', option_82),
515                            ('end')]))
516         pkts = [p]
517
518         self.pg0.add_stream(pkts)
519         self.pg_enable_capture(self.pg_interfaces)
520         self.pg_start()
521
522         rx = self.pg3.get_capture(1)
523         rx = rx[0]
524
525         self.verify_dhcp_offer(rx, self.pg3)
526
527         #
528         # Bogus Option 82:
529         #
530         # 1. not our IP address = not checked by VPP? so offer is replayed
531         #    to client
532         bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:]
533
534         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
535              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
536              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
537              BOOTP(op=1) /
538              DHCP(options=[('message-type', 'offer'),
539                            ('relay_agent_Information', bad_ip),
540                            ('end')]))
541         pkts = [p]
542         self.send_and_assert_no_replies(self.pg0, pkts,
543                                         "DHCP offer option 82 bad address")
544
545         # 2. Not a sw_if_index VPP knows
546         bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:]
547
548         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
549              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
550              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
551              BOOTP(op=1) /
552              DHCP(options=[('message-type', 'offer'),
553                            ('relay_agent_Information', bad_if_index),
554                            ('end')]))
555         pkts = [p]
556         self.send_and_assert_no_replies(self.pg0, pkts,
557                                         "DHCP offer option 82 bad if index")
558
559         #
560         # Send a DHCP request in VRF 1. should be dropped.
561         #
562         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
563                                         "DHCP with no configuration VRF 1")
564
565         #
566         # Delete the DHCP config in VRF 0
567         # Should now drop requests.
568         #
569         Proxy.remove_vpp_config()
570
571         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
572                                         "DHCP config removed VRF 0")
573         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
574                                         "DHCP config removed VRF 1")
575
576         #
577         # Add DHCP config for VRF 1 & 2
578         #
579         server_addr1 = self.pg1.remote_ip4
580         src_addr1 = self.pg1.local_ip4
581         Proxy1 = VppDHCPProxy(
582             self,
583             server_addr1,
584             src_addr1,
585             rx_vrf_id=1,
586             server_vrf_id=1)
587         Proxy1.add_vpp_config()
588
589         server_addr2 = self.pg2.remote_ip4
590         src_addr2 = self.pg2.local_ip4
591         Proxy2 = VppDHCPProxy(
592             self,
593             server_addr2,
594             src_addr2,
595             rx_vrf_id=2,
596             server_vrf_id=2)
597         Proxy2.add_vpp_config()
598
599         #
600         # Confim DHCP requests ok in VRF 1 & 2.
601         #  - dropped on IP config on client interface
602         #
603         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
604                                         "DHCP config removed VRF 1")
605         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
606                                         "DHCP config removed VRF 2")
607
608         #
609         # configure an IP address on the client facing interface
610         #
611         self.pg4.config_ip4()
612         self.pg4.add_stream(pkts_disc_vrf1)
613         self.pg_enable_capture(self.pg_interfaces)
614         self.pg_start()
615         rx = self.pg1.get_capture(1)
616         rx = rx[0]
617         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
618
619         self.pg5.config_ip4()
620         self.pg5.add_stream(pkts_disc_vrf2)
621         self.pg_enable_capture(self.pg_interfaces)
622         self.pg_start()
623         rx = self.pg2.get_capture(1)
624         rx = rx[0]
625         self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5)
626
627         #
628         # Add VSS config
629         #  table=1, vss_type=1, vpn_index=1, oui=4
630         #  table=2, vss_type=0, vpn_id = "ip4-table-2"
631         self.vapi.dhcp_proxy_set_vss(tbl_id=1, vss_type=1,
632                                      vpn_index=1, oui=4, is_add=1)
633         self.vapi.dhcp_proxy_set_vss(tbl_id=2, vss_type=0,
634                                      vpn_ascii_id="ip4-table-2", is_add=1)
635
636         self.pg4.add_stream(pkts_disc_vrf1)
637         self.pg_enable_capture(self.pg_interfaces)
638         self.pg_start()
639
640         rx = self.pg1.get_capture(1)
641         rx = rx[0]
642         self.verify_relayed_dhcp_discover(rx, self.pg1,
643                                           src_intf=self.pg4,
644                                           fib_id=1, oui=4)
645
646         self.pg5.add_stream(pkts_disc_vrf2)
647         self.pg_enable_capture(self.pg_interfaces)
648         self.pg_start()
649
650         rx = self.pg2.get_capture(1)
651         rx = rx[0]
652         self.verify_relayed_dhcp_discover(rx, self.pg2,
653                                           src_intf=self.pg5,
654                                           vpn_id="ip4-table-2")
655
656         #
657         # Add a second DHCP server in VRF 1
658         #  expect clients messages to be relay to both configured servers
659         #
660         self.pg1.generate_remote_hosts(2)
661         server_addr12 = self.pg1.remote_hosts[1].ip4
662
663         Proxy12 = VppDHCPProxy(
664             self,
665             server_addr12,
666             src_addr,
667             rx_vrf_id=1,
668             server_vrf_id=1)
669         Proxy12.add_vpp_config()
670
671         #
672         # We'll need an ARP entry for the server to send it packets
673         #
674         arp_entry = VppNeighbor(self,
675                                 self.pg1.sw_if_index,
676                                 self.pg1.remote_hosts[1].mac,
677                                 self.pg1.remote_hosts[1].ip4)
678         arp_entry.add_vpp_config()
679
680         #
681         # Send a discover from the client. expect two relayed messages
682         # The frist packet is sent to the second server
683         # We're not enforcing that here, it's just the way it is.
684         #
685         self.pg4.add_stream(pkts_disc_vrf1)
686         self.pg_enable_capture(self.pg_interfaces)
687         self.pg_start()
688
689         rx = self.pg1.get_capture(2)
690
691         option_82 = self.verify_relayed_dhcp_discover(
692             rx[0], self.pg1,
693             src_intf=self.pg4,
694             dst_mac=self.pg1.remote_hosts[1].mac,
695             dst_ip=self.pg1.remote_hosts[1].ip4,
696             fib_id=1, oui=4)
697         self.verify_relayed_dhcp_discover(rx[1], self.pg1,
698                                           src_intf=self.pg4,
699                                           fib_id=1, oui=4)
700
701         #
702         # Send both packets back. Client gets both.
703         #
704         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
705               IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
706               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
707               BOOTP(op=1) /
708               DHCP(options=[('message-type', 'offer'),
709                             ('relay_agent_Information', option_82),
710                             ('end')]))
711         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
712               IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
713               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
714               BOOTP(op=1) /
715               DHCP(options=[('message-type', 'offer'),
716                             ('relay_agent_Information', option_82),
717                             ('end')]))
718         pkts = [p1, p2]
719
720         self.pg1.add_stream(pkts)
721         self.pg_enable_capture(self.pg_interfaces)
722         self.pg_start()
723
724         rx = self.pg4.get_capture(2)
725
726         self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4)
727         self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4)
728
729         #
730         # Ensure offers from non-servers are dropeed
731         #
732         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
733               IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
734               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
735               BOOTP(op=1) /
736               DHCP(options=[('message-type', 'offer'),
737                             ('relay_agent_Information', option_82),
738                             ('end')]))
739         self.send_and_assert_no_replies(self.pg1, p2,
740                                         "DHCP offer from non-server")
741
742         #
743         # Ensure only the discover is sent to multiple servers
744         #
745         p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
746                             src=self.pg4.remote_mac) /
747                       IP(src="0.0.0.0", dst="255.255.255.255") /
748                       UDP(sport=DHCP4_CLIENT_PORT,
749                           dport=DHCP4_SERVER_PORT) /
750                       BOOTP(op=1) /
751                       DHCP(options=[('message-type', 'request'),
752                                     ('end')]))
753
754         self.pg4.add_stream(p_req_vrf1)
755         self.pg_enable_capture(self.pg_interfaces)
756         self.pg_start()
757
758         rx = self.pg1.get_capture(1)
759
760         #
761         # Remove the second DHCP server
762         #
763         Proxy12.remove_vpp_config()
764
765         #
766         # Test we can still relay with the first
767         #
768         self.pg4.add_stream(pkts_disc_vrf1)
769         self.pg_enable_capture(self.pg_interfaces)
770         self.pg_start()
771
772         rx = self.pg1.get_capture(1)
773         rx = rx[0]
774         self.verify_relayed_dhcp_discover(rx, self.pg1,
775                                           src_intf=self.pg4,
776                                           fib_id=1, oui=4)
777
778         #
779         # Remove the VSS config
780         #  relayed DHCP has default vlaues in the option.
781         #
782         self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_add=0)
783         self.vapi.dhcp_proxy_set_vss(tbl_id=2, is_add=0)
784
785         self.pg4.add_stream(pkts_disc_vrf1)
786         self.pg_enable_capture(self.pg_interfaces)
787         self.pg_start()
788
789         rx = self.pg1.get_capture(1)
790         rx = rx[0]
791         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
792
793         #
794         # remove DHCP config to cleanup
795         #
796         Proxy1.remove_vpp_config()
797         Proxy2.remove_vpp_config()
798
799         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
800                                         "DHCP cleanup VRF 0")
801         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
802                                         "DHCP cleanup VRF 1")
803         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
804                                         "DHCP cleanup VRF 2")
805
806         self.pg3.unconfig_ip4()
807         self.pg4.unconfig_ip4()
808         self.pg5.unconfig_ip4()
809
810     def test_dhcp6_proxy(self):
811         """ DHCPv6 Proxy"""
812         #
813         # Verify no response to DHCP request without DHCP config
814         #
815         dhcp_solicit_dst = "ff02::1:2"
816         dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac)
817         dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac)
818         dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac)
819         server_addr_vrf0 = self.pg0.remote_ip6
820         src_addr_vrf0 = self.pg0.local_ip6
821         server_addr_vrf1 = self.pg1.remote_ip6
822         src_addr_vrf1 = self.pg1.local_ip6
823         server_addr_vrf2 = self.pg2.remote_ip6
824         src_addr_vrf2 = self.pg2.local_ip6
825
826         dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
827         p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
828                           IPv6(src=dhcp_solicit_src_vrf0,
829                                dst=dhcp_solicit_dst) /
830                           UDP(sport=DHCP6_SERVER_PORT,
831                               dport=DHCP6_CLIENT_PORT) /
832                           DHCP6_Solicit())
833         p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
834                           IPv6(src=dhcp_solicit_src_vrf1,
835                                dst=dhcp_solicit_dst) /
836                           UDP(sport=DHCP6_SERVER_PORT,
837                               dport=DHCP6_CLIENT_PORT) /
838                           DHCP6_Solicit())
839         p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) /
840                           IPv6(src=dhcp_solicit_src_vrf2,
841                                dst=dhcp_solicit_dst) /
842                           UDP(sport=DHCP6_SERVER_PORT,
843                               dport=DHCP6_CLIENT_PORT) /
844                           DHCP6_Solicit())
845
846         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
847                                         "DHCP with no configuration")
848         self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
849                                         "DHCP with no configuration")
850         self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2,
851                                         "DHCP with no configuration")
852
853         #
854         # DHCPv6 config in VRF 0.
855         # Packets still dropped because the client facing interface has no
856         # IPv6 config
857         #
858         Proxy = VppDHCPProxy(
859             self,
860             server_addr_vrf0,
861             src_addr_vrf0,
862             rx_vrf_id=0,
863             server_vrf_id=0)
864         Proxy.add_vpp_config()
865
866         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
867                                         "DHCP with no configuration")
868         self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
869                                         "DHCP with no configuration")
870
871         #
872         # configure an IP address on the client facing interface
873         #
874         self.pg3.config_ip6()
875
876         #
877         # Now the DHCP requests are relayed to the server
878         #
879         self.pg3.add_stream(p_solicit_vrf0)
880         self.pg_enable_capture(self.pg_interfaces)
881         self.pg_start()
882
883         rx = self.pg0.get_capture(1)
884
885         self.verify_dhcp6_solicit(rx[0], self.pg0,
886                                   dhcp_solicit_src_vrf0,
887                                   self.pg3.remote_mac)
888
889         #
890         # Exception cases for rejected relay responses
891         #
892
893         # 1 - not a relay reply
894         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
895                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
896                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
897                       DHCP6_Advertise())
898         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
899                                         "DHCP6 not a relay reply")
900
901         # 2 - no relay message option
902         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
903                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
904                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
905                       DHCP6_RelayReply() /
906                       DHCP6_Advertise())
907         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
908                                         "DHCP not a relay message")
909
910         # 3 - no circuit ID
911         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
912                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
913                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
914                       DHCP6_RelayReply() /
915                       DHCP6OptRelayMsg(optlen=0) /
916                       DHCP6_Advertise())
917         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
918                                         "DHCP6 no circuit ID")
919         # 4 - wrong circuit ID
920         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
921                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
922                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
923                       DHCP6_RelayReply() /
924                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
925                       DHCP6OptRelayMsg(optlen=0) /
926                       DHCP6_Advertise())
927         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
928                                         "DHCP6 wrong circuit ID")
929
930         #
931         # Send the relay response (the advertisement)
932         #   - no peer address
933         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
934                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
935                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
936                       DHCP6_RelayReply() /
937                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
938                       DHCP6OptRelayMsg(optlen=0) /
939                       DHCP6_Advertise(trid=1) /
940                       DHCP6OptStatusCode(statuscode=0))
941         pkts_adv_vrf0 = [p_adv_vrf0]
942
943         self.pg0.add_stream(pkts_adv_vrf0)
944         self.pg_enable_capture(self.pg_interfaces)
945         self.pg_start()
946
947         rx = self.pg3.get_capture(1)
948
949         self.verify_dhcp6_advert(rx[0], self.pg3, "::")
950
951         #
952         # Send the relay response (the advertisement)
953         #   - with peer address
954         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
955                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
956                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
957                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
958                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
959                       DHCP6OptRelayMsg(optlen=0) /
960                       DHCP6_Advertise(trid=1) /
961                       DHCP6OptStatusCode(statuscode=0))
962         pkts_adv_vrf0 = [p_adv_vrf0]
963
964         self.pg0.add_stream(pkts_adv_vrf0)
965         self.pg_enable_capture(self.pg_interfaces)
966         self.pg_start()
967
968         rx = self.pg3.get_capture(1)
969
970         self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
971
972         #
973         # Add all the config for VRF 1 & 2
974         #
975         Proxy1 = VppDHCPProxy(
976             self,
977             server_addr_vrf1,
978             src_addr_vrf1,
979             rx_vrf_id=1,
980             server_vrf_id=1)
981         Proxy1.add_vpp_config()
982         self.pg4.config_ip6()
983
984         Proxy2 = VppDHCPProxy(
985             self,
986             server_addr_vrf2,
987             src_addr_vrf2,
988             rx_vrf_id=2,
989             server_vrf_id=2)
990         Proxy2.add_vpp_config()
991         self.pg5.config_ip6()
992
993         #
994         # VRF 1 solicit
995         #
996         self.pg4.add_stream(p_solicit_vrf1)
997         self.pg_enable_capture(self.pg_interfaces)
998         self.pg_start()
999
1000         rx = self.pg1.get_capture(1)
1001
1002         self.verify_dhcp6_solicit(rx[0], self.pg1,
1003                                   dhcp_solicit_src_vrf1,
1004                                   self.pg4.remote_mac)
1005
1006         #
1007         # VRF 2 solicit
1008         #
1009         self.pg5.add_stream(p_solicit_vrf2)
1010         self.pg_enable_capture(self.pg_interfaces)
1011         self.pg_start()
1012
1013         rx = self.pg2.get_capture(1)
1014
1015         self.verify_dhcp6_solicit(rx[0], self.pg2,
1016                                   dhcp_solicit_src_vrf2,
1017                                   self.pg5.remote_mac)
1018
1019         #
1020         # VRF 1 Advert
1021         #
1022         p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1023                       IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1024                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1025                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1026                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1027                       DHCP6OptRelayMsg(optlen=0) /
1028                       DHCP6_Advertise(trid=1) /
1029                       DHCP6OptStatusCode(statuscode=0))
1030         pkts_adv_vrf1 = [p_adv_vrf1]
1031
1032         self.pg1.add_stream(pkts_adv_vrf1)
1033         self.pg_enable_capture(self.pg_interfaces)
1034         self.pg_start()
1035
1036         rx = self.pg4.get_capture(1)
1037
1038         self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1039
1040         #
1041         # Add VSS config
1042         #
1043         self.vapi.dhcp_proxy_set_vss(
1044             tbl_id=1, vss_type=1, oui=4, vpn_index=1, is_ipv6=1, is_add=1)
1045         self.vapi.dhcp_proxy_set_vss(
1046             tbl_id=2,
1047             vss_type=0,
1048             vpn_ascii_id="IPv6-table-2",
1049             is_ipv6=1,
1050             is_add=1)
1051
1052         self.pg4.add_stream(p_solicit_vrf1)
1053         self.pg_enable_capture(self.pg_interfaces)
1054         self.pg_start()
1055
1056         rx = self.pg1.get_capture(1)
1057
1058         self.verify_dhcp6_solicit(rx[0], self.pg1,
1059                                   dhcp_solicit_src_vrf1,
1060                                   self.pg4.remote_mac,
1061                                   fib_id=1,
1062                                   oui=4)
1063
1064         self.pg5.add_stream(p_solicit_vrf2)
1065         self.pg_enable_capture(self.pg_interfaces)
1066         self.pg_start()
1067
1068         rx = self.pg2.get_capture(1)
1069
1070         self.verify_dhcp6_solicit(rx[0], self.pg2,
1071                                   dhcp_solicit_src_vrf2,
1072                                   self.pg5.remote_mac,
1073                                   vpn_id="IPv6-table-2")
1074
1075         #
1076         # Remove the VSS config
1077         #  relayed DHCP has default vlaues in the option.
1078         #
1079         self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_ipv6=1, is_add=0)
1080
1081         self.pg4.add_stream(p_solicit_vrf1)
1082         self.pg_enable_capture(self.pg_interfaces)
1083         self.pg_start()
1084
1085         rx = self.pg1.get_capture(1)
1086
1087         self.verify_dhcp6_solicit(rx[0], self.pg1,
1088                                   dhcp_solicit_src_vrf1,
1089                                   self.pg4.remote_mac)
1090
1091         #
1092         # Add a second DHCP server in VRF 1
1093         #  expect clients messages to be relay to both configured servers
1094         #
1095         self.pg1.generate_remote_hosts(2)
1096         server_addr12 = self.pg1.remote_hosts[1].ip6
1097
1098         Proxy12 = VppDHCPProxy(
1099             self,
1100             server_addr12,
1101             src_addr_vrf1,
1102             rx_vrf_id=1,
1103             server_vrf_id=1)
1104         Proxy12.add_vpp_config()
1105
1106         #
1107         # We'll need an ND entry for the server to send it packets
1108         #
1109         nd_entry = VppNeighbor(self,
1110                                self.pg1.sw_if_index,
1111                                self.pg1.remote_hosts[1].mac,
1112                                self.pg1.remote_hosts[1].ip6)
1113         nd_entry.add_vpp_config()
1114
1115         #
1116         # Send a discover from the client. expect two relayed messages
1117         # The frist packet is sent to the second server
1118         # We're not enforcing that here, it's just the way it is.
1119         #
1120         self.pg4.add_stream(p_solicit_vrf1)
1121         self.pg_enable_capture(self.pg_interfaces)
1122         self.pg_start()
1123
1124         rx = self.pg1.get_capture(2)
1125
1126         self.verify_dhcp6_solicit(rx[0], self.pg1,
1127                                   dhcp_solicit_src_vrf1,
1128                                   self.pg4.remote_mac)
1129         self.verify_dhcp6_solicit(rx[1], self.pg1,
1130                                   dhcp_solicit_src_vrf1,
1131                                   self.pg4.remote_mac,
1132                                   dst_mac=self.pg1.remote_hosts[1].mac,
1133                                   dst_ip=self.pg1.remote_hosts[1].ip6)
1134
1135         #
1136         # Send both packets back. Client gets both.
1137         #
1138         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1139               IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1140               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1141               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1142               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1143               DHCP6OptRelayMsg(optlen=0) /
1144               DHCP6_Advertise(trid=1) /
1145               DHCP6OptStatusCode(statuscode=0))
1146         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1147               IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
1148               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1149               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1150               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1151               DHCP6OptRelayMsg(optlen=0) /
1152               DHCP6_Advertise(trid=1) /
1153               DHCP6OptStatusCode(statuscode=0))
1154
1155         pkts = [p1, p2]
1156
1157         self.pg1.add_stream(pkts)
1158         self.pg_enable_capture(self.pg_interfaces)
1159         self.pg_start()
1160
1161         rx = self.pg4.get_capture(2)
1162
1163         self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1164         self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1)
1165
1166         #
1167         # Ensure only solicit messages are duplicated
1168         #
1169         p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
1170                           IPv6(src=dhcp_solicit_src_vrf1,
1171                                dst=dhcp_solicit_dst) /
1172                           UDP(sport=DHCP6_SERVER_PORT,
1173                               dport=DHCP6_CLIENT_PORT) /
1174                           DHCP6_Request())
1175
1176         self.pg4.add_stream(p_request_vrf1)
1177         self.pg_enable_capture(self.pg_interfaces)
1178         self.pg_start()
1179
1180         rx = self.pg1.get_capture(1)
1181
1182         #
1183         # Test we drop DHCP packets from addresses that are not configured as
1184         # DHCP servers
1185         #
1186         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1187               IPv6(dst=self.pg1.local_ip6, src="3001::1") /
1188               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1189               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1190               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1191               DHCP6OptRelayMsg(optlen=0) /
1192               DHCP6_Advertise(trid=1) /
1193               DHCP6OptStatusCode(statuscode=0))
1194         self.send_and_assert_no_replies(self.pg1, p2,
1195                                         "DHCP6 not from server")
1196
1197         #
1198         # Remove the second DHCP server
1199         #
1200         Proxy12.remove_vpp_config()
1201
1202         #
1203         # Test we can still relay with the first
1204         #
1205         self.pg4.add_stream(p_solicit_vrf1)
1206         self.pg_enable_capture(self.pg_interfaces)
1207         self.pg_start()
1208
1209         rx = self.pg1.get_capture(1)
1210
1211         self.verify_dhcp6_solicit(rx[0], self.pg1,
1212                                   dhcp_solicit_src_vrf1,
1213                                   self.pg4.remote_mac)
1214
1215         #
1216         # Cleanup
1217         #
1218         Proxy.remove_vpp_config()
1219         Proxy1.remove_vpp_config()
1220         Proxy2.remove_vpp_config()
1221
1222         self.pg3.unconfig_ip6()
1223         self.pg4.unconfig_ip6()
1224         self.pg5.unconfig_ip6()
1225
1226     def test_dhcp_client(self):
1227         """ DHCP Client"""
1228
1229         vdscp = VppEnum.vl_api_ip_dscp_t
1230         hostname = 'universal-dp'
1231
1232         self.pg_enable_capture(self.pg_interfaces)
1233
1234         #
1235         # Configure DHCP client on PG3 and capture the discover sent
1236         #
1237         Client = VppDHCPClient(self, self.pg3.sw_if_index, hostname)
1238         Client.add_vpp_config()
1239         self.assertTrue(Client.query_vpp_config())
1240
1241         rx = self.pg3.get_capture(1)
1242
1243         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1244
1245         #
1246         # Send back on offer, expect the request
1247         #
1248         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1249                    IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1250                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1251                    BOOTP(op=1,
1252                          yiaddr=self.pg3.local_ip4,
1253                          chaddr=mac_pton(self.pg3.local_mac)) /
1254                    DHCP(options=[('message-type', 'offer'),
1255                                  ('server_id', self.pg3.remote_ip4),
1256                                  'end']))
1257
1258         self.pg3.add_stream(p_offer)
1259         self.pg_enable_capture(self.pg_interfaces)
1260         self.pg_start()
1261
1262         rx = self.pg3.get_capture(1)
1263         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1264                                       self.pg3.local_ip4)
1265
1266         #
1267         # Send an acknowledgment
1268         #
1269         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1270                  IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1271                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1272                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1273                        chaddr=mac_pton(self.pg3.local_mac)) /
1274                  DHCP(options=[('message-type', 'ack'),
1275                                ('subnet_mask', "255.255.255.0"),
1276                                ('router', self.pg3.remote_ip4),
1277                                ('server_id', self.pg3.remote_ip4),
1278                                ('lease_time', 43200),
1279                                'end']))
1280
1281         self.pg3.add_stream(p_ack)
1282         self.pg_enable_capture(self.pg_interfaces)
1283         self.pg_start()
1284
1285         #
1286         # We'll get an ARP request for the router address
1287         #
1288         rx = self.pg3.get_capture(1)
1289
1290         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1291         self.pg_enable_capture(self.pg_interfaces)
1292
1293         #
1294         # At the end of this procedure there should be a connected route
1295         # in the FIB
1296         #
1297         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1298         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1299
1300         # remove the left over ARP entry
1301         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1302                                       self.pg3.remote_mac,
1303                                       self.pg3.remote_ip4,
1304                                       is_add=0)
1305
1306         #
1307         # remove the DHCP config
1308         #
1309         Client.remove_vpp_config()
1310
1311         #
1312         # and now the route should be gone
1313         #
1314         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1315         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1316
1317         #
1318         # Start the procedure again. this time have VPP send the client-ID
1319         # and set the DSCP value
1320         #
1321         self.pg3.admin_down()
1322         self.sleep(1)
1323         self.pg3.admin_up()
1324         Client.set_client(self.pg3.sw_if_index, hostname,
1325                           id=self.pg3.local_mac,
1326                           dscp=vdscp.IP_API_DSCP_EF)
1327         Client.add_vpp_config()
1328
1329         rx = self.pg3.get_capture(1)
1330
1331         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1332                                        self.pg3.local_mac,
1333                                        dscp=vdscp.IP_API_DSCP_EF)
1334
1335         # TODO: VPP DHCP client should not accept DHCP OFFER message with
1336         # the XID (Transaction ID) not matching the XID of the most recent
1337         # DHCP DISCOVERY message.
1338         # Such DHCP OFFER message must be silently discarded - RFC2131.
1339         # Reported in Jira ticket: VPP-99
1340         self.pg3.add_stream(p_offer)
1341         self.pg_enable_capture(self.pg_interfaces)
1342         self.pg_start()
1343
1344         rx = self.pg3.get_capture(1)
1345         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1346                                       self.pg3.local_ip4,
1347                                       dscp=vdscp.IP_API_DSCP_EF)
1348
1349         #
1350         # unicast the ack to the offered address
1351         #
1352         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1353                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1354                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1355                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1356                        chaddr=mac_pton(self.pg3.local_mac)) /
1357                  DHCP(options=[('message-type', 'ack'),
1358                                ('subnet_mask', "255.255.255.0"),
1359                                ('router', self.pg3.remote_ip4),
1360                                ('server_id', self.pg3.remote_ip4),
1361                                ('lease_time', 43200),
1362                                'end']))
1363
1364         self.pg3.add_stream(p_ack)
1365         self.pg_enable_capture(self.pg_interfaces)
1366         self.pg_start()
1367
1368         #
1369         # We'll get an ARP request for the router address
1370         #
1371         rx = self.pg3.get_capture(1)
1372
1373         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1374         self.pg_enable_capture(self.pg_interfaces)
1375
1376         #
1377         # At the end of this procedure there should be a connected route
1378         # in the FIB
1379         #
1380         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1381         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1382
1383         #
1384         # remove the DHCP config
1385         #
1386         Client.remove_vpp_config()
1387
1388         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1389         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1390
1391         #
1392         # Rince and repeat, this time with VPP configured not to set
1393         # the braodcast flag in the discover and request messages,
1394         # and for the server to unicast the responses.
1395         #
1396         # Configure DHCP client on PG3 and capture the discover sent
1397         #
1398         Client.set_client(
1399             self.pg3.sw_if_index,
1400             hostname,
1401             set_broadcast_flag=False)
1402         Client.add_vpp_config()
1403
1404         rx = self.pg3.get_capture(1)
1405
1406         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1407                                        broadcast=False)
1408
1409         #
1410         # Send back on offer, unicasted to the offered address.
1411         # Expect the request.
1412         #
1413         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1414                    IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1415                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1416                    BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1417                          chaddr=mac_pton(self.pg3.local_mac)) /
1418                    DHCP(options=[('message-type', 'offer'),
1419                                  ('server_id', self.pg3.remote_ip4),
1420                                  'end']))
1421
1422         self.pg3.add_stream(p_offer)
1423         self.pg_enable_capture(self.pg_interfaces)
1424         self.pg_start()
1425
1426         rx = self.pg3.get_capture(1)
1427         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1428                                       self.pg3.local_ip4,
1429                                       broadcast=False)
1430
1431         #
1432         # Send an acknowledgment, the lease renewal time is 2 seconds
1433         # so we should expect the renew straight after
1434         #
1435         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1436                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1437                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1438                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1439                        chaddr=mac_pton(self.pg3.local_mac)) /
1440                  DHCP(options=[('message-type', 'ack'),
1441                                ('subnet_mask', "255.255.255.0"),
1442                                ('router', self.pg3.remote_ip4),
1443                                ('server_id', self.pg3.remote_ip4),
1444                                ('lease_time', 43200),
1445                                ('renewal_time', 2),
1446                                'end']))
1447
1448         self.pg3.add_stream(p_ack)
1449         self.pg_enable_capture(self.pg_interfaces)
1450         self.pg_start()
1451
1452         #
1453         # We'll get an ARP request for the router address
1454         #
1455         rx = self.pg3.get_capture(1)
1456
1457         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1458         self.pg_enable_capture(self.pg_interfaces)
1459
1460         #
1461         # At the end of this procedure there should be a connected route
1462         # in the FIB
1463         #
1464         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1465         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1466
1467         #
1468         # read the DHCP client details from a dump
1469         #
1470         clients = self.vapi.dhcp_client_dump()
1471
1472         self.assertEqual(clients[0].client.sw_if_index,
1473                          self.pg3.sw_if_index)
1474         self.assertEqual(clients[0].lease.sw_if_index,
1475                          self.pg3.sw_if_index)
1476         self.assertEqual(clients[0].client.hostname, hostname)
1477         self.assertEqual(clients[0].lease.hostname, hostname)
1478         # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1479         self.assertEqual(clients[0].lease.state, 2)
1480         self.assertEqual(clients[0].lease.mask_width, 24)
1481         self.assertEqual(str(clients[0].lease.router_address),
1482                          self.pg3.remote_ip4)
1483         self.assertEqual(str(clients[0].lease.host_address),
1484                          self.pg3.local_ip4)
1485
1486         #
1487         # wait for the unicasted renewal
1488         #  the first attempt will be an ARP packet, since we have not yet
1489         #  responded to VPP's request
1490         #
1491         self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose"))
1492         rx = self.pg3.get_capture(1, timeout=10)
1493
1494         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1495
1496         # respond to the arp
1497         p_arp = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1498                  ARP(op="is-at",
1499                      hwdst=self.pg3.local_mac,
1500                      hwsrc=self.pg3.remote_mac,
1501                      pdst=self.pg3.local_ip4,
1502                      psrc=self.pg3.remote_ip4))
1503         self.pg3.add_stream(p_arp)
1504         self.pg_enable_capture(self.pg_interfaces)
1505         self.pg_start()
1506
1507         # the next packet is the unicasted renewal
1508         rx = self.pg3.get_capture(1, timeout=10)
1509         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1510                                       self.pg3.local_ip4,
1511                                       l2_bc=False,
1512                                       broadcast=False)
1513
1514         # send an ACK with different data from the original offer *
1515         self.pg3.generate_remote_hosts(4)
1516         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1517                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1518                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1519                  BOOTP(op=1, yiaddr=self.pg3.remote_hosts[3].ip4,
1520                        chaddr=mac_pton(self.pg3.local_mac)) /
1521                  DHCP(options=[('message-type', 'ack'),
1522                                ('subnet_mask', "255.255.255.0"),
1523                                ('router', self.pg3.remote_hosts[1].ip4),
1524                                ('server_id', self.pg3.remote_hosts[2].ip4),
1525                                ('lease_time', 43200),
1526                                ('renewal_time', 2),
1527                                'end']))
1528
1529         self.pg3.add_stream(p_ack)
1530         self.pg_enable_capture(self.pg_interfaces)
1531         self.pg_start()
1532
1533         #
1534         # read the DHCP client details from a dump
1535         #
1536         clients = self.vapi.dhcp_client_dump()
1537
1538         self.assertEqual(clients[0].client.sw_if_index,
1539                          self.pg3.sw_if_index)
1540         self.assertEqual(clients[0].lease.sw_if_index,
1541                          self.pg3.sw_if_index)
1542         self.assertEqual(clients[0].client.hostname, hostname)
1543         self.assertEqual(clients[0].lease.hostname, hostname)
1544         # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1545         self.assertEqual(clients[0].lease.state, 2)
1546         self.assertEqual(clients[0].lease.mask_width, 24)
1547         self.assertEqual(str(clients[0].lease.router_address),
1548                          self.pg3.remote_hosts[1].ip4)
1549         self.assertEqual(str(clients[0].lease.host_address),
1550                          self.pg3.remote_hosts[3].ip4)
1551
1552         #
1553         # remove the DHCP config
1554         #
1555         Client.remove_vpp_config()
1556
1557         #
1558         # and now the route should be gone
1559         #
1560         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1561         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1562
1563         #
1564         # Start the procedure again. Use requested lease time option.
1565         # this time wait for the lease to expire and the client to
1566         # self-destruct
1567         #
1568         hostname += "-2"
1569         self.pg3.admin_down()
1570         self.sleep(1)
1571         self.pg3.admin_up()
1572         self.pg_enable_capture(self.pg_interfaces)
1573         Client.set_client(self.pg3.sw_if_index, hostname)
1574         Client.add_vpp_config()
1575
1576         rx = self.pg3.get_capture(1)
1577
1578         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1579
1580         #
1581         # Send back on offer with requested lease time, expect the request
1582         #
1583         lease_time = 1
1584         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1585                    IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1586                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1587                    BOOTP(op=1,
1588                          yiaddr=self.pg3.local_ip4,
1589                          chaddr=mac_pton(self.pg3.local_mac)) /
1590                    DHCP(options=[('message-type', 'offer'),
1591                                  ('server_id', self.pg3.remote_ip4),
1592                                  ('lease_time', lease_time),
1593                                  'end']))
1594
1595         self.pg3.add_stream(p_offer)
1596         self.pg_enable_capture(self.pg_interfaces)
1597         self.pg_start()
1598
1599         rx = self.pg3.get_capture(1)
1600         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1601                                       self.pg3.local_ip4)
1602
1603         #
1604         # Send an acknowledgment
1605         #
1606         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1607                  IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1608                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1609                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1610                        chaddr=mac_pton(self.pg3.local_mac)) /
1611                  DHCP(options=[('message-type', 'ack'),
1612                                ('subnet_mask', '255.255.255.0'),
1613                                ('router', self.pg3.remote_ip4),
1614                                ('server_id', self.pg3.remote_ip4),
1615                                ('lease_time', lease_time),
1616                                'end']))
1617
1618         self.pg3.add_stream(p_ack)
1619         self.pg_enable_capture(self.pg_interfaces)
1620         self.pg_start()
1621
1622         #
1623         # We'll get an ARP request for the router address
1624         #
1625         rx = self.pg3.get_capture(1)
1626
1627         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1628
1629         #
1630         # At the end of this procedure there should be a connected route
1631         # in the FIB
1632         #
1633         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1634         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1635
1636         #
1637         # the route should be gone after the lease expires
1638         #
1639         self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32))
1640         self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24))
1641
1642         #
1643         # remove the DHCP config
1644         #
1645         Client.remove_vpp_config()
1646
1647     def test_dhcp_client_vlan(self):
1648         """ DHCP Client w/ VLAN"""
1649
1650         vdscp = VppEnum.vl_api_ip_dscp_t
1651         vqos = VppEnum.vl_api_qos_source_t
1652         hostname = 'universal-dp'
1653
1654         self.pg_enable_capture(self.pg_interfaces)
1655
1656         vlan_100 = VppDot1QSubint(self, self.pg3, 100)
1657         vlan_100.admin_up()
1658
1659         output = [scapy.compat.chb(4)] * 256
1660         os = b''.join(output)
1661         rows = [{'outputs': os},
1662                 {'outputs': os},
1663                 {'outputs': os},
1664                 {'outputs': os}]
1665
1666         qem1 = VppQosEgressMap(self, 1, rows).add_vpp_config()
1667         qm1 = VppQosMark(self, vlan_100, qem1,
1668                          vqos.QOS_API_SOURCE_VLAN).add_vpp_config()
1669
1670         #
1671         # Configure DHCP client on PG3 and capture the discover sent
1672         #
1673         Client = VppDHCPClient(
1674             self,
1675             vlan_100.sw_if_index,
1676             hostname,
1677             dscp=vdscp.IP_API_DSCP_EF)
1678         Client.add_vpp_config()
1679
1680         rx = self.pg3.get_capture(1)
1681
1682         self.assertEqual(rx[0][Dot1Q].vlan, 100)
1683         self.assertEqual(rx[0][Dot1Q].prio, 2)
1684
1685         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1686                                        dscp=vdscp.IP_API_DSCP_EF)
1687
1688
1689 if __name__ == '__main__':
1690     unittest.main(testRunner=VppTestRunner)