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
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
28 DHCP4_CLIENT_PORT = 68
29 DHCP4_SERVER_PORT = 67
30 DHCP6_CLIENT_PORT = 547
31 DHCP6_SERVER_PORT = 546
34 class TestDHCP(VppTestCase):
35 """ DHCP Test Case """
39 super(TestDHCP, cls).setUpClass()
42 def tearDownClass(cls):
43 super(TestDHCP, cls).tearDownClass()
46 super(TestDHCP, self).setUp()
48 # create 6 pg interfaces for pg0 to pg5
49 self.create_pg_interfaces(range(6))
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.
55 for table_id in range(1, 4):
56 tbl4 = VppIpTable(self, table_id)
58 self.tables.append(tbl4)
59 tbl6 = VppIpTable(self, table_id, is_ip6=1)
61 self.tables.append(tbl6)
64 for i in self.pg_interfaces[:3]:
66 i.set_table_ip4(table_id)
67 i.set_table_ip6(table_id)
75 for i in self.pg_interfaces[3:]:
77 i.set_table_ip4(table_id)
78 i.set_table_ip6(table_id)
82 for i in self.pg_interfaces[:3]:
86 for i in self.pg_interfaces:
90 super(TestDHCP, self).tearDown()
92 def verify_dhcp_has_option(self, pkt, option, value):
96 for i in dhcp.options:
99 self.assertEqual(i[1], value)
102 self.assertTrue(found)
104 def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui):
110 for i in dhcp.options:
112 if i[0] == "relay_agent_Information":
114 # There are two sb-options present - each of length 6.
118 self.assertEqual(len(data), 24)
119 elif len(vpn_id) > 0:
120 self.assertEqual(len(data), len(vpn_id)+17)
122 self.assertEqual(len(data), 12)
125 # First sub-option is ID 1, len 4, then encoded
126 # sw_if_index. This test uses low valued indicies
128 # The ID space is VPP internal - so no matching value
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)
139 # next sub-option is the IP address of the client side
141 # sub-option ID=5, length (of a v4 address)=4
143 claddr = socket.inet_pton(AF_INET, ip_addr)
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])
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)
167 # VSS control sub-option
168 self.assertEqual(ord(data[22]), 152)
169 self.assertEqual(ord(data[23]), 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)
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)
185 self.assertTrue(found)
189 def verify_dhcp_msg_type(self, pkt, name):
192 for o in dhcp.options:
194 if o[0] == "message-type" \
195 and DHCPTypes[o[1]] == name:
197 self.assertTrue(found)
199 def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0):
201 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
202 self.assertEqual(ether.src, intf.local_mac)
205 self.assertEqual(ip.dst, "255.255.255.255")
206 self.assertEqual(ip.src, intf.local_ip4)
209 self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
210 self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
212 self.verify_dhcp_msg_type(pkt, "offer")
213 data = self.validate_relay_options(pkt, intf, intf.local_ip4,
216 def verify_orig_dhcp_pkt(self, pkt, intf, dscp, l2_bc=True):
219 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
221 self.assertEqual(ether.dst, intf.remote_mac)
222 self.assertEqual(ether.src, intf.local_mac)
227 self.assertEqual(ip.dst, "255.255.255.255")
228 self.assertEqual(ip.src, "0.0.0.0")
230 self.assertEqual(ip.dst, intf.remote_ip4)
231 self.assertEqual(ip.src, intf.local_ip4)
232 self.assertEqual(ip.tos, dscp)
235 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
236 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
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)
242 self.verify_dhcp_msg_type(pkt, "discover")
243 self.verify_dhcp_has_option(pkt, "hostname", hostname)
245 client_id = '\x00' + client_id
246 self.verify_dhcp_has_option(pkt, "client_id", client_id)
248 self.assertEqual(bootp.ciaddr, "0.0.0.0")
249 self.assertEqual(bootp.giaddr, "0.0.0.0")
251 self.assertEqual(bootp.flags, 0x8000)
253 self.assertEqual(bootp.flags, 0x0000)
255 def verify_orig_dhcp_request(self, pkt, intf, hostname, ip,
259 self.verify_orig_dhcp_pkt(pkt, intf, dscp, l2_bc=l2_bc)
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)
267 self.assertEqual(bootp.ciaddr, "0.0.0.0")
269 self.assertEqual(bootp.ciaddr, intf.local_ip4)
270 self.assertEqual(bootp.giaddr, "0.0.0.0")
273 self.assertEqual(bootp.flags, 0x8000)
275 self.assertEqual(bootp.flags, 0x0000)
277 def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None,
280 dst_mac=None, dst_ip=None):
282 dst_mac = intf.remote_mac
284 dst_ip = intf.remote_ip4
287 self.assertEqual(ether.dst, dst_mac)
288 self.assertEqual(ether.src, intf.local_mac)
291 self.assertEqual(ip.dst, dst_ip)
292 self.assertEqual(ip.src, intf.local_ip4)
295 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
296 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
301 for o in dhcp.options:
303 if o[0] == "message-type" \
304 and DHCPTypes[o[1]] == "discover":
306 self.assertTrue(is_discover)
308 data = self.validate_relay_options(pkt, src_intf,
314 def verify_dhcp6_solicit(self, pkt, intf,
322 dst_mac = intf.remote_mac
324 dst_ip = in6_ptop(intf.remote_ip6)
327 self.assertEqual(ether.dst, dst_mac)
328 self.assertEqual(ether.src, intf.local_mac)
331 self.assertEqual(in6_ptop(ip.dst), dst_ip)
332 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
335 self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
336 self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
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)
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)
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)
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])
376 def verify_dhcp6_advert(self, pkt, intf, peer):
378 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
379 self.assertEqual(ether.src, intf.local_mac)
382 self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
383 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
386 self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
387 self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
389 # not sure why this is not decoding
390 # adv = pkt[DHCP6_Advertise]
392 def wait_for_no_route(self, address, length,
393 n_tries=50, s_time=1):
395 if not find_route(self, address, length):
397 n_tries = n_tries - 1
402 def test_dhcp_proxy(self):
406 # Verify no response to DHCP request without DHCP config
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) /
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) /
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) /
430 DHCP(options=[('message-type', 'discover'), ('end')]))
431 pkts_disc_vrf2 = [p_disc_vrf2]
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")
441 # Enable DHCP proxy in VRF 0
443 server_addr = self.pg0.remote_ip4n
444 src_addr = self.pg0.local_ip4n
446 self.vapi.dhcp_proxy_config(server_addr,
451 # Discover packets from the client are dropped because there is no
452 # IP address configured on the client facing interface
454 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
455 "Discover DHCP no relay address")
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.
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) /
466 DHCP(options=[('message-type', 'offer'), ('end')]))
469 self.send_and_assert_no_replies(self.pg3, pkts,
470 "Offer DHCP no relay address")
473 # configure an IP address on the client facing interface
475 self.pg3.config_ip4()
478 # Try again with a discover packet
479 # Rx'd packet should be to the server address and from the configured
481 # UDP source ports are unchanged
482 # we've no option 82 config so that should be absent
484 self.pg3.add_stream(pkts_disc_vrf0)
485 self.pg_enable_capture(self.pg_interfaces)
488 rx = self.pg0.get_capture(1)
491 option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
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.
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) /
503 DHCP(options=[('message-type', 'offer'),
504 ('relay_agent_Information', option_82),
508 self.pg0.add_stream(pkts)
509 self.pg_enable_capture(self.pg_interfaces)
512 rx = self.pg3.get_capture(1)
515 self.verify_dhcp_offer(rx, self.pg3)
520 # 1. not our IP address = not checked by VPP? so offer is replayed
522 bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:]
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) /
528 DHCP(options=[('message-type', 'offer'),
529 ('relay_agent_Information', bad_ip),
532 self.send_and_assert_no_replies(self.pg0, pkts,
533 "DHCP offer option 82 bad address")
535 # 2. Not a sw_if_index VPP knows
536 bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:]
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) /
542 DHCP(options=[('message-type', 'offer'),
543 ('relay_agent_Information', bad_if_index),
546 self.send_and_assert_no_replies(self.pg0, pkts,
547 "DHCP offer option 82 bad if index")
550 # Send a DHCP request in VRF 1. should be dropped.
552 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
553 "DHCP with no configuration VRF 1")
556 # Delete the DHCP config in VRF 0
557 # Should now drop requests.
559 self.vapi.dhcp_proxy_config(server_addr,
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")
570 # Add DHCP config for VRF 1 & 2
572 server_addr1 = self.pg1.remote_ip4n
573 src_addr1 = self.pg1.local_ip4n
574 self.vapi.dhcp_proxy_config(server_addr1,
578 server_addr2 = self.pg2.remote_ip4n
579 src_addr2 = self.pg2.local_ip4n
580 self.vapi.dhcp_proxy_config(server_addr2,
586 # Confim DHCP requests ok in VRF 1 & 2.
587 # - dropped on IP config on client interface
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")
595 # configure an IP address on the client facing interface
597 self.pg4.config_ip4()
598 self.pg4.add_stream(pkts_disc_vrf1)
599 self.pg_enable_capture(self.pg_interfaces)
601 rx = self.pg1.get_capture(1)
603 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
605 self.pg5.config_ip4()
606 self.pg5.add_stream(pkts_disc_vrf2)
607 self.pg_enable_capture(self.pg_interfaces)
609 rx = self.pg2.get_capture(1)
611 self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5)
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)
620 self.pg4.add_stream(pkts_disc_vrf1)
621 self.pg_enable_capture(self.pg_interfaces)
624 rx = self.pg1.get_capture(1)
626 self.verify_relayed_dhcp_discover(rx, self.pg1,
630 self.pg5.add_stream(pkts_disc_vrf2)
631 self.pg_enable_capture(self.pg_interfaces)
634 rx = self.pg2.get_capture(1)
636 self.verify_relayed_dhcp_discover(rx, self.pg2,
638 vpn_id="ip4-table-2")
641 # Add a second DHCP server in VRF 1
642 # expect clients messages to be relay to both configured servers
644 self.pg1.generate_remote_hosts(2)
645 server_addr12 = socket.inet_pton(AF_INET, self.pg1.remote_hosts[1].ip4)
647 self.vapi.dhcp_proxy_config(server_addr12,
654 # We'll need an ARP entry for the server to send it packets
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()
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.
667 self.pg4.add_stream(pkts_disc_vrf1)
668 self.pg_enable_capture(self.pg_interfaces)
671 rx = self.pg1.get_capture(2)
673 option_82 = self.verify_relayed_dhcp_discover(
676 dst_mac=self.pg1.remote_hosts[1].mac,
677 dst_ip=self.pg1.remote_hosts[1].ip4,
679 self.verify_relayed_dhcp_discover(rx[1], self.pg1,
684 # Send both packets back. Client gets both.
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) /
690 DHCP(options=[('message-type', 'offer'),
691 ('relay_agent_Information', option_82),
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) /
697 DHCP(options=[('message-type', 'offer'),
698 ('relay_agent_Information', option_82),
702 self.pg1.add_stream(pkts)
703 self.pg_enable_capture(self.pg_interfaces)
706 rx = self.pg4.get_capture(2)
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)
712 # Ensure offers from non-servers are dropeed
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) /
718 DHCP(options=[('message-type', 'offer'),
719 ('relay_agent_Information', option_82),
721 self.send_and_assert_no_replies(self.pg1, p2,
722 "DHCP offer from non-server")
725 # Ensure only the discover is sent to multiple servers
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) /
733 DHCP(options=[('message-type', 'request'),
736 self.pg4.add_stream(p_req_vrf1)
737 self.pg_enable_capture(self.pg_interfaces)
740 rx = self.pg1.get_capture(1)
743 # Remove the second DHCP server
745 self.vapi.dhcp_proxy_config(server_addr12,
752 # Test we can still relay with the first
754 self.pg4.add_stream(pkts_disc_vrf1)
755 self.pg_enable_capture(self.pg_interfaces)
758 rx = self.pg1.get_capture(1)
760 self.verify_relayed_dhcp_discover(rx, self.pg1,
765 # Remove the VSS config
766 # relayed DHCP has default vlaues in the option.
768 self.vapi.dhcp_proxy_set_vss(1, is_add=0)
769 self.vapi.dhcp_proxy_set_vss(2, is_add=0)
771 self.pg4.add_stream(pkts_disc_vrf1)
772 self.pg_enable_capture(self.pg_interfaces)
775 rx = self.pg1.get_capture(1)
777 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
780 # remove DHCP config to cleanup
782 self.vapi.dhcp_proxy_config(server_addr1,
787 self.vapi.dhcp_proxy_config(server_addr2,
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")
800 self.pg3.unconfig_ip4()
801 self.pg4.unconfig_ip4()
802 self.pg5.unconfig_ip4()
804 def test_dhcp6_proxy(self):
807 # Verify no response to DHCP request without DHCP config
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
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) /
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) /
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) /
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")
848 # DHCPv6 config in VRF 0.
849 # Packets still dropped because the client facing interface has no
852 self.vapi.dhcp_proxy_config(server_addr_vrf0,
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")
864 # configure an IP address on the client facing interface
866 self.pg3.config_ip6()
869 # Now the DHCP requests are relayed to the server
871 self.pg3.add_stream(p_solicit_vrf0)
872 self.pg_enable_capture(self.pg_interfaces)
875 rx = self.pg0.get_capture(1)
877 self.verify_dhcp6_solicit(rx[0], self.pg0,
878 dhcp_solicit_src_vrf0,
882 # Exception cases for rejected relay responses
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) /
890 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
891 "DHCP6 not a relay reply")
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) /
899 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
900 "DHCP not a relay message")
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) /
907 DHCP6OptRelayMsg(optlen=0) /
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) /
916 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
917 DHCP6OptRelayMsg(optlen=0) /
919 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
920 "DHCP6 wrong circuit ID")
923 # Send the relay response (the advertisement)
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) /
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]
935 self.pg0.add_stream(pkts_adv_vrf0)
936 self.pg_enable_capture(self.pg_interfaces)
939 rx = self.pg3.get_capture(1)
941 self.verify_dhcp6_advert(rx[0], self.pg3, "::")
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]
956 self.pg0.add_stream(pkts_adv_vrf0)
957 self.pg_enable_capture(self.pg_interfaces)
960 rx = self.pg3.get_capture(1)
962 self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
965 # Add all the config for VRF 1 & 2
967 self.vapi.dhcp_proxy_config(server_addr_vrf1,
972 self.pg4.config_ip6()
974 self.vapi.dhcp_proxy_config(server_addr_vrf2,
979 self.pg5.config_ip6()
984 self.pg4.add_stream(p_solicit_vrf1)
985 self.pg_enable_capture(self.pg_interfaces)
988 rx = self.pg1.get_capture(1)
990 self.verify_dhcp6_solicit(rx[0], self.pg1,
991 dhcp_solicit_src_vrf1,
997 self.pg5.add_stream(p_solicit_vrf2)
998 self.pg_enable_capture(self.pg_interfaces)
1001 rx = self.pg2.get_capture(1)
1003 self.verify_dhcp6_solicit(rx[0], self.pg2,
1004 dhcp_solicit_src_vrf2,
1005 self.pg5.remote_mac)
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]
1020 self.pg1.add_stream(pkts_adv_vrf1)
1021 self.pg_enable_capture(self.pg_interfaces)
1024 rx = self.pg4.get_capture(1)
1026 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
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)
1035 self.pg4.add_stream(p_solicit_vrf1)
1036 self.pg_enable_capture(self.pg_interfaces)
1039 rx = self.pg1.get_capture(1)
1041 self.verify_dhcp6_solicit(rx[0], self.pg1,
1042 dhcp_solicit_src_vrf1,
1043 self.pg4.remote_mac,
1047 self.pg5.add_stream(p_solicit_vrf2)
1048 self.pg_enable_capture(self.pg_interfaces)
1051 rx = self.pg2.get_capture(1)
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")
1059 # Remove the VSS config
1060 # relayed DHCP has default vlaues in the option.
1062 self.vapi.dhcp_proxy_set_vss(1, is_ip6=1, is_add=0)
1064 self.pg4.add_stream(p_solicit_vrf1)
1065 self.pg_enable_capture(self.pg_interfaces)
1068 rx = self.pg1.get_capture(1)
1070 self.verify_dhcp6_solicit(rx[0], self.pg1,
1071 dhcp_solicit_src_vrf1,
1072 self.pg4.remote_mac)
1075 # Add a second DHCP server in VRF 1
1076 # expect clients messages to be relay to both configured servers
1078 self.pg1.generate_remote_hosts(2)
1079 server_addr12 = socket.inet_pton(AF_INET6,
1080 self.pg1.remote_hosts[1].ip6)
1082 self.vapi.dhcp_proxy_config(server_addr12,
1089 # We'll need an ND entry for the server to send it packets
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()
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.
1102 self.pg4.add_stream(p_solicit_vrf1)
1103 self.pg_enable_capture(self.pg_interfaces)
1106 rx = self.pg1.get_capture(2)
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)
1118 # Send both packets back. Client gets both.
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))
1139 self.pg1.add_stream(pkts)
1140 self.pg_enable_capture(self.pg_interfaces)
1143 rx = self.pg4.get_capture(2)
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)
1149 # Ensure only solicit messages are duplicated
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) /
1158 self.pg4.add_stream(p_request_vrf1)
1159 self.pg_enable_capture(self.pg_interfaces)
1162 rx = self.pg1.get_capture(1)
1165 # Test we drop DHCP packets from addresses that are not configured as
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")
1180 # Remove the second DHCP server
1182 self.vapi.dhcp_proxy_config(server_addr12,
1190 # Test we can still relay with the first
1192 self.pg4.add_stream(p_solicit_vrf1)
1193 self.pg_enable_capture(self.pg_interfaces)
1196 rx = self.pg1.get_capture(1)
1198 self.verify_dhcp6_solicit(rx[0], self.pg1,
1199 dhcp_solicit_src_vrf1,
1200 self.pg4.remote_mac)
1205 self.vapi.dhcp_proxy_config(server_addr_vrf2,
1211 self.vapi.dhcp_proxy_config(server_addr_vrf1,
1217 self.vapi.dhcp_proxy_config(server_addr_vrf0,
1225 self.vapi.dhcp_proxy_config(server_addr_vrf0,
1231 self.pg3.unconfig_ip6()
1232 self.pg4.unconfig_ip6()
1233 self.pg5.unconfig_ip6()
1235 def test_dhcp_client(self):
1238 vdscp = VppEnum.vl_api_ip_dscp_t
1239 hostname = 'universal-dp'
1241 self.pg_enable_capture(self.pg_interfaces)
1244 # Configure DHCP client on PG3 and capture the discover sent
1246 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname)
1248 rx = self.pg3.get_capture(1)
1250 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1253 # Send back on offer, expect the request
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) /
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),
1265 self.pg3.add_stream(p_offer)
1266 self.pg_enable_capture(self.pg_interfaces)
1269 rx = self.pg3.get_capture(1)
1270 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1274 # Send an acknowledgment
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),
1288 self.pg3.add_stream(p_ack)
1289 self.pg_enable_capture(self.pg_interfaces)
1293 # We'll get an ARP request for the router address
1295 rx = self.pg3.get_capture(1)
1297 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1298 self.pg_enable_capture(self.pg_interfaces)
1301 # At the end of this procedure there should be a connected route
1304 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1305 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
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,
1314 # remove the DHCP config
1316 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1319 # and now the route should be gone
1321 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1322 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1325 # Start the procedure again. this time have VPP send the client-ID
1326 # and set the DSCP value
1328 self.pg3.admin_down()
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)
1335 rx = self.pg3.get_capture(1)
1337 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1339 dscp=vdscp.IP_API_DSCP_EF)
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)
1350 rx = self.pg3.get_capture(1)
1351 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1353 dscp=vdscp.IP_API_DSCP_EF)
1356 # unicast the ack to the offered address
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),
1370 self.pg3.add_stream(p_ack)
1371 self.pg_enable_capture(self.pg_interfaces)
1375 # We'll get an ARP request for the router address
1377 rx = self.pg3.get_capture(1)
1379 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1380 self.pg_enable_capture(self.pg_interfaces)
1383 # At the end of this procedure there should be a connected route
1386 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1387 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1390 # remove the DHCP config
1392 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1394 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1395 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
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.
1402 # Configure DHCP client on PG3 and capture the discover sent
1404 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname,
1405 set_broadcast_flag=0)
1407 rx = self.pg3.get_capture(1)
1409 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1413 # Send back on offer, unicasted to the offered address.
1414 # Expect the request.
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),
1425 self.pg3.add_stream(p_offer)
1426 self.pg_enable_capture(self.pg_interfaces)
1429 rx = self.pg3.get_capture(1)
1430 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1435 # Send an acknowledgment, the lease renewal time is 2 seconds
1436 # so we should expect the renew straight after
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),
1451 self.pg3.add_stream(p_ack)
1452 self.pg_enable_capture(self.pg_interfaces)
1456 # We'll get an ARP request for the router address
1458 rx = self.pg3.get_capture(1)
1460 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1461 self.pg_enable_capture(self.pg_interfaces)
1464 # At the end of this procedure there should be a connected route
1467 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1468 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
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
1475 self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose"))
1476 rx = self.pg3.get_capture(1, timeout=10)
1478 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1480 # respond to the arp
1481 p_arp = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
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)
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,
1499 # read the DHCP client details from a dump
1501 clients = self.vapi.dhcp_client_dump()
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'),
1509 self.assertEqual(clients[0].lease.hostname.rstrip('\0'),
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)
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,
1527 # remove the DHCP config
1529 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1532 # and now the route should be gone
1534 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1535 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1538 # Start the procedure again. Use requested lease time option.
1541 self.pg3.admin_down()
1544 self.pg_enable_capture(self.pg_interfaces)
1545 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname)
1547 rx = self.pg3.get_capture(1)
1549 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1552 # Send back on offer with requested lease time, expect the request
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) /
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),
1566 self.pg3.add_stream(p_offer)
1567 self.pg_enable_capture(self.pg_interfaces)
1570 rx = self.pg3.get_capture(1)
1571 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1575 # Send an acknowledgment
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),
1589 self.pg3.add_stream(p_ack)
1590 self.pg_enable_capture(self.pg_interfaces)
1594 # We'll get an ARP request for the router address
1596 rx = self.pg3.get_capture(1)
1598 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1601 # At the end of this procedure there should be a connected route
1604 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1605 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
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,
1614 # the route should be gone after the lease expires
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))
1620 # remove the DHCP config
1622 self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1624 def test_dhcp_client_vlan(self):
1625 """ DHCP Client w/ VLAN"""
1627 vdscp = VppEnum.vl_api_ip_dscp_t
1628 vqos = VppEnum.vl_api_qos_source_t
1629 hostname = 'universal-dp'
1631 self.pg_enable_capture(self.pg_interfaces)
1633 vlan_100 = VppDot1QSubint(self, self.pg3, 100)
1636 output = [scapy.compat.chb(4)] * 256
1637 os = b''.join(output)
1638 rows = [{'outputs': os},
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()
1648 # Configure DHCP client on PG3 and capture the discover sent
1650 self.vapi.dhcp_client_config(vlan_100.sw_if_index,
1652 dscp=vdscp.IP_API_DSCP_EF)
1654 rx = self.pg3.get_capture(1)
1656 self.assertEqual(rx[0][Dot1Q].vlan, 100)
1657 self.assertEqual(rx[0][Dot1Q].prio, 2)
1659 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1660 dscp=vdscp.IP_API_DSCP_EF)
1662 self.vapi.dhcp_client_config(vlan_100.sw_if_index,
1667 if __name__ == '__main__':
1668 unittest.main(testRunner=VppTestRunner)