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
13 from scapy.layers.inet import IP, UDP, ICMP
14 from scapy.layers.inet6 import IPv6, in6_getnsmac, in6_mactoifaceid
15 from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
16 from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
17 DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \
18 DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request
19 from socket import AF_INET, AF_INET6
20 from scapy.utils import inet_pton, inet_ntop
21 from scapy.utils6 import in6_ptop
22 from util import mactobinary
24 DHCP4_CLIENT_PORT = 68
25 DHCP4_SERVER_PORT = 67
26 DHCP6_CLIENT_PORT = 547
27 DHCP6_SERVER_PORT = 546
30 class TestDHCP(VppTestCase):
31 """ DHCP Test Case """
34 super(TestDHCP, self).setUp()
36 # create 6 pg interfaces for pg0 to pg5
37 self.create_pg_interfaces(range(6))
40 # pg0 to 2 are IP configured in VRF 0, 1 and 2.
41 # pg3 to 5 are non IP-configured in VRF 0, 1 and 2.
43 for table_id in range(1, 4):
44 tbl4 = VppIpTable(self, table_id)
46 self.tables.append(tbl4)
47 tbl6 = VppIpTable(self, table_id, is_ip6=1)
49 self.tables.append(tbl6)
52 for i in self.pg_interfaces[:3]:
54 i.set_table_ip4(table_id)
55 i.set_table_ip6(table_id)
63 for i in self.pg_interfaces[3:]:
65 i.set_table_ip4(table_id)
66 i.set_table_ip6(table_id)
70 for i in self.pg_interfaces[:3]:
74 for i in self.pg_interfaces:
78 super(TestDHCP, self).tearDown()
80 def verify_dhcp_has_option(self, pkt, option, value):
84 for i in dhcp.options:
87 self.assertEqual(i[1], value)
90 self.assertTrue(found)
92 def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui):
98 for i in dhcp.options:
100 if i[0] == "relay_agent_Information":
102 # There are two sb-options present - each of length 6.
106 self.assertEqual(len(data), 24)
107 elif len(vpn_id) > 0:
108 self.assertEqual(len(data), len(vpn_id)+17)
110 self.assertEqual(len(data), 12)
113 # First sub-option is ID 1, len 4, then encoded
114 # sw_if_index. This test uses low valued indicies
116 # The ID space is VPP internal - so no matching value
119 self.assertEqual(ord(data[0]), 1)
120 self.assertEqual(ord(data[1]), 4)
121 self.assertEqual(ord(data[2]), 0)
122 self.assertEqual(ord(data[3]), 0)
123 self.assertEqual(ord(data[4]), 0)
124 self.assertEqual(ord(data[5]), intf._sw_if_index)
127 # next sub-option is the IP address of the client side
129 # sub-option ID=5, length (of a v4 address)=4
131 claddr = socket.inet_pton(AF_INET, ip_addr)
133 self.assertEqual(ord(data[6]), 5)
134 self.assertEqual(ord(data[7]), 4)
135 self.assertEqual(data[8], claddr[0])
136 self.assertEqual(data[9], claddr[1])
137 self.assertEqual(data[10], claddr[2])
138 self.assertEqual(data[11], claddr[3])
141 # sub-option 151 encodes vss_type 1,
142 # the 3 byte oui and the 4 byte fib_id
143 self.assertEqual(id_len, 0)
144 self.assertEqual(ord(data[12]), 151)
145 self.assertEqual(ord(data[13]), 8)
146 self.assertEqual(ord(data[14]), 1)
147 self.assertEqual(ord(data[15]), 0)
148 self.assertEqual(ord(data[16]), 0)
149 self.assertEqual(ord(data[17]), oui)
150 self.assertEqual(ord(data[18]), 0)
151 self.assertEqual(ord(data[19]), 0)
152 self.assertEqual(ord(data[20]), 0)
153 self.assertEqual(ord(data[21]), fib_id)
155 # VSS control sub-option
156 self.assertEqual(ord(data[22]), 152)
157 self.assertEqual(ord(data[23]), 0)
160 # sub-option 151 encode vss_type of 0
161 # followerd by vpn_id in ascii
162 self.assertEqual(oui, 0)
163 self.assertEqual(ord(data[12]), 151)
164 self.assertEqual(ord(data[13]), id_len+1)
165 self.assertEqual(ord(data[14]), 0)
166 self.assertEqual(data[15:15+id_len], vpn_id)
168 # VSS control sub-option
169 self.assertEqual(ord(data[15+len(vpn_id)]), 152)
170 self.assertEqual(ord(data[16+len(vpn_id)]), 0)
173 self.assertTrue(found)
177 def verify_dhcp_msg_type(self, pkt, name):
180 for o in dhcp.options:
182 if o[0] == "message-type" \
183 and DHCPTypes[o[1]] == name:
185 self.assertTrue(found)
187 def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0):
189 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
190 self.assertEqual(ether.src, intf.local_mac)
193 self.assertEqual(ip.dst, "255.255.255.255")
194 self.assertEqual(ip.src, intf.local_ip4)
197 self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
198 self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
200 self.verify_dhcp_msg_type(pkt, "offer")
201 data = self.validate_relay_options(pkt, intf, intf.local_ip4,
204 def verify_orig_dhcp_pkt(self, pkt, intf):
206 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
207 self.assertEqual(ether.src, intf.local_mac)
210 self.assertEqual(ip.dst, "255.255.255.255")
211 self.assertEqual(ip.src, "0.0.0.0")
214 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
215 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
217 def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None,
219 self.verify_orig_dhcp_pkt(pkt, intf)
221 self.verify_dhcp_msg_type(pkt, "discover")
222 self.verify_dhcp_has_option(pkt, "hostname", hostname)
224 self.verify_dhcp_has_option(pkt, "client_id", client_id)
226 self.assertEqual(bootp.ciaddr, "0.0.0.0")
227 self.assertEqual(bootp.giaddr, "0.0.0.0")
229 self.assertEqual(bootp.flags, 0x8000)
231 self.assertEqual(bootp.flags, 0x0000)
233 def verify_orig_dhcp_request(self, pkt, intf, hostname, ip,
235 self.verify_orig_dhcp_pkt(pkt, intf)
237 self.verify_dhcp_msg_type(pkt, "request")
238 self.verify_dhcp_has_option(pkt, "hostname", hostname)
239 self.verify_dhcp_has_option(pkt, "requested_addr", ip)
241 self.assertEqual(bootp.ciaddr, "0.0.0.0")
242 self.assertEqual(bootp.giaddr, "0.0.0.0")
244 self.assertEqual(bootp.flags, 0x8000)
246 self.assertEqual(bootp.flags, 0x0000)
248 def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None,
251 dst_mac=None, dst_ip=None):
253 dst_mac = intf.remote_mac
255 dst_ip = intf.remote_ip4
258 self.assertEqual(ether.dst, dst_mac)
259 self.assertEqual(ether.src, intf.local_mac)
262 self.assertEqual(ip.dst, dst_ip)
263 self.assertEqual(ip.src, intf.local_ip4)
266 self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
267 self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
272 for o in dhcp.options:
274 if o[0] == "message-type" \
275 and DHCPTypes[o[1]] == "discover":
277 self.assertTrue(is_discover)
279 data = self.validate_relay_options(pkt, src_intf,
285 def verify_dhcp6_solicit(self, pkt, intf,
293 dst_mac = intf.remote_mac
295 dst_ip = in6_ptop(intf.remote_ip6)
298 self.assertEqual(ether.dst, dst_mac)
299 self.assertEqual(ether.src, intf.local_mac)
302 self.assertEqual(in6_ptop(ip.dst), dst_ip)
303 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
306 self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
307 self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
309 relay = pkt[DHCP6_RelayForward]
310 self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
311 oid = pkt[DHCP6OptIfaceId]
312 cll = pkt[DHCP6OptClientLinkLayerAddr]
313 self.assertEqual(cll.optlen, 8)
314 self.assertEqual(cll.lltype, 1)
315 self.assertEqual(cll.clladdr, peer_mac)
320 self.assertEqual(id_len, 0)
321 vss = pkt[DHCP6OptVSS]
322 self.assertEqual(vss.optlen, 8)
323 self.assertEqual(vss.type, 1)
324 # the OUI and FIB-id are really 3 and 4 bytes resp.
325 # but the tested range is small
326 self.assertEqual(ord(vss.data[0]), 0)
327 self.assertEqual(ord(vss.data[1]), 0)
328 self.assertEqual(ord(vss.data[2]), oui)
329 self.assertEqual(ord(vss.data[3]), 0)
330 self.assertEqual(ord(vss.data[4]), 0)
331 self.assertEqual(ord(vss.data[5]), 0)
332 self.assertEqual(ord(vss.data[6]), fib_id)
335 self.assertEqual(oui, 0)
336 vss = pkt[DHCP6OptVSS]
337 self.assertEqual(vss.optlen, id_len+1)
338 self.assertEqual(vss.type, 0)
339 self.assertEqual(vss.data[0:id_len], vpn_id)
341 # the relay message should be an encoded Solicit
342 msg = pkt[DHCP6OptRelayMsg]
343 sol = DHCP6_Solicit()
344 self.assertEqual(msg.optlen, len(str(sol)))
345 self.assertEqual(str(sol), (str(msg[1]))[:msg.optlen])
347 def verify_dhcp6_advert(self, pkt, intf, peer):
349 self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
350 self.assertEqual(ether.src, intf.local_mac)
353 self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
354 self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
357 self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
358 self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
360 # not sure why this is not decoding
361 # adv = pkt[DHCP6_Advertise]
363 def test_dhcp_proxy(self):
367 # Verify no response to DHCP request without DHCP config
369 p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff",
370 src=self.pg3.remote_mac) /
371 IP(src="0.0.0.0", dst="255.255.255.255") /
372 UDP(sport=DHCP4_CLIENT_PORT,
373 dport=DHCP4_SERVER_PORT) /
375 DHCP(options=[('message-type', 'discover'), ('end')]))
376 pkts_disc_vrf0 = [p_disc_vrf0]
377 p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
378 src=self.pg4.remote_mac) /
379 IP(src="0.0.0.0", dst="255.255.255.255") /
380 UDP(sport=DHCP4_CLIENT_PORT,
381 dport=DHCP4_SERVER_PORT) /
383 DHCP(options=[('message-type', 'discover'), ('end')]))
384 pkts_disc_vrf1 = [p_disc_vrf1]
385 p_disc_vrf2 = (Ether(dst="ff:ff:ff:ff:ff:ff",
386 src=self.pg5.remote_mac) /
387 IP(src="0.0.0.0", dst="255.255.255.255") /
388 UDP(sport=DHCP4_CLIENT_PORT,
389 dport=DHCP4_SERVER_PORT) /
391 DHCP(options=[('message-type', 'discover'), ('end')]))
392 pkts_disc_vrf2 = [p_disc_vrf2]
394 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
395 "DHCP with no configuration")
396 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
397 "DHCP with no configuration")
398 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
399 "DHCP with no configuration")
402 # Enable DHCP proxy in VRF 0
404 server_addr = self.pg0.remote_ip4n
405 src_addr = self.pg0.local_ip4n
407 self.vapi.dhcp_proxy_config(server_addr,
412 # Discover packets from the client are dropped because there is no
413 # IP address configured on the client facing interface
415 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
416 "Discover DHCP no relay address")
419 # Inject a response from the server
420 # dropped, because there is no IP addrees on the
421 # client interfce to fill in the option.
423 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
424 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
425 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
427 DHCP(options=[('message-type', 'offer'), ('end')]))
430 self.send_and_assert_no_replies(self.pg3, pkts,
431 "Offer DHCP no relay address")
434 # configure an IP address on the client facing interface
436 self.pg3.config_ip4()
439 # Try again with a discover packet
440 # Rx'd packet should be to the server address and from the configured
442 # UDP source ports are unchanged
443 # we've no option 82 config so that should be absent
445 self.pg3.add_stream(pkts_disc_vrf0)
446 self.pg_enable_capture(self.pg_interfaces)
449 rx = self.pg0.get_capture(1)
452 option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
456 # Create an DHCP offer reply from the server with a correctly formatted
457 # option 82. i.e. send back what we just captured
458 # The offer, sent mcast to the client, still has option 82.
460 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
461 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
462 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
464 DHCP(options=[('message-type', 'offer'),
465 ('relay_agent_Information', option_82),
469 self.pg0.add_stream(pkts)
470 self.pg_enable_capture(self.pg_interfaces)
473 rx = self.pg3.get_capture(1)
476 self.verify_dhcp_offer(rx, self.pg3)
481 # 1. not our IP address = not checked by VPP? so offer is replayed
483 bad_ip = option_82[0:8] + chr(33) + option_82[9:]
485 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
486 IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
487 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
489 DHCP(options=[('message-type', 'offer'),
490 ('relay_agent_Information', bad_ip),
493 self.send_and_assert_no_replies(self.pg0, pkts,
494 "DHCP offer option 82 bad address")
496 # 2. Not a sw_if_index VPP knows
497 bad_if_index = option_82[0:2] + chr(33) + option_82[3:]
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', bad_if_index),
507 self.send_and_assert_no_replies(self.pg0, pkts,
508 "DHCP offer option 82 bad if index")
511 # Send a DHCP request in VRF 1. should be dropped.
513 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
514 "DHCP with no configuration VRF 1")
517 # Delete the DHCP config in VRF 0
518 # Should now drop requests.
520 self.vapi.dhcp_proxy_config(server_addr,
525 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
526 "DHCP config removed VRF 0")
527 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
528 "DHCP config removed VRF 1")
531 # Add DHCP config for VRF 1 & 2
533 server_addr1 = self.pg1.remote_ip4n
534 src_addr1 = self.pg1.local_ip4n
535 self.vapi.dhcp_proxy_config(server_addr1,
539 server_addr2 = self.pg2.remote_ip4n
540 src_addr2 = self.pg2.local_ip4n
541 self.vapi.dhcp_proxy_config(server_addr2,
547 # Confim DHCP requests ok in VRF 1 & 2.
548 # - dropped on IP config on client interface
550 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
551 "DHCP config removed VRF 1")
552 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
553 "DHCP config removed VRF 2")
556 # configure an IP address on the client facing interface
558 self.pg4.config_ip4()
559 self.pg4.add_stream(pkts_disc_vrf1)
560 self.pg_enable_capture(self.pg_interfaces)
562 rx = self.pg1.get_capture(1)
564 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
566 self.pg5.config_ip4()
567 self.pg5.add_stream(pkts_disc_vrf2)
568 self.pg_enable_capture(self.pg_interfaces)
570 rx = self.pg2.get_capture(1)
572 self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5)
576 # table=1, vss_type=1, vpn_index=1, oui=4
577 # table=2, vss_type=0, vpn_id = "ip4-table-2"
578 self.vapi.dhcp_proxy_set_vss(1, 1, vpn_index=1, oui=4, is_add=1)
579 self.vapi.dhcp_proxy_set_vss(2, 0, "ip4-table-2", is_add=1)
581 self.pg4.add_stream(pkts_disc_vrf1)
582 self.pg_enable_capture(self.pg_interfaces)
585 rx = self.pg1.get_capture(1)
587 self.verify_relayed_dhcp_discover(rx, self.pg1,
591 self.pg5.add_stream(pkts_disc_vrf2)
592 self.pg_enable_capture(self.pg_interfaces)
595 rx = self.pg2.get_capture(1)
597 self.verify_relayed_dhcp_discover(rx, self.pg2,
599 vpn_id="ip4-table-2")
602 # Add a second DHCP server in VRF 1
603 # expect clients messages to be relay to both configured servers
605 self.pg1.generate_remote_hosts(2)
606 server_addr12 = socket.inet_pton(AF_INET, self.pg1.remote_hosts[1].ip4)
608 self.vapi.dhcp_proxy_config(server_addr12,
615 # We'll need an ARP entry for the server to send it packets
617 arp_entry = VppNeighbor(self,
618 self.pg1.sw_if_index,
619 self.pg1.remote_hosts[1].mac,
620 self.pg1.remote_hosts[1].ip4)
621 arp_entry.add_vpp_config()
624 # Send a discover from the client. expect two relayed messages
625 # The frist packet is sent to the second server
626 # We're not enforcing that here, it's just the way it is.
628 self.pg4.add_stream(pkts_disc_vrf1)
629 self.pg_enable_capture(self.pg_interfaces)
632 rx = self.pg1.get_capture(2)
634 option_82 = self.verify_relayed_dhcp_discover(
637 dst_mac=self.pg1.remote_hosts[1].mac,
638 dst_ip=self.pg1.remote_hosts[1].ip4,
640 self.verify_relayed_dhcp_discover(rx[1], self.pg1,
645 # Send both packets back. Client gets both.
647 p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
648 IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
649 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
651 DHCP(options=[('message-type', 'offer'),
652 ('relay_agent_Information', option_82),
654 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
655 IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
656 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
658 DHCP(options=[('message-type', 'offer'),
659 ('relay_agent_Information', option_82),
663 self.pg1.add_stream(pkts)
664 self.pg_enable_capture(self.pg_interfaces)
667 rx = self.pg4.get_capture(2)
669 self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4)
670 self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4)
673 # Ensure offers from non-servers are dropeed
675 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
676 IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
677 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
679 DHCP(options=[('message-type', 'offer'),
680 ('relay_agent_Information', option_82),
682 self.send_and_assert_no_replies(self.pg1, p2,
683 "DHCP offer from non-server")
686 # Ensure only the discover is sent to multiple servers
688 p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
689 src=self.pg4.remote_mac) /
690 IP(src="0.0.0.0", dst="255.255.255.255") /
691 UDP(sport=DHCP4_CLIENT_PORT,
692 dport=DHCP4_SERVER_PORT) /
694 DHCP(options=[('message-type', 'request'),
697 self.pg4.add_stream(p_req_vrf1)
698 self.pg_enable_capture(self.pg_interfaces)
701 rx = self.pg1.get_capture(1)
704 # Remove the second DHCP server
706 self.vapi.dhcp_proxy_config(server_addr12,
713 # Test we can still relay with the first
715 self.pg4.add_stream(pkts_disc_vrf1)
716 self.pg_enable_capture(self.pg_interfaces)
719 rx = self.pg1.get_capture(1)
721 self.verify_relayed_dhcp_discover(rx, self.pg1,
726 # Remove the VSS config
727 # relayed DHCP has default vlaues in the option.
729 self.vapi.dhcp_proxy_set_vss(1, is_add=0)
730 self.vapi.dhcp_proxy_set_vss(2, is_add=0)
732 self.pg4.add_stream(pkts_disc_vrf1)
733 self.pg_enable_capture(self.pg_interfaces)
736 rx = self.pg1.get_capture(1)
738 self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
741 # remove DHCP config to cleanup
743 self.vapi.dhcp_proxy_config(server_addr1,
748 self.vapi.dhcp_proxy_config(server_addr2,
754 self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
755 "DHCP cleanup VRF 0")
756 self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
757 "DHCP cleanup VRF 1")
758 self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
759 "DHCP cleanup VRF 2")
761 self.pg3.unconfig_ip4()
762 self.pg4.unconfig_ip4()
763 self.pg5.unconfig_ip4()
765 def test_dhcp6_proxy(self):
768 # Verify no response to DHCP request without DHCP config
770 dhcp_solicit_dst = "ff02::1:2"
771 dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac)
772 dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac)
773 dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac)
774 server_addr_vrf0 = self.pg0.remote_ip6n
775 src_addr_vrf0 = self.pg0.local_ip6n
776 server_addr_vrf1 = self.pg1.remote_ip6n
777 src_addr_vrf1 = self.pg1.local_ip6n
778 server_addr_vrf2 = self.pg2.remote_ip6n
779 src_addr_vrf2 = self.pg2.local_ip6n
781 dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
782 p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
783 IPv6(src=dhcp_solicit_src_vrf0,
784 dst=dhcp_solicit_dst) /
785 UDP(sport=DHCP6_SERVER_PORT,
786 dport=DHCP6_CLIENT_PORT) /
788 p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
789 IPv6(src=dhcp_solicit_src_vrf1,
790 dst=dhcp_solicit_dst) /
791 UDP(sport=DHCP6_SERVER_PORT,
792 dport=DHCP6_CLIENT_PORT) /
794 p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) /
795 IPv6(src=dhcp_solicit_src_vrf2,
796 dst=dhcp_solicit_dst) /
797 UDP(sport=DHCP6_SERVER_PORT,
798 dport=DHCP6_CLIENT_PORT) /
801 self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
802 "DHCP with no configuration")
803 self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
804 "DHCP with no configuration")
805 self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2,
806 "DHCP with no configuration")
809 # DHCPv6 config in VRF 0.
810 # Packets still dropped because the client facing interface has no
813 self.vapi.dhcp_proxy_config(server_addr_vrf0,
819 self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
820 "DHCP with no configuration")
821 self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
822 "DHCP with no configuration")
825 # configure an IP address on the client facing interface
827 self.pg3.config_ip6()
830 # Now the DHCP requests are relayed to the server
832 self.pg3.add_stream(p_solicit_vrf0)
833 self.pg_enable_capture(self.pg_interfaces)
836 rx = self.pg0.get_capture(1)
838 self.verify_dhcp6_solicit(rx[0], self.pg0,
839 dhcp_solicit_src_vrf0,
843 # Exception cases for rejected relay responses
846 # 1 - not a relay reply
847 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
848 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
849 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
851 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
852 "DHCP6 not a relay reply")
854 # 2 - no relay message option
855 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
856 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
857 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
860 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
861 "DHCP not a relay message")
864 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
865 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
866 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
868 DHCP6OptRelayMsg(optlen=0) /
870 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
871 "DHCP6 no circuit ID")
872 # 4 - wrong circuit ID
873 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
874 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
875 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
877 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
878 DHCP6OptRelayMsg(optlen=0) /
880 self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
881 "DHCP6 wrong circuit ID")
884 # Send the relay response (the advertisement)
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 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
891 DHCP6OptRelayMsg(optlen=0) /
892 DHCP6_Advertise(trid=1) /
893 DHCP6OptStatusCode(statuscode=0))
894 pkts_adv_vrf0 = [p_adv_vrf0]
896 self.pg0.add_stream(pkts_adv_vrf0)
897 self.pg_enable_capture(self.pg_interfaces)
900 rx = self.pg3.get_capture(1)
902 self.verify_dhcp6_advert(rx[0], self.pg3, "::")
905 # Send the relay response (the advertisement)
906 # - with peer address
907 p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
908 IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
909 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
910 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
911 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
912 DHCP6OptRelayMsg(optlen=0) /
913 DHCP6_Advertise(trid=1) /
914 DHCP6OptStatusCode(statuscode=0))
915 pkts_adv_vrf0 = [p_adv_vrf0]
917 self.pg0.add_stream(pkts_adv_vrf0)
918 self.pg_enable_capture(self.pg_interfaces)
921 rx = self.pg3.get_capture(1)
923 self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
926 # Add all the config for VRF 1 & 2
928 self.vapi.dhcp_proxy_config(server_addr_vrf1,
933 self.pg4.config_ip6()
935 self.vapi.dhcp_proxy_config(server_addr_vrf2,
940 self.pg5.config_ip6()
945 self.pg4.add_stream(p_solicit_vrf1)
946 self.pg_enable_capture(self.pg_interfaces)
949 rx = self.pg1.get_capture(1)
951 self.verify_dhcp6_solicit(rx[0], self.pg1,
952 dhcp_solicit_src_vrf1,
958 self.pg5.add_stream(p_solicit_vrf2)
959 self.pg_enable_capture(self.pg_interfaces)
962 rx = self.pg2.get_capture(1)
964 self.verify_dhcp6_solicit(rx[0], self.pg2,
965 dhcp_solicit_src_vrf2,
971 p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
972 IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
973 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
974 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
975 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
976 DHCP6OptRelayMsg(optlen=0) /
977 DHCP6_Advertise(trid=1) /
978 DHCP6OptStatusCode(statuscode=0))
979 pkts_adv_vrf1 = [p_adv_vrf1]
981 self.pg1.add_stream(pkts_adv_vrf1)
982 self.pg_enable_capture(self.pg_interfaces)
985 rx = self.pg4.get_capture(1)
987 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
991 # table=1, vss_type=1, vpn_index=1, oui=4
992 # table=2, vss_type=0, vpn_id = "ip6-table-2"
993 self.vapi.dhcp_proxy_set_vss(1, 1, oui=4, vpn_index=1, is_ip6=1)
994 self.vapi.dhcp_proxy_set_vss(2, 0, "IPv6-table-2", is_ip6=1)
996 self.pg4.add_stream(p_solicit_vrf1)
997 self.pg_enable_capture(self.pg_interfaces)
1000 rx = self.pg1.get_capture(1)
1002 self.verify_dhcp6_solicit(rx[0], self.pg1,
1003 dhcp_solicit_src_vrf1,
1004 self.pg4.remote_mac,
1008 self.pg5.add_stream(p_solicit_vrf2)
1009 self.pg_enable_capture(self.pg_interfaces)
1012 rx = self.pg2.get_capture(1)
1014 self.verify_dhcp6_solicit(rx[0], self.pg2,
1015 dhcp_solicit_src_vrf2,
1016 self.pg5.remote_mac,
1017 vpn_id="IPv6-table-2")
1020 # Remove the VSS config
1021 # relayed DHCP has default vlaues in the option.
1023 self.vapi.dhcp_proxy_set_vss(1, is_ip6=1, is_add=0)
1025 self.pg4.add_stream(p_solicit_vrf1)
1026 self.pg_enable_capture(self.pg_interfaces)
1029 rx = self.pg1.get_capture(1)
1031 self.verify_dhcp6_solicit(rx[0], self.pg1,
1032 dhcp_solicit_src_vrf1,
1033 self.pg4.remote_mac)
1036 # Add a second DHCP server in VRF 1
1037 # expect clients messages to be relay to both configured servers
1039 self.pg1.generate_remote_hosts(2)
1040 server_addr12 = socket.inet_pton(AF_INET6,
1041 self.pg1.remote_hosts[1].ip6)
1043 self.vapi.dhcp_proxy_config(server_addr12,
1050 # We'll need an ND entry for the server to send it packets
1052 nd_entry = VppNeighbor(self,
1053 self.pg1.sw_if_index,
1054 self.pg1.remote_hosts[1].mac,
1055 self.pg1.remote_hosts[1].ip6,
1057 nd_entry.add_vpp_config()
1060 # Send a discover from the client. expect two relayed messages
1061 # The frist packet is sent to the second server
1062 # We're not enforcing that here, it's just the way it is.
1064 self.pg4.add_stream(p_solicit_vrf1)
1065 self.pg_enable_capture(self.pg_interfaces)
1068 rx = self.pg1.get_capture(2)
1070 self.verify_dhcp6_solicit(rx[0], self.pg1,
1071 dhcp_solicit_src_vrf1,
1072 self.pg4.remote_mac)
1073 self.verify_dhcp6_solicit(rx[1], self.pg1,
1074 dhcp_solicit_src_vrf1,
1075 self.pg4.remote_mac,
1076 dst_mac=self.pg1.remote_hosts[1].mac,
1077 dst_ip=self.pg1.remote_hosts[1].ip6)
1080 # Send both packets back. Client gets both.
1082 p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1083 IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1084 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1085 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1086 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1087 DHCP6OptRelayMsg(optlen=0) /
1088 DHCP6_Advertise(trid=1) /
1089 DHCP6OptStatusCode(statuscode=0))
1090 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1091 IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
1092 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1093 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1094 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1095 DHCP6OptRelayMsg(optlen=0) /
1096 DHCP6_Advertise(trid=1) /
1097 DHCP6OptStatusCode(statuscode=0))
1101 self.pg1.add_stream(pkts)
1102 self.pg_enable_capture(self.pg_interfaces)
1105 rx = self.pg4.get_capture(2)
1107 self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1108 self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1)
1111 # Ensure only solicit messages are duplicated
1113 p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
1114 IPv6(src=dhcp_solicit_src_vrf1,
1115 dst=dhcp_solicit_dst) /
1116 UDP(sport=DHCP6_SERVER_PORT,
1117 dport=DHCP6_CLIENT_PORT) /
1120 self.pg4.add_stream(p_request_vrf1)
1121 self.pg_enable_capture(self.pg_interfaces)
1124 rx = self.pg1.get_capture(1)
1127 # Test we drop DHCP packets from addresses that are not configured as
1130 p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1131 IPv6(dst=self.pg1.local_ip6, src="3001::1") /
1132 UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1133 DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1134 DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1135 DHCP6OptRelayMsg(optlen=0) /
1136 DHCP6_Advertise(trid=1) /
1137 DHCP6OptStatusCode(statuscode=0))
1138 self.send_and_assert_no_replies(self.pg1, p2,
1139 "DHCP6 not from server")
1142 # Remove the second DHCP server
1144 self.vapi.dhcp_proxy_config(server_addr12,
1152 # Test we can still relay with the first
1154 self.pg4.add_stream(p_solicit_vrf1)
1155 self.pg_enable_capture(self.pg_interfaces)
1158 rx = self.pg1.get_capture(1)
1160 self.verify_dhcp6_solicit(rx[0], self.pg1,
1161 dhcp_solicit_src_vrf1,
1162 self.pg4.remote_mac)
1167 self.vapi.dhcp_proxy_config(server_addr_vrf2,
1173 self.vapi.dhcp_proxy_config(server_addr_vrf1,
1179 self.vapi.dhcp_proxy_config(server_addr_vrf0,
1187 self.vapi.dhcp_proxy_config(server_addr_vrf0,
1193 self.pg3.unconfig_ip6()
1194 self.pg4.unconfig_ip6()
1195 self.pg5.unconfig_ip6()
1197 def test_dhcp_client(self):
1200 hostname = 'universal-dp'
1202 self.pg_enable_capture(self.pg_interfaces)
1205 # Configure DHCP client on PG3 and capture the discover sent
1207 self.vapi.dhcp_client(self.pg3.sw_if_index, hostname)
1209 rx = self.pg3.get_capture(1)
1211 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1214 # Send back on offer, expect the request
1216 p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1217 IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1218 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1219 BOOTP(op=1, yiaddr=self.pg3.local_ip4) /
1220 DHCP(options=[('message-type', 'offer'),
1221 ('server_id', self.pg3.remote_ip4),
1224 self.pg3.add_stream(p_offer)
1225 self.pg_enable_capture(self.pg_interfaces)
1228 rx = self.pg3.get_capture(1)
1229 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1233 # Send an acknowloedgement
1235 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1236 IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1237 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1238 BOOTP(op=1, yiaddr=self.pg3.local_ip4) /
1239 DHCP(options=[('message-type', 'ack'),
1240 ('subnet_mask', "255.255.255.0"),
1241 ('router', self.pg3.remote_ip4),
1242 ('server_id', self.pg3.remote_ip4),
1243 ('lease_time', 43200),
1246 self.pg3.add_stream(p_ack)
1247 self.pg_enable_capture(self.pg_interfaces)
1251 # We'll get an ARP request for the router address
1253 rx = self.pg3.get_capture(1)
1255 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1256 self.pg_enable_capture(self.pg_interfaces)
1259 # At the end of this procedure there should be a connected route
1262 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1263 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1265 # remove the left over ARP entry
1266 self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1267 mactobinary(self.pg3.remote_mac),
1268 self.pg3.remote_ip4,
1271 # remove the DHCP config
1273 self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, is_add=0)
1276 # and now the route should be gone
1278 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1279 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1282 # Start the procedure again. this time have VPP send the client-ID
1284 self.pg3.admin_down()
1287 self.vapi.dhcp_client(self.pg3.sw_if_index, hostname,
1288 client_id=self.pg3.local_mac)
1290 rx = self.pg3.get_capture(1)
1292 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1295 self.pg3.add_stream(p_offer)
1296 self.pg_enable_capture(self.pg_interfaces)
1299 rx = self.pg3.get_capture(1)
1300 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1304 # unicast the ack to the offered address
1306 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1307 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1308 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1309 BOOTP(op=1, yiaddr=self.pg3.local_ip4) /
1310 DHCP(options=[('message-type', 'ack'),
1311 ('subnet_mask', "255.255.255.0"),
1312 ('router', self.pg3.remote_ip4),
1313 ('server_id', self.pg3.remote_ip4),
1314 ('lease_time', 43200),
1317 self.pg3.add_stream(p_ack)
1318 self.pg_enable_capture(self.pg_interfaces)
1322 # We'll get an ARP request for the router address
1324 rx = self.pg3.get_capture(1)
1326 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1327 self.pg_enable_capture(self.pg_interfaces)
1330 # At the end of this procedure there should be a connected route
1333 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1334 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1337 # remove the DHCP config
1339 self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, is_add=0)
1341 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1342 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1345 # Rince and repeat, this time with VPP configured not to set
1346 # the braodcast flag in the discover and request messages,
1347 # and for the server to unicast the responses.
1349 # Configure DHCP client on PG3 and capture the discover sent
1351 self.vapi.dhcp_client(self.pg3.sw_if_index, hostname,
1352 set_broadcast_flag=0)
1354 rx = self.pg3.get_capture(1)
1356 self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1360 # Send back on offer, unicasted to the offered address.
1361 # Expect the request.
1363 p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1364 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1365 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1366 BOOTP(op=1, yiaddr=self.pg3.local_ip4) /
1367 DHCP(options=[('message-type', 'offer'),
1368 ('server_id', self.pg3.remote_ip4),
1371 self.pg3.add_stream(p_offer)
1372 self.pg_enable_capture(self.pg_interfaces)
1375 rx = self.pg3.get_capture(1)
1376 self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1381 # Send an acknowloedgement
1383 p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1384 IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1385 UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1386 BOOTP(op=1, yiaddr=self.pg3.local_ip4) /
1387 DHCP(options=[('message-type', 'ack'),
1388 ('subnet_mask', "255.255.255.0"),
1389 ('router', self.pg3.remote_ip4),
1390 ('server_id', self.pg3.remote_ip4),
1391 ('lease_time', 43200),
1394 self.pg3.add_stream(p_ack)
1395 self.pg_enable_capture(self.pg_interfaces)
1399 # We'll get an ARP request for the router address
1401 rx = self.pg3.get_capture(1)
1403 self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1404 self.pg_enable_capture(self.pg_interfaces)
1407 # At the end of this procedure there should be a connected route
1410 self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1411 self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1413 # remove the left over ARP entry
1414 self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1415 mactobinary(self.pg3.remote_mac),
1416 self.pg3.remote_ip4,
1419 # remove the DHCP config
1421 self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, is_add=0)
1424 # and now the route should be gone
1426 self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1427 self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1430 if __name__ == '__main__':
1431 unittest.main(testRunner=VppTestRunner)