diff options
-rw-r--r-- | src/vnet/dhcp/proxy_node.c | 30 | ||||
-rw-r--r-- | test/patches/scapy-2.3.3/dhcp6-options.patch | 58 | ||||
-rw-r--r-- | test/test_dhcp.py | 739 | ||||
-rw-r--r-- | test/vpp_ip_route.py | 67 | ||||
-rw-r--r-- | test/vpp_papi_provider.py | 36 |
5 files changed, 911 insertions, 19 deletions
diff --git a/src/vnet/dhcp/proxy_node.c b/src/vnet/dhcp/proxy_node.c index d0d99d7e..6a58fcdb 100644 --- a/src/vnet/dhcp/proxy_node.c +++ b/src/vnet/dhcp/proxy_node.c @@ -703,6 +703,12 @@ int dhcp_proxy_set_server_2 (ip4_address_t *addr, ip4_address_t *src_address, dhcp_server_t * server = 0; u32 server_index = 0; u32 rx_fib_index = 0; + const fib_prefix_t all_1s = + { + .fp_len = 32, + .fp_addr.ip4.as_u32 = 0xffffffff, + .fp_proto = FIB_PROTOCOL_IP4, + }; if (addr->as_u32 == 0) return VNET_API_ERROR_INVALID_DST_ADDRESS; @@ -720,8 +726,18 @@ int dhcp_proxy_set_server_2 (ip4_address_t *addr, ip4_address_t *src_address, if (is_del) { memset (server, 0, sizeof (*server)); - return 0; + fib_table_entry_special_remove(rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP); + return 0; } + if (!server->valid) + fib_table_entry_special_add(rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP, + FIB_ENTRY_FLAG_LOCAL, + ADJ_INDEX_INVALID); + goto initialize_it; } @@ -738,6 +754,11 @@ int dhcp_proxy_set_server_2 (ip4_address_t *addr, ip4_address_t *src_address, server = pool_elt_at_index (dpm->dhcp_servers, server_index); memset (server, 0, sizeof (*server)); pool_put (dpm->dhcp_servers, server); + + fib_table_entry_special_remove(rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP); + return 0; } @@ -752,9 +773,16 @@ int dhcp_proxy_set_server_2 (ip4_address_t *addr, ip4_address_t *src_address, } pool_get (dpm->dhcp_servers, server); + + fib_table_entry_special_add(rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP, + FIB_ENTRY_FLAG_LOCAL, + ADJ_INDEX_INVALID); initialize_it: + server->dhcp_server.as_u32 = addr->as_u32; server->server_fib_index = fib_table_find_or_create_and_lock(FIB_PROTOCOL_IP4, diff --git a/test/patches/scapy-2.3.3/dhcp6-options.patch b/test/patches/scapy-2.3.3/dhcp6-options.patch new file mode 100644 index 00000000..0e649398 --- /dev/null +++ b/test/patches/scapy-2.3.3/dhcp6-options.patch @@ -0,0 +1,58 @@ +diff --git a/scapy/layers/dhcp6.py b/scapy/layers/dhcp6.py +index 4cb9291..a1adcfc 100644 +--- a/scapy/layers/dhcp6.py ++++ b/scapy/layers/dhcp6.py +@@ -74,7 +74,9 @@ dhcp6opts = { 1: "CLIENTID", + 36: "OPTION_GEOCONF_CIVIC", #RFC-ietf-geopriv-dhcp-civil-09.txt + 37: "OPTION_REMOTE_ID", #RFC4649 + 38: "OPTION_SUBSCRIBER_ID", #RFC4580 +- 39: "OPTION_CLIENT_FQDN" } #RFC4704 ++ 39: "OPTION_CLIENT_FQDN", #RFC4704 ++ 68: "OPTION_VSS", #RFC6607 ++ 79: "OPTION_CLIENT_LINKLAYER_ADDR" } #RFC6939 + + dhcp6opts_by_code = { 1: "DHCP6OptClientId", + 2: "DHCP6OptServerId", +@@ -116,12 +118,14 @@ dhcp6opts_by_code = { 1: "DHCP6OptClientId", + #40: "DHCP6OptPANAAgent", #RFC-ietf-dhc-paa-option-05.txt + #41: "DHCP6OptNewPOSIXTimeZone, #RFC4833 + #42: "DHCP6OptNewTZDBTimeZone, #RFC4833 +- 43: "DHCP6OptRelayAgentERO" #RFC4994 ++ 43: "DHCP6OptRelayAgentERO", #RFC4994 + #44: "DHCP6OptLQQuery", #RFC5007 + #45: "DHCP6OptLQClientData", #RFC5007 + #46: "DHCP6OptLQClientTime", #RFC5007 + #47: "DHCP6OptLQRelayData", #RFC5007 + #48: "DHCP6OptLQClientLink", #RFC5007 ++ 68: "DHCP6OptVSS", #RFC6607 ++ 79: "DHCP6OptClientLinkLayerAddr", #RFC6939 + } + + +@@ -838,6 +842,26 @@ class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload): # RFC4994 + _OptReqListField("reqopts", [23, 24], + length_from = lambda pkt: pkt.optlen) ] + ++# "Client link-layer address type. The link-layer type MUST be a valid hardware ++# type assigned by the IANA, as described in [RFC0826] ++class DHCP6OptClientLinkLayerAddr(_DHCP6OptGuessPayload): #RFC6939 ++ name = "DHCP6 Option - Client Link Layer address" ++ fields_desc = [ ShortEnumField("optcode", 79, dhcp6opts), ++ FieldLenField("optlen", None, length_of="clladdr", ++ adjust = lambda pkt,x: x+1), ++ ShortField("lltype", 1), # ethernet ++ _LLAddrField("clladdr", ETHER_ANY) ] ++ ++# Virtual Subnet selection ++class DHCP6OptVSS(_DHCP6OptGuessPayload): #RFC6607 ++ name = "DHCP6 Option - Virtual Subnet Selection" ++ fields_desc = [ ShortEnumField("optcode", 68, dhcp6opts), ++ FieldLenField("optlen", None, length_of="data", ++ adjust = lambda pkt,x: x+1), ++ ByteField("type", 255), # Default Global/default table ++ StrLenField("data", "", ++ length_from = lambda pkt: pkt.optlen) ] ++ + ##################################################################### + ### DHCPv6 messages ### + ##################################################################### diff --git a/test/test_dhcp.py b/test/test_dhcp.py new file mode 100644 index 00000000..bdff679c --- /dev/null +++ b/test/test_dhcp.py @@ -0,0 +1,739 @@ +#!/usr/bin/env python + +import unittest +import socket + +from framework import VppTestCase, VppTestRunner +from vpp_ip_route import IpRoute, RoutePath +from vpp_lo_interface import VppLoInterface + +from scapy.layers.l2 import Ether, getmacbyip +from scapy.layers.inet import IP, UDP, ICMP +from scapy.layers.inet6 import IPv6, in6_getnsmac, 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, \ + DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr +from socket import AF_INET, AF_INET6 +from scapy.utils import inet_pton, inet_ntop +from scapy.utils6 import in6_ptop + +DHCP4_CLIENT_PORT = 68 +DHCP4_SERVER_PORT = 67 +DHCP6_CLIENT_PORT = 547 +DHCP6_SERVER_PORT = 546 + + +def mk_ll_addr(mac): + + euid = in6_mactoifaceid(mac) + addr = "fe80::" + euid + return addr + + +class TestDHCP(VppTestCase): + """ DHCP Test Case """ + + def setUp(self): + super(TestDHCP, self).setUp() + + # create 3 pg interfaces + self.create_pg_interfaces(range(4)) + + # pg0 and 1 are IP configured in VRF 0 and 1. + # pg2 and 3 are non IP-configured in VRF 0 and 1 + table_id = 0 + for i in self.pg_interfaces[:2]: + i.admin_up() + i.set_table_ip4(table_id) + i.set_table_ip6(table_id) + i.config_ip4() + i.resolve_arp() + i.config_ip6() + i.resolve_ndp() + table_id += 1 + + table_id = 0 + for i in self.pg_interfaces[2:]: + i.admin_up() + i.set_table_ip4(table_id) + i.set_table_ip6(table_id) + table_id += 1 + + 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 validate_option_82(self, pkt, intf, ip_addr): + dhcp = pkt[DHCP] + found = 0 + data = [] + + for i in dhcp.options: + if type(i) is tuple: + if i[0] == "relay_agent_Information": + # + # There are two sb-options present - each of length 6. + # + data = i[1] + self.assertEqual(len(data), 12) + + # + # First sub-option is ID 1, len 4, then encoded + # sw_if_index. This test uses low valued indicies + # so [2:4] are 0. + # The ID space is VPP internal - so no matching value + # scapy + # + self.assertEqual(ord(data[0]), 1) + self.assertEqual(ord(data[1]), 4) + self.assertEqual(ord(data[2]), 0) + self.assertEqual(ord(data[3]), 0) + self.assertEqual(ord(data[4]), 0) + self.assertEqual(ord(data[5]), intf._sw_if_index) + + # + # next sub-option is the IP address of the client side + # interface. + # sub-option ID=5, length (of a v4 address)=4 + # + claddr = socket.inet_pton(AF_INET, ip_addr) + + self.assertEqual(ord(data[6]), 5) + self.assertEqual(ord(data[7]), 4) + self.assertEqual(data[8], claddr[0]) + self.assertEqual(data[9], claddr[1]) + self.assertEqual(data[10], claddr[2]) + self.assertEqual(data[11], claddr[3]) + + found = 1 + self.assertTrue(found) + + return data + + def verify_dhcp_offer(self, pkt, intf, check_option_82=True): + ether = pkt[Ether] + self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IP] + self.assertEqual(ip.dst, "255.255.255.255") + self.assertEqual(ip.src, intf.local_ip4) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP4_CLIENT_PORT) + self.assertEqual(udp.sport, DHCP4_SERVER_PORT) + + dhcp = pkt[DHCP] + is_offer = False + for o in dhcp.options: + if type(o) is tuple: + if o[0] == "message-type" \ + and DHCPTypes[o[1]] == "offer": + is_offer = True + self.assertTrue(is_offer) + + if check_option_82: + data = self.validate_option_82(pkt, intf, intf.local_ip4) + + def verify_dhcp_discover(self, pkt, intf, src_intf=None, + option_82_present=True): + ether = pkt[Ether] + self.assertEqual(ether.dst, intf.remote_mac) + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IP] + self.assertEqual(ip.dst, intf.remote_ip4) + self.assertEqual(ip.src, intf.local_ip4) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP4_SERVER_PORT) + self.assertEqual(udp.sport, DHCP4_CLIENT_PORT) + + dhcp = pkt[DHCP] + + is_discover = False + for o in dhcp.options: + if type(o) is tuple: + if o[0] == "message-type" \ + and DHCPTypes[o[1]] == "discover": + is_discover = True + self.assertTrue(is_discover) + + if option_82_present: + data = self.validate_option_82(pkt, src_intf, src_intf.local_ip4) + return data + else: + for i in dhcp.options: + if type(i) is tuple: + self.assertNotEqual(i[0], "relay_agent_Information") + + def verify_dhcp6_solicit(self, pkt, intf, + peer_ip, peer_mac, + fib_id=0, + oui=0): + ether = pkt[Ether] + self.assertEqual(ether.dst, intf.remote_mac) + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IPv6] + self.assertEqual(in6_ptop(ip.dst), in6_ptop(intf.remote_ip6)) + self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6)) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP6_CLIENT_PORT) + self.assertEqual(udp.sport, DHCP6_SERVER_PORT) + + relay = pkt[DHCP6_RelayForward] + self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip)) + oid = pkt[DHCP6OptIfaceId] + cll = pkt[DHCP6OptClientLinkLayerAddr] + self.assertEqual(cll.optlen, 8) + self.assertEqual(cll.lltype, 1) + self.assertEqual(cll.clladdr, peer_mac) + + vss = pkt[DHCP6OptVSS] + self.assertEqual(vss.optlen, 8) + self.assertEqual(vss.type, 1) + # the OUI and FIB-id are really 3 and 4 bytes resp. + # but the tested range is small + self.assertEqual(ord(vss.data[0]), 0) + self.assertEqual(ord(vss.data[1]), 0) + self.assertEqual(ord(vss.data[2]), oui) + self.assertEqual(ord(vss.data[3]), 0) + self.assertEqual(ord(vss.data[4]), 0) + self.assertEqual(ord(vss.data[5]), 0) + self.assertEqual(ord(vss.data[6]), fib_id) + + # the relay message should be an encoded Solicit + msg = pkt[DHCP6OptRelayMsg] + sol = DHCP6_Solicit() + self.assertEqual(msg.optlen, len(str(sol))) + self.assertEqual(str(sol), (str(msg[1]))[:msg.optlen]) + + def verify_dhcp6_advert(self, pkt, intf, peer): + ether = pkt[Ether] + self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IPv6] + self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer)) + self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6)) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP6_SERVER_PORT) + self.assertEqual(udp.sport, DHCP6_CLIENT_PORT) + + # not sure why this is not decoding + # adv = pkt[DHCP6_Advertise] + + def test_dhcp_proxy(self): + """ DHCPv4 Proxy """ + + # + # Verify no response to DHCP request without DHCP config + # + p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff", + src=self.pg2.remote_mac) / + IP(src="0.0.0.0", dst="255.255.255.255") / + UDP(sport=DHCP4_CLIENT_PORT, + dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'discover'), ('end')])) + pkts_disc_vrf0 = [p_disc_vrf0] + p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff", + src=self.pg3.remote_mac) / + IP(src="0.0.0.0", dst="255.255.255.255") / + UDP(sport=DHCP4_CLIENT_PORT, + dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'discover'), ('end')])) + pkts_disc_vrf1 = [p_disc_vrf0] + + self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1, + "DHCP with no configuration") + + # + # Enable DHCP proxy in VRF 0 + # + server_addr = self.pg0.remote_ip4n + src_addr = self.pg0.local_ip4n + + self.vapi.dhcp_proxy_config(server_addr, + src_addr, + rx_table_id=0) + + # + # Now a DHCP request on pg2, which is in the same VRF + # as the DHCP config, will result in a relayed DHCP + # message to the [fake] server + # + self.pg2.add_stream(pkts_disc_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + rx = rx[0] + + # + # Rx'd packet should be to the server address and from the configured + # source address + # UDP source ports are unchanged + # we've no option 82 config so that should be absent + # + self.verify_dhcp_discover(rx, self.pg0, option_82_present=False) + + # + # Inject a response from the server + # VPP will only relay the offer if option 82 is present. + # so this one is dropped + # + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), ('end')])) + pkts = [p] + + self.send_and_assert_no_replies(self.pg0, pkts, + "DHCP offer no option 82") + + # + # Configure sending option 82 in relayed messages + # + self.vapi.dhcp_proxy_config(server_addr, + src_addr, + rx_table_id=0, + insert_circuit_id=1) + + # + # Send a request: + # again dropped, but ths time because there is no IP addrees on the + # clinet interfce to fill in the option. + # + self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0, + "DHCP no relay address") + + # + # configure an IP address on the client facing interface + # + self.pg2.config_ip4() + + # + # Try again with a discover packet + # Rx'd packet should be to the server address and from the configured + # source address + # UDP source ports are unchanged + # we've no option 82 config so that should be absent + # + self.pg2.add_stream(pkts_disc_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + rx = rx[0] + + option_82 = self.verify_dhcp_discover(rx, self.pg0, src_intf=self.pg2) + + # + # Create an DHCP offer reply from the server with a correctly formatted + # option 82. i.e. send back what we just captured + # The offer, sent mcast to the client, still has option 82. + # + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', option_82), + ('end')])) + pkts = [p] + + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg2.get_capture(1) + rx = rx[0] + + self.verify_dhcp_offer(rx, self.pg2) + + # + # Bogus Option 82: + # + # 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:] + + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', bad_ip), + ('end')])) + pkts = [p] + + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg2.get_capture(1) + rx = rx[0] + + self.verify_dhcp_offer(rx, self.pg2, check_option_82=False) + self.pg0.assert_nothing_captured(remark="") + + # 2. Not a sw_if_index VPP knows + bad_if_index = option_82[0:2] + chr(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) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', bad_if_index), + ('end')])) + pkts = [p] + self.send_and_assert_no_replies(self.pg0, pkts, + "DHCP offer option 82 bad if index") + + # + # Send a DHCP request in VRF 1. should be dropped. + # + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1, + "DHCP with no configuration VRF 1") + + # + # Delete the DHCP config in VRF 0 + # Should now drop requests. + # + self.vapi.dhcp_proxy_config(server_addr, + src_addr, + rx_table_id=0, + is_add=0, + insert_circuit_id=1) + + self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0, + "DHCP config removed VRF 0") + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1, + "DHCP config removed VRF 1") + + # + # Add DHCP config for VRF 1 + # + server_addr = self.pg1.remote_ip4n + src_addr = self.pg1.local_ip4n + self.vapi.dhcp_proxy_config(server_addr, + src_addr, + rx_table_id=1, + server_table_id=1, + insert_circuit_id=1) + + # + # Confim DHCP requests ok in VRF 1. + # - dropped on IP config on client interface + # + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1, + "DHCP config removed VRF 1") + + # + # configure an IP address on the client facing interface + # + self.pg3.config_ip4() + + self.pg3.add_stream(pkts_disc_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_dhcp_discover(rx, self.pg1, src_intf=self.pg3) + + # + # remove DHCP config to cleanup + # + self.vapi.dhcp_proxy_config(server_addr, + src_addr, + rx_table_id=1, + server_table_id=1, + insert_circuit_id=1, + is_add=0) + + self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0, + "DHCP cleanup VRF 0") + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1, + "DHCP cleanup VRF 1") + + def test_dhcp6_proxy(self): + """ DHCPv6 Proxy""" + # + # Verify no response to DHCP request without DHCP config + # + dhcp_solicit_dst = "ff02::1:2" + dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg2.remote_mac) + dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg3.remote_mac) + server_addr_vrf0 = self.pg0.remote_ip6n + src_addr_vrf0 = self.pg0.local_ip6n + server_addr_vrf1 = self.pg1.remote_ip6n + src_addr_vrf1 = self.pg1.local_ip6n + + # + # Add the Route to receive the DHCP packets + # + route_dhcp_vrf0 = IpRoute(self, dhcp_solicit_dst, 128, + [], is_local=1, is_ip6=1) + route_dhcp_vrf0.add_vpp_config() + route_dhcp_vrf1 = IpRoute(self, dhcp_solicit_dst, 128, + [], is_local=1, is_ip6=1, + table_id=1) + route_dhcp_vrf1.add_vpp_config() + + dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst)) + p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg2.remote_mac) / + IPv6(src=dhcp_solicit_src_vrf0, + dst=dhcp_solicit_dst) / + UDP(sport=DHCP6_SERVER_PORT, + dport=DHCP6_CLIENT_PORT) / + DHCP6_Solicit()) + pkts_solicit_vrf0 = [p_solicit_vrf0] + p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg3.remote_mac) / + IPv6(src=dhcp_solicit_src_vrf1, + dst=dhcp_solicit_dst) / + UDP(sport=DHCP6_SERVER_PORT, + dport=DHCP6_CLIENT_PORT) / + DHCP6_Solicit()) + pkts_solicit_vrf1 = [p_solicit_vrf1] + + self.send_and_assert_no_replies(self.pg2, pkts_solicit_vrf0, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg3, pkts_solicit_vrf1, + "DHCP with no configuration") + + # + # DHCPv6 config in VRF 0. + # Packets still dropped because the client facing interface has no + # IPv6 config + # + self.vapi.dhcp_proxy_config(server_addr_vrf0, + src_addr_vrf0, + rx_table_id=0, + server_table_id=0, + insert_circuit_id=1, + is_ipv6=1) + + self.send_and_assert_no_replies(self.pg2, pkts_solicit_vrf0, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg3, pkts_solicit_vrf1, + "DHCP with no configuration") + + # + # configure an IP address on the client facing interface + # + self.pg2.config_ip6() + + # + # Now the DHCP requests are relayed to the server + # + self.pg2.add_stream(pkts_solicit_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + rx = rx[0] + self.verify_dhcp6_solicit(rx, self.pg0, + dhcp_solicit_src_vrf0, + self.pg2.remote_mac) + + # + # Exception cases for rejected relay responses + # + + # 1 - not a relay reply + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_Advertise()) + pkts_adv_vrf0 = [p_adv_vrf0] + self.send_and_assert_no_replies(self.pg2, pkts_adv_vrf0, + "DHCP6 not a relay reply") + + # 2 - no relay message option + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6_Advertise()) + pkts_adv_vrf0 = [p_adv_vrf0] + self.send_and_assert_no_replies(self.pg2, pkts_adv_vrf0, + "DHCP not a relay message") + + # 3 - no circuit ID + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise()) + pkts_adv_vrf0 = [p_adv_vrf0] + self.send_and_assert_no_replies(self.pg2, pkts_adv_vrf0, + "DHCP6 no circuit ID") + # 4 - wrong circuit ID + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise()) + pkts_adv_vrf0 = [p_adv_vrf0] + self.send_and_assert_no_replies(self.pg2, pkts_adv_vrf0, + "DHCP6 wrong circuit ID") + + # + # Send the relay response (the advertisement) + # - no peer address + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x03') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + pkts_adv_vrf0 = [p_adv_vrf0] + + self.pg0.add_stream(pkts_adv_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg2.get_capture(1) + rx = rx[0] + self.verify_dhcp6_advert(rx, self.pg2, "::") + + # + # Send the relay response (the advertisement) + # - with peer address + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x03') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + pkts_adv_vrf0 = [p_adv_vrf0] + + self.pg0.add_stream(pkts_adv_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg2.get_capture(1) + rx = rx[0] + self.verify_dhcp6_advert(rx, self.pg2, dhcp_solicit_src_vrf0) + + # + # Add all the config for VRF 1 + # + self.vapi.dhcp_proxy_config(server_addr_vrf1, + src_addr_vrf1, + rx_table_id=1, + server_table_id=1, + insert_circuit_id=1, + is_ipv6=1) + self.pg3.config_ip6() + + # + # VRF 1 solicit + # + self.pg3.add_stream(pkts_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_dhcp6_solicit(rx, self.pg1, + dhcp_solicit_src_vrf1, + self.pg3.remote_mac) + + # + # VRF 1 Advert + # + p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / + IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + pkts_adv_vrf1 = [p_adv_vrf1] + + self.pg1.add_stream(pkts_adv_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + rx = rx[0] + self.verify_dhcp6_advert(rx, self.pg3, dhcp_solicit_src_vrf1) + + # + # Add VSS config + # table=1, fib=id=1, oui=4 + self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_ip6=1) + + self.pg3.add_stream(pkts_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_dhcp6_solicit(rx, self.pg1, + dhcp_solicit_src_vrf1, + self.pg3.remote_mac, + fib_id=1, + oui=4) + + # + # Remove the VSS config + # relayed DHCP has default vlaues in the option. + # + self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_ip6=1, is_add=0) + + self.pg3.add_stream(pkts_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_dhcp6_solicit(rx, self.pg1, + dhcp_solicit_src_vrf1, + self.pg3.remote_mac) + + # + # Cleanup + # + self.vapi.dhcp_proxy_config(server_addr_vrf1, + src_addr_vrf1, + rx_table_id=1, + server_table_id=1, + insert_circuit_id=1, + is_ipv6=1, + is_add=0) + self.vapi.dhcp_proxy_config(server_addr_vrf1, + src_addr_vrf1, + rx_table_id=0, + server_table_id=0, + insert_circuit_id=1, + is_ipv6=1, + is_add=0) + + route_dhcp_vrf0.remove_vpp_config() + route_dhcp_vrf1.remove_vpp_config() + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index 975e3934..fc9133fb 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -19,12 +19,16 @@ class RoutePath: nh_sw_if_index, nh_table_id=0, labels=[], - nh_via_label=MPLS_LABEL_INVALID): - self.nh_addr = socket.inet_pton(socket.AF_INET, nh_addr) + nh_via_label=MPLS_LABEL_INVALID, + is_ip6=0): self.nh_itf = nh_sw_if_index self.nh_table_id = nh_table_id self.nh_via_label = nh_via_label self.nh_labels = labels + if is_ip6: + self.nh_addr = socket.inet_pton(socket.AF_INET6, nh_addr) + else: + self.nh_addr = socket.inet_pton(socket.AF_INET, nh_addr) class IpRoute: @@ -33,34 +37,61 @@ class IpRoute: """ def __init__(self, test, dest_addr, - dest_addr_len, paths, table_id=0): + dest_addr_len, paths, table_id=0, is_ip6=0, is_local=0): self._test = test self.paths = paths - self.dest_addr = socket.inet_pton(socket.AF_INET, dest_addr) self.dest_addr_len = dest_addr_len self.table_id = table_id + self.is_ip6 = is_ip6 + self.is_local = is_local + if is_ip6: + self.dest_addr = socket.inet_pton(socket.AF_INET6, dest_addr) + else: + self.dest_addr = socket.inet_pton(socket.AF_INET, dest_addr) def add_vpp_config(self): - for path in self.paths: + if self.is_local: self._test.vapi.ip_add_del_route( self.dest_addr, self.dest_addr_len, - path.nh_addr, - path.nh_itf, + socket.inet_pton(socket.AF_INET6, "::"), + 0xffffffff, + is_local=1, table_id=self.table_id, - next_hop_out_label_stack=path.nh_labels, - next_hop_n_out_labels=len( - path.nh_labels), - next_hop_via_label=path.nh_via_label) + is_ipv6=self.is_ip6) + else: + for path in self.paths: + self._test.vapi.ip_add_del_route( + self.dest_addr, + self.dest_addr_len, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + next_hop_out_label_stack=path.nh_labels, + next_hop_n_out_labels=len( + path.nh_labels), + next_hop_via_label=path.nh_via_label, + is_ipv6=self.is_ip6) def remove_vpp_config(self): - for path in self.paths: - self._test.vapi.ip_add_del_route(self.dest_addr, - self.dest_addr_len, - path.nh_addr, - path.nh_itf, - table_id=self.table_id, - is_add=0) + if self.is_local: + self._test.vapi.ip_add_del_route( + self.dest_addr, + self.dest_addr_len, + socket.inet_pton(socket.AF_INET6, "::"), + 0xffffffff, + is_local=1, + is_add=0, + table_id=self.table_id, + is_ipv6=self.is_ip6) + else: + for path in self.paths: + self._test.vapi.ip_add_del_route(self.dest_addr, + self.dest_addr_len, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + is_add=0) class MplsIpBind: diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 4f2cea8b..1b2895e9 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1142,3 +1142,39 @@ class VppPapiProvider(object): 'template_interval': template_interval, 'udp_checksum': udp_checksum, }) + + def dhcp_proxy_config(self, + dhcp_server, + dhcp_src_address, + rx_table_id=0, + server_table_id=0, + is_add=1, + is_ipv6=0, + insert_circuit_id=0): + return self.api( + self.papi.dhcp_proxy_config_2, + { + 'rx_vrf_id': rx_table_id, + 'server_vrf_id': server_table_id, + 'is_ipv6': is_ipv6, + 'is_add': is_add, + 'insert_circuit_id': insert_circuit_id, + 'dhcp_server': dhcp_server, + 'dhcp_src_address': dhcp_src_address, + }) + + def dhcp_proxy_set_vss(self, + table_id, + fib_id, + oui, + is_add=1, + is_ip6=0): + return self.api( + self.papi.dhcp_proxy_set_vss, + { + 'tbl_id': table_id, + 'fib_id': fib_id, + 'is_ipv6': is_ip6, + 'is_add': is_add, + 'oui': oui, + }) |