mactime: add a "top" command to watch device stats
[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 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(1, 1, vpn_index=1, oui=4, is_add=1)
621         self.vapi.dhcp_proxy_set_vss(2, 0, "ip4-table-2", is_add=1)
622
623         self.pg4.add_stream(pkts_disc_vrf1)
624         self.pg_enable_capture(self.pg_interfaces)
625         self.pg_start()
626
627         rx = self.pg1.get_capture(1)
628         rx = rx[0]
629         self.verify_relayed_dhcp_discover(rx, self.pg1,
630                                           src_intf=self.pg4,
631                                           fib_id=1, oui=4)
632
633         self.pg5.add_stream(pkts_disc_vrf2)
634         self.pg_enable_capture(self.pg_interfaces)
635         self.pg_start()
636
637         rx = self.pg2.get_capture(1)
638         rx = rx[0]
639         self.verify_relayed_dhcp_discover(rx, self.pg2,
640                                           src_intf=self.pg5,
641                                           vpn_id="ip4-table-2")
642
643         #
644         # Add a second DHCP server in VRF 1
645         #  expect clients messages to be relay to both configured servers
646         #
647         self.pg1.generate_remote_hosts(2)
648         server_addr12 = self.pg1.remote_hosts[1].ip4
649
650         Proxy12 = VppDHCPProxy(
651             self,
652             server_addr12,
653             src_addr,
654             rx_vrf_id=1,
655             server_vrf_id=1)
656         Proxy12.add_vpp_config()
657
658         #
659         # We'll need an ARP entry for the server to send it packets
660         #
661         arp_entry = VppNeighbor(self,
662                                 self.pg1.sw_if_index,
663                                 self.pg1.remote_hosts[1].mac,
664                                 self.pg1.remote_hosts[1].ip4)
665         arp_entry.add_vpp_config()
666
667         #
668         # Send a discover from the client. expect two relayed messages
669         # The frist packet is sent to the second server
670         # We're not enforcing that here, it's just the way it is.
671         #
672         self.pg4.add_stream(pkts_disc_vrf1)
673         self.pg_enable_capture(self.pg_interfaces)
674         self.pg_start()
675
676         rx = self.pg1.get_capture(2)
677
678         option_82 = self.verify_relayed_dhcp_discover(
679             rx[0], self.pg1,
680             src_intf=self.pg4,
681             dst_mac=self.pg1.remote_hosts[1].mac,
682             dst_ip=self.pg1.remote_hosts[1].ip4,
683             fib_id=1, oui=4)
684         self.verify_relayed_dhcp_discover(rx[1], self.pg1,
685                                           src_intf=self.pg4,
686                                           fib_id=1, oui=4)
687
688         #
689         # Send both packets back. Client gets both.
690         #
691         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
692               IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
693               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
694               BOOTP(op=1) /
695               DHCP(options=[('message-type', 'offer'),
696                             ('relay_agent_Information', option_82),
697                             ('end')]))
698         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
699               IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
700               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
701               BOOTP(op=1) /
702               DHCP(options=[('message-type', 'offer'),
703                             ('relay_agent_Information', option_82),
704                             ('end')]))
705         pkts = [p1, p2]
706
707         self.pg1.add_stream(pkts)
708         self.pg_enable_capture(self.pg_interfaces)
709         self.pg_start()
710
711         rx = self.pg4.get_capture(2)
712
713         self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4)
714         self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4)
715
716         #
717         # Ensure offers from non-servers are dropeed
718         #
719         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
720               IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
721               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
722               BOOTP(op=1) /
723               DHCP(options=[('message-type', 'offer'),
724                             ('relay_agent_Information', option_82),
725                             ('end')]))
726         self.send_and_assert_no_replies(self.pg1, p2,
727                                         "DHCP offer from non-server")
728
729         #
730         # Ensure only the discover is sent to multiple servers
731         #
732         p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
733                             src=self.pg4.remote_mac) /
734                       IP(src="0.0.0.0", dst="255.255.255.255") /
735                       UDP(sport=DHCP4_CLIENT_PORT,
736                           dport=DHCP4_SERVER_PORT) /
737                       BOOTP(op=1) /
738                       DHCP(options=[('message-type', 'request'),
739                                     ('end')]))
740
741         self.pg4.add_stream(p_req_vrf1)
742         self.pg_enable_capture(self.pg_interfaces)
743         self.pg_start()
744
745         rx = self.pg1.get_capture(1)
746
747         #
748         # Remove the second DHCP server
749         #
750         Proxy12.remove_vpp_config()
751
752         #
753         # Test we can still relay with the first
754         #
755         self.pg4.add_stream(pkts_disc_vrf1)
756         self.pg_enable_capture(self.pg_interfaces)
757         self.pg_start()
758
759         rx = self.pg1.get_capture(1)
760         rx = rx[0]
761         self.verify_relayed_dhcp_discover(rx, self.pg1,
762                                           src_intf=self.pg4,
763                                           fib_id=1, oui=4)
764
765         #
766         # Remove the VSS config
767         #  relayed DHCP has default vlaues in the option.
768         #
769         self.vapi.dhcp_proxy_set_vss(1, is_add=0)
770         self.vapi.dhcp_proxy_set_vss(2, is_add=0)
771
772         self.pg4.add_stream(pkts_disc_vrf1)
773         self.pg_enable_capture(self.pg_interfaces)
774         self.pg_start()
775
776         rx = self.pg1.get_capture(1)
777         rx = rx[0]
778         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
779
780         #
781         # remove DHCP config to cleanup
782         #
783         Proxy1.remove_vpp_config()
784         Proxy2.remove_vpp_config()
785
786         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
787                                         "DHCP cleanup VRF 0")
788         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
789                                         "DHCP cleanup VRF 1")
790         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
791                                         "DHCP cleanup VRF 2")
792
793         self.pg3.unconfig_ip4()
794         self.pg4.unconfig_ip4()
795         self.pg5.unconfig_ip4()
796
797     def test_dhcp6_proxy(self):
798         """ DHCPv6 Proxy"""
799         #
800         # Verify no response to DHCP request without DHCP config
801         #
802         dhcp_solicit_dst = "ff02::1:2"
803         dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac)
804         dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac)
805         dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac)
806         server_addr_vrf0 = self.pg0.remote_ip6
807         src_addr_vrf0 = self.pg0.local_ip6
808         server_addr_vrf1 = self.pg1.remote_ip6
809         src_addr_vrf1 = self.pg1.local_ip6
810         server_addr_vrf2 = self.pg2.remote_ip6
811         src_addr_vrf2 = self.pg2.local_ip6
812
813         dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
814         p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
815                           IPv6(src=dhcp_solicit_src_vrf0,
816                                dst=dhcp_solicit_dst) /
817                           UDP(sport=DHCP6_SERVER_PORT,
818                               dport=DHCP6_CLIENT_PORT) /
819                           DHCP6_Solicit())
820         p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
821                           IPv6(src=dhcp_solicit_src_vrf1,
822                                dst=dhcp_solicit_dst) /
823                           UDP(sport=DHCP6_SERVER_PORT,
824                               dport=DHCP6_CLIENT_PORT) /
825                           DHCP6_Solicit())
826         p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) /
827                           IPv6(src=dhcp_solicit_src_vrf2,
828                                dst=dhcp_solicit_dst) /
829                           UDP(sport=DHCP6_SERVER_PORT,
830                               dport=DHCP6_CLIENT_PORT) /
831                           DHCP6_Solicit())
832
833         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
834                                         "DHCP with no configuration")
835         self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
836                                         "DHCP with no configuration")
837         self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2,
838                                         "DHCP with no configuration")
839
840         #
841         # DHCPv6 config in VRF 0.
842         # Packets still dropped because the client facing interface has no
843         # IPv6 config
844         #
845         Proxy = VppDHCPProxy(
846             self,
847             server_addr_vrf0,
848             src_addr_vrf0,
849             rx_vrf_id=0,
850             server_vrf_id=0)
851         Proxy.add_vpp_config()
852
853         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
854                                         "DHCP with no configuration")
855         self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
856                                         "DHCP with no configuration")
857
858         #
859         # configure an IP address on the client facing interface
860         #
861         self.pg3.config_ip6()
862
863         #
864         # Now the DHCP requests are relayed to the server
865         #
866         self.pg3.add_stream(p_solicit_vrf0)
867         self.pg_enable_capture(self.pg_interfaces)
868         self.pg_start()
869
870         rx = self.pg0.get_capture(1)
871
872         self.verify_dhcp6_solicit(rx[0], self.pg0,
873                                   dhcp_solicit_src_vrf0,
874                                   self.pg3.remote_mac)
875
876         #
877         # Exception cases for rejected relay responses
878         #
879
880         # 1 - not a relay reply
881         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
882                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
883                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
884                       DHCP6_Advertise())
885         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
886                                         "DHCP6 not a relay reply")
887
888         # 2 - no relay message option
889         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
890                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
891                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
892                       DHCP6_RelayReply() /
893                       DHCP6_Advertise())
894         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
895                                         "DHCP not a relay message")
896
897         # 3 - no circuit ID
898         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
899                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
900                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
901                       DHCP6_RelayReply() /
902                       DHCP6OptRelayMsg(optlen=0) /
903                       DHCP6_Advertise())
904         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
905                                         "DHCP6 no circuit ID")
906         # 4 - wrong circuit ID
907         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
908                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
909                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
910                       DHCP6_RelayReply() /
911                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
912                       DHCP6OptRelayMsg(optlen=0) /
913                       DHCP6_Advertise())
914         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
915                                         "DHCP6 wrong circuit ID")
916
917         #
918         # Send the relay response (the advertisement)
919         #   - no peer address
920         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
921                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
922                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
923                       DHCP6_RelayReply() /
924                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
925                       DHCP6OptRelayMsg(optlen=0) /
926                       DHCP6_Advertise(trid=1) /
927                       DHCP6OptStatusCode(statuscode=0))
928         pkts_adv_vrf0 = [p_adv_vrf0]
929
930         self.pg0.add_stream(pkts_adv_vrf0)
931         self.pg_enable_capture(self.pg_interfaces)
932         self.pg_start()
933
934         rx = self.pg3.get_capture(1)
935
936         self.verify_dhcp6_advert(rx[0], self.pg3, "::")
937
938         #
939         # Send the relay response (the advertisement)
940         #   - with peer address
941         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
942                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
943                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
944                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
945                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
946                       DHCP6OptRelayMsg(optlen=0) /
947                       DHCP6_Advertise(trid=1) /
948                       DHCP6OptStatusCode(statuscode=0))
949         pkts_adv_vrf0 = [p_adv_vrf0]
950
951         self.pg0.add_stream(pkts_adv_vrf0)
952         self.pg_enable_capture(self.pg_interfaces)
953         self.pg_start()
954
955         rx = self.pg3.get_capture(1)
956
957         self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
958
959         #
960         # Add all the config for VRF 1 & 2
961         #
962         Proxy1 = VppDHCPProxy(
963             self,
964             server_addr_vrf1,
965             src_addr_vrf1,
966             rx_vrf_id=1,
967             server_vrf_id=1)
968         Proxy1.add_vpp_config()
969         self.pg4.config_ip6()
970
971         Proxy2 = VppDHCPProxy(
972             self,
973             server_addr_vrf2,
974             src_addr_vrf2,
975             rx_vrf_id=2,
976             server_vrf_id=2)
977         Proxy2.add_vpp_config()
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         #
1030         self.vapi.dhcp_proxy_set_vss(
1031             tbl_id=1, vss_type=1, oui=4, vpn_index=1, is_ipv6=1)
1032         self.vapi.dhcp_proxy_set_vss(
1033             tbl_id=2,
1034             vss_type=0,
1035             vpn_ascii_id="IPv6-table-2",
1036             is_ipv6=1)
1037
1038         self.pg4.add_stream(p_solicit_vrf1)
1039         self.pg_enable_capture(self.pg_interfaces)
1040         self.pg_start()
1041
1042         rx = self.pg1.get_capture(1)
1043
1044         self.verify_dhcp6_solicit(rx[0], self.pg1,
1045                                   dhcp_solicit_src_vrf1,
1046                                   self.pg4.remote_mac,
1047                                   fib_id=1,
1048                                   oui=4)
1049
1050         self.pg5.add_stream(p_solicit_vrf2)
1051         self.pg_enable_capture(self.pg_interfaces)
1052         self.pg_start()
1053
1054         rx = self.pg2.get_capture(1)
1055
1056         self.verify_dhcp6_solicit(rx[0], self.pg2,
1057                                   dhcp_solicit_src_vrf2,
1058                                   self.pg5.remote_mac,
1059                                   vpn_id="IPv6-table-2")
1060
1061         #
1062         # Remove the VSS config
1063         #  relayed DHCP has default vlaues in the option.
1064         #
1065         self.vapi.dhcp_proxy_set_vss(1, is_ipv6=1, is_add=0)
1066
1067         self.pg4.add_stream(p_solicit_vrf1)
1068         self.pg_enable_capture(self.pg_interfaces)
1069         self.pg_start()
1070
1071         rx = self.pg1.get_capture(1)
1072
1073         self.verify_dhcp6_solicit(rx[0], self.pg1,
1074                                   dhcp_solicit_src_vrf1,
1075                                   self.pg4.remote_mac)
1076
1077         #
1078         # Add a second DHCP server in VRF 1
1079         #  expect clients messages to be relay to both configured servers
1080         #
1081         self.pg1.generate_remote_hosts(2)
1082         server_addr12 = self.pg1.remote_hosts[1].ip6
1083
1084         Proxy12 = VppDHCPProxy(
1085             self,
1086             server_addr12,
1087             src_addr_vrf1,
1088             rx_vrf_id=1,
1089             server_vrf_id=1)
1090         Proxy12.add_vpp_config()
1091
1092         #
1093         # We'll need an ND entry for the server to send it packets
1094         #
1095         nd_entry = VppNeighbor(self,
1096                                self.pg1.sw_if_index,
1097                                self.pg1.remote_hosts[1].mac,
1098                                self.pg1.remote_hosts[1].ip6)
1099         nd_entry.add_vpp_config()
1100
1101         #
1102         # Send a discover from the client. expect two relayed messages
1103         # The frist packet is sent to the second server
1104         # We're not enforcing that here, it's just the way it is.
1105         #
1106         self.pg4.add_stream(p_solicit_vrf1)
1107         self.pg_enable_capture(self.pg_interfaces)
1108         self.pg_start()
1109
1110         rx = self.pg1.get_capture(2)
1111
1112         self.verify_dhcp6_solicit(rx[0], self.pg1,
1113                                   dhcp_solicit_src_vrf1,
1114                                   self.pg4.remote_mac)
1115         self.verify_dhcp6_solicit(rx[1], self.pg1,
1116                                   dhcp_solicit_src_vrf1,
1117                                   self.pg4.remote_mac,
1118                                   dst_mac=self.pg1.remote_hosts[1].mac,
1119                                   dst_ip=self.pg1.remote_hosts[1].ip6)
1120
1121         #
1122         # Send both packets back. Client gets both.
1123         #
1124         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1125               IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1126               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1127               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1128               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1129               DHCP6OptRelayMsg(optlen=0) /
1130               DHCP6_Advertise(trid=1) /
1131               DHCP6OptStatusCode(statuscode=0))
1132         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1133               IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
1134               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1135               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1136               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1137               DHCP6OptRelayMsg(optlen=0) /
1138               DHCP6_Advertise(trid=1) /
1139               DHCP6OptStatusCode(statuscode=0))
1140
1141         pkts = [p1, p2]
1142
1143         self.pg1.add_stream(pkts)
1144         self.pg_enable_capture(self.pg_interfaces)
1145         self.pg_start()
1146
1147         rx = self.pg4.get_capture(2)
1148
1149         self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1150         self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1)
1151
1152         #
1153         # Ensure only solicit messages are duplicated
1154         #
1155         p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
1156                           IPv6(src=dhcp_solicit_src_vrf1,
1157                                dst=dhcp_solicit_dst) /
1158                           UDP(sport=DHCP6_SERVER_PORT,
1159                               dport=DHCP6_CLIENT_PORT) /
1160                           DHCP6_Request())
1161
1162         self.pg4.add_stream(p_request_vrf1)
1163         self.pg_enable_capture(self.pg_interfaces)
1164         self.pg_start()
1165
1166         rx = self.pg1.get_capture(1)
1167
1168         #
1169         # Test we drop DHCP packets from addresses that are not configured as
1170         # DHCP servers
1171         #
1172         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1173               IPv6(dst=self.pg1.local_ip6, src="3001::1") /
1174               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1175               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1176               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1177               DHCP6OptRelayMsg(optlen=0) /
1178               DHCP6_Advertise(trid=1) /
1179               DHCP6OptStatusCode(statuscode=0))
1180         self.send_and_assert_no_replies(self.pg1, p2,
1181                                         "DHCP6 not from server")
1182
1183         #
1184         # Remove the second DHCP server
1185         #
1186         Proxy12.remove_vpp_config()
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         Proxy.remove_vpp_config()
1205         Proxy1.remove_vpp_config()
1206         Proxy2.remove_vpp_config()
1207
1208         self.pg3.unconfig_ip6()
1209         self.pg4.unconfig_ip6()
1210         self.pg5.unconfig_ip6()
1211
1212     def test_dhcp_client(self):
1213         """ DHCP Client"""
1214
1215         vdscp = VppEnum.vl_api_ip_dscp_t
1216         hostname = 'universal-dp'
1217
1218         self.pg_enable_capture(self.pg_interfaces)
1219
1220         #
1221         # Configure DHCP client on PG3 and capture the discover sent
1222         #
1223         Client = VppDHCPClient(self, self.pg3.sw_if_index, hostname)
1224         Client.add_vpp_config()
1225         self.assertTrue(Client.query_vpp_config())
1226
1227         rx = self.pg3.get_capture(1)
1228
1229         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1230
1231         #
1232         # Send back on offer, expect the request
1233         #
1234         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1235                    IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1236                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1237                    BOOTP(op=1,
1238                          yiaddr=self.pg3.local_ip4,
1239                          chaddr=mac_pton(self.pg3.local_mac)) /
1240                    DHCP(options=[('message-type', 'offer'),
1241                                  ('server_id', self.pg3.remote_ip4),
1242                                  'end']))
1243
1244         self.pg3.add_stream(p_offer)
1245         self.pg_enable_capture(self.pg_interfaces)
1246         self.pg_start()
1247
1248         rx = self.pg3.get_capture(1)
1249         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1250                                       self.pg3.local_ip4)
1251
1252         #
1253         # Send an acknowledgment
1254         #
1255         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1256                  IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1257                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1258                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1259                        chaddr=mac_pton(self.pg3.local_mac)) /
1260                  DHCP(options=[('message-type', 'ack'),
1261                                ('subnet_mask', "255.255.255.0"),
1262                                ('router', self.pg3.remote_ip4),
1263                                ('server_id', self.pg3.remote_ip4),
1264                                ('lease_time', 43200),
1265                                'end']))
1266
1267         self.pg3.add_stream(p_ack)
1268         self.pg_enable_capture(self.pg_interfaces)
1269         self.pg_start()
1270
1271         #
1272         # We'll get an ARP request for the router address
1273         #
1274         rx = self.pg3.get_capture(1)
1275
1276         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1277         self.pg_enable_capture(self.pg_interfaces)
1278
1279         #
1280         # At the end of this procedure there should be a connected route
1281         # in the FIB
1282         #
1283         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1284         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1285
1286         # remove the left over ARP entry
1287         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1288                                       self.pg3.remote_mac,
1289                                       self.pg3.remote_ip4,
1290                                       is_add=0)
1291
1292         #
1293         # remove the DHCP config
1294         #
1295         Client.remove_vpp_config()
1296
1297         #
1298         # and now the route should be gone
1299         #
1300         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1301         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1302
1303         #
1304         # Start the procedure again. this time have VPP send the client-ID
1305         # and set the DSCP value
1306         #
1307         self.pg3.admin_down()
1308         self.sleep(1)
1309         self.pg3.admin_up()
1310         Client.set_client(self.pg3.sw_if_index, hostname,
1311                           id=self.pg3.local_mac,
1312                           dscp=vdscp.IP_API_DSCP_EF)
1313         Client.add_vpp_config()
1314
1315         rx = self.pg3.get_capture(1)
1316
1317         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1318                                        self.pg3.local_mac,
1319                                        dscp=vdscp.IP_API_DSCP_EF)
1320
1321         # TODO: VPP DHCP client should not accept DHCP OFFER message with
1322         # the XID (Transaction ID) not matching the XID of the most recent
1323         # DHCP DISCOVERY message.
1324         # Such DHCP OFFER message must be silently discarded - RFC2131.
1325         # Reported in Jira ticket: VPP-99
1326         self.pg3.add_stream(p_offer)
1327         self.pg_enable_capture(self.pg_interfaces)
1328         self.pg_start()
1329
1330         rx = self.pg3.get_capture(1)
1331         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1332                                       self.pg3.local_ip4,
1333                                       dscp=vdscp.IP_API_DSCP_EF)
1334
1335         #
1336         # unicast the ack to the offered address
1337         #
1338         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1339                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1340                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1341                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1342                        chaddr=mac_pton(self.pg3.local_mac)) /
1343                  DHCP(options=[('message-type', 'ack'),
1344                                ('subnet_mask', "255.255.255.0"),
1345                                ('router', self.pg3.remote_ip4),
1346                                ('server_id', self.pg3.remote_ip4),
1347                                ('lease_time', 43200),
1348                                'end']))
1349
1350         self.pg3.add_stream(p_ack)
1351         self.pg_enable_capture(self.pg_interfaces)
1352         self.pg_start()
1353
1354         #
1355         # We'll get an ARP request for the router address
1356         #
1357         rx = self.pg3.get_capture(1)
1358
1359         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1360         self.pg_enable_capture(self.pg_interfaces)
1361
1362         #
1363         # At the end of this procedure there should be a connected route
1364         # in the FIB
1365         #
1366         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1367         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1368
1369         #
1370         # remove the DHCP config
1371         #
1372         Client.remove_vpp_config()
1373
1374         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1375         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1376
1377         #
1378         # Rince and repeat, this time with VPP configured not to set
1379         # the braodcast flag in the discover and request messages,
1380         # and for the server to unicast the responses.
1381         #
1382         # Configure DHCP client on PG3 and capture the discover sent
1383         #
1384         Client.set_client(
1385             self.pg3.sw_if_index,
1386             hostname,
1387             set_broadcast_flag=False)
1388         Client.add_vpp_config()
1389
1390         rx = self.pg3.get_capture(1)
1391
1392         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1393                                        broadcast=False)
1394
1395         #
1396         # Send back on offer, unicasted to the offered address.
1397         # Expect the request.
1398         #
1399         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1400                    IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1401                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1402                    BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1403                          chaddr=mac_pton(self.pg3.local_mac)) /
1404                    DHCP(options=[('message-type', 'offer'),
1405                                  ('server_id', self.pg3.remote_ip4),
1406                                  'end']))
1407
1408         self.pg3.add_stream(p_offer)
1409         self.pg_enable_capture(self.pg_interfaces)
1410         self.pg_start()
1411
1412         rx = self.pg3.get_capture(1)
1413         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1414                                       self.pg3.local_ip4,
1415                                       broadcast=False)
1416
1417         #
1418         # Send an acknowledgment, the lease renewal time is 2 seconds
1419         # so we should expect the renew straight after
1420         #
1421         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1422                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1423                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1424                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1425                        chaddr=mac_pton(self.pg3.local_mac)) /
1426                  DHCP(options=[('message-type', 'ack'),
1427                                ('subnet_mask', "255.255.255.0"),
1428                                ('router', self.pg3.remote_ip4),
1429                                ('server_id', self.pg3.remote_ip4),
1430                                ('lease_time', 43200),
1431                                ('renewal_time', 2),
1432                                'end']))
1433
1434         self.pg3.add_stream(p_ack)
1435         self.pg_enable_capture(self.pg_interfaces)
1436         self.pg_start()
1437
1438         #
1439         # We'll get an ARP request for the router address
1440         #
1441         rx = self.pg3.get_capture(1)
1442
1443         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1444         self.pg_enable_capture(self.pg_interfaces)
1445
1446         #
1447         # At the end of this procedure there should be a connected route
1448         # in the FIB
1449         #
1450         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1451         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1452
1453         #
1454         # wait for the unicasted renewal
1455         #  the first attempt will be an ARP packet, since we have not yet
1456         #  responded to VPP's request
1457         #
1458         self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose"))
1459         rx = self.pg3.get_capture(1, timeout=10)
1460
1461         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1462
1463         # respond to the arp
1464         p_arp = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1465                  ARP(op="is-at",
1466                      hwdst=self.pg3.local_mac,
1467                      hwsrc=self.pg3.remote_mac,
1468                      pdst=self.pg3.local_ip4,
1469                      psrc=self.pg3.remote_ip4))
1470         self.pg3.add_stream(p_arp)
1471         self.pg_enable_capture(self.pg_interfaces)
1472         self.pg_start()
1473
1474         # the next packet is the unicasted renewal
1475         rx = self.pg3.get_capture(1, timeout=10)
1476         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1477                                       self.pg3.local_ip4,
1478                                       l2_bc=False,
1479                                       broadcast=False)
1480
1481         #
1482         # read the DHCP client details from a dump
1483         #
1484         clients = self.vapi.dhcp_client_dump()
1485
1486         self.assertEqual(clients[0].client.sw_if_index,
1487                          self.pg3.sw_if_index)
1488         self.assertEqual(clients[0].lease.sw_if_index,
1489                          self.pg3.sw_if_index)
1490         self.assertEqual(clients[0].client.hostname.rstrip('\0'),
1491                          hostname)
1492         self.assertEqual(clients[0].lease.hostname.rstrip('\0'),
1493                          hostname)
1494         # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1495         self.assertEqual(clients[0].lease.state, 2)
1496         self.assertEqual(clients[0].lease.mask_width, 24)
1497         self.assertEqual(str(clients[0].lease.router_address),
1498                          self.pg3.remote_ip4)
1499         self.assertEqual(str(clients[0].lease.host_address),
1500                          self.pg3.local_ip4)
1501
1502         # remove the left over ARP entry
1503         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1504                                       self.pg3.remote_mac,
1505                                       self.pg3.remote_ip4,
1506                                       is_add=0)
1507
1508         #
1509         # remove the DHCP config
1510         #
1511         Client.remove_vpp_config()
1512
1513         #
1514         # and now the route should be gone
1515         #
1516         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1517         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1518
1519         #
1520         # Start the procedure again. Use requested lease time option.
1521         #
1522         hostname += "-2"
1523         self.pg3.admin_down()
1524         self.sleep(1)
1525         self.pg3.admin_up()
1526         self.pg_enable_capture(self.pg_interfaces)
1527         Client.set_client(self.pg3.sw_if_index, hostname)
1528         Client.add_vpp_config()
1529
1530         rx = self.pg3.get_capture(1)
1531
1532         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1533
1534         #
1535         # Send back on offer with requested lease time, expect the request
1536         #
1537         lease_time = 1
1538         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1539                    IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1540                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1541                    BOOTP(op=1,
1542                          yiaddr=self.pg3.local_ip4,
1543                          chaddr=mac_pton(self.pg3.local_mac)) /
1544                    DHCP(options=[('message-type', 'offer'),
1545                                  ('server_id', self.pg3.remote_ip4),
1546                                  ('lease_time', lease_time),
1547                                  'end']))
1548
1549         self.pg3.add_stream(p_offer)
1550         self.pg_enable_capture(self.pg_interfaces)
1551         self.pg_start()
1552
1553         rx = self.pg3.get_capture(1)
1554         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1555                                       self.pg3.local_ip4)
1556
1557         #
1558         # Send an acknowledgment
1559         #
1560         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1561                  IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1562                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1563                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1564                        chaddr=mac_pton(self.pg3.local_mac)) /
1565                  DHCP(options=[('message-type', 'ack'),
1566                                ('subnet_mask', '255.255.255.0'),
1567                                ('router', self.pg3.remote_ip4),
1568                                ('server_id', self.pg3.remote_ip4),
1569                                ('lease_time', lease_time),
1570                                'end']))
1571
1572         self.pg3.add_stream(p_ack)
1573         self.pg_enable_capture(self.pg_interfaces)
1574         self.pg_start()
1575
1576         #
1577         # We'll get an ARP request for the router address
1578         #
1579         rx = self.pg3.get_capture(1)
1580
1581         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1582
1583         #
1584         # At the end of this procedure there should be a connected route
1585         # in the FIB
1586         #
1587         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1588         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1589
1590         # remove the left over ARP entry
1591         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1592                                       self.pg3.remote_mac,
1593                                       self.pg3.remote_ip4,
1594                                       is_add=0)
1595
1596         #
1597         # the route should be gone after the lease expires
1598         #
1599         self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32))
1600         self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24))
1601
1602         #
1603         # remove the DHCP config
1604         #
1605         Client.remove_vpp_config()
1606
1607     def test_dhcp_client_vlan(self):
1608         """ DHCP Client w/ VLAN"""
1609
1610         vdscp = VppEnum.vl_api_ip_dscp_t
1611         vqos = VppEnum.vl_api_qos_source_t
1612         hostname = 'universal-dp'
1613
1614         self.pg_enable_capture(self.pg_interfaces)
1615
1616         vlan_100 = VppDot1QSubint(self, self.pg3, 100)
1617         vlan_100.admin_up()
1618
1619         output = [scapy.compat.chb(4)] * 256
1620         os = b''.join(output)
1621         rows = [{'outputs': os},
1622                 {'outputs': os},
1623                 {'outputs': os},
1624                 {'outputs': os}]
1625
1626         qem1 = VppQosEgressMap(self, 1, rows).add_vpp_config()
1627         qm1 = VppQosMark(self, vlan_100, qem1,
1628                          vqos.QOS_API_SOURCE_VLAN).add_vpp_config()
1629
1630         #
1631         # Configure DHCP client on PG3 and capture the discover sent
1632         #
1633         Client = VppDHCPClient(
1634             self,
1635             vlan_100.sw_if_index,
1636             hostname,
1637             dscp=vdscp.IP_API_DSCP_EF)
1638         Client.add_vpp_config()
1639
1640         rx = self.pg3.get_capture(1)
1641
1642         self.assertEqual(rx[0][Dot1Q].vlan, 100)
1643         self.assertEqual(rx[0][Dot1Q].prio, 2)
1644
1645         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1646                                        dscp=vdscp.IP_API_DSCP_EF)
1647
1648
1649 if __name__ == '__main__':
1650     unittest.main(testRunner=VppTestRunner)