X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=test%2Ftest_dhcp.py;h=bdc5df7a26731144d035a92b4025bb4383f752d4;hb=1fbc3d4f5bb1c142f0c87925c2e25974cb48e6a6;hp=7ae98e8e83b551caa5f098c02144a763eeab2671;hpb=70bfcaf47779340951c1e6f169b1cedcabe708d1;p=vpp.git diff --git a/test/test_dhcp.py b/test/test_dhcp.py index 7ae98e8e83b..bdc5df7a267 100644 --- a/test/test_dhcp.py +++ b/test/test_dhcp.py @@ -8,10 +8,11 @@ from framework import VppTestCase, VppTestRunner, running_extended_tests from vpp_neighbor import VppNeighbor from vpp_ip_route import find_route, VppIpTable from util import mk_ll_addr - +import scapy.compat from scapy.layers.l2 import Ether, getmacbyip, ARP from scapy.layers.inet import IP, UDP, ICMP -from scapy.layers.inet6 import IPv6, in6_getnsmac, in6_mactoifaceid +from scapy.layers.inet6 import IPv6, in6_getnsmac +from scapy.utils6 import in6_mactoifaceid from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \ DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \ @@ -19,7 +20,7 @@ from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \ from socket import AF_INET, AF_INET6 from scapy.utils import inet_pton, inet_ntop from scapy.utils6 import in6_ptop -from util import mactobinary +from vpp_papi import mac_pton DHCP4_CLIENT_PORT = 68 DHCP4_SERVER_PORT = 67 @@ -30,6 +31,14 @@ DHCP6_SERVER_PORT = 546 class TestDHCP(VppTestCase): """ DHCP Test Case """ + @classmethod + def setUpClass(cls): + super(TestDHCP, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestDHCP, cls).tearDownClass() + def setUp(self): super(TestDHCP, self).setUp() @@ -77,13 +86,6 @@ class TestDHCP(VppTestCase): i.admin_down() super(TestDHCP, self).tearDown() - def send_and_assert_no_replies(self, intf, pkts, remark): - intf.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - for i in self.pg_interfaces: - i.assert_nothing_captured(remark=remark) - def verify_dhcp_has_option(self, pkt, option, value): dhcp = pkt[DHCP] found = False @@ -221,7 +223,8 @@ class TestDHCP(VppTestCase): self.assertEqual(udp.dport, DHCP4_SERVER_PORT) self.assertEqual(udp.sport, DHCP4_CLIENT_PORT) - def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None): + def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None, + broadcast=1): self.verify_orig_dhcp_pkt(pkt, intf) self.verify_dhcp_msg_type(pkt, "discover") @@ -231,9 +234,13 @@ class TestDHCP(VppTestCase): bootp = pkt[BOOTP] self.assertEqual(bootp.ciaddr, "0.0.0.0") self.assertEqual(bootp.giaddr, "0.0.0.0") - self.assertEqual(bootp.flags, 0x8000) + if broadcast: + self.assertEqual(bootp.flags, 0x8000) + else: + self.assertEqual(bootp.flags, 0x0000) - def verify_orig_dhcp_request(self, pkt, intf, hostname, ip): + def verify_orig_dhcp_request(self, pkt, intf, hostname, ip, + broadcast=1): self.verify_orig_dhcp_pkt(pkt, intf) self.verify_dhcp_msg_type(pkt, "request") @@ -242,7 +249,10 @@ class TestDHCP(VppTestCase): bootp = pkt[BOOTP] self.assertEqual(bootp.ciaddr, "0.0.0.0") self.assertEqual(bootp.giaddr, "0.0.0.0") - self.assertEqual(bootp.flags, 0x8000) + if broadcast: + self.assertEqual(bootp.flags, 0x8000) + else: + self.assertEqual(bootp.flags, 0x0000) def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None, fib_id=0, oui=0, @@ -359,6 +369,16 @@ class TestDHCP(VppTestCase): # not sure why this is not decoding # adv = pkt[DHCP6_Advertise] + def wait_for_no_route(self, address, length, + n_tries=50, s_time=1): + while (n_tries): + if not find_route(self, address, length): + return True + n_tries = n_tries - 1 + self.sleep(s_time) + + return False + def test_dhcp_proxy(self): """ DHCPv4 Proxy """ @@ -479,7 +499,7 @@ class TestDHCP(VppTestCase): # # 1. not our IP address = not checked by VPP? so offer is replayed # to client - bad_ip = option_82[0:8] + chr(33) + option_82[9:] + bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:] p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / @@ -493,7 +513,7 @@ class TestDHCP(VppTestCase): "DHCP offer option 82 bad address") # 2. Not a sw_if_index VPP knows - bad_if_index = option_82[0:2] + chr(33) + option_82[3:] + bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:] p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / @@ -1051,8 +1071,7 @@ class TestDHCP(VppTestCase): nd_entry = VppNeighbor(self, self.pg1.sw_if_index, self.pg1.remote_hosts[1].mac, - self.pg1.remote_hosts[1].ip6, - af=AF_INET6) + self.pg1.remote_hosts[1].ip6) nd_entry.add_vpp_config() # @@ -1203,7 +1222,7 @@ class TestDHCP(VppTestCase): # # Configure DHCP client on PG3 and capture the discover sent # - self.vapi.dhcp_client(self.pg3.sw_if_index, hostname) + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname) rx = self.pg3.get_capture(1) @@ -1215,10 +1234,12 @@ class TestDHCP(VppTestCase): p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst="255.255.255.255") / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / - BOOTP(op=1, yiaddr=self.pg3.local_ip4) / + BOOTP(op=1, + yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / DHCP(options=[('message-type', 'offer'), ('server_id', self.pg3.remote_ip4), - ('end')])) + 'end'])) self.pg3.add_stream(p_offer) self.pg_enable_capture(self.pg_interfaces) @@ -1229,18 +1250,19 @@ class TestDHCP(VppTestCase): self.pg3.local_ip4) # - # Send an acknowloedgement + # Send an acknowledgment # p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst="255.255.255.255") / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / - BOOTP(op=1, yiaddr=self.pg3.local_ip4) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / DHCP(options=[('message-type', 'ack'), ('subnet_mask', "255.255.255.0"), ('router', self.pg3.remote_ip4), ('server_id', self.pg3.remote_ip4), ('lease_time', 43200), - ('end')])) + 'end'])) self.pg3.add_stream(p_ack) self.pg_enable_capture(self.pg_interfaces) @@ -1263,13 +1285,14 @@ class TestDHCP(VppTestCase): # remove the left over ARP entry self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, - mactobinary(self.pg3.remote_mac), + self.pg3.remote_mac, self.pg3.remote_ip4, is_add=0) + # # remove the DHCP config # - self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, is_add=0) + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0) # # and now the route should be gone @@ -1283,14 +1306,19 @@ class TestDHCP(VppTestCase): self.pg3.admin_down() self.sleep(1) self.pg3.admin_up() - self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, - client_id=self.pg3.local_mac) + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, + client_id=self.pg3.local_mac) rx = self.pg3.get_capture(1) self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, self.pg3.local_mac) + # TODO: VPP DHCP client should not accept DHCP OFFER message with + # the XID (Transaction ID) not matching the XID of the most recent + # DHCP DISCOVERY message. + # Such DHCP OFFER message must be silently discarded - RFC2131. + # Reported in Jira ticket: VPP-99 self.pg3.add_stream(p_offer) self.pg_enable_capture(self.pg_interfaces) self.pg_start() @@ -1305,18 +1333,27 @@ class TestDHCP(VppTestCase): p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / - BOOTP(op=1, yiaddr=self.pg3.local_ip4) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / DHCP(options=[('message-type', 'ack'), ('subnet_mask', "255.255.255.0"), ('router', self.pg3.remote_ip4), ('server_id', self.pg3.remote_ip4), ('lease_time', 43200), - ('end')])) + 'end'])) self.pg3.add_stream(p_ack) self.pg_enable_capture(self.pg_interfaces) self.pg_start() + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + # # At the end of this procedure there should be a connected route # in the FIB @@ -1327,11 +1364,206 @@ class TestDHCP(VppTestCase): # # remove the DHCP config # - self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, is_add=0) + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0) self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + # + # Rince and repeat, this time with VPP configured not to set + # the braodcast flag in the discover and request messages, + # and for the server to unicast the responses. + # + # Configure DHCP client on PG3 and capture the discover sent + # + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, + set_broadcast_flag=0) + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, + broadcast=0) + + # + # Send back on offer, unicasted to the offered address. + # Expect the request. + # + p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'offer'), + ('server_id', self.pg3.remote_ip4), + 'end'])) + + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4, + broadcast=0) + + # + # Send an acknowledgment + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', "255.255.255.0"), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', 43200), + 'end'])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + + # remove the left over ARP entry + self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, + self.pg3.remote_mac, + self.pg3.remote_ip4, + is_add=0) + + # + # read the DHCP client details from a dump + # + clients = self.vapi.dhcp_client_dump() + + self.assertEqual(clients[0].client.sw_if_index, + self.pg3.sw_if_index) + self.assertEqual(clients[0].lease.sw_if_index, + self.pg3.sw_if_index) + self.assertEqual(clients[0].client.hostname.rstrip('\0'), + hostname) + self.assertEqual(clients[0].lease.hostname.rstrip('\0'), + hostname) + self.assertEqual(clients[0].lease.is_ipv6, 0) + # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND + self.assertEqual(clients[0].lease.state, 2) + self.assertEqual(clients[0].lease.mask_width, 24) + self.assertEqual(clients[0].lease.router_address.rstrip('\0'), + self.pg3.remote_ip4n) + self.assertEqual(clients[0].lease.host_address.rstrip('\0'), + self.pg3.local_ip4n) + + # + # remove the DHCP config + # + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0) + + # + # and now the route should be gone + # + self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) + self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + + # + # Start the procedure again. Use requested lease time option. + # + self.pg3.admin_down() + self.sleep(1) + self.pg3.admin_up() + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname) + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname) + + # + # Send back on offer with requested lease time, expect the request + # + lease_time = 1 + p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst='255.255.255.255') / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, + yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'offer'), + ('server_id', self.pg3.remote_ip4), + ('lease_time', lease_time), + 'end'])) + + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4) + + # + # Send an acknowledgment + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst='255.255.255.255') / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', '255.255.255.0'), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', lease_time), + 'end'])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + + # remove the left over ARP entry + self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, + self.pg3.remote_mac, + self.pg3.remote_ip4, + is_add=0) + + # + # the route should be gone after the lease expires + # + self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32)) + self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24)) + + # + # remove the DHCP config + # + self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0) + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner)