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