IPv6 ND Router discovery control plane (VPP-1095)

Change-Id: I4b5b60e7c6f618bb935eab1e96a2e79bbb14f58f
Signed-off-by: Juraj Sloboda <jsloboda@cisco.com>
This commit is contained in:
Juraj Sloboda
2018-02-01 15:18:49 +01:00
committed by Ole Trøan
parent ff92efe107
commit c03742346f
9 changed files with 1239 additions and 11 deletions

View File

@ -342,6 +342,7 @@ libvnet_la_SOURCES += \
vnet/ip/ip6_neighbor.c \
vnet/ip/ip6_pg.c \
vnet/ip/ip6_reassembly.c \
vnet/ip/rd_cp.c \
vnet/ip/ip_api.c \
vnet/ip/ip_checksum.c \
vnet/ip/ip_frag.c \
@ -360,6 +361,7 @@ nobase_include_HEADERS += \
vnet/ip/icmp6.h \
vnet/ip/igmp_packet.h \
vnet/ip/ip.api.h \
vnet/ip/rd_cp.api.h \
vnet/ip/ip4_error.h \
vnet/ip/ip4.h \
vnet/ip/ip4_mtrie.h \
@ -382,6 +384,7 @@ nobase_include_HEADERS += \
API_FILES += \
vnet/ip/ip.api \
vnet/ip/rd_cp.api \
vnet/ip/punt.api
########################################

View File

@ -1,4 +1,4 @@
option version = "1.0.0";
option version = "1.1.0";
service {
rpc want_interface_events returns want_interface_events_reply
@ -336,6 +336,30 @@ autoreply define sw_interface_set_mac_address
u8 mac_address[6];
};
/** \brief Get interface's MAC address
@param client_index - opaque cookie to identify the sender
@param context - sender context, to match reply w/ request
@param sw_if_index - the interface whose MAC will be returned
*/
define sw_interface_get_mac_address
{
u32 client_index;
u32 context;
u32 sw_if_index;
};
/** \brief Reply for get interface's MAC address request
@param context - returned sender context, to match reply w/ request
@param retval - return code
@param mac_addr - returned interface's MAC address
*/
define sw_interface_get_mac_address_reply
{
u32 context;
i32 retval;
u8 mac_address[6];
};
/** \brief Set an interface's rx-mode
@param client_index - opaque cookie to identify the sender
@param context - sender context, to match reply w/ request

View File

@ -61,6 +61,7 @@ _(SW_INTERFACE_SET_UNNUMBERED, sw_interface_set_unnumbered) \
_(SW_INTERFACE_CLEAR_STATS, sw_interface_clear_stats) \
_(SW_INTERFACE_TAG_ADD_DEL, sw_interface_tag_add_del) \
_(SW_INTERFACE_SET_MAC_ADDRESS, sw_interface_set_mac_address) \
_(SW_INTERFACE_GET_MAC_ADDRESS, sw_interface_get_mac_address) \
_(CREATE_VLAN_SUBIF, create_vlan_subif) \
_(CREATE_SUBIF, create_subif) \
_(DELETE_SUBIF, delete_subif) \
@ -903,6 +904,37 @@ out:
REPLY_MACRO (VL_API_SW_INTERFACE_SET_MAC_ADDRESS_REPLY);
}
static void vl_api_sw_interface_get_mac_address_t_handler
(vl_api_sw_interface_get_mac_address_t * mp)
{
vl_api_sw_interface_get_mac_address_reply_t *rmp;
vl_api_registration_t *reg;
vnet_main_t *vnm = vnet_get_main ();
u32 sw_if_index = ntohl (mp->sw_if_index);
vnet_sw_interface_t *si;
ethernet_interface_t *eth_if = 0;
int rv = 0;
VALIDATE_SW_IF_INDEX (mp);
si = vnet_get_sup_sw_interface (vnm, sw_if_index);
if (si->type == VNET_SW_INTERFACE_TYPE_HARDWARE)
eth_if = ethernet_get_interface (&ethernet_main, si->hw_if_index);
BAD_SW_IF_INDEX_LABEL;
reg = vl_api_client_index_to_registration (mp->client_index);
if (!reg)
return;
rmp = vl_msg_api_alloc (sizeof (*rmp));
rmp->_vl_msg_id = htons (VL_API_SW_INTERFACE_GET_MAC_ADDRESS_REPLY);
rmp->context = mp->context;
rmp->retval = htonl (rv);
if (!rv && eth_if)
memcpy (rmp->mac_address, eth_if->address, 6);
vl_api_send_msg (reg, (u8 *) rmp);
}
static void vl_api_sw_interface_set_rx_mode_t_handler
(vl_api_sw_interface_set_rx_mode_t * mp)
{

42
src/vnet/ip/rd_cp.api Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
option version = "1.0.0";
/** \brief Enable/disable IPv6 ND address autoconfiguration
and setting up default routes
@param client_index - opaque cookie to identify the sender
@param context - sender context, to match reply w/ request
@param sw_if_index - interface to enable the autoconfigutation on
@param enable - 1 to enable address autoconfiguration, 0 to disable
@param install_default_routes - 1 to enable installing defaut routes,
0 to disable installing defaut routes,
the value is ignored (and taken as 0)
when enable param is set to 0
*/
autoreply define ip6_nd_address_autoconfig
{
u32 client_index;
u32 context;
u32 sw_if_index;
u8 enable;
u8 install_default_routes;
};
/*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/

929
src/vnet/ip/rd_cp.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,7 @@
#include <vnet/l2tp/l2tp.api.h>
#include <vnet/span/span.api.h>
#include <vnet/ip/ip.api.h>
#include <vnet/ip/rd_cp.api.h>
#include <vnet/unix/tap.api.h>
#include <vnet/vxlan/vxlan.api.h>
#include <vnet/geneve/geneve.api.h>

View File

@ -1005,6 +1005,181 @@ class TestIPv6RD(TestIPv6ND):
self.verify_prefix_info(ev.prefixes[1], prefix_info_2)
class TestIPv6RDControlPlane(TestIPv6ND):
""" IPv6 Router Discovery Control Plane Test Case """
@classmethod
def setUpClass(cls):
super(TestIPv6RDControlPlane, cls).setUpClass()
def setUp(self):
super(TestIPv6RDControlPlane, self).setUp()
# create 1 pg interface
self.create_pg_interfaces(range(1))
self.interfaces = list(self.pg_interfaces)
# setup all interfaces
for i in self.interfaces:
i.admin_up()
i.config_ip6()
def tearDown(self):
super(TestIPv6RDControlPlane, self).tearDown()
@staticmethod
def create_ra_packet(pg, routerlifetime=None):
src_ip = pg.remote_ip6_ll
dst_ip = pg.local_ip6
if routerlifetime is not None:
ra = ICMPv6ND_RA(routerlifetime=routerlifetime)
else:
ra = ICMPv6ND_RA()
p = (Ether(dst=pg.local_mac, src=pg.remote_mac) /
IPv6(dst=dst_ip, src=src_ip) / ra)
return p
@staticmethod
def get_default_routes(fib):
list = []
for entry in fib:
if entry.address_length == 0:
for path in entry.path:
if path.sw_if_index != 0xFFFFFFFF:
defaut_route = {}
defaut_route['sw_if_index'] = path.sw_if_index
defaut_route['next_hop'] = path.next_hop
list.append(defaut_route)
return list
@staticmethod
def get_interface_addresses(fib, pg):
list = []
for entry in fib:
if entry.address_length == 128:
path = entry.path[0]
if path.sw_if_index == pg.sw_if_index:
list.append(entry.address)
return list
def test_all(self):
""" Test handling of SLAAC addresses and default routes """
fib = self.vapi.ip6_fib_dump()
default_routes = self.get_default_routes(fib)
initial_addresses = set(self.get_interface_addresses(fib, self.pg0))
self.assertEqual(default_routes, [])
router_address = self.pg0.remote_ip6n_ll
self.vapi.ip6_nd_address_autoconfig(self.pg0.sw_if_index, 1, 1)
self.sleep(0.1)
# send RA
packet = (self.create_ra_packet(self.pg0) / ICMPv6NDOptPrefixInfo(
prefix="1::",
prefixlen=64,
validlifetime=2,
preferredlifetime=2,
L=1,
A=1,
) / ICMPv6NDOptPrefixInfo(
prefix="7::",
prefixlen=20,
validlifetime=1500,
preferredlifetime=1000,
L=1,
A=0,
))
self.pg0.add_stream([packet])
self.pg_start()
self.sleep(0.1)
fib = self.vapi.ip6_fib_dump()
# check FIB for new address
addresses = set(self.get_interface_addresses(fib, self.pg0))
new_addresses = addresses.difference(initial_addresses)
self.assertEqual(len(new_addresses), 1)
prefix = list(new_addresses)[0][:8] + '\0\0\0\0\0\0\0\0'
self.assertEqual(inet_ntop(AF_INET6, prefix), '1::')
# check FIB for new default route
default_routes = self.get_default_routes(fib)
self.assertEqual(len(default_routes), 1)
dr = default_routes[0]
self.assertEqual(dr['sw_if_index'], self.pg0.sw_if_index)
self.assertEqual(dr['next_hop'], router_address)
# send RA to delete default route
packet = self.create_ra_packet(self.pg0, routerlifetime=0)
self.pg0.add_stream([packet])
self.pg_start()
self.sleep(0.1)
# check that default route is deleted
fib = self.vapi.ip6_fib_dump()
default_routes = self.get_default_routes(fib)
self.assertEqual(len(default_routes), 0)
self.sleep(0.1)
# send RA
packet = self.create_ra_packet(self.pg0)
self.pg0.add_stream([packet])
self.pg_start()
self.sleep(0.1)
# check FIB for new default route
fib = self.vapi.ip6_fib_dump()
default_routes = self.get_default_routes(fib)
self.assertEqual(len(default_routes), 1)
dr = default_routes[0]
self.assertEqual(dr['sw_if_index'], self.pg0.sw_if_index)
self.assertEqual(dr['next_hop'], router_address)
# send RA, updating router lifetime to 1s
packet = self.create_ra_packet(self.pg0, 1)
self.pg0.add_stream([packet])
self.pg_start()
self.sleep(0.1)
# check that default route still exists
fib = self.vapi.ip6_fib_dump()
default_routes = self.get_default_routes(fib)
self.assertEqual(len(default_routes), 1)
dr = default_routes[0]
self.assertEqual(dr['sw_if_index'], self.pg0.sw_if_index)
self.assertEqual(dr['next_hop'], router_address)
self.sleep(1)
# check that default route is deleted
fib = self.vapi.ip6_fib_dump()
default_routes = self.get_default_routes(fib)
self.assertEqual(len(default_routes), 0)
# check FIB still contains the SLAAC address
addresses = set(self.get_interface_addresses(fib, self.pg0))
new_addresses = addresses.difference(initial_addresses)
self.assertEqual(len(new_addresses), 1)
prefix = list(new_addresses)[0][:8] + '\0\0\0\0\0\0\0\0'
self.assertEqual(inet_ntop(AF_INET6, prefix), '1::')
self.sleep(1)
# check that SLAAC address is deleted
fib = self.vapi.ip6_fib_dump()
addresses = set(self.get_interface_addresses(fib, self.pg0))
new_addresses = addresses.difference(initial_addresses)
self.assertEqual(len(new_addresses), 0)
class IPv6NDProxyTest(TestIPv6ND):
""" IPv6 ND ProxyTest Case """

View File

@ -54,16 +54,6 @@ class VppInterface(object):
"""Local IPv6 address - raw, suitable as API parameter."""
return socket.inet_pton(socket.AF_INET6, self.local_ip6)
@property
def local_ip6_ll(self):
"""Local IPv6 linnk-local address on VPP interface (string)."""
return self._local_ip6_ll
@property
def local_ip6n_ll(self):
"""Local IPv6 link-local address - raw, suitable as API parameter."""
return self.local_ip6n_ll
@property
def remote_ip6(self):
"""IPv6 address of remote peer "connected" to this interface."""
@ -74,6 +64,28 @@ class VppInterface(object):
"""IPv6 address of remote peer - raw, suitable as API parameter"""
return socket.inet_pton(socket.AF_INET6, self.remote_ip6)
@property
def local_ip6_ll(self):
"""Local IPv6 linnk-local address on VPP interface (string)."""
return self._local_ip6_ll
@property
def local_ip6n_ll(self):
"""Local IPv6 link-local address - raw, suitable as API parameter."""
return self._local_ip6n_ll
@property
def remote_ip6_ll(self):
"""Link-local IPv6 address of remote peer
"connected" to this interface."""
return self._remote_ip6_ll
@property
def remote_ip6n_ll(self):
"""Link-local IPv6 address of remote peer
- raw, suitable as API parameter"""
return self._remote_ip6n_ll
@property
def name(self):
"""Name of the interface."""
@ -196,6 +208,9 @@ class VppInterface(object):
self._local_ip6_ll = mk_ll_addr(self.local_mac)
self._local_ip6n_ll = socket.inet_pton(socket.AF_INET6,
self.local_ip6_ll)
self._remote_ip6_ll = mk_ll_addr(self.remote_mac)
self._remote_ip6n_ll = socket.inet_pton(socket.AF_INET6,
self.remote_ip6_ll)
def config_ip4(self):
"""Configure IPv4 address on the VPP interface."""

View File

@ -480,6 +480,13 @@ class VppPapiProvider(object):
'mrd': mrd,
'sw_if_index': sw_if_index})
def ip6_nd_address_autoconfig(self, sw_if_index, enable,
install_default_routes):
return self.api(self.papi.ip6_nd_address_autoconfig,
{'sw_if_index': sw_if_index,
'enable': enable,
'install_default_routes': install_default_routes})
def want_macs_learn_events(self, enable_disable=1, scan_delay=0,
max_macs_in_event=0, learn_limit=0):
return self.api(self.papi.want_l2_macs_events,