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