IPv6 ND Router discovery control plane (VPP-1095)
Change-Id: I4b5b60e7c6f618bb935eab1e96a2e79bbb14f58f Signed-off-by: Juraj Sloboda <jsloboda@cisco.com>
This commit is contained in:
@ -342,6 +342,7 @@ libvnet_la_SOURCES += \
|
|||||||
vnet/ip/ip6_neighbor.c \
|
vnet/ip/ip6_neighbor.c \
|
||||||
vnet/ip/ip6_pg.c \
|
vnet/ip/ip6_pg.c \
|
||||||
vnet/ip/ip6_reassembly.c \
|
vnet/ip/ip6_reassembly.c \
|
||||||
|
vnet/ip/rd_cp.c \
|
||||||
vnet/ip/ip_api.c \
|
vnet/ip/ip_api.c \
|
||||||
vnet/ip/ip_checksum.c \
|
vnet/ip/ip_checksum.c \
|
||||||
vnet/ip/ip_frag.c \
|
vnet/ip/ip_frag.c \
|
||||||
@ -360,6 +361,7 @@ nobase_include_HEADERS += \
|
|||||||
vnet/ip/icmp6.h \
|
vnet/ip/icmp6.h \
|
||||||
vnet/ip/igmp_packet.h \
|
vnet/ip/igmp_packet.h \
|
||||||
vnet/ip/ip.api.h \
|
vnet/ip/ip.api.h \
|
||||||
|
vnet/ip/rd_cp.api.h \
|
||||||
vnet/ip/ip4_error.h \
|
vnet/ip/ip4_error.h \
|
||||||
vnet/ip/ip4.h \
|
vnet/ip/ip4.h \
|
||||||
vnet/ip/ip4_mtrie.h \
|
vnet/ip/ip4_mtrie.h \
|
||||||
@ -382,6 +384,7 @@ nobase_include_HEADERS += \
|
|||||||
|
|
||||||
API_FILES += \
|
API_FILES += \
|
||||||
vnet/ip/ip.api \
|
vnet/ip/ip.api \
|
||||||
|
vnet/ip/rd_cp.api \
|
||||||
vnet/ip/punt.api
|
vnet/ip/punt.api
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
option version = "1.0.0";
|
option version = "1.1.0";
|
||||||
|
|
||||||
service {
|
service {
|
||||||
rpc want_interface_events returns want_interface_events_reply
|
rpc want_interface_events returns want_interface_events_reply
|
||||||
@ -336,6 +336,30 @@ autoreply define sw_interface_set_mac_address
|
|||||||
u8 mac_address[6];
|
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
|
/** \brief Set an interface's rx-mode
|
||||||
@param client_index - opaque cookie to identify the sender
|
@param client_index - opaque cookie to identify the sender
|
||||||
@param context - sender context, to match reply w/ request
|
@param context - sender context, to match reply w/ request
|
||||||
|
@ -61,6 +61,7 @@ _(SW_INTERFACE_SET_UNNUMBERED, sw_interface_set_unnumbered) \
|
|||||||
_(SW_INTERFACE_CLEAR_STATS, sw_interface_clear_stats) \
|
_(SW_INTERFACE_CLEAR_STATS, sw_interface_clear_stats) \
|
||||||
_(SW_INTERFACE_TAG_ADD_DEL, sw_interface_tag_add_del) \
|
_(SW_INTERFACE_TAG_ADD_DEL, sw_interface_tag_add_del) \
|
||||||
_(SW_INTERFACE_SET_MAC_ADDRESS, sw_interface_set_mac_address) \
|
_(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_VLAN_SUBIF, create_vlan_subif) \
|
||||||
_(CREATE_SUBIF, create_subif) \
|
_(CREATE_SUBIF, create_subif) \
|
||||||
_(DELETE_SUBIF, delete_subif) \
|
_(DELETE_SUBIF, delete_subif) \
|
||||||
@ -903,6 +904,37 @@ out:
|
|||||||
REPLY_MACRO (VL_API_SW_INTERFACE_SET_MAC_ADDRESS_REPLY);
|
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 (ðernet_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
|
static void vl_api_sw_interface_set_rx_mode_t_handler
|
||||||
(vl_api_sw_interface_set_rx_mode_t * mp)
|
(vl_api_sw_interface_set_rx_mode_t * mp)
|
||||||
{
|
{
|
||||||
|
42
src/vnet/ip/rd_cp.api
Normal file
42
src/vnet/ip/rd_cp.api
Normal 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
929
src/vnet/ip/rd_cp.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,7 @@
|
|||||||
#include <vnet/l2tp/l2tp.api.h>
|
#include <vnet/l2tp/l2tp.api.h>
|
||||||
#include <vnet/span/span.api.h>
|
#include <vnet/span/span.api.h>
|
||||||
#include <vnet/ip/ip.api.h>
|
#include <vnet/ip/ip.api.h>
|
||||||
|
#include <vnet/ip/rd_cp.api.h>
|
||||||
#include <vnet/unix/tap.api.h>
|
#include <vnet/unix/tap.api.h>
|
||||||
#include <vnet/vxlan/vxlan.api.h>
|
#include <vnet/vxlan/vxlan.api.h>
|
||||||
#include <vnet/geneve/geneve.api.h>
|
#include <vnet/geneve/geneve.api.h>
|
||||||
|
175
test/test_ip6.py
175
test/test_ip6.py
@ -1005,6 +1005,181 @@ class TestIPv6RD(TestIPv6ND):
|
|||||||
self.verify_prefix_info(ev.prefixes[1], prefix_info_2)
|
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):
|
class IPv6NDProxyTest(TestIPv6ND):
|
||||||
""" IPv6 ND ProxyTest Case """
|
""" IPv6 ND ProxyTest Case """
|
||||||
|
|
||||||
|
@ -54,16 +54,6 @@ class VppInterface(object):
|
|||||||
"""Local IPv6 address - raw, suitable as API parameter."""
|
"""Local IPv6 address - raw, suitable as API parameter."""
|
||||||
return socket.inet_pton(socket.AF_INET6, self.local_ip6)
|
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
|
@property
|
||||||
def remote_ip6(self):
|
def remote_ip6(self):
|
||||||
"""IPv6 address of remote peer "connected" to this interface."""
|
"""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"""
|
"""IPv6 address of remote peer - raw, suitable as API parameter"""
|
||||||
return socket.inet_pton(socket.AF_INET6, self.remote_ip6)
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Name of the interface."""
|
"""Name of the interface."""
|
||||||
@ -196,6 +208,9 @@ class VppInterface(object):
|
|||||||
self._local_ip6_ll = mk_ll_addr(self.local_mac)
|
self._local_ip6_ll = mk_ll_addr(self.local_mac)
|
||||||
self._local_ip6n_ll = socket.inet_pton(socket.AF_INET6,
|
self._local_ip6n_ll = socket.inet_pton(socket.AF_INET6,
|
||||||
self.local_ip6_ll)
|
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):
|
def config_ip4(self):
|
||||||
"""Configure IPv4 address on the VPP interface."""
|
"""Configure IPv4 address on the VPP interface."""
|
||||||
|
@ -480,6 +480,13 @@ class VppPapiProvider(object):
|
|||||||
'mrd': mrd,
|
'mrd': mrd,
|
||||||
'sw_if_index': sw_if_index})
|
'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,
|
def want_macs_learn_events(self, enable_disable=1, scan_delay=0,
|
||||||
max_macs_in_event=0, learn_limit=0):
|
max_macs_in_event=0, learn_limit=0):
|
||||||
return self.api(self.papi.want_l2_macs_events,
|
return self.api(self.papi.want_l2_macs_events,
|
||||||
|
Reference in New Issue
Block a user