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