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