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