7 from framework import VppTestCase
8 from asfframework import VppTestRunner, tag_run_solo
9 from vpp_neighbor import VppNeighbor
10 from vpp_ip_route import find_route, VppIpTable
11 from util import mk_ll_addr
13 from scapy.layers.l2 import Ether, ARP, Dot1Q
14 from scapy.layers.inet import IP, UDP
15 from scapy.layers.inet6 import IPv6, in6_getnsmac
16 from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
17 from scapy.layers.dhcp6 import (
26 DHCP6OptClientLinkLayerAddr,
29 from socket import AF_INET, AF_INET6, inet_pton
30 from scapy.utils6 import in6_ptop
31 from vpp_papi import mac_pton, VppEnum
32 from vpp_sub_interface import VppDot1QSubint
33 from vpp_qos import VppQosEgressMap, VppQosMark
34 from vpp_dhcp import VppDHCPClient, VppDHCPProxy
37 DHCP4_CLIENT_PORT = 68
38 DHCP4_SERVER_PORT = 67
39 DHCP6_CLIENT_PORT = 547
40 DHCP6_SERVER_PORT = 546
44 class TestDHCP(VppTestCase):
49 super(TestDHCP, cls).setUpClass()
52 def tearDownClass(cls):
53 super(TestDHCP, cls).tearDownClass()
56 super(TestDHCP, self).setUp()
58 # create 6 pg interfaces for pg0 to pg5
59 self.create_pg_interfaces(range(6))
62 # pg0 to 2 are IP configured in VRF 0, 1 and 2.
63 # pg3 to 5 are non IP-configured in VRF 0, 1 and 2.
65 for table_id in range(1, 4):
66 tbl4 = VppIpTable(self, table_id)
68 self.tables.append(tbl4)
69 tbl6 = VppIpTable(self, table_id, is_ip6=1)
71 self.tables.append(tbl6)
74 for i in self.pg_interfaces[:3]:
76 i.set_table_ip4(table_id)
77 i.set_table_ip6(table_id)
85 for i in self.pg_interfaces[3:]:
87 i.set_table_ip4(table_id)
88 i.set_table_ip6(table_id)
92 for i in self.pg_interfaces[:3]:
96 for i in self.pg_interfaces:
100 super(TestDHCP, self).tearDown()
102 def verify_dhcp_has_option(self, pkt, option, value):
106 for i in dhcp.options:
107 if isinstance(i, tuple):
109 self.assertEqual(i[1], value)
112 self.assertTrue(found)
114 def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui):
120 for i in dhcp.options:
121 if isinstance(i, tuple):
122 if i[0] == "relay_agent_Information":
124 # There are two sb-options present - each of length 6.
128 self.assertEqual(len(data), 24)
129 elif len(vpn_id) > 0:
130 self.assertEqual(len(data), len(vpn_id) + 17)
132 self.assertEqual(len(data), 12)
135 # First sub-option is ID 1, len 4, then encoded
136 # sw_if_index. This test uses low valued indicies
138 # The ID space is VPP internal - so no matching value
141 self.assertEqual(six.byte2int(data[0:1]), 1)
142 self.assertEqual(six.byte2int(data[1:2]), 4)
143 self.assertEqual(six.byte2int(data[2:3]), 0)
144 self.assertEqual(six.byte2int(data[3:4]), 0)
145 self.assertEqual(six.byte2int(data[4:5]), 0)
146 self.assertEqual(six.byte2int(data[5:6]), intf._sw_if_index)
149 # next sub-option is the IP address of the client side
151 # sub-option ID=5, length (of a v4 address)=4
153 claddr = socket.inet_pton(AF_INET, ip_addr)
155 self.assertEqual(six.byte2int(data[6:7]), 5)
156 self.assertEqual(six.byte2int(data[7:8]), 4)
157 self.assertEqual(data[8], claddr[0])
158 self.assertEqual(data[9], claddr[1])
159 self.assertEqual(data[10], claddr[2])
160 self.assertEqual(data[11], claddr[3])
163 # sub-option 151 encodes vss_type 1,
164 # the 3 byte oui and the 4 byte fib_id
165 self.assertEqual(id_len, 0)
166 self.assertEqual(six.byte2int(data[12:13]), 151)
167 self.assertEqual(six.byte2int(data[13:14]), 8)
168 self.assertEqual(six.byte2int(data[14:15]), 1)
169 self.assertEqual(six.byte2int(data[15:16]), 0)
170 self.assertEqual(six.byte2int(data[16:17]), 0)
171 self.assertEqual(six.byte2int(data[17:18]), oui)
172 self.assertEqual(six.byte2int(data[18:19]), 0)
173 self.assertEqual(six.byte2int(data[19:20]), 0)
174 self.assertEqual(six.byte2int(data[20:21]), 0)
175 self.assertEqual(six.byte2int(data[21:22]), fib_id)
177 # VSS control sub-option
178 self.assertEqual(six.byte2int(data[22:23]), 152)
179 self.assertEqual(six.byte2int(data[23:24]), 0)
182 # sub-option 151 encode vss_type of 0
183 # followerd by vpn_id in ascii
184 self.assertEqual(oui, 0)
185 self.assertEqual(six.byte2int(data[12:13]), 151)
186 self.assertEqual(six.byte2int(data[13:14]), id_len + 1)
187 self.assertEqual(six.byte2int(data[14:15]), 0)
188 self.assertEqual(data[15 : 15 + id_len].decode("ascii"), vpn_id)
190 # VSS control sub-option
192 six.byte2int(data[15 + len(vpn_id) : 16 + len(vpn_id)]), 152
195 six.byte2int(data[16 + len(vpn_id) : 17 + len(vpn_id)]), 0
199 self.assertTrue(found)
203 def verify_dhcp_msg_type(self, pkt, name):
206 for o in dhcp.options:
207 if isinstance(o, tuple):
208 if o[0] == "message-type" and DHCPTypes[o[1]] == name:
210 self.assertTrue(found)
212 def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0):
214 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
215 self.assertEqual(ether.src, intf.local_mac)
218 self.assertEqual(ip.dst, "255.255.255.255")
219 self.assertEqual(ip.src, intf.local_ip4)
222 self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
223 self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
225 self.verify_dhcp_msg_type(pkt, "offer")
226 data = self.validate_relay_options(
227 pkt, intf, intf.local_ip4, vpn_id, fib_id, oui
230 def verify_orig_dhcp_pkt(self, pkt, intf, dscp, l2_bc=True):
233 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
235 self.assertEqual(ether.dst, intf.remote_mac)
236 self.assertEqual(ether.src, intf.local_mac)
241 self.assertEqual(ip.dst, "255.255.255.255")
242 self.assertEqual(ip.src, "0.0.0.0")
244 self.assertEqual(ip.dst, intf.remote_ip4)
245 self.assertEqual(ip.src, intf.local_ip4)
246 self.assertEqual(ip.tos, dscp)
249 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
250 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
252 def verify_orig_dhcp_discover(
253 self, pkt, intf, hostname, client_id=None, broadcast=True, dscp=0
255 self.verify_orig_dhcp_pkt(pkt, intf, dscp)
257 self.verify_dhcp_msg_type(pkt, "discover")
258 self.verify_dhcp_has_option(pkt, "hostname", hostname.encode("ascii"))
260 client_id = "\x00" + client_id
261 self.verify_dhcp_has_option(pkt, "client_id", client_id.encode("ascii"))
263 self.assertEqual(bootp.ciaddr, "0.0.0.0")
264 self.assertEqual(bootp.giaddr, "0.0.0.0")
266 self.assertEqual(bootp.flags, 0x8000)
268 self.assertEqual(bootp.flags, 0x0000)
270 def verify_orig_dhcp_request(
271 self, pkt, intf, hostname, ip, broadcast=True, l2_bc=True, dscp=0
273 self.verify_orig_dhcp_pkt(pkt, intf, dscp, l2_bc=l2_bc)
275 self.verify_dhcp_msg_type(pkt, "request")
276 self.verify_dhcp_has_option(pkt, "hostname", hostname.encode("ascii"))
277 self.verify_dhcp_has_option(pkt, "requested_addr", ip)
281 self.assertEqual(bootp.ciaddr, "0.0.0.0")
283 self.assertEqual(bootp.ciaddr, intf.local_ip4)
284 self.assertEqual(bootp.giaddr, "0.0.0.0")
287 self.assertEqual(bootp.flags, 0x8000)
289 self.assertEqual(bootp.flags, 0x0000)
291 def verify_relayed_dhcp_discover(
303 dst_mac = intf.remote_mac
305 dst_ip = intf.remote_ip4
308 self.assertEqual(ether.dst, dst_mac)
309 self.assertEqual(ether.src, intf.local_mac)
312 self.assertEqual(ip.dst, dst_ip)
313 self.assertEqual(ip.src, intf.local_ip4)
316 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
317 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
322 for o in dhcp.options:
323 if isinstance(o, tuple):
324 if o[0] == "message-type" and DHCPTypes[o[1]] == "discover":
326 self.assertTrue(is_discover)
328 data = self.validate_relay_options(
329 pkt, src_intf, src_intf.local_ip4, vpn_id, fib_id, oui
333 def verify_dhcp6_solicit(
346 dst_mac = intf.remote_mac
348 dst_ip = in6_ptop(intf.remote_ip6)
351 self.assertEqual(ether.dst, dst_mac)
352 self.assertEqual(ether.src, intf.local_mac)
355 self.assertEqual(in6_ptop(ip.dst), dst_ip)
356 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
359 self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
360 self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
362 relay = pkt[DHCP6_RelayForward]
363 self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
364 oid = pkt[DHCP6OptIfaceId]
365 cll = pkt[DHCP6OptClientLinkLayerAddr]
366 self.assertEqual(cll.optlen, 8)
367 self.assertEqual(cll.lltype, 1)
368 self.assertEqual(cll.clladdr, peer_mac)
373 self.assertEqual(id_len, 0)
374 vss = pkt[DHCP6OptVSS]
375 self.assertEqual(vss.optlen, 8)
376 self.assertEqual(vss.type, 1)
377 # the OUI and FIB-id are really 3 and 4 bytes resp.
378 # but the tested range is small
379 self.assertEqual(six.byte2int(vss.data[0:1]), 0)
380 self.assertEqual(six.byte2int(vss.data[1:2]), 0)
381 self.assertEqual(six.byte2int(vss.data[2:3]), oui)
382 self.assertEqual(six.byte2int(vss.data[3:4]), 0)
383 self.assertEqual(six.byte2int(vss.data[4:5]), 0)
384 self.assertEqual(six.byte2int(vss.data[5:6]), 0)
385 self.assertEqual(six.byte2int(vss.data[6:7]), fib_id)
388 self.assertEqual(oui, 0)
389 vss = pkt[DHCP6OptVSS]
390 self.assertEqual(vss.optlen, id_len + 1)
391 self.assertEqual(vss.type, 0)
392 self.assertEqual(vss.data[0:id_len].decode("ascii"), vpn_id)
394 # the relay message should be an encoded Solicit
395 msg = pkt[DHCP6OptRelayMsg]
396 sol = DHCP6_Solicit()
397 self.assertEqual(msg.optlen, len(sol))
398 self.assertEqual(sol, msg[1])
400 def verify_dhcp6_advert(self, pkt, intf, peer):
402 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
403 self.assertEqual(ether.src, intf.local_mac)
406 self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
407 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
410 self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
411 self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
413 # not sure why this is not decoding
414 # adv = pkt[DHCP6_Advertise]
416 def wait_for_no_route(self, address, length, n_tries=50, s_time=1):
418 if not find_route(self, address, length):
420 n_tries = n_tries - 1
425 def test_dhcp_proxy(self):
429 # Verify no response to DHCP request without DHCP config
432 Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg3.remote_mac)
433 / IP(src="0.0.0.0", dst="255.255.255.255")
434 / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT)
436 / DHCP(options=[("message-type", "discover"), ("end")])
438 pkts_disc_vrf0 = [p_disc_vrf0]
440 Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg4.remote_mac)
441 / IP(src="0.0.0.0", dst="255.255.255.255")
442 / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT)
444 / DHCP(options=[("message-type", "discover"), ("end")])
446 pkts_disc_vrf1 = [p_disc_vrf1]
448 Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg5.remote_mac)
449 / IP(src="0.0.0.0", dst="255.255.255.255")
450 / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT)
452 / DHCP(options=[("message-type", "discover"), ("end")])
454 pkts_disc_vrf2 = [p_disc_vrf2]
456 self.send_and_assert_no_replies(
457 self.pg3, pkts_disc_vrf0, "DHCP with no configuration"
459 self.send_and_assert_no_replies(
460 self.pg4, pkts_disc_vrf1, "DHCP with no configuration"
462 self.send_and_assert_no_replies(
463 self.pg5, pkts_disc_vrf2, "DHCP with no configuration"
467 # Enable DHCP proxy in VRF 0
469 server_addr = self.pg0.remote_ip4
470 src_addr = self.pg0.local_ip4
472 Proxy = VppDHCPProxy(self, server_addr, src_addr, rx_vrf_id=0)
473 Proxy.add_vpp_config()
476 # Discover packets from the client are dropped because there is no
477 # IP address configured on the client facing interface
479 self.send_and_assert_no_replies(
480 self.pg3, pkts_disc_vrf0, "Discover DHCP no relay address"
484 # Inject a response from the server
485 # dropped, because there is no IP addrees on the
486 # client interfce to fill in the option.
489 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
490 / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4)
491 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
493 / DHCP(options=[("message-type", "offer"), ("end")])
497 self.send_and_assert_no_replies(self.pg3, pkts, "Offer DHCP no relay address")
500 # configure an IP address on the client facing interface
502 self.pg3.config_ip4()
505 # Try again with a discover packet
506 # Rx'd packet should be to the server address and from the configured
508 # UDP source ports are unchanged
509 # we've no option 82 config so that should be absent
511 self.pg3.add_stream(pkts_disc_vrf0)
512 self.pg_enable_capture(self.pg_interfaces)
515 rx = self.pg0.get_capture(1)
518 option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0, src_intf=self.pg3)
521 # Create an DHCP offer reply from the server with a correctly formatted
522 # option 82. i.e. send back what we just captured
523 # The offer, sent mcast to the client, still has option 82.
526 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
527 / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4)
528 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
532 ("message-type", "offer"),
533 ("relay_agent_Information", option_82),
540 self.pg0.add_stream(pkts)
541 self.pg_enable_capture(self.pg_interfaces)
544 rx = self.pg3.get_capture(1)
547 self.verify_dhcp_offer(rx, self.pg3)
552 # 1. not our IP address = not checked by VPP? so offer is replayed
554 bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:]
557 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
558 / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4)
559 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
563 ("message-type", "offer"),
564 ("relay_agent_Information", bad_ip),
570 self.send_and_assert_no_replies(
571 self.pg0, pkts, "DHCP offer option 82 bad address"
574 # 2. Not a sw_if_index VPP knows
575 bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:]
578 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
579 / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4)
580 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
584 ("message-type", "offer"),
585 ("relay_agent_Information", bad_if_index),
591 self.send_and_assert_no_replies(
592 self.pg0, pkts, "DHCP offer option 82 bad if index"
596 # Send a DHCP request in VRF 1. should be dropped.
598 self.send_and_assert_no_replies(
599 self.pg4, pkts_disc_vrf1, "DHCP with no configuration VRF 1"
603 # Delete the DHCP config in VRF 0
604 # Should now drop requests.
606 Proxy.remove_vpp_config()
608 self.send_and_assert_no_replies(
609 self.pg3, pkts_disc_vrf0, "DHCP config removed VRF 0"
611 self.send_and_assert_no_replies(
612 self.pg4, pkts_disc_vrf1, "DHCP config removed VRF 1"
616 # Add DHCP config for VRF 1 & 2
618 server_addr1 = self.pg1.remote_ip4
619 src_addr1 = self.pg1.local_ip4
620 Proxy1 = VppDHCPProxy(
621 self, server_addr1, src_addr1, rx_vrf_id=1, server_vrf_id=1
623 Proxy1.add_vpp_config()
625 server_addr2 = self.pg2.remote_ip4
626 src_addr2 = self.pg2.local_ip4
627 Proxy2 = VppDHCPProxy(
628 self, server_addr2, src_addr2, rx_vrf_id=2, server_vrf_id=2
630 Proxy2.add_vpp_config()
633 # Confim DHCP requests ok in VRF 1 & 2.
634 # - dropped on IP config on client interface
636 self.send_and_assert_no_replies(
637 self.pg4, pkts_disc_vrf1, "DHCP config removed VRF 1"
639 self.send_and_assert_no_replies(
640 self.pg5, pkts_disc_vrf2, "DHCP config removed VRF 2"
644 # configure an IP address on the client facing interface
646 self.pg4.config_ip4()
647 self.pg4.add_stream(pkts_disc_vrf1)
648 self.pg_enable_capture(self.pg_interfaces)
650 rx = self.pg1.get_capture(1)
652 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
654 self.pg5.config_ip4()
655 self.pg5.add_stream(pkts_disc_vrf2)
656 self.pg_enable_capture(self.pg_interfaces)
658 rx = self.pg2.get_capture(1)
660 self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5)
664 # table=1, vss_type=1, vpn_index=1, oui=4
665 # table=2, vss_type=0, vpn_id = "ip4-table-2"
666 self.vapi.dhcp_proxy_set_vss(tbl_id=1, vss_type=1, vpn_index=1, oui=4, is_add=1)
667 self.vapi.dhcp_proxy_set_vss(
668 tbl_id=2, vss_type=0, vpn_ascii_id="ip4-table-2", is_add=1
671 self.pg4.add_stream(pkts_disc_vrf1)
672 self.pg_enable_capture(self.pg_interfaces)
675 rx = self.pg1.get_capture(1)
677 self.verify_relayed_dhcp_discover(
678 rx, self.pg1, src_intf=self.pg4, fib_id=1, oui=4
681 self.pg5.add_stream(pkts_disc_vrf2)
682 self.pg_enable_capture(self.pg_interfaces)
685 rx = self.pg2.get_capture(1)
687 self.verify_relayed_dhcp_discover(
688 rx, self.pg2, src_intf=self.pg5, vpn_id="ip4-table-2"
692 # Add a second DHCP server in VRF 1
693 # expect clients messages to be relay to both configured servers
695 self.pg1.generate_remote_hosts(2)
696 server_addr12 = self.pg1.remote_hosts[1].ip4
698 Proxy12 = VppDHCPProxy(
699 self, server_addr12, src_addr, rx_vrf_id=1, server_vrf_id=1
701 Proxy12.add_vpp_config()
704 # We'll need an ARP entry for the server to send it packets
706 arp_entry = VppNeighbor(
708 self.pg1.sw_if_index,
709 self.pg1.remote_hosts[1].mac,
710 self.pg1.remote_hosts[1].ip4,
712 arp_entry.add_vpp_config()
715 # Send a discover from the client. expect two relayed messages
716 # The frist packet is sent to the second server
717 # We're not enforcing that here, it's just the way it is.
719 self.pg4.add_stream(pkts_disc_vrf1)
720 self.pg_enable_capture(self.pg_interfaces)
723 rx = self.pg1.get_capture(2)
725 option_82 = self.verify_relayed_dhcp_discover(
729 dst_mac=self.pg1.remote_hosts[1].mac,
730 dst_ip=self.pg1.remote_hosts[1].ip4,
734 self.verify_relayed_dhcp_discover(
735 rx[1], self.pg1, src_intf=self.pg4, fib_id=1, oui=4
739 # Send both packets back. Client gets both.
742 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
743 / IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4)
744 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
748 ("message-type", "offer"),
749 ("relay_agent_Information", option_82),
755 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
756 / IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4)
757 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
761 ("message-type", "offer"),
762 ("relay_agent_Information", option_82),
769 self.pg1.add_stream(pkts)
770 self.pg_enable_capture(self.pg_interfaces)
773 rx = self.pg4.get_capture(2)
775 self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4)
776 self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4)
779 # Ensure offers from non-servers are dropeed
782 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
783 / IP(src="8.8.8.8", dst=self.pg1.local_ip4)
784 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT)
788 ("message-type", "offer"),
789 ("relay_agent_Information", option_82),
794 self.send_and_assert_no_replies(self.pg1, p2, "DHCP offer from non-server")
797 # Ensure only the discover is sent to multiple servers
800 Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg4.remote_mac)
801 / IP(src="0.0.0.0", dst="255.255.255.255")
802 / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT)
804 / DHCP(options=[("message-type", "request"), ("end")])
807 self.pg4.add_stream(p_req_vrf1)
808 self.pg_enable_capture(self.pg_interfaces)
811 rx = self.pg1.get_capture(1)
814 # Remove the second DHCP server
816 Proxy12.remove_vpp_config()
819 # Test we can still relay with the first
821 self.pg4.add_stream(pkts_disc_vrf1)
822 self.pg_enable_capture(self.pg_interfaces)
825 rx = self.pg1.get_capture(1)
827 self.verify_relayed_dhcp_discover(
828 rx, self.pg1, src_intf=self.pg4, fib_id=1, oui=4
832 # Remove the VSS config
833 # relayed DHCP has default vlaues in the option.
835 self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_add=0)
836 self.vapi.dhcp_proxy_set_vss(tbl_id=2, is_add=0)
838 self.pg4.add_stream(pkts_disc_vrf1)
839 self.pg_enable_capture(self.pg_interfaces)
842 rx = self.pg1.get_capture(1)
844 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
847 # remove DHCP config to cleanup
849 Proxy1.remove_vpp_config()
850 Proxy2.remove_vpp_config()
852 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, "DHCP cleanup VRF 0")
853 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, "DHCP cleanup VRF 1")
854 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, "DHCP cleanup VRF 2")
856 self.pg3.unconfig_ip4()
857 self.pg4.unconfig_ip4()
858 self.pg5.unconfig_ip4()
860 def test_dhcp6_proxy(self):
863 # Verify no response to DHCP request without DHCP config
865 dhcp_solicit_dst = "ff02::1:2"
866 dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac)
867 dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac)
868 dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac)
869 server_addr_vrf0 = self.pg0.remote_ip6
870 src_addr_vrf0 = self.pg0.local_ip6
871 server_addr_vrf1 = self.pg1.remote_ip6
872 src_addr_vrf1 = self.pg1.local_ip6
873 server_addr_vrf2 = self.pg2.remote_ip6
874 src_addr_vrf2 = self.pg2.local_ip6
876 dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
878 Ether(dst=dmac, src=self.pg3.remote_mac)
879 / IPv6(src=dhcp_solicit_src_vrf0, dst=dhcp_solicit_dst)
880 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT)
884 Ether(dst=dmac, src=self.pg4.remote_mac)
885 / IPv6(src=dhcp_solicit_src_vrf1, dst=dhcp_solicit_dst)
886 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT)
890 Ether(dst=dmac, src=self.pg5.remote_mac)
891 / IPv6(src=dhcp_solicit_src_vrf2, dst=dhcp_solicit_dst)
892 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT)
896 self.send_and_assert_no_replies(
897 self.pg3, p_solicit_vrf0, "DHCP with no configuration"
899 self.send_and_assert_no_replies(
900 self.pg4, p_solicit_vrf1, "DHCP with no configuration"
902 self.send_and_assert_no_replies(
903 self.pg5, p_solicit_vrf2, "DHCP with no configuration"
907 # DHCPv6 config in VRF 0.
908 # Packets still dropped because the client facing interface has no
911 Proxy = VppDHCPProxy(
912 self, server_addr_vrf0, src_addr_vrf0, rx_vrf_id=0, server_vrf_id=0
914 Proxy.add_vpp_config()
916 self.send_and_assert_no_replies(
917 self.pg3, p_solicit_vrf0, "DHCP with no configuration"
919 self.send_and_assert_no_replies(
920 self.pg4, p_solicit_vrf1, "DHCP with no configuration"
924 # configure an IP address on the client facing interface
926 self.pg3.config_ip6()
929 # Now the DHCP requests are relayed to the server
931 self.pg3.add_stream(p_solicit_vrf0)
932 self.pg_enable_capture(self.pg_interfaces)
935 rx = self.pg0.get_capture(1)
937 self.verify_dhcp6_solicit(
938 rx[0], self.pg0, dhcp_solicit_src_vrf0, self.pg3.remote_mac
942 # Exception cases for rejected relay responses
945 # 1 - not a relay reply
947 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
948 / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6)
949 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
952 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP6 not a relay reply")
954 # 2 - no relay message option
956 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
957 / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6)
958 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
962 self.send_and_assert_no_replies(
963 self.pg3, p_adv_vrf0, "DHCP not a relay message"
968 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
969 / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6)
970 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
972 / DHCP6OptRelayMsg(optlen=0)
975 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP6 no circuit ID")
976 # 4 - wrong circuit ID
978 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
979 / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6)
980 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
982 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x05")
983 / DHCP6OptRelayMsg(optlen=0)
986 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP6 wrong circuit ID")
989 # Send the relay response (the advertisement)
992 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
993 / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6)
994 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
996 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x04")
997 / DHCP6OptRelayMsg(optlen=0)
998 / DHCP6_Advertise(trid=1)
999 / DHCP6OptStatusCode(statuscode=0)
1001 pkts_adv_vrf0 = [p_adv_vrf0]
1003 self.pg0.add_stream(pkts_adv_vrf0)
1004 self.pg_enable_capture(self.pg_interfaces)
1007 rx = self.pg3.get_capture(1)
1009 self.verify_dhcp6_advert(rx[0], self.pg3, "::")
1012 # Send the relay response (the advertisement)
1013 # - with peer address
1015 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1016 / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6)
1017 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
1018 / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0)
1019 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x04")
1020 / DHCP6OptRelayMsg(optlen=0)
1021 / DHCP6_Advertise(trid=1)
1022 / DHCP6OptStatusCode(statuscode=0)
1024 pkts_adv_vrf0 = [p_adv_vrf0]
1026 self.pg0.add_stream(pkts_adv_vrf0)
1027 self.pg_enable_capture(self.pg_interfaces)
1030 rx = self.pg3.get_capture(1)
1032 self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
1035 # Add all the config for VRF 1 & 2
1037 Proxy1 = VppDHCPProxy(
1038 self, server_addr_vrf1, src_addr_vrf1, rx_vrf_id=1, server_vrf_id=1
1040 Proxy1.add_vpp_config()
1041 self.pg4.config_ip6()
1043 Proxy2 = VppDHCPProxy(
1044 self, server_addr_vrf2, src_addr_vrf2, rx_vrf_id=2, server_vrf_id=2
1046 Proxy2.add_vpp_config()
1047 self.pg5.config_ip6()
1052 self.pg4.add_stream(p_solicit_vrf1)
1053 self.pg_enable_capture(self.pg_interfaces)
1056 rx = self.pg1.get_capture(1)
1058 self.verify_dhcp6_solicit(
1059 rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac
1065 self.pg5.add_stream(p_solicit_vrf2)
1066 self.pg_enable_capture(self.pg_interfaces)
1069 rx = self.pg2.get_capture(1)
1071 self.verify_dhcp6_solicit(
1072 rx[0], self.pg2, dhcp_solicit_src_vrf2, self.pg5.remote_mac
1079 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
1080 / IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6)
1081 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
1082 / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1)
1083 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x05")
1084 / DHCP6OptRelayMsg(optlen=0)
1085 / DHCP6_Advertise(trid=1)
1086 / DHCP6OptStatusCode(statuscode=0)
1088 pkts_adv_vrf1 = [p_adv_vrf1]
1090 self.pg1.add_stream(pkts_adv_vrf1)
1091 self.pg_enable_capture(self.pg_interfaces)
1094 rx = self.pg4.get_capture(1)
1096 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1101 self.vapi.dhcp_proxy_set_vss(
1102 tbl_id=1, vss_type=1, oui=4, vpn_index=1, is_ipv6=1, is_add=1
1104 self.vapi.dhcp_proxy_set_vss(
1105 tbl_id=2, vss_type=0, vpn_ascii_id="IPv6-table-2", is_ipv6=1, is_add=1
1108 self.pg4.add_stream(p_solicit_vrf1)
1109 self.pg_enable_capture(self.pg_interfaces)
1112 rx = self.pg1.get_capture(1)
1114 self.verify_dhcp6_solicit(
1115 rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac, fib_id=1, oui=4
1118 self.pg5.add_stream(p_solicit_vrf2)
1119 self.pg_enable_capture(self.pg_interfaces)
1122 rx = self.pg2.get_capture(1)
1124 self.verify_dhcp6_solicit(
1127 dhcp_solicit_src_vrf2,
1128 self.pg5.remote_mac,
1129 vpn_id="IPv6-table-2",
1133 # Remove the VSS config
1134 # relayed DHCP has default vlaues in the option.
1136 self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_ipv6=1, is_add=0)
1138 self.pg4.add_stream(p_solicit_vrf1)
1139 self.pg_enable_capture(self.pg_interfaces)
1142 rx = self.pg1.get_capture(1)
1144 self.verify_dhcp6_solicit(
1145 rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac
1149 # Add a second DHCP server in VRF 1
1150 # expect clients messages to be relay to both configured servers
1152 self.pg1.generate_remote_hosts(2)
1153 server_addr12 = self.pg1.remote_hosts[1].ip6
1155 Proxy12 = VppDHCPProxy(
1156 self, server_addr12, src_addr_vrf1, rx_vrf_id=1, server_vrf_id=1
1158 Proxy12.add_vpp_config()
1161 # We'll need an ND entry for the server to send it packets
1163 nd_entry = VppNeighbor(
1165 self.pg1.sw_if_index,
1166 self.pg1.remote_hosts[1].mac,
1167 self.pg1.remote_hosts[1].ip6,
1169 nd_entry.add_vpp_config()
1172 # Send a discover from the client. expect two relayed messages
1173 # The frist packet is sent to the second server
1174 # We're not enforcing that here, it's just the way it is.
1176 self.pg4.add_stream(p_solicit_vrf1)
1177 self.pg_enable_capture(self.pg_interfaces)
1180 rx = self.pg1.get_capture(2)
1182 self.verify_dhcp6_solicit(
1183 rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac
1185 self.verify_dhcp6_solicit(
1188 dhcp_solicit_src_vrf1,
1189 self.pg4.remote_mac,
1190 dst_mac=self.pg1.remote_hosts[1].mac,
1191 dst_ip=self.pg1.remote_hosts[1].ip6,
1195 # Send both packets back. Client gets both.
1198 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
1199 / IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6)
1200 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
1201 / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1)
1202 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x05")
1203 / DHCP6OptRelayMsg(optlen=0)
1204 / DHCP6_Advertise(trid=1)
1205 / DHCP6OptStatusCode(statuscode=0)
1208 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac)
1209 / IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6)
1210 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
1211 / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1)
1212 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x05")
1213 / DHCP6OptRelayMsg(optlen=0)
1214 / DHCP6_Advertise(trid=1)
1215 / DHCP6OptStatusCode(statuscode=0)
1220 self.pg1.add_stream(pkts)
1221 self.pg_enable_capture(self.pg_interfaces)
1224 rx = self.pg4.get_capture(2)
1226 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1227 self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1)
1230 # Ensure only solicit messages are duplicated
1233 Ether(dst=dmac, src=self.pg4.remote_mac)
1234 / IPv6(src=dhcp_solicit_src_vrf1, dst=dhcp_solicit_dst)
1235 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT)
1239 self.pg4.add_stream(p_request_vrf1)
1240 self.pg_enable_capture(self.pg_interfaces)
1243 rx = self.pg1.get_capture(1)
1246 # Test we drop DHCP packets from addresses that are not configured as
1250 Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac)
1251 / IPv6(dst=self.pg1.local_ip6, src="3001::1")
1252 / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT)
1253 / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1)
1254 / DHCP6OptIfaceId(optlen=4, ifaceid="\x00\x00\x00\x05")
1255 / DHCP6OptRelayMsg(optlen=0)
1256 / DHCP6_Advertise(trid=1)
1257 / DHCP6OptStatusCode(statuscode=0)
1259 self.send_and_assert_no_replies(self.pg1, p2, "DHCP6 not from server")
1262 # Remove the second DHCP server
1264 Proxy12.remove_vpp_config()
1267 # Test we can still relay with the first
1269 self.pg4.add_stream(p_solicit_vrf1)
1270 self.pg_enable_capture(self.pg_interfaces)
1273 rx = self.pg1.get_capture(1)
1275 self.verify_dhcp6_solicit(
1276 rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac
1282 Proxy.remove_vpp_config()
1283 Proxy1.remove_vpp_config()
1284 Proxy2.remove_vpp_config()
1286 self.pg3.unconfig_ip6()
1287 self.pg4.unconfig_ip6()
1288 self.pg5.unconfig_ip6()
1290 def test_dhcp_client(self):
1293 vdscp = VppEnum.vl_api_ip_dscp_t
1294 hostname = "universal-dp"
1296 self.pg_enable_capture(self.pg_interfaces)
1299 # Configure DHCP client on PG3 and capture the discover sent
1301 Client = VppDHCPClient(self, self.pg3.sw_if_index, hostname)
1302 Client.add_vpp_config()
1303 self.assertTrue(Client.query_vpp_config())
1305 rx = self.pg3.get_capture(1)
1307 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1310 # Send back on offer, expect the request
1313 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1314 / IP(src=self.pg3.remote_ip4, dst="255.255.255.255")
1315 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1317 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1321 ("message-type", "offer"),
1322 ("server_id", self.pg3.remote_ip4),
1328 self.pg3.add_stream(p_offer)
1329 self.pg_enable_capture(self.pg_interfaces)
1332 rx = self.pg3.get_capture(1)
1333 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, self.pg3.local_ip4)
1336 # Send an acknowledgment
1339 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1340 / IP(src=self.pg3.remote_ip4, dst="255.255.255.255")
1341 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1343 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1347 ("message-type", "ack"),
1348 ("subnet_mask", "255.255.255.0"),
1349 ("router", self.pg3.remote_ip4),
1350 ("server_id", self.pg3.remote_ip4),
1351 ("lease_time", 43200),
1357 self.pg3.add_stream(p_ack)
1358 self.pg_enable_capture(self.pg_interfaces)
1362 # We'll get an ARP request for the router address
1364 rx = self.pg3.get_capture(1)
1366 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1367 self.pg_enable_capture(self.pg_interfaces)
1370 # At the end of this procedure there should be a connected route
1373 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1374 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1377 # remove the DHCP config
1379 Client.remove_vpp_config()
1382 # and now the route should be gone
1384 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1385 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1388 # Start the procedure again. this time have VPP send the client-ID
1389 # and set the DSCP value
1391 self.pg3.admin_down()
1395 self.pg3.sw_if_index,
1397 id=self.pg3.local_mac,
1398 dscp=vdscp.IP_API_DSCP_EF,
1400 Client.add_vpp_config()
1402 rx = self.pg3.get_capture(1)
1404 self.verify_orig_dhcp_discover(
1405 rx[0], self.pg3, hostname, self.pg3.local_mac, dscp=vdscp.IP_API_DSCP_EF
1408 # TODO: VPP DHCP client should not accept DHCP OFFER message with
1409 # the XID (Transaction ID) not matching the XID of the most recent
1410 # DHCP DISCOVERY message.
1411 # Such DHCP OFFER message must be silently discarded - RFC2131.
1412 # Reported in Jira ticket: VPP-99
1413 self.pg3.add_stream(p_offer)
1414 self.pg_enable_capture(self.pg_interfaces)
1417 rx = self.pg3.get_capture(1)
1418 self.verify_orig_dhcp_request(
1419 rx[0], self.pg3, hostname, self.pg3.local_ip4, dscp=vdscp.IP_API_DSCP_EF
1423 # unicast the ack to the offered address
1426 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1427 / IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4)
1428 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1430 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1434 ("message-type", "ack"),
1435 ("subnet_mask", "255.255.255.0"),
1436 ("router", self.pg3.remote_ip4),
1437 ("server_id", self.pg3.remote_ip4),
1438 ("lease_time", 43200),
1444 self.pg3.add_stream(p_ack)
1445 self.pg_enable_capture(self.pg_interfaces)
1449 # We'll get an ARP request for the router address
1451 rx = self.pg3.get_capture(1)
1453 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1454 self.pg_enable_capture(self.pg_interfaces)
1457 # At the end of this procedure there should be a connected route
1460 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1461 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1464 # remove the DHCP config
1466 Client.remove_vpp_config()
1468 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1469 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1472 # Rince and repeat, this time with VPP configured not to set
1473 # the braodcast flag in the discover and request messages,
1474 # and for the server to unicast the responses.
1476 # Configure DHCP client on PG3 and capture the discover sent
1478 Client.set_client(self.pg3.sw_if_index, hostname, set_broadcast_flag=False)
1479 Client.add_vpp_config()
1481 rx = self.pg3.get_capture(1)
1483 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, broadcast=False)
1486 # Send back on offer, unicasted to the offered address.
1487 # Expect the request.
1490 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1491 / IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4)
1492 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1494 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1498 ("message-type", "offer"),
1499 ("server_id", self.pg3.remote_ip4),
1505 self.pg3.add_stream(p_offer)
1506 self.pg_enable_capture(self.pg_interfaces)
1509 rx = self.pg3.get_capture(1)
1510 self.verify_orig_dhcp_request(
1511 rx[0], self.pg3, hostname, self.pg3.local_ip4, broadcast=False
1515 # Send an acknowledgment, the lease renewal time is 2 seconds
1516 # so we should expect the renew straight after
1519 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1520 / IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4)
1521 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1523 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1527 ("message-type", "ack"),
1528 ("subnet_mask", "255.255.255.0"),
1529 ("router", self.pg3.remote_ip4),
1530 ("server_id", self.pg3.remote_ip4),
1531 ("lease_time", 43200),
1532 ("renewal_time", 2),
1538 self.pg3.add_stream(p_ack)
1539 self.pg_enable_capture(self.pg_interfaces)
1543 # We'll get an ARP request for the router address
1545 rx = self.pg3.get_capture(1)
1547 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1548 self.pg_enable_capture(self.pg_interfaces)
1551 # At the end of this procedure there should be a connected route
1554 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1555 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1558 # read the DHCP client details from a dump
1560 clients = self.vapi.dhcp_client_dump()
1562 self.assertEqual(clients[0].client.sw_if_index, self.pg3.sw_if_index)
1563 self.assertEqual(clients[0].lease.sw_if_index, self.pg3.sw_if_index)
1564 self.assertEqual(clients[0].client.hostname, hostname)
1565 self.assertEqual(clients[0].lease.hostname, hostname)
1566 # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1567 self.assertEqual(clients[0].lease.state, 2)
1568 self.assertEqual(clients[0].lease.mask_width, 24)
1569 self.assertEqual(str(clients[0].lease.router_address), self.pg3.remote_ip4)
1570 self.assertEqual(str(clients[0].lease.host_address), self.pg3.local_ip4)
1573 # wait for the unicasted renewal
1574 # the first attempt will be an ARP packet, since we have not yet
1575 # responded to VPP's request
1577 self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose"))
1578 rx = self.pg3.get_capture(1, timeout=10)
1580 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1582 # respond to the arp
1583 p_arp = Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / ARP(
1585 hwdst=self.pg3.local_mac,
1586 hwsrc=self.pg3.remote_mac,
1587 pdst=self.pg3.local_ip4,
1588 psrc=self.pg3.remote_ip4,
1590 self.pg3.add_stream(p_arp)
1591 self.pg_enable_capture(self.pg_interfaces)
1594 # the next packet is the unicasted renewal
1595 rx = self.pg3.get_capture(1, timeout=10)
1596 self.verify_orig_dhcp_request(
1597 rx[0], self.pg3, hostname, self.pg3.local_ip4, l2_bc=False, broadcast=False
1600 # send an ACK with different data from the original offer *
1601 self.pg3.generate_remote_hosts(4)
1603 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1604 / IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4)
1605 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1608 yiaddr=self.pg3.remote_hosts[3].ip4,
1609 chaddr=mac_pton(self.pg3.local_mac),
1613 ("message-type", "ack"),
1614 ("subnet_mask", "255.255.255.0"),
1615 ("router", self.pg3.remote_hosts[1].ip4),
1616 ("server_id", self.pg3.remote_hosts[2].ip4),
1617 ("lease_time", 43200),
1618 ("renewal_time", 2),
1624 self.pg3.add_stream(p_ack)
1625 self.pg_enable_capture(self.pg_interfaces)
1629 # read the DHCP client details from a dump
1631 clients = self.vapi.dhcp_client_dump()
1633 self.assertEqual(clients[0].client.sw_if_index, self.pg3.sw_if_index)
1634 self.assertEqual(clients[0].lease.sw_if_index, self.pg3.sw_if_index)
1635 self.assertEqual(clients[0].client.hostname, hostname)
1636 self.assertEqual(clients[0].lease.hostname, hostname)
1637 # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1638 self.assertEqual(clients[0].lease.state, 2)
1639 self.assertEqual(clients[0].lease.mask_width, 24)
1641 str(clients[0].lease.router_address), self.pg3.remote_hosts[1].ip4
1644 str(clients[0].lease.host_address), self.pg3.remote_hosts[3].ip4
1648 # remove the DHCP config
1650 Client.remove_vpp_config()
1653 # and now the route should be gone
1655 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1656 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1659 # Start the procedure again. Use requested lease time option.
1660 # this time wait for the lease to expire and the client to
1664 self.pg3.admin_down()
1667 self.pg_enable_capture(self.pg_interfaces)
1668 Client.set_client(self.pg3.sw_if_index, hostname)
1669 Client.add_vpp_config()
1671 rx = self.pg3.get_capture(1)
1673 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1676 # Send back on offer with requested lease time, expect the request
1680 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1681 / IP(src=self.pg3.remote_ip4, dst="255.255.255.255")
1682 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1684 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1688 ("message-type", "offer"),
1689 ("server_id", self.pg3.remote_ip4),
1690 ("lease_time", lease_time),
1696 self.pg3.add_stream(p_offer)
1697 self.pg_enable_capture(self.pg_interfaces)
1700 rx = self.pg3.get_capture(1)
1701 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, self.pg3.local_ip4)
1704 # Send an acknowledgment
1707 Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac)
1708 / IP(src=self.pg3.remote_ip4, dst="255.255.255.255")
1709 / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT)
1711 op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)
1715 ("message-type", "ack"),
1716 ("subnet_mask", "255.255.255.0"),
1717 ("router", self.pg3.remote_ip4),
1718 ("server_id", self.pg3.remote_ip4),
1719 ("lease_time", lease_time),
1725 self.pg3.add_stream(p_ack)
1726 self.pg_enable_capture(self.pg_interfaces)
1730 # We'll get an ARP request for the router address
1732 rx = self.pg3.get_capture(1)
1734 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1737 # At the end of this procedure there should be a connected route
1740 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1741 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1744 # the route should be gone after the lease expires
1746 self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32))
1747 self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24))
1750 # remove the DHCP config
1752 Client.remove_vpp_config()
1754 def test_dhcp_client_vlan(self):
1755 """DHCP Client w/ VLAN"""
1757 vdscp = VppEnum.vl_api_ip_dscp_t
1758 vqos = VppEnum.vl_api_qos_source_t
1759 hostname = "universal-dp"
1761 self.pg_enable_capture(self.pg_interfaces)
1763 vlan_100 = VppDot1QSubint(self, self.pg3, 100)
1766 output = [scapy.compat.chb(4)] * 256
1767 os = b"".join(output)
1768 rows = [{"outputs": os}, {"outputs": os}, {"outputs": os}, {"outputs": os}]
1770 qem1 = VppQosEgressMap(self, 1, rows).add_vpp_config()
1772 self, vlan_100, qem1, vqos.QOS_API_SOURCE_VLAN
1776 # Configure DHCP client on PG3 and capture the discover sent
1778 Client = VppDHCPClient(
1779 self, vlan_100.sw_if_index, hostname, dscp=vdscp.IP_API_DSCP_EF
1781 Client.add_vpp_config()
1783 rx = self.pg3.get_capture(1)
1785 self.assertEqual(rx[0][Dot1Q].vlan, 100)
1786 self.assertEqual(rx[0][Dot1Q].prio, 2)
1788 self.verify_orig_dhcp_discover(
1789 rx[0], self.pg3, hostname, dscp=vdscp.IP_API_DSCP_EF
1793 if __name__ == "__main__":
1794 unittest.main(testRunner=VppTestRunner)