8 from framework import VppTestCase, VppTestRunner, running_extended_tests
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, getmacbyip, ARP, Dot1Q
14 from scapy.layers.inet import IP, UDP, ICMP
15 from scapy.layers.inet6 import IPv6, in6_getnsmac
16 from scapy.utils6 import in6_mactoifaceid
17 from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
18 from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
19 DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \
20 DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request
21 from socket import AF_INET, AF_INET6, inet_pton, inet_ntop
22 from scapy.utils6 import in6_ptop
23 from vpp_papi import mac_pton, VppEnum
24 from vpp_sub_interface import VppDot1QSubint
25 from vpp_qos import VppQosEgressMap, VppQosMark
26 from vpp_dhcp import VppDHCPClient, VppDHCPProxy
29 DHCP4_CLIENT_PORT = 68
30 DHCP4_SERVER_PORT = 67
31 DHCP6_CLIENT_PORT = 547
32 DHCP6_SERVER_PORT = 546
35 class TestDHCP(VppTestCase):
36 """ DHCP Test Case """
44 super(TestDHCP, cls).setUpClass()
47 def tearDownClass(cls):
48 super(TestDHCP, cls).tearDownClass()
51 super(TestDHCP, self).setUp()
53 # create 6 pg interfaces for pg0 to pg5
54 self.create_pg_interfaces(range(6))
57 # pg0 to 2 are IP configured in VRF 0, 1 and 2.
58 # pg3 to 5 are non IP-configured in VRF 0, 1 and 2.
60 for table_id in range(1, 4):
61 tbl4 = VppIpTable(self, table_id)
63 self.tables.append(tbl4)
64 tbl6 = VppIpTable(self, table_id, is_ip6=1)
66 self.tables.append(tbl6)
69 for i in self.pg_interfaces[:3]:
71 i.set_table_ip4(table_id)
72 i.set_table_ip6(table_id)
80 for i in self.pg_interfaces[3:]:
82 i.set_table_ip4(table_id)
83 i.set_table_ip6(table_id)
87 for i in self.pg_interfaces[:3]:
91 for i in self.pg_interfaces:
95 super(TestDHCP, self).tearDown()
97 def verify_dhcp_has_option(self, pkt, option, value):
101 for i in dhcp.options:
102 if isinstance(i, tuple):
104 self.assertEqual(i[1], value)
107 self.assertTrue(found)
109 def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui):
115 for i in dhcp.options:
116 if isinstance(i, tuple):
117 if i[0] == "relay_agent_Information":
119 # There are two sb-options present - each of length 6.
123 self.assertEqual(len(data), 24)
124 elif len(vpn_id) > 0:
125 self.assertEqual(len(data), len(vpn_id) + 17)
127 self.assertEqual(len(data), 12)
130 # First sub-option is ID 1, len 4, then encoded
131 # sw_if_index. This test uses low valued indicies
133 # The ID space is VPP internal - so no matching value
136 self.assertEqual(six.byte2int(data[0:1]), 1)
137 self.assertEqual(six.byte2int(data[1:2]), 4)
138 self.assertEqual(six.byte2int(data[2:3]), 0)
139 self.assertEqual(six.byte2int(data[3:4]), 0)
140 self.assertEqual(six.byte2int(data[4:5]), 0)
141 self.assertEqual(six.byte2int(data[5:6]),
145 # next sub-option is the IP address of the client side
147 # sub-option ID=5, length (of a v4 address)=4
149 claddr = socket.inet_pton(AF_INET, ip_addr)
151 self.assertEqual(six.byte2int(data[6:7]), 5)
152 self.assertEqual(six.byte2int(data[7:8]), 4)
153 self.assertEqual(data[8], claddr[0])
154 self.assertEqual(data[9], claddr[1])
155 self.assertEqual(data[10], claddr[2])
156 self.assertEqual(data[11], claddr[3])
159 # sub-option 151 encodes vss_type 1,
160 # the 3 byte oui and the 4 byte fib_id
161 self.assertEqual(id_len, 0)
162 self.assertEqual(six.byte2int(data[12:13]), 151)
163 self.assertEqual(six.byte2int(data[13:14]), 8)
164 self.assertEqual(six.byte2int(data[14:15]), 1)
165 self.assertEqual(six.byte2int(data[15:16]), 0)
166 self.assertEqual(six.byte2int(data[16:17]), 0)
167 self.assertEqual(six.byte2int(data[17:18]), oui)
168 self.assertEqual(six.byte2int(data[18:19]), 0)
169 self.assertEqual(six.byte2int(data[19:20]), 0)
170 self.assertEqual(six.byte2int(data[20:21]), 0)
171 self.assertEqual(six.byte2int(data[21:22]), fib_id)
173 # VSS control sub-option
174 self.assertEqual(six.byte2int(data[22:23]), 152)
175 self.assertEqual(six.byte2int(data[23:24]), 0)
178 # sub-option 151 encode vss_type of 0
179 # followerd by vpn_id in ascii
180 self.assertEqual(oui, 0)
181 self.assertEqual(six.byte2int(data[12:13]), 151)
182 self.assertEqual(six.byte2int(data[13:14]), id_len + 1)
183 self.assertEqual(six.byte2int(data[14:15]), 0)
184 self.assertEqual(data[15:15 + id_len].decode('ascii'),
187 # VSS control sub-option
188 self.assertEqual(six.byte2int(data[15 + len(vpn_id):
191 self.assertEqual(six.byte2int(data[16 + len(vpn_id):
196 self.assertTrue(found)
200 def verify_dhcp_msg_type(self, pkt, name):
203 for o in dhcp.options:
204 if isinstance(o, tuple):
205 if o[0] == "message-type" \
206 and DHCPTypes[o[1]] == name:
208 self.assertTrue(found)
210 def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0):
212 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
213 self.assertEqual(ether.src, intf.local_mac)
216 self.assertEqual(ip.dst, "255.255.255.255")
217 self.assertEqual(ip.src, intf.local_ip4)
220 self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
221 self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
223 self.verify_dhcp_msg_type(pkt, "offer")
224 data = self.validate_relay_options(pkt, intf, intf.local_ip4,
227 def verify_orig_dhcp_pkt(self, pkt, intf, dscp, l2_bc=True):
230 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
232 self.assertEqual(ether.dst, intf.remote_mac)
233 self.assertEqual(ether.src, intf.local_mac)
238 self.assertEqual(ip.dst, "255.255.255.255")
239 self.assertEqual(ip.src, "0.0.0.0")
241 self.assertEqual(ip.dst, intf.remote_ip4)
242 self.assertEqual(ip.src, intf.local_ip4)
243 self.assertEqual(ip.tos, dscp)
246 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
247 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
249 def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None,
250 broadcast=True, dscp=0):
251 self.verify_orig_dhcp_pkt(pkt, intf, dscp)
253 self.verify_dhcp_msg_type(pkt, "discover")
254 self.verify_dhcp_has_option(pkt, "hostname",
255 hostname.encode('ascii'))
257 client_id = '\x00' + client_id
258 self.verify_dhcp_has_option(pkt, "client_id",
259 client_id.encode('ascii'))
261 self.assertEqual(bootp.ciaddr, "0.0.0.0")
262 self.assertEqual(bootp.giaddr, "0.0.0.0")
264 self.assertEqual(bootp.flags, 0x8000)
266 self.assertEqual(bootp.flags, 0x0000)
268 def verify_orig_dhcp_request(self, pkt, intf, hostname, ip,
272 self.verify_orig_dhcp_pkt(pkt, intf, dscp, l2_bc=l2_bc)
274 self.verify_dhcp_msg_type(pkt, "request")
275 self.verify_dhcp_has_option(pkt, "hostname",
276 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(self, pkt, intf, src_intf=None,
294 dst_mac=None, dst_ip=None):
296 dst_mac = intf.remote_mac
298 dst_ip = intf.remote_ip4
301 self.assertEqual(ether.dst, dst_mac)
302 self.assertEqual(ether.src, intf.local_mac)
305 self.assertEqual(ip.dst, dst_ip)
306 self.assertEqual(ip.src, intf.local_ip4)
309 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
310 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
315 for o in dhcp.options:
316 if isinstance(o, tuple):
317 if o[0] == "message-type" \
318 and DHCPTypes[o[1]] == "discover":
320 self.assertTrue(is_discover)
322 data = self.validate_relay_options(pkt, src_intf,
328 def verify_dhcp6_solicit(self, pkt, intf,
336 dst_mac = intf.remote_mac
338 dst_ip = in6_ptop(intf.remote_ip6)
341 self.assertEqual(ether.dst, dst_mac)
342 self.assertEqual(ether.src, intf.local_mac)
345 self.assertEqual(in6_ptop(ip.dst), dst_ip)
346 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
349 self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
350 self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
352 relay = pkt[DHCP6_RelayForward]
353 self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
354 oid = pkt[DHCP6OptIfaceId]
355 cll = pkt[DHCP6OptClientLinkLayerAddr]
356 self.assertEqual(cll.optlen, 8)
357 self.assertEqual(cll.lltype, 1)
358 self.assertEqual(cll.clladdr, peer_mac)
363 self.assertEqual(id_len, 0)
364 vss = pkt[DHCP6OptVSS]
365 self.assertEqual(vss.optlen, 8)
366 self.assertEqual(vss.type, 1)
367 # the OUI and FIB-id are really 3 and 4 bytes resp.
368 # but the tested range is small
369 self.assertEqual(six.byte2int(vss.data[0:1]), 0)
370 self.assertEqual(six.byte2int(vss.data[1:2]), 0)
371 self.assertEqual(six.byte2int(vss.data[2:3]), oui)
372 self.assertEqual(six.byte2int(vss.data[3:4]), 0)
373 self.assertEqual(six.byte2int(vss.data[4:5]), 0)
374 self.assertEqual(six.byte2int(vss.data[5:6]), 0)
375 self.assertEqual(six.byte2int(vss.data[6:7]), fib_id)
378 self.assertEqual(oui, 0)
379 vss = pkt[DHCP6OptVSS]
380 self.assertEqual(vss.optlen, id_len + 1)
381 self.assertEqual(vss.type, 0)
382 self.assertEqual(vss.data[0:id_len].decode('ascii'),
385 # the relay message should be an encoded Solicit
386 msg = pkt[DHCP6OptRelayMsg]
387 sol = DHCP6_Solicit()
388 self.assertEqual(msg.optlen, len(sol))
389 self.assertEqual(sol, msg[1])
391 def verify_dhcp6_advert(self, pkt, intf, peer):
393 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
394 self.assertEqual(ether.src, intf.local_mac)
397 self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
398 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
401 self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
402 self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
404 # not sure why this is not decoding
405 # adv = pkt[DHCP6_Advertise]
407 def wait_for_no_route(self, address, length,
408 n_tries=50, s_time=1):
410 if not find_route(self, address, length):
412 n_tries = n_tries - 1
417 def test_dhcp_proxy(self):
421 # Verify no response to DHCP request without DHCP config
423 p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff",
424 src=self.pg3.remote_mac) /
425 IP(src="0.0.0.0", dst="255.255.255.255") /
426 UDP(sport=DHCP4_CLIENT_PORT,
427 dport=DHCP4_SERVER_PORT) /
429 DHCP(options=[('message-type', 'discover'), ('end')]))
430 pkts_disc_vrf0 = [p_disc_vrf0]
431 p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
432 src=self.pg4.remote_mac) /
433 IP(src="0.0.0.0", dst="255.255.255.255") /
434 UDP(sport=DHCP4_CLIENT_PORT,
435 dport=DHCP4_SERVER_PORT) /
437 DHCP(options=[('message-type', 'discover'), ('end')]))
438 pkts_disc_vrf1 = [p_disc_vrf1]
439 p_disc_vrf2 = (Ether(dst="ff:ff:ff:ff:ff:ff",
440 src=self.pg5.remote_mac) /
441 IP(src="0.0.0.0", dst="255.255.255.255") /
442 UDP(sport=DHCP4_CLIENT_PORT,
443 dport=DHCP4_SERVER_PORT) /
445 DHCP(options=[('message-type', 'discover'), ('end')]))
446 pkts_disc_vrf2 = [p_disc_vrf2]
448 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
449 "DHCP with no configuration")
450 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
451 "DHCP with no configuration")
452 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
453 "DHCP with no configuration")
456 # Enable DHCP proxy in VRF 0
458 server_addr = self.pg0.remote_ip4
459 src_addr = self.pg0.local_ip4
461 Proxy = VppDHCPProxy(self, server_addr, src_addr, rx_vrf_id=0)
462 Proxy.add_vpp_config()
465 # Discover packets from the client are dropped because there is no
466 # IP address configured on the client facing interface
468 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
469 "Discover DHCP no relay address")
472 # Inject a response from the server
473 # dropped, because there is no IP addrees on the
474 # client interfce to fill in the option.
476 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
477 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
478 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
480 DHCP(options=[('message-type', 'offer'), ('end')]))
483 self.send_and_assert_no_replies(self.pg3, pkts,
484 "Offer DHCP no relay address")
487 # configure an IP address on the client facing interface
489 self.pg3.config_ip4()
492 # Try again with a discover packet
493 # Rx'd packet should be to the server address and from the configured
495 # UDP source ports are unchanged
496 # we've no option 82 config so that should be absent
498 self.pg3.add_stream(pkts_disc_vrf0)
499 self.pg_enable_capture(self.pg_interfaces)
502 rx = self.pg0.get_capture(1)
505 option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
509 # Create an DHCP offer reply from the server with a correctly formatted
510 # option 82. i.e. send back what we just captured
511 # The offer, sent mcast to the client, still has option 82.
513 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
514 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
515 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
517 DHCP(options=[('message-type', 'offer'),
518 ('relay_agent_Information', option_82),
522 self.pg0.add_stream(pkts)
523 self.pg_enable_capture(self.pg_interfaces)
526 rx = self.pg3.get_capture(1)
529 self.verify_dhcp_offer(rx, self.pg3)
534 # 1. not our IP address = not checked by VPP? so offer is replayed
536 bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:]
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_ip),
546 self.send_and_assert_no_replies(self.pg0, pkts,
547 "DHCP offer option 82 bad address")
549 # 2. Not a sw_if_index VPP knows
550 bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:]
552 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
553 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
554 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
556 DHCP(options=[('message-type', 'offer'),
557 ('relay_agent_Information', bad_if_index),
560 self.send_and_assert_no_replies(self.pg0, pkts,
561 "DHCP offer option 82 bad if index")
564 # Send a DHCP request in VRF 1. should be dropped.
566 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
567 "DHCP with no configuration VRF 1")
570 # Delete the DHCP config in VRF 0
571 # Should now drop requests.
573 Proxy.remove_vpp_config()
575 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
576 "DHCP config removed VRF 0")
577 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
578 "DHCP config removed VRF 1")
581 # Add DHCP config for VRF 1 & 2
583 server_addr1 = self.pg1.remote_ip4
584 src_addr1 = self.pg1.local_ip4
585 Proxy1 = VppDHCPProxy(
591 Proxy1.add_vpp_config()
593 server_addr2 = self.pg2.remote_ip4
594 src_addr2 = self.pg2.local_ip4
595 Proxy2 = VppDHCPProxy(
601 Proxy2.add_vpp_config()
604 # Confim DHCP requests ok in VRF 1 & 2.
605 # - dropped on IP config on client interface
607 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
608 "DHCP config removed VRF 1")
609 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
610 "DHCP config removed VRF 2")
613 # configure an IP address on the client facing interface
615 self.pg4.config_ip4()
616 self.pg4.add_stream(pkts_disc_vrf1)
617 self.pg_enable_capture(self.pg_interfaces)
619 rx = self.pg1.get_capture(1)
621 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
623 self.pg5.config_ip4()
624 self.pg5.add_stream(pkts_disc_vrf2)
625 self.pg_enable_capture(self.pg_interfaces)
627 rx = self.pg2.get_capture(1)
629 self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5)
633 # table=1, vss_type=1, vpn_index=1, oui=4
634 # table=2, vss_type=0, vpn_id = "ip4-table-2"
635 self.vapi.dhcp_proxy_set_vss(tbl_id=1, vss_type=1,
636 vpn_index=1, oui=4, is_add=1)
637 self.vapi.dhcp_proxy_set_vss(tbl_id=2, vss_type=0,
638 vpn_ascii_id="ip4-table-2", is_add=1)
640 self.pg4.add_stream(pkts_disc_vrf1)
641 self.pg_enable_capture(self.pg_interfaces)
644 rx = self.pg1.get_capture(1)
646 self.verify_relayed_dhcp_discover(rx, self.pg1,
650 self.pg5.add_stream(pkts_disc_vrf2)
651 self.pg_enable_capture(self.pg_interfaces)
654 rx = self.pg2.get_capture(1)
656 self.verify_relayed_dhcp_discover(rx, self.pg2,
658 vpn_id="ip4-table-2")
661 # Add a second DHCP server in VRF 1
662 # expect clients messages to be relay to both configured servers
664 self.pg1.generate_remote_hosts(2)
665 server_addr12 = self.pg1.remote_hosts[1].ip4
667 Proxy12 = VppDHCPProxy(
673 Proxy12.add_vpp_config()
676 # We'll need an ARP entry for the server to send it packets
678 arp_entry = VppNeighbor(self,
679 self.pg1.sw_if_index,
680 self.pg1.remote_hosts[1].mac,
681 self.pg1.remote_hosts[1].ip4)
682 arp_entry.add_vpp_config()
685 # Send a discover from the client. expect two relayed messages
686 # The frist packet is sent to the second server
687 # We're not enforcing that here, it's just the way it is.
689 self.pg4.add_stream(pkts_disc_vrf1)
690 self.pg_enable_capture(self.pg_interfaces)
693 rx = self.pg1.get_capture(2)
695 option_82 = self.verify_relayed_dhcp_discover(
698 dst_mac=self.pg1.remote_hosts[1].mac,
699 dst_ip=self.pg1.remote_hosts[1].ip4,
701 self.verify_relayed_dhcp_discover(rx[1], self.pg1,
706 # Send both packets back. Client gets both.
708 p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
709 IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
710 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
712 DHCP(options=[('message-type', 'offer'),
713 ('relay_agent_Information', option_82),
715 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
716 IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
717 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
719 DHCP(options=[('message-type', 'offer'),
720 ('relay_agent_Information', option_82),
724 self.pg1.add_stream(pkts)
725 self.pg_enable_capture(self.pg_interfaces)
728 rx = self.pg4.get_capture(2)
730 self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4)
731 self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4)
734 # Ensure offers from non-servers are dropeed
736 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
737 IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
738 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
740 DHCP(options=[('message-type', 'offer'),
741 ('relay_agent_Information', option_82),
743 self.send_and_assert_no_replies(self.pg1, p2,
744 "DHCP offer from non-server")
747 # Ensure only the discover is sent to multiple servers
749 p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
750 src=self.pg4.remote_mac) /
751 IP(src="0.0.0.0", dst="255.255.255.255") /
752 UDP(sport=DHCP4_CLIENT_PORT,
753 dport=DHCP4_SERVER_PORT) /
755 DHCP(options=[('message-type', 'request'),
758 self.pg4.add_stream(p_req_vrf1)
759 self.pg_enable_capture(self.pg_interfaces)
762 rx = self.pg1.get_capture(1)
765 # Remove the second DHCP server
767 Proxy12.remove_vpp_config()
770 # Test we can still relay with the first
772 self.pg4.add_stream(pkts_disc_vrf1)
773 self.pg_enable_capture(self.pg_interfaces)
776 rx = self.pg1.get_capture(1)
778 self.verify_relayed_dhcp_discover(rx, self.pg1,
783 # Remove the VSS config
784 # relayed DHCP has default vlaues in the option.
786 self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_add=0)
787 self.vapi.dhcp_proxy_set_vss(tbl_id=2, is_add=0)
789 self.pg4.add_stream(pkts_disc_vrf1)
790 self.pg_enable_capture(self.pg_interfaces)
793 rx = self.pg1.get_capture(1)
795 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
798 # remove DHCP config to cleanup
800 Proxy1.remove_vpp_config()
801 Proxy2.remove_vpp_config()
803 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
804 "DHCP cleanup VRF 0")
805 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
806 "DHCP cleanup VRF 1")
807 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
808 "DHCP cleanup VRF 2")
810 self.pg3.unconfig_ip4()
811 self.pg4.unconfig_ip4()
812 self.pg5.unconfig_ip4()
814 def test_dhcp6_proxy(self):
817 # Verify no response to DHCP request without DHCP config
819 dhcp_solicit_dst = "ff02::1:2"
820 dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac)
821 dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac)
822 dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac)
823 server_addr_vrf0 = self.pg0.remote_ip6
824 src_addr_vrf0 = self.pg0.local_ip6
825 server_addr_vrf1 = self.pg1.remote_ip6
826 src_addr_vrf1 = self.pg1.local_ip6
827 server_addr_vrf2 = self.pg2.remote_ip6
828 src_addr_vrf2 = self.pg2.local_ip6
830 dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
831 p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
832 IPv6(src=dhcp_solicit_src_vrf0,
833 dst=dhcp_solicit_dst) /
834 UDP(sport=DHCP6_SERVER_PORT,
835 dport=DHCP6_CLIENT_PORT) /
837 p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
838 IPv6(src=dhcp_solicit_src_vrf1,
839 dst=dhcp_solicit_dst) /
840 UDP(sport=DHCP6_SERVER_PORT,
841 dport=DHCP6_CLIENT_PORT) /
843 p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) /
844 IPv6(src=dhcp_solicit_src_vrf2,
845 dst=dhcp_solicit_dst) /
846 UDP(sport=DHCP6_SERVER_PORT,
847 dport=DHCP6_CLIENT_PORT) /
850 self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
851 "DHCP with no configuration")
852 self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
853 "DHCP with no configuration")
854 self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2,
855 "DHCP with no configuration")
858 # DHCPv6 config in VRF 0.
859 # Packets still dropped because the client facing interface has no
862 Proxy = VppDHCPProxy(
868 Proxy.add_vpp_config()
870 self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
871 "DHCP with no configuration")
872 self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
873 "DHCP with no configuration")
876 # configure an IP address on the client facing interface
878 self.pg3.config_ip6()
881 # Now the DHCP requests are relayed to the server
883 self.pg3.add_stream(p_solicit_vrf0)
884 self.pg_enable_capture(self.pg_interfaces)
887 rx = self.pg0.get_capture(1)
889 self.verify_dhcp6_solicit(rx[0], self.pg0,
890 dhcp_solicit_src_vrf0,
894 # Exception cases for rejected relay responses
897 # 1 - not a relay reply
898 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
899 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
900 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
902 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
903 "DHCP6 not a relay reply")
905 # 2 - no relay message option
906 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
907 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
908 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
911 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
912 "DHCP not a relay message")
915 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
916 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
917 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
919 DHCP6OptRelayMsg(optlen=0) /
921 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
922 "DHCP6 no circuit ID")
923 # 4 - wrong circuit ID
924 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
925 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
926 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
928 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
929 DHCP6OptRelayMsg(optlen=0) /
931 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
932 "DHCP6 wrong circuit ID")
935 # Send the relay response (the advertisement)
937 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
938 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
939 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
941 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
942 DHCP6OptRelayMsg(optlen=0) /
943 DHCP6_Advertise(trid=1) /
944 DHCP6OptStatusCode(statuscode=0))
945 pkts_adv_vrf0 = [p_adv_vrf0]
947 self.pg0.add_stream(pkts_adv_vrf0)
948 self.pg_enable_capture(self.pg_interfaces)
951 rx = self.pg3.get_capture(1)
953 self.verify_dhcp6_advert(rx[0], self.pg3, "::")
956 # Send the relay response (the advertisement)
957 # - with peer address
958 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
959 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
960 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
961 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
962 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
963 DHCP6OptRelayMsg(optlen=0) /
964 DHCP6_Advertise(trid=1) /
965 DHCP6OptStatusCode(statuscode=0))
966 pkts_adv_vrf0 = [p_adv_vrf0]
968 self.pg0.add_stream(pkts_adv_vrf0)
969 self.pg_enable_capture(self.pg_interfaces)
972 rx = self.pg3.get_capture(1)
974 self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
977 # Add all the config for VRF 1 & 2
979 Proxy1 = VppDHCPProxy(
985 Proxy1.add_vpp_config()
986 self.pg4.config_ip6()
988 Proxy2 = VppDHCPProxy(
994 Proxy2.add_vpp_config()
995 self.pg5.config_ip6()
1000 self.pg4.add_stream(p_solicit_vrf1)
1001 self.pg_enable_capture(self.pg_interfaces)
1004 rx = self.pg1.get_capture(1)
1006 self.verify_dhcp6_solicit(rx[0], self.pg1,
1007 dhcp_solicit_src_vrf1,
1008 self.pg4.remote_mac)
1013 self.pg5.add_stream(p_solicit_vrf2)
1014 self.pg_enable_capture(self.pg_interfaces)
1017 rx = self.pg2.get_capture(1)
1019 self.verify_dhcp6_solicit(rx[0], self.pg2,
1020 dhcp_solicit_src_vrf2,
1021 self.pg5.remote_mac)
1026 p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1027 IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1028 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1029 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1030 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1031 DHCP6OptRelayMsg(optlen=0) /
1032 DHCP6_Advertise(trid=1) /
1033 DHCP6OptStatusCode(statuscode=0))
1034 pkts_adv_vrf1 = [p_adv_vrf1]
1036 self.pg1.add_stream(pkts_adv_vrf1)
1037 self.pg_enable_capture(self.pg_interfaces)
1040 rx = self.pg4.get_capture(1)
1042 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1047 self.vapi.dhcp_proxy_set_vss(
1048 tbl_id=1, vss_type=1, oui=4, vpn_index=1, is_ipv6=1, is_add=1)
1049 self.vapi.dhcp_proxy_set_vss(
1052 vpn_ascii_id="IPv6-table-2",
1056 self.pg4.add_stream(p_solicit_vrf1)
1057 self.pg_enable_capture(self.pg_interfaces)
1060 rx = self.pg1.get_capture(1)
1062 self.verify_dhcp6_solicit(rx[0], self.pg1,
1063 dhcp_solicit_src_vrf1,
1064 self.pg4.remote_mac,
1068 self.pg5.add_stream(p_solicit_vrf2)
1069 self.pg_enable_capture(self.pg_interfaces)
1072 rx = self.pg2.get_capture(1)
1074 self.verify_dhcp6_solicit(rx[0], self.pg2,
1075 dhcp_solicit_src_vrf2,
1076 self.pg5.remote_mac,
1077 vpn_id="IPv6-table-2")
1080 # Remove the VSS config
1081 # relayed DHCP has default vlaues in the option.
1083 self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_ipv6=1, is_add=0)
1085 self.pg4.add_stream(p_solicit_vrf1)
1086 self.pg_enable_capture(self.pg_interfaces)
1089 rx = self.pg1.get_capture(1)
1091 self.verify_dhcp6_solicit(rx[0], self.pg1,
1092 dhcp_solicit_src_vrf1,
1093 self.pg4.remote_mac)
1096 # Add a second DHCP server in VRF 1
1097 # expect clients messages to be relay to both configured servers
1099 self.pg1.generate_remote_hosts(2)
1100 server_addr12 = self.pg1.remote_hosts[1].ip6
1102 Proxy12 = VppDHCPProxy(
1108 Proxy12.add_vpp_config()
1111 # We'll need an ND entry for the server to send it packets
1113 nd_entry = VppNeighbor(self,
1114 self.pg1.sw_if_index,
1115 self.pg1.remote_hosts[1].mac,
1116 self.pg1.remote_hosts[1].ip6)
1117 nd_entry.add_vpp_config()
1120 # Send a discover from the client. expect two relayed messages
1121 # The frist packet is sent to the second server
1122 # We're not enforcing that here, it's just the way it is.
1124 self.pg4.add_stream(p_solicit_vrf1)
1125 self.pg_enable_capture(self.pg_interfaces)
1128 rx = self.pg1.get_capture(2)
1130 self.verify_dhcp6_solicit(rx[0], self.pg1,
1131 dhcp_solicit_src_vrf1,
1132 self.pg4.remote_mac)
1133 self.verify_dhcp6_solicit(rx[1], self.pg1,
1134 dhcp_solicit_src_vrf1,
1135 self.pg4.remote_mac,
1136 dst_mac=self.pg1.remote_hosts[1].mac,
1137 dst_ip=self.pg1.remote_hosts[1].ip6)
1140 # Send both packets back. Client gets both.
1142 p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1143 IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1144 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1145 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1146 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1147 DHCP6OptRelayMsg(optlen=0) /
1148 DHCP6_Advertise(trid=1) /
1149 DHCP6OptStatusCode(statuscode=0))
1150 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1151 IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
1152 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1153 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1154 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1155 DHCP6OptRelayMsg(optlen=0) /
1156 DHCP6_Advertise(trid=1) /
1157 DHCP6OptStatusCode(statuscode=0))
1161 self.pg1.add_stream(pkts)
1162 self.pg_enable_capture(self.pg_interfaces)
1165 rx = self.pg4.get_capture(2)
1167 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1168 self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1)
1171 # Ensure only solicit messages are duplicated
1173 p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
1174 IPv6(src=dhcp_solicit_src_vrf1,
1175 dst=dhcp_solicit_dst) /
1176 UDP(sport=DHCP6_SERVER_PORT,
1177 dport=DHCP6_CLIENT_PORT) /
1180 self.pg4.add_stream(p_request_vrf1)
1181 self.pg_enable_capture(self.pg_interfaces)
1184 rx = self.pg1.get_capture(1)
1187 # Test we drop DHCP packets from addresses that are not configured as
1190 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1191 IPv6(dst=self.pg1.local_ip6, src="3001::1") /
1192 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1193 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1194 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1195 DHCP6OptRelayMsg(optlen=0) /
1196 DHCP6_Advertise(trid=1) /
1197 DHCP6OptStatusCode(statuscode=0))
1198 self.send_and_assert_no_replies(self.pg1, p2,
1199 "DHCP6 not from server")
1202 # Remove the second DHCP server
1204 Proxy12.remove_vpp_config()
1207 # Test we can still relay with the first
1209 self.pg4.add_stream(p_solicit_vrf1)
1210 self.pg_enable_capture(self.pg_interfaces)
1213 rx = self.pg1.get_capture(1)
1215 self.verify_dhcp6_solicit(rx[0], self.pg1,
1216 dhcp_solicit_src_vrf1,
1217 self.pg4.remote_mac)
1222 Proxy.remove_vpp_config()
1223 Proxy1.remove_vpp_config()
1224 Proxy2.remove_vpp_config()
1226 self.pg3.unconfig_ip6()
1227 self.pg4.unconfig_ip6()
1228 self.pg5.unconfig_ip6()
1230 def test_dhcp_client(self):
1233 vdscp = VppEnum.vl_api_ip_dscp_t
1234 hostname = 'universal-dp'
1236 self.pg_enable_capture(self.pg_interfaces)
1239 # Configure DHCP client on PG3 and capture the discover sent
1241 Client = VppDHCPClient(self, self.pg3.sw_if_index, hostname)
1242 Client.add_vpp_config()
1243 self.assertTrue(Client.query_vpp_config())
1245 rx = self.pg3.get_capture(1)
1247 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1250 # Send back on offer, expect the request
1252 p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1253 IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1254 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1256 yiaddr=self.pg3.local_ip4,
1257 chaddr=mac_pton(self.pg3.local_mac)) /
1258 DHCP(options=[('message-type', 'offer'),
1259 ('server_id', self.pg3.remote_ip4),
1262 self.pg3.add_stream(p_offer)
1263 self.pg_enable_capture(self.pg_interfaces)
1266 rx = self.pg3.get_capture(1)
1267 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1271 # Send an acknowledgment
1273 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1274 IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1275 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1276 BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1277 chaddr=mac_pton(self.pg3.local_mac)) /
1278 DHCP(options=[('message-type', 'ack'),
1279 ('subnet_mask', "255.255.255.0"),
1280 ('router', self.pg3.remote_ip4),
1281 ('server_id', self.pg3.remote_ip4),
1282 ('lease_time', 43200),
1285 self.pg3.add_stream(p_ack)
1286 self.pg_enable_capture(self.pg_interfaces)
1290 # We'll get an ARP request for the router address
1292 rx = self.pg3.get_capture(1)
1294 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1295 self.pg_enable_capture(self.pg_interfaces)
1298 # At the end of this procedure there should be a connected route
1301 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1302 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1305 # remove the DHCP config
1307 Client.remove_vpp_config()
1310 # and now the route should be gone
1312 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1313 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1316 # Start the procedure again. this time have VPP send the client-ID
1317 # and set the DSCP value
1319 self.pg3.admin_down()
1322 Client.set_client(self.pg3.sw_if_index, hostname,
1323 id=self.pg3.local_mac,
1324 dscp=vdscp.IP_API_DSCP_EF)
1325 Client.add_vpp_config()
1327 rx = self.pg3.get_capture(1)
1329 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1331 dscp=vdscp.IP_API_DSCP_EF)
1333 # TODO: VPP DHCP client should not accept DHCP OFFER message with
1334 # the XID (Transaction ID) not matching the XID of the most recent
1335 # DHCP DISCOVERY message.
1336 # Such DHCP OFFER message must be silently discarded - RFC2131.
1337 # Reported in Jira ticket: VPP-99
1338 self.pg3.add_stream(p_offer)
1339 self.pg_enable_capture(self.pg_interfaces)
1342 rx = self.pg3.get_capture(1)
1343 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1345 dscp=vdscp.IP_API_DSCP_EF)
1348 # unicast the ack to the offered address
1350 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1351 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1352 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1353 BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1354 chaddr=mac_pton(self.pg3.local_mac)) /
1355 DHCP(options=[('message-type', 'ack'),
1356 ('subnet_mask', "255.255.255.0"),
1357 ('router', self.pg3.remote_ip4),
1358 ('server_id', self.pg3.remote_ip4),
1359 ('lease_time', 43200),
1362 self.pg3.add_stream(p_ack)
1363 self.pg_enable_capture(self.pg_interfaces)
1367 # We'll get an ARP request for the router address
1369 rx = self.pg3.get_capture(1)
1371 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1372 self.pg_enable_capture(self.pg_interfaces)
1375 # At the end of this procedure there should be a connected route
1378 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1379 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1382 # remove the DHCP config
1384 Client.remove_vpp_config()
1386 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1387 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1390 # Rince and repeat, this time with VPP configured not to set
1391 # the braodcast flag in the discover and request messages,
1392 # and for the server to unicast the responses.
1394 # Configure DHCP client on PG3 and capture the discover sent
1397 self.pg3.sw_if_index,
1399 set_broadcast_flag=False)
1400 Client.add_vpp_config()
1402 rx = self.pg3.get_capture(1)
1404 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1408 # Send back on offer, unicasted to the offered address.
1409 # Expect the request.
1411 p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1412 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1413 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1414 BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1415 chaddr=mac_pton(self.pg3.local_mac)) /
1416 DHCP(options=[('message-type', 'offer'),
1417 ('server_id', self.pg3.remote_ip4),
1420 self.pg3.add_stream(p_offer)
1421 self.pg_enable_capture(self.pg_interfaces)
1424 rx = self.pg3.get_capture(1)
1425 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1430 # Send an acknowledgment, the lease renewal time is 2 seconds
1431 # so we should expect the renew straight after
1433 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1434 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1435 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1436 BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1437 chaddr=mac_pton(self.pg3.local_mac)) /
1438 DHCP(options=[('message-type', 'ack'),
1439 ('subnet_mask', "255.255.255.0"),
1440 ('router', self.pg3.remote_ip4),
1441 ('server_id', self.pg3.remote_ip4),
1442 ('lease_time', 43200),
1443 ('renewal_time', 2),
1446 self.pg3.add_stream(p_ack)
1447 self.pg_enable_capture(self.pg_interfaces)
1451 # We'll get an ARP request for the router address
1453 rx = self.pg3.get_capture(1)
1455 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1456 self.pg_enable_capture(self.pg_interfaces)
1459 # At the end of this procedure there should be a connected route
1462 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1463 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1466 # read the DHCP client details from a dump
1468 clients = self.vapi.dhcp_client_dump()
1470 self.assertEqual(clients[0].client.sw_if_index,
1471 self.pg3.sw_if_index)
1472 self.assertEqual(clients[0].lease.sw_if_index,
1473 self.pg3.sw_if_index)
1474 self.assertEqual(clients[0].client.hostname, hostname)
1475 self.assertEqual(clients[0].lease.hostname, hostname)
1476 # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1477 self.assertEqual(clients[0].lease.state, 2)
1478 self.assertEqual(clients[0].lease.mask_width, 24)
1479 self.assertEqual(str(clients[0].lease.router_address),
1480 self.pg3.remote_ip4)
1481 self.assertEqual(str(clients[0].lease.host_address),
1485 # wait for the unicasted renewal
1486 # the first attempt will be an ARP packet, since we have not yet
1487 # responded to VPP's request
1489 self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose"))
1490 rx = self.pg3.get_capture(1, timeout=10)
1492 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1494 # respond to the arp
1495 p_arp = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1497 hwdst=self.pg3.local_mac,
1498 hwsrc=self.pg3.remote_mac,
1499 pdst=self.pg3.local_ip4,
1500 psrc=self.pg3.remote_ip4))
1501 self.pg3.add_stream(p_arp)
1502 self.pg_enable_capture(self.pg_interfaces)
1505 # the next packet is the unicasted renewal
1506 rx = self.pg3.get_capture(1, timeout=10)
1507 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1512 # send an ACK with different data from the original offer *
1513 self.pg3.generate_remote_hosts(4)
1514 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1515 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1516 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1517 BOOTP(op=1, yiaddr=self.pg3.remote_hosts[3].ip4,
1518 chaddr=mac_pton(self.pg3.local_mac)) /
1519 DHCP(options=[('message-type', 'ack'),
1520 ('subnet_mask', "255.255.255.0"),
1521 ('router', self.pg3.remote_hosts[1].ip4),
1522 ('server_id', self.pg3.remote_hosts[2].ip4),
1523 ('lease_time', 43200),
1524 ('renewal_time', 2),
1527 self.pg3.add_stream(p_ack)
1528 self.pg_enable_capture(self.pg_interfaces)
1532 # read the DHCP client details from a dump
1534 clients = self.vapi.dhcp_client_dump()
1536 self.assertEqual(clients[0].client.sw_if_index,
1537 self.pg3.sw_if_index)
1538 self.assertEqual(clients[0].lease.sw_if_index,
1539 self.pg3.sw_if_index)
1540 self.assertEqual(clients[0].client.hostname, hostname)
1541 self.assertEqual(clients[0].lease.hostname, hostname)
1542 # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1543 self.assertEqual(clients[0].lease.state, 2)
1544 self.assertEqual(clients[0].lease.mask_width, 24)
1545 self.assertEqual(str(clients[0].lease.router_address),
1546 self.pg3.remote_hosts[1].ip4)
1547 self.assertEqual(str(clients[0].lease.host_address),
1548 self.pg3.remote_hosts[3].ip4)
1551 # remove the DHCP config
1553 Client.remove_vpp_config()
1556 # and now the route should be gone
1558 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1559 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1562 # Start the procedure again. Use requested lease time option.
1563 # this time wait for the lease to expire and the client to
1567 self.pg3.admin_down()
1570 self.pg_enable_capture(self.pg_interfaces)
1571 Client.set_client(self.pg3.sw_if_index, hostname)
1572 Client.add_vpp_config()
1574 rx = self.pg3.get_capture(1)
1576 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1579 # Send back on offer with requested lease time, expect the request
1582 p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1583 IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1584 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1586 yiaddr=self.pg3.local_ip4,
1587 chaddr=mac_pton(self.pg3.local_mac)) /
1588 DHCP(options=[('message-type', 'offer'),
1589 ('server_id', self.pg3.remote_ip4),
1590 ('lease_time', lease_time),
1593 self.pg3.add_stream(p_offer)
1594 self.pg_enable_capture(self.pg_interfaces)
1597 rx = self.pg3.get_capture(1)
1598 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1602 # Send an acknowledgment
1604 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1605 IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1606 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1607 BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1608 chaddr=mac_pton(self.pg3.local_mac)) /
1609 DHCP(options=[('message-type', 'ack'),
1610 ('subnet_mask', '255.255.255.0'),
1611 ('router', self.pg3.remote_ip4),
1612 ('server_id', self.pg3.remote_ip4),
1613 ('lease_time', lease_time),
1616 self.pg3.add_stream(p_ack)
1617 self.pg_enable_capture(self.pg_interfaces)
1621 # We'll get an ARP request for the router address
1623 rx = self.pg3.get_capture(1)
1625 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1628 # At the end of this procedure there should be a connected route
1631 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1632 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1635 # the route should be gone after the lease expires
1637 self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32))
1638 self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24))
1641 # remove the DHCP config
1643 Client.remove_vpp_config()
1645 def test_dhcp_client_vlan(self):
1646 """ DHCP Client w/ VLAN"""
1648 vdscp = VppEnum.vl_api_ip_dscp_t
1649 vqos = VppEnum.vl_api_qos_source_t
1650 hostname = 'universal-dp'
1652 self.pg_enable_capture(self.pg_interfaces)
1654 vlan_100 = VppDot1QSubint(self, self.pg3, 100)
1657 output = [scapy.compat.chb(4)] * 256
1658 os = b''.join(output)
1659 rows = [{'outputs': os},
1664 qem1 = VppQosEgressMap(self, 1, rows).add_vpp_config()
1665 qm1 = VppQosMark(self, vlan_100, qem1,
1666 vqos.QOS_API_SOURCE_VLAN).add_vpp_config()
1669 # Configure DHCP client on PG3 and capture the discover sent
1671 Client = VppDHCPClient(
1673 vlan_100.sw_if_index,
1675 dscp=vdscp.IP_API_DSCP_EF)
1676 Client.add_vpp_config()
1678 rx = self.pg3.get_capture(1)
1680 self.assertEqual(rx[0][Dot1Q].vlan, 100)
1681 self.assertEqual(rx[0][Dot1Q].prio, 2)
1683 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1684 dscp=vdscp.IP_API_DSCP_EF)
1687 if __name__ == '__main__':
1688 unittest.main(testRunner=VppTestRunner)