From 7cd468a3d7dee7d6c92f69a0bb7061ae208ec727 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Mon, 19 Dec 2016 23:05:39 +0100 Subject: Reorganize source tree to use single autotools instance Change-Id: I7b51f88292e057c6443b12224486f2d0c9f8ae23 Signed-off-by: Damjan Marion --- src/vnet/bfd/bfd_main.h | 220 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/vnet/bfd/bfd_main.h (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h new file mode 100644 index 00000000..c72ea92a --- /dev/null +++ b/src/vnet/bfd/bfd_main.h @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2011-2016 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. + */ +/** + * @file + * @brief BFD global declarations + */ +#ifndef __included_bfd_main_h__ +#define __included_bfd_main_h__ + +#include +#include +#include +#include + +#define foreach_bfd_transport(F) \ + F (UDP4, "ip4-rewrite") \ + F (UDP6, "ip6-rewrite") + +typedef enum +{ +#define F(t, n) BFD_TRANSPORT_##t, + foreach_bfd_transport (F) +#undef F +} bfd_transport_t; + +#define foreach_bfd_mode(F) \ + F (asynchronous) \ + F (demand) + +typedef enum +{ +#define F(x) BFD_MODE_##x, + foreach_bfd_mode (F) +#undef F +} bfd_mode_e; + +typedef struct +{ + /* index in bfd_main.sessions pool */ + u32 bs_idx; + + /* session state */ + bfd_state_e local_state; + + /* local diagnostics */ + bfd_diag_code_e local_diag; + + /* remote session state */ + bfd_state_e remote_state; + + /* local discriminator */ + u32 local_discr; + + /* remote discriminator */ + u32 remote_discr; + + /* configured desired min tx interval (microseconds) */ + u32 config_desired_min_tx_us; + + /* desired min tx interval (microseconds) */ + u32 desired_min_tx_us; + + /* desired min tx interval (clocks) */ + u64 desired_min_tx_clocks; + + /* required min rx interval */ + u32 required_min_rx_us; + + /* remote min rx interval (microseconds) */ + u32 remote_min_rx_us; + + /* remote min rx interval (clocks) */ + u64 remote_min_rx_clocks; + + /* remote desired min tx interval */ + u32 remote_desired_min_tx_us; + + /* 1 if in demand mode, 0 otherwise */ + u8 local_demand; + + /* 1 if remote system sets demand mode, 0 otherwise */ + u8 remote_demand; + + /* local detect multiplier */ + u8 local_detect_mult; + + /* remote detect multiplier */ + u8 remote_detect_mult; + + /* set to value of timer in timing wheel, 0 if never set */ + u64 wheel_time_clocks; + + /* transmit interval */ + u64 transmit_interval_clocks; + + /* next time at which to transmit a packet */ + u64 tx_timeout_clocks; + + /* timestamp of last packet received */ + u64 last_rx_clocks; + + /* detection time */ + u64 detection_time_clocks; + + /* transport type for this session */ + bfd_transport_t transport; + + union + { + bfd_udp_session_t udp; + }; +} bfd_session_t; + +typedef struct +{ + u32 client_index; + u32 client_pid; +} event_subscriber_t; + +typedef struct +{ + /* pool of bfd sessions context data */ + bfd_session_t *sessions; + + /* timing wheel for scheduling timeouts */ + timing_wheel_t wheel; + + /* timing wheel inaccuracy, in clocks */ + u64 wheel_inaccuracy; + + /* hashmap - bfd session by discriminator */ + u32 *session_by_disc; + + /* background process node index */ + u32 bfd_process_node_index; + + /* convenience variables */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; + + /* cpu clocks per second */ + f64 cpu_cps; + + /* for generating random numbers */ + u32 random_seed; + +} bfd_main_t; + +extern bfd_main_t bfd_main; + +/* Packet counters */ +#define foreach_bfd_error(F) \ + F (NONE, "good bfd packets (processed)") \ + F (BAD, "invalid bfd packets") \ + F (DISABLED, "bfd packets received on disabled interfaces") + +typedef enum +{ +#define F(sym, str) BFD_ERROR_##sym, + foreach_bfd_error (F) +#undef F + BFD_N_ERROR, +} bfd_error_t; + +/* bfd packet trace capture */ +typedef struct +{ + u32 len; + u8 data[400]; +} bfd_input_trace_t; + +enum +{ + BFD_EVENT_RESCHEDULE = 1, + BFD_EVENT_NEW_SESSION, +} bfd_process_event_e; + +u8 *bfd_input_format_trace (u8 * s, va_list * args); + +bfd_session_t *bfd_get_session (bfd_main_t * bm, bfd_transport_t t); +void bfd_put_session (bfd_main_t * bm, bfd_session_t * bs); +bfd_session_t *bfd_find_session_by_idx (bfd_main_t * bm, uword bs_idx); +bfd_session_t *bfd_find_session_by_disc (bfd_main_t * bm, u32 disc); +void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs); +void bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * bfd, u32 bs_idx); +int bfd_verify_pkt_common (const bfd_pkt_t * pkt); +int bfd_verify_pkt_session (const bfd_pkt_t * pkt, u16 pkt_size, + const bfd_session_t * bs); +void bfd_event (bfd_main_t * bm, bfd_session_t * bs); +void bfd_send_final (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs); +u8 *format_bfd_session (u8 * s, va_list * args); + + +#define USEC_PER_MS 1000LL +#define USEC_PER_SECOND (1000 * USEC_PER_MS) + +/* default, slow transmission interval for BFD packets, per spec at least 1s */ +#define BFD_DEFAULT_DESIRED_MIN_TX_US USEC_PER_SECOND + +#endif /* __included_bfd_main_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- cgit 1.2.3-korg From 3e0a35613602ff7abf7348f7652b1d29b1352d1f Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 19 Dec 2016 09:05:21 +0100 Subject: BFD: immediately honor reduced remote_min_rx interval Change-Id: I7f09b45c926557d2ad0e2706b38fa56ff8194a3d Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd_main.c | 42 ++++++++++++------------ src/vnet/bfd/bfd_main.h | 10 ++++-- src/vnet/bfd/bfd_udp.c | 1 + test/test_bfd.py | 86 +++++++++++++++++++++++++++++++++++-------------- 4 files changed, 93 insertions(+), 46 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index e25eadfc..62be1842 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -126,38 +126,36 @@ bfd_calc_next_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now) if (bs->local_detect_mult > 1) { /* common case - 75-100% of transmit interval */ - bs->tx_timeout_clocks = now + + bs->tx_timeout_clocks = bs->last_tx_clocks + (1 - .25 * (random_f64 (&bm->random_seed))) * bs->transmit_interval_clocks; if (bs->tx_timeout_clocks < now) { - /* huh, we've missed it already, skip the missed events */ - const u64 missed = - (now - bs->tx_timeout_clocks) / bs->transmit_interval_clocks; - BFD_ERR ("Missed %lu transmit events (now is %lu, calc " - "tx_timeout is %lu)!", - missed, now, bs->tx_timeout_clocks); - bs->tx_timeout_clocks += - (missed + 1) * bs->transmit_interval_clocks; + /* huh, we've missed it already, transmit now */ + BFD_DBG ("Missed %lu transmit events (now is %lu, calc " + "tx_timeout is %lu)", + (now - bs->tx_timeout_clocks) / + bs->transmit_interval_clocks, + now, bs->tx_timeout_clocks); + bs->tx_timeout_clocks = now; } } else { /* special case - 75-90% of transmit interval */ bs->tx_timeout_clocks = - now + + bs->last_tx_clocks + (.9 - .15 * (random_f64 (&bm->random_seed))) * bs->transmit_interval_clocks; if (bs->tx_timeout_clocks < now) { - /* huh, we've missed it already, skip the missed events */ - const u64 missed = - (now - bs->tx_timeout_clocks) / bs->transmit_interval_clocks; - BFD_ERR ("Missed %lu transmit events (now is %lu, calc " - "tx_timeout is %lu)!", - missed, now, bs->tx_timeout_clocks); - bs->tx_timeout_clocks += - (missed + 1) * bs->transmit_interval_clocks; + /* huh, we've missed it already, transmit now */ + BFD_DBG ("Missed %lu transmit events (now is %lu, calc " + "tx_timeout is %lu)", + (now - bs->tx_timeout_clocks) / + bs->transmit_interval_clocks, + now, bs->tx_timeout_clocks); + bs->tx_timeout_clocks = now; } } } @@ -485,7 +483,7 @@ bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) pkt->your_disc = bs->remote_discr; pkt->des_min_tx = clib_host_to_net_u32 (bs->desired_min_tx_us); pkt->req_min_rx = clib_host_to_net_u32 (bs->required_min_rx_us); - pkt->req_min_echo_rx = clib_host_to_net_u32 (0); /* FIXME */ + pkt->req_min_echo_rx = clib_host_to_net_u32 (bs->required_min_echo_rx_us); b->current_length = bfd_length; } @@ -519,6 +517,7 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, } bfd_init_control_frame (b, bs); bfd_add_transport_layer (vm, b, bs); + bs->last_tx_clocks = now; bfd_calc_next_tx (bm, bs, now); } else @@ -537,6 +536,7 @@ bfd_send_final (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs) bfd_init_control_frame (b, bs); bfd_pkt_set_final (vlib_buffer_get_current (b)); bfd_add_transport_layer (vm, b, bs); + bs->last_tx_clocks = clib_cpu_time_now (); } static void @@ -946,6 +946,7 @@ format_bfd_session (u8 * s, va_list * args) "bfd.LocalDiag=%s, " "bfd.DesiredMinTxInterval=%u, " "bfd.RequiredMinRxInterval=%u, " + "bfd.RequiredMinEchoRxInterval=%u, " "bfd.RemoteMinRxInterval=%u, " "bfd.DemandMode=%s, " "bfd.RemoteDemandMode=%s, " @@ -954,7 +955,8 @@ format_bfd_session (u8 * s, va_list * args) bfd_state_string (bs->remote_state), bs->local_discr, bs->remote_discr, bfd_diag_code_string (bs->local_diag), bs->desired_min_tx_us, bs->required_min_rx_us, - bs->remote_min_rx_us, (bs->local_demand ? "yes" : "no"), + bs->required_min_echo_rx_us, bs->remote_min_rx_us, + (bs->local_demand ? "yes" : "no"), (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult); } diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index c72ea92a..cc82c839 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -75,16 +75,19 @@ typedef struct /* desired min tx interval (clocks) */ u64 desired_min_tx_clocks; - /* required min rx interval */ + /* required min rx interval (microseconds) */ u32 required_min_rx_us; + /* required min echo rx interval (microseconds) */ + u32 required_min_echo_rx_us; + /* remote min rx interval (microseconds) */ u32 remote_min_rx_us; /* remote min rx interval (clocks) */ u64 remote_min_rx_clocks; - /* remote desired min tx interval */ + /* remote desired min tx interval (microseconds) */ u32 remote_desired_min_tx_us; /* 1 if in demand mode, 0 otherwise */ @@ -108,6 +111,9 @@ typedef struct /* next time at which to transmit a packet */ u64 tx_timeout_clocks; + /* timestamp of last packet transmitted */ + u64 last_tx_clocks; + /* timestamp of last packet received */ u64 last_rx_clocks; diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index 3c747d86..677f1e22 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -146,6 +146,7 @@ bfd_udp_add_session_internal (bfd_udp_main_t *bum, u32 sw_if_index, } bs->config_desired_min_tx_us = desired_min_tx_us; bs->required_min_rx_us = required_min_rx_us; + bs->required_min_echo_rx_us = required_min_rx_us; /* FIXME */ bs->local_detect_mult = detect_mult; bfd_session_start (bum->bfd_main, bs); return 0; diff --git a/test/test_bfd.py b/test/test_bfd.py index 1ea69f55..4aa99533 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -7,6 +7,8 @@ from bfd import * from framework import * from util import ppp +us_in_sec = 1000000 + class BFDAPITestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) - API""" @@ -74,6 +76,7 @@ def verify_udp(test, packet): class BFDTestSession(object): + """ BFD session as seen from test framework side """ def __init__(self, test, interface, detect_mult=3): self.test = test @@ -90,7 +93,9 @@ class BFDTestSession(object): def create_packet(self): packet = create_packet(self.interface) + self.test.logger.debug("BFD: Creating packet") for name, value in self.bfd_values.iteritems(): + self.test.logger.debug("BFD: setting packet.%s=%s", name, value) packet[BFD].setfieldval(name, value) return packet @@ -134,11 +139,12 @@ class BFDTestCase(VppTestCase): self.vpp_session.add_vpp_config() self.vpp_session.admin_up() self.test_session = BFDTestSession(self, self.pg0) + self.test_session.update(required_min_rx_interval=100000) def tearDown(self): - self.vapi.want_bfd_events(enable_disable=0) self.vapi.collect_events() # clear the event queue if not self.vpp_dead: + self.vapi.want_bfd_events(enable_disable=0) self.vpp_session.remove_vpp_config() super(BFDTestCase, self).tearDown() @@ -167,8 +173,16 @@ class BFDTestCase(VppTestCase): self.assert_equal(e.state, expected_state, BFDState) def wait_for_bfd_packet(self, timeout=1): + """ wait for BFD packet + + :param timeout: how long to wait max + + :returns: tuple (packet, time spent waiting for packet) + """ self.logger.info("BFD: Waiting for BFD packet") + before = time.time() p = self.pg0.wait_for_packet(timeout=timeout) + after = time.time() bfd = p[BFD] if bfd is None: raise Exception(ppp("Unexpected or invalid BFD packet:", p)) @@ -177,7 +191,26 @@ class BFDTestCase(VppTestCase): verify_ip(self, p, self.pg0.local_ip4, self.pg0.remote_ip4) verify_udp(self, p) self.test_session.verify_packet(p) - return p + return p, after - before + + def bfd_session_up(self): + self.pg_enable_capture([self.pg0]) + self.logger.info("BFD: Waiting for slow hello") + p, ttp = self.wait_for_bfd_packet() + self.logger.info("BFD: Sending Init") + self.test_session.update(my_discriminator=randint(0, 40000000), + your_discriminator=p[BFD].my_discriminator, + state=BFDState.init) + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.up) + self.logger.info("BFD: Session is Up") + self.test_session.update(state=BFDState.up) + + def test_session_up(self): + """ bring BFD session up """ + self.bfd_session_up() def test_slow_timer(self): """ verify slow periodic control frames while session down """ @@ -198,7 +231,7 @@ class BFDTestCase(VppTestCase): def test_zero_remote_min_rx(self): """ no packets when zero BFD RemoteMinRxInterval """ self.pg_enable_capture([self.pg0]) - p = self.wait_for_bfd_packet(2) + p, timeout = self.wait_for_bfd_packet(2) self.test_session.update(my_discriminator=randint(0, 40000000), your_discriminator=p[BFD].my_discriminator, state=BFDState.init, @@ -213,26 +246,6 @@ class BFDTestCase(VppTestCase): return raise Exception(ppp("Received unexpected BFD packet:", p)) - def bfd_session_up(self): - self.pg_enable_capture([self.pg0]) - self.logger.info("BFD: Waiting for slow hello") - p = self.wait_for_bfd_packet(2) - self.logger.info("BFD: Sending Init") - self.test_session.update(my_discriminator=randint(0, 40000000), - your_discriminator=p[BFD].my_discriminator, - state=BFDState.init, - required_min_rx_interval=100000) - self.test_session.send_packet() - self.logger.info("BFD: Waiting for event") - e = self.vapi.wait_for_event(1, "bfd_udp_session_details") - self.verify_event(e, expected_state=BFDState.up) - self.logger.info("BFD: Session is Up") - self.test_session.update(state=BFDState.up) - - def test_session_up(self): - """ bring BFD session up """ - self.bfd_session_up() - def test_hold_up(self): """ hold BFD session up """ self.bfd_session_up() @@ -260,7 +273,7 @@ class BFDTestCase(VppTestCase): self.test_session.send_packet() now = time.time() count = 0 - while time.time() < now + interval / 1000000: + while time.time() < now + interval / us_in_sec: try: p = self.wait_for_bfd_packet() if count > 1: @@ -270,6 +283,31 @@ class BFDTestCase(VppTestCase): pass self.assert_in_range(count, 0, 1, "number of packets received") + def test_immediate_remote_min_rx_reduce(self): + """ immediately honor remote min rx reduction """ + self.vpp_session.remove_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + desired_min_tx=10000) + self.vpp_session.add_vpp_config() + self.test_session.update(desired_min_tx_interval=1000000, + required_min_rx_interval=1000000) + self.bfd_session_up() + self.wait_for_bfd_packet() + interval = 100000 + self.test_session.update(required_min_rx_interval=interval) + self.test_session.send_packet() + p, ttp = self.wait_for_bfd_packet() + # allow extra 10% to work around timing issues, first packet is special + self.assert_in_range(ttp, 0, 1.10 * interval / us_in_sec, + "time between BFD packets") + p, ttp = self.wait_for_bfd_packet() + self.assert_in_range(ttp, .9 * .75 * interval / us_in_sec, + 1.10 * interval / us_in_sec, + "time between BFD packets") + p, ttp = self.wait_for_bfd_packet() + self.assert_in_range(ttp, .9 * .75 * interval / us_in_sec, + 1.10 * interval / us_in_sec, + "time between BFD packets") if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg From 46a87adf10d41af4b1b14f06bdab33228cbaae95 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 2 Jan 2017 08:22:23 +0100 Subject: BFD: IPv6 support Change-Id: Iaa9538c7cca500c04cf2704e5bf87480543cfcdf Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd_main.c | 52 +++++----- src/vnet/bfd/bfd_main.h | 2 +- src/vnet/bfd/bfd_udp.c | 245 ++++++++++++++++++++++++++++++++++++++---------- src/vnet/bfd/bfd_udp.h | 6 +- test/bfd.py | 14 ++- test/test_bfd.py | 221 +++++++++++++++++++++++++++---------------- test/util.py | 11 ++- test/vpp_interface.py | 11 ++- 8 files changed, 394 insertions(+), 168 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 62be1842..7e1a2ef2 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -34,16 +34,9 @@ bfd_us_to_clocks (bfd_main_t * bm, u64 us) static vlib_node_registration_t bfd_process_node; -typedef enum -{ -#define F(t, n) BFD_OUTPUT_##t, - foreach_bfd_transport (F) -#undef F - BFD_OUTPUT_N_NEXT, -} bfd_output_next_t; - -static u32 bfd_next_index_by_transport[] = { -#define F(t, n) [BFD_TRANSPORT_##t] = BFD_OUTPUT_##t, +/* set to 0 here, real values filled at startup */ +static u32 bfd_node_index_by_transport[] = { +#define F(t, n) [BFD_TRANSPORT_##t] = 0, foreach_bfd_transport (F) #undef F }; @@ -378,7 +371,7 @@ bfd_input_format_trace (u8 * s, va_list * args) clib_net_to_host_u32 (pkt->des_min_tx)); s = format (s, " required min rx interval: %u\n", clib_net_to_host_u32 (pkt->req_min_rx)); - s = format (s, " required min echo rx interval: %u\n", + s = format (s, " required min echo rx interval: %u", clib_net_to_host_u32 (pkt->req_min_echo_rx)); } } @@ -426,10 +419,12 @@ bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, switch (bs->transport) { case BFD_TRANSPORT_UDP4: - /* fallthrough */ + BFD_DBG ("Transport bfd via udp4, bs_idx=%u", bs->bs_idx); + bfd_add_udp4_transport (vm, b, &bs->udp); + break; case BFD_TRANSPORT_UDP6: - BFD_DBG ("Transport bfd via udp, bs_idx=%u", bs->bs_idx); - bfd_add_udp_transport (vm, b, &bs->udp); + BFD_DBG ("Transport bfd via udp6, bs_idx=%u", bs->bs_idx); + bfd_add_udp6_transport (vm, b, &bs->udp); break; } } @@ -448,17 +443,14 @@ bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_buffer_t *b = vlib_get_buffer (vm, bi); ASSERT (b->current_data == 0); - u32 *to_next; - u32 n_left_to_next; - - vlib_get_next_frame (vm, rt, bfd_next_index_by_transport[bs->transport], - to_next, n_left_to_next); + vlib_frame_t *f = + vlib_get_frame_to_node (vm, bfd_node_index_by_transport[bs->transport]); + u32 *to_next = vlib_frame_vector_args (f); to_next[0] = bi; - n_left_to_next -= 1; + f->n_vectors = 1; - vlib_put_next_frame (vm, rt, bfd_next_index_by_transport[bs->transport], - n_left_to_next); + vlib_put_frame_to_node (vm, bfd_node_index_by_transport[bs->transport], f); return b; } @@ -680,13 +672,8 @@ VLIB_REGISTER_NODE (bfd_process_node, static) = { .function = bfd_process, .type = VLIB_NODE_TYPE_PROCESS, .name = "bfd-process", - .n_next_nodes = BFD_OUTPUT_N_NEXT, - .next_nodes = - { -#define F(t, n) [BFD_OUTPUT_##t] = n, - foreach_bfd_transport (F) -#undef F - }, + .n_next_nodes = 0, + .next_nodes = {}, }; /* *INDENT-ON* */ @@ -734,6 +721,13 @@ bfd_main_init (vlib_main_t * vm) timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; + vlib_node_t *node = NULL; +#define F(t, n) \ + node = vlib_get_node_by_name (vm, (u8 *)n); \ + bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index;\ + BFD_DBG("node '%s' has index %u", n, node->index); + foreach_bfd_transport (F); +#undef F return 0; } diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index cc82c839..20da381a 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -25,7 +25,7 @@ #include #define foreach_bfd_transport(F) \ - F (UDP4, "ip4-rewrite") \ + F (UDP4, "ip4-rewrite") \ F (UDP6, "ip6-rewrite") typedef enum diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index c1596bf6..fe348404 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -31,53 +31,80 @@ static vlib_node_registration_t bfd_udp6_input_node; bfd_udp_main_t bfd_udp_main; -void bfd_udp_transport_to_buffer (vlib_main_t *vm, vlib_buffer_t *b, - bfd_udp_session_t *bus) +void bfd_add_udp4_transport (vlib_main_t *vm, vlib_buffer_t *b, + bfd_udp_session_t *bus) { udp_header_t *udp; - u16 udp_length, ip_length; - bfd_udp_key_t *key = &bus->key; + const bfd_udp_key_t *key = &bus->key; b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; - if (ip46_address_is_ip4 (&key->local_addr)) - { - ip4_header_t *ip4; - const size_t data_size = sizeof (*ip4) + sizeof (*udp); - vlib_buffer_advance (b, -data_size); - ip4 = vlib_buffer_get_current (b); - udp = (udp_header_t *)(ip4 + 1); - memset (ip4, 0, data_size); - ip4->ip_version_and_header_length = 0x45; - ip4->ttl = 255; - ip4->protocol = IP_PROTOCOL_UDP; - ip4->src_address.as_u32 = key->local_addr.ip4.as_u32; - ip4->dst_address.as_u32 = key->peer_addr.ip4.as_u32; - - udp->src_port = clib_host_to_net_u16 (50000); /* FIXME */ - udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); - - /* fix ip length, checksum and udp length */ - ip_length = vlib_buffer_length_in_chain (vm, b); - - ip4->length = clib_host_to_net_u16 (ip_length); - ip4->checksum = ip4_header_checksum (ip4); - - udp_length = ip_length - (sizeof (*ip4)); - udp->length = clib_host_to_net_u16 (udp_length); - } - else - { - BFD_ERR ("not implemented"); - abort (); - } + vnet_buffer (b)->ip.adj_index[VLIB_RX] = bus->adj_index; + vnet_buffer (b)->ip.adj_index[VLIB_TX] = bus->adj_index; + ip4_header_t *ip4; + const size_t headers_size = sizeof (*ip4) + sizeof (*udp); + vlib_buffer_advance (b, -headers_size); + ip4 = vlib_buffer_get_current (b); + udp = (udp_header_t *)(ip4 + 1); + memset (ip4, 0, headers_size); + ip4->ip_version_and_header_length = 0x45; + ip4->ttl = 255; + ip4->protocol = IP_PROTOCOL_UDP; + ip4->src_address.as_u32 = key->local_addr.ip4.as_u32; + ip4->dst_address.as_u32 = key->peer_addr.ip4.as_u32; + + udp->src_port = clib_host_to_net_u16 (50000); /* FIXME */ + udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); + + /* fix ip length, checksum and udp length */ + const u16 ip_length = vlib_buffer_length_in_chain (vm, b); + + ip4->length = clib_host_to_net_u16 (ip_length); + ip4->checksum = ip4_header_checksum (ip4); + + const u16 udp_length = ip_length - (sizeof (*ip4)); + udp->length = clib_host_to_net_u16 (udp_length); } -void bfd_add_udp_transport (vlib_main_t *vm, vlib_buffer_t *b, - bfd_udp_session_t *bus) +void bfd_add_udp6_transport (vlib_main_t *vm, vlib_buffer_t *b, + bfd_udp_session_t *bus) { + udp_header_t *udp; + const bfd_udp_key_t *key = &bus->key; + + b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; vnet_buffer (b)->ip.adj_index[VLIB_RX] = bus->adj_index; vnet_buffer (b)->ip.adj_index[VLIB_TX] = bus->adj_index; - bfd_udp_transport_to_buffer (vm, b, bus); + ip6_header_t *ip6; + const size_t headers_size = sizeof (*ip6) + sizeof (*udp); + vlib_buffer_advance (b, -headers_size); + ip6 = vlib_buffer_get_current (b); + udp = (udp_header_t *)(ip6 + 1); + memset (ip6, 0, headers_size); + ip6->ip_version_traffic_class_and_flow_label = + clib_host_to_net_u32 (0x6 << 28); + ip6->hop_limit = 255; + ip6->protocol = IP_PROTOCOL_UDP; + clib_memcpy (&ip6->src_address, &key->local_addr.ip6, + sizeof (ip6->src_address)); + clib_memcpy (&ip6->dst_address, &key->peer_addr.ip6, + sizeof (ip6->dst_address)); + + udp->src_port = clib_host_to_net_u16 (50000); /* FIXME */ + udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd6); + + /* fix ip payload length and udp length */ + const u16 udp_length = vlib_buffer_length_in_chain (vm, b) - (sizeof (*ip6)); + udp->length = clib_host_to_net_u16 (udp_length); + ip6->payload_length = udp->length; + + /* IPv6 UDP checksum is mandatory */ + int bogus = 0; + udp->checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6, &bogus); + ASSERT (bogus == 0); + if (udp->checksum == 0) + { + udp->checksum = 0xffff; + } } static bfd_session_t *bfd_lookup_session (bfd_udp_main_t *bum, @@ -345,29 +372,29 @@ static bfd_udp_error_t bfd_udp4_verify_transport (const ip4_header_t *ip4, const bfd_udp_key_t *key = &bus->key; if (ip4->src_address.as_u32 != key->peer_addr.ip4.as_u32) { - BFD_ERR ("IP src addr mismatch, got %U, expected %U", format_ip4_address, - ip4->src_address.as_u32, format_ip4_address, - key->peer_addr.ip4.as_u32); + BFD_ERR ("IPv4 src addr mismatch, got %U, expected %U", + format_ip4_address, ip4->src_address.as_u8, format_ip4_address, + key->peer_addr.ip4.as_u8); return BFD_UDP_ERROR_BAD; } if (ip4->dst_address.as_u32 != key->local_addr.ip4.as_u32) { - BFD_ERR ("IP dst addr mismatch, got %U, expected %U", format_ip4_address, - ip4->dst_address.as_u32, format_ip4_address, - key->local_addr.ip4.as_u32); + BFD_ERR ("IPv4 dst addr mismatch, got %U, expected %U", + format_ip4_address, ip4->dst_address.as_u8, format_ip4_address, + key->local_addr.ip4.as_u8); return BFD_UDP_ERROR_BAD; } const u8 expected_ttl = 255; if (ip4->ttl != expected_ttl) { - BFD_ERR ("IP unexpected TTL value %d, expected %d", ip4->ttl, + BFD_ERR ("IPv4 unexpected TTL value %u, expected %u", ip4->ttl, expected_ttl); return BFD_UDP_ERROR_BAD; } if (clib_net_to_host_u16 (udp->src_port) < 49152 || clib_net_to_host_u16 (udp->src_port) > 65535) { - BFD_ERR ("Invalid UDP src port %d, out of range <49152,65535>", + BFD_ERR ("Invalid UDP src port %u, out of range <49152,65535>", udp->src_port); } return BFD_UDP_ERROR_NONE; @@ -460,10 +487,128 @@ static bfd_udp_error_t bfd_udp4_scan (vlib_main_t *vm, vlib_node_runtime_t *rt, return BFD_UDP_ERROR_NONE; } -static bfd_udp_error_t bfd_udp6_scan (vlib_main_t *vm, vlib_buffer_t *b) +static void bfd_udp6_find_headers (vlib_buffer_t *b, const ip6_header_t **ip6, + const udp_header_t **udp) +{ + /* sanity check first */ + const i32 start = vnet_buffer (b)->ip.start_of_ip_header; + if (start < 0 && start < sizeof (b->pre_data)) + { + BFD_ERR ("Start of ip header is before pre_data, ignoring"); + *ip6 = NULL; + *udp = NULL; + return; + } + *ip6 = (ip6_header_t *)(b->data + start); + if ((u8 *)*ip6 > (u8 *)vlib_buffer_get_current (b)) + { + BFD_ERR ("Start of ip header is beyond current data, ignoring"); + *ip6 = NULL; + *udp = NULL; + return; + } + *udp = (udp_header_t *)((*ip6) + 1); +} + +static bfd_udp_error_t bfd_udp6_verify_transport (const ip6_header_t *ip6, + const udp_header_t *udp, + const bfd_session_t *bs) { - /* TODO */ - return BFD_UDP_ERROR_BAD; + const bfd_udp_session_t *bus = &bs->udp; + const bfd_udp_key_t *key = &bus->key; + if (ip6->src_address.as_u64[0] != key->peer_addr.ip6.as_u64[0] && + ip6->src_address.as_u64[1] != key->peer_addr.ip6.as_u64[1]) + { + BFD_ERR ("IP src addr mismatch, got %U, expected %U", format_ip6_address, + ip6, format_ip6_address, &key->peer_addr.ip6); + return BFD_UDP_ERROR_BAD; + } + if (ip6->dst_address.as_u64[0] != key->local_addr.ip6.as_u64[0] && + ip6->dst_address.as_u64[1] != key->local_addr.ip6.as_u64[1]) + { + BFD_ERR ("IP dst addr mismatch, got %U, expected %U", format_ip6_address, + ip6, format_ip6_address, &key->local_addr.ip6); + return BFD_UDP_ERROR_BAD; + } + const u8 expected_hop_limit = 255; + if (ip6->hop_limit != expected_hop_limit) + { + BFD_ERR ("IPv6 unexpected hop-limit value %u, expected %u", + ip6->hop_limit, expected_hop_limit); + return BFD_UDP_ERROR_BAD; + } + if (clib_net_to_host_u16 (udp->src_port) < 49152 || + clib_net_to_host_u16 (udp->src_port) > 65535) + { + BFD_ERR ("Invalid UDP src port %u, out of range <49152,65535>", + udp->src_port); + } + return BFD_UDP_ERROR_NONE; +} + +static bfd_udp_error_t bfd_udp6_scan (vlib_main_t *vm, vlib_node_runtime_t *rt, + vlib_buffer_t *b, bfd_session_t **bs_out) +{ + const bfd_pkt_t *pkt = vlib_buffer_get_current (b); + if (sizeof (*pkt) > b->current_length) + { + BFD_ERR ( + "Payload size %d too small to hold bfd packet of minimum size %d", + b->current_length, sizeof (*pkt)); + return BFD_UDP_ERROR_BAD; + } + const ip6_header_t *ip6; + const udp_header_t *udp; + bfd_udp6_find_headers (b, &ip6, &udp); + if (!ip6 || !udp) + { + BFD_ERR ("Couldn't find ip6 or udp header"); + return BFD_UDP_ERROR_BAD; + } + if (!bfd_verify_pkt_common (pkt)) + { + return BFD_UDP_ERROR_BAD; + } + bfd_session_t *bs = NULL; + if (pkt->your_disc) + { + BFD_DBG ("Looking up BFD session using discriminator %u", + pkt->your_disc); + bs = bfd_find_session_by_disc (bfd_udp_main.bfd_main, pkt->your_disc); + } + else + { + bfd_udp_key_t key; + memset (&key, 0, sizeof (key)); + key.sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + key.local_addr.ip6.as_u64[0] = ip6->dst_address.as_u64[0]; + key.local_addr.ip6.as_u64[1] = ip6->dst_address.as_u64[1]; + key.peer_addr.ip6.as_u64[0] = ip6->src_address.as_u64[0]; + key.peer_addr.ip6.as_u64[1] = ip6->src_address.as_u64[1]; + BFD_DBG ("Looking up BFD session using key (sw_if_index=%u, local=%U, " + "peer=%U)", + key.sw_if_index, format_ip6_address, &key.local_addr, + format_ip6_address, &key.peer_addr); + bs = bfd_lookup_session (&bfd_udp_main, &key); + } + if (!bs) + { + BFD_ERR ("BFD session lookup failed - no session matches BFD pkt"); + return BFD_UDP_ERROR_BAD; + } + BFD_DBG ("BFD session found, bs_idx=%u", bs->bs_idx); + if (!bfd_verify_pkt_session (pkt, b->current_length, bs)) + { + return BFD_UDP_ERROR_BAD; + } + bfd_udp_error_t err; + if (BFD_UDP_ERROR_NONE != (err = bfd_udp6_verify_transport (ip6, udp, bs))) + { + return err; + } + bfd_rpc_update_session (bs->bs_idx, pkt); + *bs_out = bs; + return BFD_UDP_ERROR_NONE; } /* @@ -504,7 +649,7 @@ static uword bfd_udp_input (vlib_main_t *vm, vlib_node_runtime_t *rt, /* scan this bfd pkt. error0 is the counter index to bmp */ if (is_ipv6) { - error0 = bfd_udp6_scan (vm, b0); + error0 = bfd_udp6_scan (vm, rt, b0, &bs); } else { diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index 51f5327b..2cd89ca2 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -42,8 +42,10 @@ typedef struct adj_index_t adj_index; } bfd_udp_session_t; -void bfd_add_udp_transport (vlib_main_t * vm, vlib_buffer_t * b, - bfd_udp_session_t * bs); +void bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, + bfd_udp_session_t * bs); +void bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, + bfd_udp_session_t * bs); #endif /* __included_bfd_udp_h__ */ diff --git a/test/bfd.py b/test/bfd.py index 57a5bd86..51716813 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -111,14 +111,24 @@ class VppBFDUDPSession(VppObject): def local_addr(self): """ BFD session local address (VPP address) """ if self._local_addr is None: - return self._interface.local_ip4 + if self.af == AF_INET: + return self._interface.local_ip4 + elif self.af == AF_INET6: + return self._interface.local_ip6 + else: + raise Exception("Unexpected af %s' % af" % self.af) return self._local_addr @property def local_addr_n(self): """ BFD session local address (VPP address) - raw, suitable for API """ if self._local_addr is None: - return self._interface.local_ip4n + if self.af == AF_INET: + return self._interface.local_ip4n + elif self.af == AF_INET6: + return self._interface.local_ip6n + else: + raise Exception("Unexpected af %s' % af" % self.af) return self._local_addr_n @property diff --git a/test/test_bfd.py b/test/test_bfd.py index b7832247..b56df339 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -62,36 +62,14 @@ class BFDAPITestCase(VppTestCase): session1.bs_index) -def create_packet(interface, ttl=255, src_port=50000, **kwargs): - p = (Ether(src=interface.remote_mac, dst=interface.local_mac) / - IP(src=interface.remote_ip4, dst=interface.local_ip4, ttl=ttl) / - UDP(sport=src_port, dport=BFD.udp_dport) / - BFD(*kwargs)) - return p - - -def verify_ip(test, packet, local_ip, remote_ip): - """ Verify correctness of IP layer. """ - ip = packet[IP] - test.assert_equal(ip.src, local_ip, "IP source address") - test.assert_equal(ip.dst, remote_ip, "IP destination address") - test.assert_equal(ip.ttl, 255, "IP TTL") - - -def verify_udp(test, packet): - """ Verify correctness of UDP layer. """ - udp = packet[UDP] - test.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port") - test.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max, - "UDP source port") - - class BFDTestSession(object): """ BFD session as seen from test framework side """ - def __init__(self, test, interface, detect_mult=3): + def __init__(self, test, interface, af, detect_mult=3): self.test = test + self.af = af self.interface = interface + self.udp_sport = 50000 self.bfd_values = { 'my_discriminator': 0, 'desired_min_tx_interval': 100000, @@ -103,7 +81,22 @@ class BFDTestSession(object): self.bfd_values.update(kwargs) def create_packet(self): - packet = create_packet(self.interface) + if self.af == AF_INET6: + packet = (Ether(src=self.interface.remote_mac, + dst=self.interface.local_mac) / + IPv6(src=self.interface.remote_ip6, + dst=self.interface.local_ip6, + hlim=255) / + UDP(sport=self.udp_sport, dport=BFD.udp_dport) / + BFD()) + else: + packet = (Ether(src=self.interface.remote_mac, + dst=self.interface.local_mac) / + IP(src=self.interface.remote_ip4, + dst=self.interface.local_ip4, + ttl=255) / + UDP(sport=self.udp_sport, dport=BFD.udp_dport) / + BFD()) self.test.logger.debug("BFD: Creating packet") for name, value in self.bfd_values.iteritems(): self.test.logger.debug("BFD: setting packet.%s=%s", name, value) @@ -125,41 +118,52 @@ class BFDTestSession(object): "BFD - your discriminator") -@unittest.skip("") -class BFDTestCase(VppTestCase): - """Bidirectional Forwarding Detection (BFD)""" - - @classmethod - def setUpClass(cls): - super(BFDTestCase, cls).setUpClass() - try: - cls.create_pg_interfaces([0]) - cls.pg0.config_ip4() - cls.pg0.generate_remote_hosts() - cls.pg0.configure_ipv4_neighbors() - cls.pg0.admin_up() - cls.pg0.resolve_arp() - - except Exception: - super(BFDTestCase, cls).tearDownClass() - raise - - def setUp(self): - super(BFDTestCase, self).setUp() - self.vapi.want_bfd_events() - self.vpp_session = VppBFDUDPSession( - self, self.pg0, self.pg0.remote_ip4) - self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() - self.test_session = BFDTestSession(self, self.pg0) - self.test_session.update(required_min_rx_interval=100000) +class BFDCommonCode: + """Common code used by both IPv4 and IPv6 Test Cases""" def tearDown(self): self.vapi.collect_events() # clear the event queue if not self.vpp_dead: self.vapi.want_bfd_events(enable_disable=0) self.vpp_session.remove_vpp_config() - super(BFDTestCase, self).tearDown() + + def bfd_session_up(self): + self.pg_enable_capture([self.pg0]) + self.logger.info("BFD: Waiting for slow hello") + p, timeout = self.wait_for_bfd_packet(2) + self.logger.info("BFD: Sending Init") + self.test_session.update(my_discriminator=randint(0, 40000000), + your_discriminator=p[BFD].my_discriminator, + state=BFDState.init, + required_min_rx_interval=100000) + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.up) + self.logger.info("BFD: Session is Up") + self.test_session.update(state=BFDState.up) + + def verify_ip(self, packet): + """ Verify correctness of IP layer. """ + if self.vpp_session.af == AF_INET6: + ip = packet[IPv6] + local_ip = self.pg0.local_ip6 + remote_ip = self.pg0.remote_ip6 + self.assert_equal(ip.hlim, 255, "IPv6 hop limit") + else: + ip = packet[IP] + local_ip = self.pg0.local_ip4 + remote_ip = self.pg0.remote_ip4 + self.assert_equal(ip.ttl, 255, "IPv4 TTL") + self.assert_equal(ip.src, local_ip, "IP source address") + self.assert_equal(ip.dst, remote_ip, "IP destination address") + + def verify_udp(self, packet): + """ Verify correctness of UDP layer. """ + udp = packet[UDP] + self.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port") + self.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max, + "UDP source port") def verify_event(self, event, expected_state): """ Verify correctness of event values. """ @@ -198,35 +202,64 @@ class BFDTestCase(VppTestCase): before = time.time() p = self.pg0.wait_for_packet(timeout=timeout) after = time.time() + self.logger.debug(ppp("Got packet:", p)) bfd = p[BFD] if bfd is None: raise Exception(ppp("Unexpected or invalid BFD packet:", p)) if bfd.payload: raise Exception(ppp("Unexpected payload in BFD packet:", bfd)) - verify_ip(self, p, self.pg0.local_ip4, self.pg0.remote_ip4) - verify_udp(self, p) + self.verify_ip(p) + self.verify_udp(p) self.test_session.verify_packet(p) return p, after - before - def bfd_session_up(self): - self.pg_enable_capture([self.pg0]) - self.logger.info("BFD: Waiting for slow hello") - p, ttp = self.wait_for_bfd_packet() - self.logger.info("BFD: Sending Init") - self.test_session.update(my_discriminator=randint(0, 40000000), - your_discriminator=p[BFD].my_discriminator, - state=BFDState.init) - self.test_session.send_packet() - self.logger.info("BFD: Waiting for event") - e = self.vapi.wait_for_event(1, "bfd_udp_session_details") - self.verify_event(e, expected_state=BFDState.up) - self.logger.info("BFD: Session is Up") - self.test_session.update(state=BFDState.up) - def test_session_up(self): """ bring BFD session up """ self.bfd_session_up() + def test_hold_up(self): + """ hold BFD session up """ + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + + +class BFD4TestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD)""" + + @classmethod + def setUpClass(cls): + super(BFD4TestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.generate_remote_hosts() + cls.pg0.configure_ipv4_neighbors() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFD4TestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFD4TestCase, self).setUp() + self.vapi.want_bfd_events() + try: + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET) + except: + self.vapi.want_bfd_events(enable_disable=0) + raise + + def tearDown(self): + BFDCommonCode.tearDown(self) + super(BFD4TestCase, self).tearDown() + def test_slow_timer(self): """ verify slow periodic control frames while session down """ self.pg_enable_capture([self.pg0]) @@ -261,13 +294,6 @@ class BFDTestCase(VppTestCase): return raise Exception(ppp("Received unexpected BFD packet:", p)) - def test_hold_up(self): - """ hold BFD session up """ - self.bfd_session_up() - for i in range(5): - self.wait_for_bfd_packet() - self.test_session.send_packet() - def test_conn_down(self): """ verify session goes down after inactivity """ self.bfd_session_up() @@ -324,5 +350,42 @@ class BFDTestCase(VppTestCase): 1.10 * interval / us_in_sec, "time between BFD packets") + +class BFD6TestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD) (IPv6) """ + + @classmethod + def setUpClass(cls): + super(BFD6TestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip6() + cls.pg0.configure_ipv6_neighbors() + cls.pg0.admin_up() + cls.pg0.resolve_ndp() + + except Exception: + super(BFD6TestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFD6TestCase, self).setUp() + self.vapi.want_bfd_events() + try: + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip6, + af=AF_INET6) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET6) + self.logger.debug(self.vapi.cli("show adj nbr")) + except: + self.vapi.want_bfd_events(enable_disable=0) + raise + + def tearDown(self): + BFDCommonCode.tearDown(self) + super(BFD6TestCase, self).tearDown() + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/util.py b/test/util.py index 79893602..24e9af44 100644 --- a/test/util.py +++ b/test/util.py @@ -76,19 +76,24 @@ class Host(object): @property def ip4(self): - """ IPv4 address """ + """ IPv4 address - string """ return self._ip4 @property def ip4n(self): - """ IPv4 address """ + """ IPv4 address of remote host - raw, suitable as API parameter.""" return socket.inet_pton(socket.AF_INET, self._ip4) @property def ip6(self): - """ IPv6 address """ + """ IPv6 address - string """ return self._ip6 + @property + def ip6n(self): + """ IPv6 address of remote host - raw, suitable as API parameter.""" + return socket.inet_pton(socket.AF_INET6, self._ip6) + def __init__(self, mac=None, ip4=None, ip6=None): self._mac = mac self._ip4 = ip4 diff --git a/test/vpp_interface.py b/test/vpp_interface.py index e0a29f94..ee4a9ef6 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -131,7 +131,7 @@ class VppInterface(object): 2, count + 2): # 0: network address, 1: local vpp address mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i) ip4 = "172.16.%u.%u" % (self.sw_if_index, i) - ip6 = "fd01:%04x::%04x" % (self.sw_if_index, i) + ip6 = "fd01:%x::%x" % (self.sw_if_index, i) host = Host(mac, ip4, ip6) self._remote_hosts.append(host) self._hosts_by_mac[mac] = host @@ -155,7 +155,7 @@ class VppInterface(object): self.has_ip4_config = False self.ip4_table_id = 0 - self._local_ip6 = "fd01:%04x::1" % self.sw_if_index + self._local_ip6 = "fd01:%x::1" % self.sw_if_index self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6) self.local_ip6_prefix_len = 64 self.has_ip6_config = False @@ -226,6 +226,13 @@ class VppInterface(object): self.has_ip6_config = False self.has_ip6_config = False + def configure_ipv6_neighbors(self): + """For every remote host assign neighbor's MAC to IPv6 address.""" + for host in self._remote_hosts: + macn = host.mac.replace(":", "").decode('hex') + self.test.vapi.ip_neighbor_add_del( + self.sw_if_index, macn, host.ip6n, is_ipv6=1) + def unconfig(self): """Unconfigure IPv6 and IPv4 address on the VPP interface.""" self.unconfig_ip4() -- cgit 1.2.3-korg From b17dd9607ee8ecba5ae3ef69c7b4915b57de292a Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 9 Jan 2017 07:43:48 +0100 Subject: BFD: SHA1 authentication Add authentication support to BFD feature. Out of three existing authentication types, implement SHA1 (sole RFC requirement). Simple password is insecure and MD5 is discouraged by the RFC, so ignore those. Add/change APIs to allow configuring BFD authentication keys and their usage with BFD sessions. Change-Id: Ifb0fb5b19c2e72196d84c1cde919bd4c074ea415 Signed-off-by: Klement Sekera --- src/vnet/api_errno.h | 6 +- src/vnet/bfd/bfd.api | 207 +++++++++++-- src/vnet/bfd/bfd_api.c | 177 ++++++++--- src/vnet/bfd/bfd_api.h | 31 +- src/vnet/bfd/bfd_main.c | 628 +++++++++++++++++++++++++++++++++---- src/vnet/bfd/bfd_main.h | 79 ++++- src/vnet/bfd/bfd_protocol.c | 127 ++++++-- src/vnet/bfd/bfd_protocol.h | 119 +++++-- src/vnet/bfd/bfd_udp.c | 343 +++++++++++++++++---- test/bfd.py | 265 ++++++++++++---- test/framework.py | 15 +- test/test_bfd.py | 733 +++++++++++++++++++++++++++++++++++++++++--- test/vpp_papi_provider.py | 77 ++++- 13 files changed, 2437 insertions(+), 370 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/api_errno.h b/src/vnet/api_errno.h index 7166da67..192bfaa4 100644 --- a/src/vnet/api_errno.h +++ b/src/vnet/api_errno.h @@ -91,8 +91,10 @@ _(INVALID_ADDRESS_FAMILY, -97, "Invalid address family") \ _(INVALID_SUB_SW_IF_INDEX, -98, "Invalid sub-interface sw_if_index") \ _(TABLE_TOO_BIG, -99, "Table too big") \ _(CANNOT_ENABLE_DISABLE_FEATURE, -100, "Cannot enable/disable feature") \ -_(BFD_EEXIST, -101, "Duplicate BFD session") \ -_(BFD_NOENT, -102, "No such BFD session") +_(BFD_EEXIST, -101, "Duplicate BFD object") \ +_(BFD_ENOENT, -102, "No such BFD object") \ +_(BFD_EINUSE, -103, "BFD object in use") \ +_(BFD_NOTSUPP, -104, "BFD feature not supported") typedef enum { diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api index 5798ee69..17ca35b6 100644 --- a/src/vnet/bfd/bfd.api +++ b/src/vnet/bfd/bfd.api @@ -21,7 +21,8 @@ @param min_rx - desired min rx interval @param detect_mult - desired detection multiplier */ -define bfd_set_config { +define bfd_set_config +{ u32 client_index; u32 context; u32 slow_timer; @@ -34,14 +35,16 @@ define bfd_set_config { @param context - sender context, to match reply w/ request @param retval - return code for the request */ -define bfd_set_config_reply { +define bfd_set_config_reply +{ u32 context; i32 retval; }; /** \brief Get BFD configuration */ -define bfd_get_config { +define bfd_get_config +{ u32 client_index; u32 context; }; @@ -54,7 +57,8 @@ define bfd_get_config { @param min_rx - desired min rx interval @param detect_mult - desired detection multiplier */ -define bfd_get_config_reply { +define bfd_get_config_reply +{ u32 client_index; u32 context; u32 slow_timer; @@ -69,12 +73,16 @@ define bfd_get_config_reply { @param sw_if_index - sw index of the interface @param desired_min_tx - desired min transmit interval (microseconds) @param required_min_rx - required min receive interval (microseconds) - @param detect_mult - detect multiplier (# of packets missed between connection goes down) @param local_addr - local address @param peer_addr - peer address @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param detect_mult - detect multiplier (# of packets missed before connection goes down) + @param is_authenticated - non-zero if authentication is required + @param bfd_key_id - key id sent out in BFD packets (if is_authenticated) + @param conf_key_id - id of already configured key (if is_authenticated) */ -define bfd_udp_add { +define bfd_udp_add +{ u32 client_index; u32 context; u32 sw_if_index; @@ -84,17 +92,19 @@ define bfd_udp_add { u8 peer_addr[16]; u8 is_ipv6; u8 detect_mult; + u8 is_authenticated; + u8 bfd_key_id; + u32 conf_key_id; }; /** \brief Add UDP BFD session response @param context - sender context, to match reply w/ request @param retval - return code for the request - @param bs_index - index of the session created */ -define bfd_udp_add_reply { +define bfd_udp_add_reply +{ u32 context; i32 retval; - u32 bs_index; }; /** \brief Delete UDP BFD session on interface @@ -105,7 +115,8 @@ define bfd_udp_add_reply { @param peer_addr - peer address @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 */ -define bfd_udp_del { +define bfd_udp_del +{ u32 client_index; u32 context; u32 sw_if_index; @@ -118,7 +129,8 @@ define bfd_udp_del { @param context - sender context, to match reply w/ request @param retval - return code for the request */ -define bfd_udp_del_reply { +define bfd_udp_del_reply +{ u32 context; i32 retval; }; @@ -127,48 +139,61 @@ define bfd_udp_del_reply { @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request */ -define bfd_udp_session_dump { +define bfd_udp_session_dump +{ u32 client_index; u32 context; }; /** \brief BFD session details structure @param context - sender context, to match reply w/ request - @param bs_index - index of the session @param sw_if_index - sw index of the interface @param local_addr - local address @param peer_addr - peer address @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 @param state - session state + @param is_authenticated - non-zero if authentication in-use, zero otherwise + @param bfd_key_id - ID of key currently in-use if auth is on + @param conf_key_id - configured key ID for this session */ -define bfd_udp_session_details { +define bfd_udp_session_details +{ u32 context; - u32 bs_index; u32 sw_if_index; u8 local_addr[16]; u8 peer_addr[16]; u8 is_ipv6; u8 state; + u8 is_authenticated; + u8 bfd_key_id; + u32 conf_key_id; }; -/** \brief Set flags of BFD session +/** \brief Set flags of BFD UDP session @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request - @param bs_index - index of the bfd session to set flags on + @param sw_if_index - sw index of the interface + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 @param admin_up_down - set the admin state, 1 = up, 0 = down */ -define bfd_session_set_flags { +define bfd_udp_session_set_flags +{ u32 client_index; u32 context; - u32 bs_index; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; u8 admin_up_down; }; -/** \brief Reply to bfd_session_set_flags +/** \brief Reply to bfd_udp_session_set_flags @param context - sender context which was passed in the request @param retval - return code of the set flags request */ -define bfd_session_set_flags_reply +define bfd_udp_session_set_flags_reply { u32 context; i32 retval; @@ -198,6 +223,146 @@ define want_bfd_events_reply i32 retval; }; +/** \brief BFD UDP - add/replace key to configuration + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param conf_key_id - key ID to add/replace/delete + @param key_len - length of key (must be non-zero) + @param auth_type - authentication type (RFC 5880/4.1/Auth Type) + @param key - key data +*/ +define bfd_auth_set_key +{ + u32 client_index; + u32 context; + u32 conf_key_id; + u8 key_len; + u8 auth_type; + u8 key[20]; +}; + +/** \brief BFD UDP - add/replace key reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_auth_set_key_reply +{ + u32 context; + i32 retval; +}; + +/** \brief BFD UDP - delete key from configuration + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param conf_key_id - key ID to add/replace/delete + @param key_len - length of key (must be non-zero) + @param key - key data +*/ +define bfd_auth_del_key +{ + u32 client_index; + u32 context; + u32 conf_key_id; +}; + +/** \brief BFD UDP - delete key reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_auth_del_key_reply +{ + u32 context; + i32 retval; +}; + +/** \brief Get a list of configured authentication keys + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define bfd_auth_keys_dump +{ + u32 client_index; + u32 context; +}; + +/** \brief BFD authentication key details + @param context - sender context, to match reply w/ request + @param conf_key_id - configured key ID + @param use_count - how many BFD sessions currently use this key + @param auth_type - authentication type (RFC 5880/4.1/Auth Type) +*/ +define bfd_auth_keys_details +{ + u32 context; + u32 conf_key_id; + u32 use_count; + u8 auth_type; +}; + +/** \brief BFD UDP - activate/change authentication + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - sw index of the interface + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param is_delayed - change is applied once peer applies the change (on first received packet with this auth) + @param bfd_key_id - key id sent out in BFD packets + @param conf_key_id - id of already configured key +*/ +define bfd_udp_auth_activate +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; + u8 is_delayed; + u8 bfd_key_id; + u32 conf_key_id; +}; + +/** \brief BFD UDP - activate/change authentication reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_udp_auth_activate_reply +{ + u32 context; + i32 retval; +}; + +/** \brief BFD UDP - deactivate authentication + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - sw index of the interface + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param is_delayed - change is applied once peer applies the change (on first received non-authenticated packet) +*/ +define bfd_udp_auth_deactivate +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; + u8 is_delayed; +}; + +/** \brief BFD UDP - deactivate authentication reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_udp_auth_deactivate_reply +{ + u32 context; + i32 retval; +}; + /* * Local Variables: * eval: (c-set-style "gnu") diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c index 2e63fe90..cfc3a38d 100644 --- a/src/vnet/bfd/bfd_api.c +++ b/src/vnet/bfd/bfd_api.c @@ -43,15 +43,39 @@ #include -#define foreach_vpe_api_msg \ - _ (BFD_UDP_ADD, bfd_udp_add) \ - _ (BFD_UDP_DEL, bfd_udp_del) \ - _ (BFD_UDP_SESSION_DUMP, bfd_udp_session_dump) \ - _ (BFD_SESSION_SET_FLAGS, bfd_session_set_flags) \ - _ (WANT_BFD_EVENTS, want_bfd_events) +#define foreach_vpe_api_msg \ + _ (BFD_UDP_ADD, bfd_udp_add) \ + _ (BFD_UDP_DEL, bfd_udp_del) \ + _ (BFD_UDP_SESSION_DUMP, bfd_udp_session_dump) \ + _ (BFD_UDP_SESSION_SET_FLAGS, bfd_udp_session_set_flags) \ + _ (WANT_BFD_EVENTS, want_bfd_events) \ + _ (BFD_AUTH_SET_KEY, bfd_auth_set_key) \ + _ (BFD_AUTH_DEL_KEY, bfd_auth_del_key) \ + _ (BFD_AUTH_KEYS_DUMP, bfd_auth_keys_dump) \ + _ (BFD_UDP_AUTH_ACTIVATE, bfd_udp_auth_activate) \ + _ (BFD_UDP_AUTH_DEACTIVATE, bfd_udp_auth_deactivate) pub_sub_handler (bfd_events, BFD_EVENTS); +#define BFD_UDP_API_PARAM_COMMON_CODE \ + ip46_address_t local_addr; \ + memset (&local_addr, 0, sizeof (local_addr)); \ + ip46_address_t peer_addr; \ + memset (&peer_addr, 0, sizeof (peer_addr)); \ + if (mp->is_ipv6) \ + { \ + clib_memcpy (&local_addr.ip6, mp->local_addr, sizeof (local_addr.ip6)); \ + clib_memcpy (&peer_addr.ip6, mp->peer_addr, sizeof (peer_addr.ip6)); \ + } \ + else \ + { \ + clib_memcpy (&local_addr.ip4, mp->local_addr, sizeof (local_addr.ip4)); \ + clib_memcpy (&peer_addr.ip4, mp->peer_addr, sizeof (peer_addr.ip4)); \ + } + +#define BFD_UDP_API_PARAM_FROM_MP(mp) \ + clib_net_to_host_u32 (mp->sw_if_index), &local_addr, &peer_addr + static void vl_api_bfd_udp_add_t_handler (vl_api_bfd_udp_add_t * mp) { @@ -60,31 +84,17 @@ vl_api_bfd_udp_add_t_handler (vl_api_bfd_udp_add_t * mp) VALIDATE_SW_IF_INDEX (mp); - ip46_address_t local_addr; - memset (&local_addr, 0, sizeof (local_addr)); - ip46_address_t peer_addr; - memset (&peer_addr, 0, sizeof (peer_addr)); - if (mp->is_ipv6) - { - clib_memcpy (&local_addr.ip6, mp->local_addr, sizeof (local_addr.ip6)); - clib_memcpy (&peer_addr.ip6, mp->peer_addr, sizeof (peer_addr.ip6)); - } - else - { - clib_memcpy (&local_addr.ip4, mp->local_addr, sizeof (local_addr.ip4)); - clib_memcpy (&peer_addr.ip4, mp->peer_addr, sizeof (peer_addr.ip4)); - } + BFD_UDP_API_PARAM_COMMON_CODE; - u32 bs_index = 0; - rv = bfd_udp_add_session (clib_net_to_host_u32 (mp->sw_if_index), + rv = bfd_udp_add_session (BFD_UDP_API_PARAM_FROM_MP (mp), clib_net_to_host_u32 (mp->desired_min_tx), clib_net_to_host_u32 (mp->required_min_rx), - mp->detect_mult, &local_addr, &peer_addr, - &bs_index); + mp->detect_mult, mp->is_authenticated, + clib_net_to_host_u32 (mp->conf_key_id), + mp->bfd_key_id); BAD_SW_IF_INDEX_LABEL; - REPLY_MACRO2 (VL_API_BFD_UDP_ADD_REPLY, - rmp->bs_index = clib_host_to_net_u32 (bs_index)); + REPLY_MACRO (VL_API_BFD_UDP_ADD_REPLY); } static void @@ -95,23 +105,9 @@ vl_api_bfd_udp_del_t_handler (vl_api_bfd_udp_del_t * mp) VALIDATE_SW_IF_INDEX (mp); - ip46_address_t local_addr; - memset (&local_addr, 0, sizeof (local_addr)); - ip46_address_t peer_addr; - memset (&peer_addr, 0, sizeof (peer_addr)); - if (mp->is_ipv6) - { - clib_memcpy (&local_addr.ip6, mp->local_addr, sizeof (local_addr.ip6)); - clib_memcpy (&peer_addr.ip6, mp->peer_addr, sizeof (peer_addr.ip6)); - } - else - { - clib_memcpy (&local_addr.ip4, mp->local_addr, sizeof (local_addr.ip4)); - clib_memcpy (&peer_addr.ip4, mp->peer_addr, sizeof (peer_addr.ip4)); - } + BFD_UDP_API_PARAM_COMMON_CODE; - rv = bfd_udp_del_session (clib_net_to_host_u32 (mp->sw_if_index), - &local_addr, &peer_addr); + rv = bfd_udp_del_session (BFD_UDP_API_PARAM_FROM_MP (mp)); BAD_SW_IF_INDEX_LABEL; REPLY_MACRO (VL_API_BFD_UDP_DEL_REPLY); @@ -131,7 +127,6 @@ send_bfd_udp_session_details (unix_shared_memory_queue_t * q, u32 context, memset (mp, 0, sizeof (*mp)); mp->_vl_msg_id = ntohs (VL_API_BFD_UDP_SESSION_DETAILS); mp->context = context; - mp->bs_index = clib_host_to_net_u32 (bs->bs_idx); mp->state = bs->local_state; bfd_udp_session_t *bus = &bs->udp; bfd_udp_key_t *key = &bus->key; @@ -198,15 +193,101 @@ vl_api_bfd_udp_session_dump_t_handler (vl_api_bfd_udp_session_dump_t * mp) } static void -vl_api_bfd_session_set_flags_t_handler (vl_api_bfd_session_set_flags_t * mp) +vl_api_bfd_udp_session_set_flags_t_handler (vl_api_bfd_udp_session_set_flags_t + * mp) { - vl_api_bfd_session_set_flags_reply_t *rmp; + vl_api_bfd_udp_session_set_flags_reply_t *rmp; int rv; - rv = bfd_session_set_flags (clib_net_to_host_u32 (mp->bs_index), - mp->admin_up_down); + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = bfd_udp_session_set_flags (BFD_UDP_API_PARAM_FROM_MP (mp), + mp->admin_up_down); + + REPLY_MACRO (VL_API_BFD_UDP_SESSION_SET_FLAGS_REPLY); +} + +static void +vl_api_bfd_auth_set_key_t_handler (vl_api_bfd_auth_set_key_t * mp) +{ + vl_api_bfd_auth_set_key_reply_t *rmp; + int rv = bfd_auth_set_key (clib_net_to_host_u32 (mp->conf_key_id), + mp->auth_type, mp->key_len, mp->key); + + REPLY_MACRO (VL_API_BFD_AUTH_SET_KEY_REPLY); +} + +static void +vl_api_bfd_auth_del_key_t_handler (vl_api_bfd_auth_del_key_t * mp) +{ + vl_api_bfd_auth_del_key_reply_t *rmp; + int rv = bfd_auth_del_key (clib_net_to_host_u32 (mp->conf_key_id)); + + REPLY_MACRO (VL_API_BFD_AUTH_DEL_KEY_REPLY); +} + +static void +vl_api_bfd_auth_keys_dump_t_handler (vl_api_bfd_auth_keys_dump_t * mp) +{ + unix_shared_memory_queue_t *q; - REPLY_MACRO (VL_API_BFD_SESSION_SET_FLAGS_REPLY); + q = vl_api_client_index_to_input_queue (mp->client_index); + + if (q == 0) + return; + + bfd_auth_key_t *key = NULL; + vl_api_bfd_auth_keys_details_t *rmp = NULL; + + /* *INDENT-OFF* */ + pool_foreach (key, bfd_main.auth_keys, ({ + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_BFD_AUTH_KEYS_DETAILS); + rmp->context = mp->context; + rmp->conf_key_id = clib_host_to_net_u32 (key->conf_key_id); + rmp->auth_type = key->auth_type; + rmp->use_count = clib_host_to_net_u32 (key->use_count); + vl_msg_api_send_shmem (q, (u8 *)&rmp); + })); + /* *INDENT-ON* */ +} + +static void +vl_api_bfd_udp_auth_activate_t_handler (vl_api_bfd_udp_auth_activate_t * mp) +{ + vl_api_bfd_udp_auth_activate_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = + bfd_udp_auth_activate (BFD_UDP_API_PARAM_FROM_MP (mp), + clib_net_to_host_u32 (mp->conf_key_id), + mp->bfd_key_id, mp->is_delayed); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_AUTH_ACTIVATE_REPLY); +} + +static void +vl_api_bfd_udp_auth_deactivate_t_handler (vl_api_bfd_udp_auth_deactivate_t * + mp) +{ + vl_api_bfd_udp_auth_deactivate_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = + bfd_udp_auth_deactivate (BFD_UDP_API_PARAM_FROM_MP (mp), mp->is_delayed); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_AUTH_DEACTIVATE_REPLY); } /* diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h index a9bc5a1f..128a3dc4 100644 --- a/src/vnet/bfd/bfd_api.h +++ b/src/vnet/bfd/bfd_api.h @@ -24,17 +24,36 @@ #include #include -vnet_api_error_t bfd_udp_add_session (u32 sw_if_index, u32 desired_min_tx_us, - u32 required_min_rx_us, u8 detect_mult, - const ip46_address_t * local_addr, - const ip46_address_t * peer_addr, - u32 * bs_index); +vnet_api_error_t +bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u32 desired_min_tx_us, + u32 required_min_rx_us, u8 detect_mult, + u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id); vnet_api_error_t bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr); -vnet_api_error_t bfd_session_set_flags (u32 bs_index, u8 admin_up_down); +vnet_api_error_t bfd_udp_session_set_flags (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u8 admin_up_down); + +vnet_api_error_t bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, + const u8 * key); + +vnet_api_error_t bfd_auth_del_key (u32 conf_key_id); + +vnet_api_error_t bfd_udp_auth_activate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 conf_key_id, u8 bfd_key_id, + u8 is_delayed); + +vnet_api_error_t bfd_udp_auth_deactivate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u8 is_delayed); #endif /* __included_bfd_api_h__ */ diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 7e1a2ef2..8f2fae2b 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -25,6 +25,9 @@ #include #include #include +#if WITH_LIBSSL > 0 +#include +#endif static u64 bfd_us_to_clocks (bfd_main_t * bm, u64 us) @@ -41,6 +44,23 @@ static u32 bfd_node_index_by_transport[] = { #undef F }; +static u8 * +format_bfd_auth_key (u8 * s, va_list * args) +{ + const bfd_auth_key_t *key = va_arg (*args, bfd_auth_key_t *); + if (key) + { + s = format (s, "{auth-type=%u:%s, conf-key-id=%u, use-count=%u}, ", + key->auth_type, bfd_auth_type_str (key->auth_type), + key->conf_key_id, key->use_count); + } + else + { + s = format (s, "{none}"); + } + return s; +} + /* * We actually send all bfd pkts to the "error" node after scanning * them, so the graph node has only one next-index. The "error-drop" @@ -67,6 +87,9 @@ bfd_set_defaults (bfd_main_t * bm, bfd_session_t * bs) bs->desired_min_tx_clocks = bfd_us_to_clocks (bm, bs->desired_min_tx_us); bs->remote_min_rx_us = 1; bs->remote_demand = 0; + bs->auth.remote_seq_number = 0; + bs->auth.remote_seq_number_known = 0; + bs->auth.local_seq_number = random_u32 (&bm->random_seed); } static void @@ -288,7 +311,7 @@ bfd_del_session (uword bs_idx) else { BFD_ERR ("no such session"); - return VNET_API_ERROR_BFD_NOENT; + return VNET_API_ERROR_BFD_ENOENT; } return 0; } @@ -319,16 +342,10 @@ bfd_state_string (bfd_state_e state) #undef F } -vnet_api_error_t -bfd_session_set_flags (u32 bs_idx, u8 admin_up_down) +void +bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down) { bfd_main_t *bm = &bfd_main; - if (pool_is_free_index (bm->sessions, bs_idx)) - { - BFD_ERR ("invalid bs_idx=%u", bs_idx); - return VNET_API_ERROR_BFD_NOENT; - } - bfd_session_t *bs = pool_elt_at_index (bm->sessions, bs_idx); if (admin_up_down) { bfd_set_state (bm, bs, BFD_STATE_down, 0); @@ -338,7 +355,6 @@ bfd_session_set_flags (u32 bs_idx, u8 admin_up_down) bfd_set_diag (bs, BFD_DIAG_CODE_neighbor_sig_down); bfd_set_state (bm, bs, BFD_STATE_admin_down, 0); } - return 0; } u8 * @@ -351,8 +367,8 @@ bfd_input_format_trace (u8 * s, va_list * args) if (t->len > STRUCT_SIZE_OF (bfd_pkt_t, head)) { s = format (s, "BFD v%u, diag=%u(%s), state=%u(%s),\n" - " flags=(P:%u, F:%u, C:%u, A:%u, D:%u, M:%u), detect_mult=%u, " - "length=%u\n", + " flags=(P:%u, F:%u, C:%u, A:%u, D:%u, M:%u), " + "detect_mult=%u, length=%u\n", bfd_pkt_get_version (pkt), bfd_pkt_get_diag_code (pkt), bfd_diag_code_string (bfd_pkt_get_diag_code (pkt)), bfd_pkt_get_state (pkt), @@ -362,8 +378,8 @@ bfd_input_format_trace (u8 * s, va_list * args) bfd_pkt_get_auth_present (pkt), bfd_pkt_get_demand (pkt), bfd_pkt_get_multipoint (pkt), pkt->head.detect_mult, pkt->head.length); - if (t->len >= sizeof (bfd_pkt_t) - && pkt->head.length >= sizeof (bfd_pkt_t)) + if (t->len >= sizeof (bfd_pkt_t) && + pkt->head.length >= sizeof (bfd_pkt_t)) { s = format (s, " my discriminator: %u\n", pkt->my_disc); s = format (s, " your discriminator: %u\n", pkt->your_disc); @@ -430,8 +446,7 @@ bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, } static vlib_buffer_t * -bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, - bfd_session_t * bs) +bfd_create_frame_to_next_node (vlib_main_t * vm, bfd_session_t * bs) { u32 bi; if (vlib_buffer_alloc (vm, &bi, 1) != 1) @@ -454,13 +469,82 @@ bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, return b; } +#if WITH_LIBSSL > 0 +static void +bfd_add_sha1_auth_section (vlib_buffer_t * b, bfd_session_t * bs) +{ + bfd_pkt_with_sha1_auth_t *pkt = vlib_buffer_get_current (b); + bfd_auth_sha1_t *auth = &pkt->sha1_auth; + b->current_length += sizeof (*auth); + pkt->pkt.head.length += sizeof (*auth); + bfd_pkt_set_auth_present (&pkt->pkt); + memset (auth, 0, sizeof (*auth)); + auth->type_len.type = bs->auth.curr_key->auth_type; + /* + * only meticulous authentication types require incrementing seq number + * for every message, but doing so doesn't violate the RFC + */ + ++bs->auth.local_seq_number; + auth->type_len.len = sizeof (bfd_auth_sha1_t); + auth->key_id = bs->auth.curr_bfd_key_id; + auth->seq_num = clib_host_to_net_u32 (bs->auth.local_seq_number); + /* + * first copy the password into the packet, then calculate the hash + * and finally replace the password with the calculated hash + */ + clib_memcpy (auth->hash, bs->auth.curr_key->key, + sizeof (bs->auth.curr_key->key)); + unsigned char hash[sizeof (auth->hash)]; + SHA1 ((unsigned char *) pkt, sizeof (*pkt), hash); + BFD_DBG ("hashing: %U", format_hex_bytes, pkt, sizeof (*pkt)); + clib_memcpy (auth->hash, hash, sizeof (hash)); +#endif +} + +static void +bfd_add_auth_section (vlib_buffer_t * b, bfd_session_t * bs) +{ + if (bs->auth.curr_key) + { + const bfd_auth_type_e auth_type = bs->auth.curr_key->auth_type; + switch (auth_type) + { + case BFD_AUTH_TYPE_reserved: + /* fallthrough */ + case BFD_AUTH_TYPE_simple_password: + /* fallthrough */ + case BFD_AUTH_TYPE_keyed_md5: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_md5: + clib_warning ("Internal error, unexpected BFD auth type '%d'", + auth_type); + break; +#if WITH_LIBSSL > 0 + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: + bfd_add_sha1_auth_section (b, bs); + break; +#else + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: + clib_warning ("Internal error, unexpected BFD auth type '%d'", + auth_type); + break; +#endif + } + } +} + static void bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) { bfd_pkt_t *pkt = vlib_buffer_get_current (b); - const u32 bfd_length = 24; - memset (pkt, 0, sizeof (*pkt)); + u32 bfd_length = 0; + bfd_length = sizeof (bfd_pkt_t); + memset (pkt, 0, sizeof (*pkt)); bfd_pkt_set_version (pkt, 1); bfd_pkt_set_diag_code (pkt, bs->local_diag); bfd_pkt_set_state (pkt, bs->local_state); @@ -477,6 +561,7 @@ bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) pkt->req_min_rx = clib_host_to_net_u32 (bs->required_min_rx_us); pkt->req_min_echo_rx = clib_host_to_net_u32 (bs->required_min_echo_rx_us); b->current_length = bfd_length; + bfd_add_auth_section (b, bs); } static void @@ -502,7 +587,7 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, if (now + bm->wheel_inaccuracy >= bs->tx_timeout_clocks) { BFD_DBG ("Send periodic control frame for bs_idx=%lu", bs->bs_idx); - vlib_buffer_t *b = bfd_create_frame (vm, rt, bs); + vlib_buffer_t *b = bfd_create_frame_to_next_node (vm, bs); if (!b) { return; @@ -522,7 +607,8 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, } void -bfd_send_final (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs) +bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, + bfd_session_t * bs) { BFD_DBG ("Send final control frame for bs_idx=%lu", bs->bs_idx); bfd_init_control_frame (b, bs); @@ -624,13 +710,17 @@ bfd_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) * each event or timeout */ break; case BFD_EVENT_NEW_SESSION: - do + if (!pool_is_free_index (bm->sessions, *event_data)) { bfd_session_t *bs = pool_elt_at_index (bm->sessions, *event_data); bfd_send_periodic (vm, rt, bm, bs, now, 1); } - while (0); + else + { + BFD_DBG ("Ignoring event for non-existent session index %u", + (u32) * event_data); + } break; default: clib_warning ("BUG: event type 0x%wx", event_type); @@ -710,22 +800,25 @@ VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (bfd_hw_interface_up_down); static clib_error_t * bfd_main_init (vlib_main_t * vm) { +#if BFD_DEBUG + setbuf (stdout, NULL); +#endif bfd_main_t *bm = &bfd_main; bm->random_seed = random_default_seed (); bm->vlib_main = vm; bm->vnet_main = vnet_get_main (); memset (&bm->wheel, 0, sizeof (bm->wheel)); - bm->cpu_cps = 2590000000; // vm->clib_time.clocks_per_second; + bm->cpu_cps = vm->clib_time.clocks_per_second; BFD_DBG ("cps is %.2f", bm->cpu_cps); const u64 now = clib_cpu_time_now (); timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; vlib_node_t *node = NULL; -#define F(t, n) \ - node = vlib_get_node_by_name (vm, (u8 *)n); \ - bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index;\ - BFD_DBG("node '%s' has index %u", n, node->index); +#define F(t, n) \ + node = vlib_get_node_by_name (vm, (u8 *)n); \ + bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index; \ + BFD_DBG ("node '%s' has index %u", n, node->index); foreach_bfd_transport (F); #undef F return 0; @@ -750,6 +843,14 @@ bfd_get_session (bfd_main_t * bm, bfd_transport_t t) void bfd_put_session (bfd_main_t * bm, bfd_session_t * bs) { + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + } + if (bs->auth.next_key) + { + --bs->auth.next_key->use_count; + } hash_unset (bm->session_by_disc, bs->local_discr); pool_put (bm->sessions, bs); } @@ -793,7 +894,7 @@ bfd_verify_pkt_common (const bfd_pkt_t * pkt) } if (pkt->head.length < sizeof (bfd_pkt_t) || (bfd_pkt_get_auth_present (pkt) && - pkt->head.length < sizeof (bfd_pkt_with_auth_t))) + pkt->head.length < sizeof (bfd_pkt_with_common_auth_t))) { BFD_ERR ("BFD verification failed - unexpected length: '%d' (auth " "present: %d)", @@ -831,6 +932,226 @@ bfd_verify_pkt_common (const bfd_pkt_t * pkt) return 1; } +static void +bfd_session_switch_auth_to_next (bfd_session_t * bs) +{ + BFD_DBG ("Switching authentication key from %U to %U for bs_idx=%u", + format_bfd_auth_key, bs->auth.curr_key, format_bfd_auth_key, + bs->auth.next_key, bs->bs_idx); + bs->auth.is_delayed = 0; + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + } + bs->auth.curr_key = bs->auth.next_key; + bs->auth.next_key = NULL; + bs->auth.curr_bfd_key_id = bs->auth.next_bfd_key_id; +} + +static int +bfd_auth_type_is_meticulous (bfd_auth_type_e auth_type) +{ + if (BFD_AUTH_TYPE_meticulous_keyed_md5 == auth_type || + BFD_AUTH_TYPE_meticulous_keyed_sha1 == auth_type) + { + return 1; + } + return 0; +} + +static int +bfd_verify_pkt_auth_seq_num (bfd_session_t * bs, + u32 received_seq_num, int is_meticulous) +{ + /* + * RFC 5880 6.8.1: + * + * This variable MUST be set to zero after no packets have been + * received on this session for at least twice the Detection Time. + */ + u64 now = clib_cpu_time_now (); + if (now - bs->last_rx_clocks > bs->detection_time_clocks * 2) + { + BFD_DBG ("BFD peer unresponsive for %lu clocks, which is > 2 * " + "detection_time=%u clocks, resetting remote_seq_number_known " + "flag", + now - bs->last_rx_clocks, bs->detection_time_clocks * 2); + bs->auth.remote_seq_number_known = 0; + } + if (bs->auth.remote_seq_number_known) + { + /* remote sequence number is known, verify its validity */ + const u32 max_u32 = 0xffffffff; + /* the calculation might wrap, account for the special case... */ + if (bs->auth.remote_seq_number > max_u32 - 3 * bs->local_detect_mult) + { + /* + * special case + * + * x y z + * |----------+----------------------------+-----------| + * 0 ^ ^ 0xffffffff + * | remote_seq_num------+ + * | + * +-----(remote_seq_num + 3*detect_mult) % * 0xffffffff + * + * x + y + z = 0xffffffff + * x + z = 3 * detect_mult + */ + const u32 z = max_u32 - bs->auth.remote_seq_number; + const u32 x = 3 * bs->local_detect_mult - z; + if (received_seq_num > x && + received_seq_num < bs->auth.remote_seq_number + is_meticulous) + { + BFD_ERR + ("Recvd sequence number=%u out of ranges <0, %u>, <%u, %u>", + received_seq_num, x, + bs->auth.remote_seq_number + is_meticulous, max_u32); + return 0; + } + } + else + { + /* regular case */ + const u32 min = bs->auth.remote_seq_number + is_meticulous; + const u32 max = + bs->auth.remote_seq_number + 3 * bs->local_detect_mult; + if (received_seq_num < min || received_seq_num > max) + { + BFD_ERR ("Recvd sequence number=%u out of range <%u, %u>", + received_seq_num, min, max); + return 0; + } + } + } + return 1; +} + +static int +bfd_verify_pkt_auth_key_sha1 (const bfd_pkt_t * pkt, u32 pkt_size, + bfd_session_t * bs, u8 bfd_key_id, + bfd_auth_key_t * auth_key) +{ + ASSERT (auth_key->auth_type == BFD_AUTH_TYPE_keyed_sha1 || + auth_key->auth_type == BFD_AUTH_TYPE_meticulous_keyed_sha1); + + u8 result[SHA_DIGEST_LENGTH]; + bfd_pkt_with_common_auth_t *with_common = (void *) pkt; + if (pkt_size < sizeof (*with_common)) + { + BFD_ERR ("Packet size too small to hold authentication common header"); + return 0; + } + if (with_common->common_auth.type != auth_key->auth_type) + { + BFD_ERR ("BFD auth type mismatch, packet auth=%d:%s doesn't match " + "in-use auth=%d:%s", + with_common->common_auth.type, + bfd_auth_type_str (with_common->common_auth.type), + auth_key->auth_type, bfd_auth_type_str (auth_key->auth_type)); + return 0; + } + bfd_pkt_with_sha1_auth_t *with_sha1 = (void *) pkt; + if (pkt_size < sizeof (*with_sha1) || + with_sha1->sha1_auth.type_len.len < sizeof (with_sha1->sha1_auth)) + { + BFD_ERR + ("BFD size mismatch, payload size=%u, expected=%u, auth_len=%u, " + "expected=%u", pkt_size, sizeof (*with_sha1), + with_sha1->sha1_auth.type_len.len, sizeof (with_sha1->sha1_auth)); + return 0; + } + if (with_sha1->sha1_auth.key_id != bfd_key_id) + { + BFD_ERR + ("BFD key ID mismatch, packet key ID=%u doesn't match key ID=%u%s", + with_sha1->sha1_auth.key_id, bfd_key_id, + bs-> + auth.is_delayed ? " (but a delayed auth change is scheduled)" : ""); + return 0; + } + SHA_CTX ctx; + if (!SHA1_Init (&ctx)) + { + BFD_ERR ("SHA1_Init failed"); + return 0; + } + /* ignore last 20 bytes - use the actual key data instead pkt data */ + if (!SHA1_Update (&ctx, with_sha1, + sizeof (*with_sha1) - sizeof (with_sha1->sha1_auth.hash))) + { + BFD_ERR ("SHA1_Update failed"); + return 0; + } + if (!SHA1_Update (&ctx, auth_key->key, sizeof (auth_key->key))) + { + BFD_ERR ("SHA1_Update failed"); + return 0; + } + if (!SHA1_Final (result, &ctx)) + { + BFD_ERR ("SHA1_Final failed"); + return 0; + } + if (0 == memcmp (result, with_sha1->sha1_auth.hash, SHA_DIGEST_LENGTH)) + { + return 1; + } + BFD_ERR ("SHA1 hash: %U doesn't match the expected value: %U", + format_hex_bytes, with_sha1->sha1_auth.hash, SHA_DIGEST_LENGTH, + format_hex_bytes, result, SHA_DIGEST_LENGTH); + return 0; +} + +static int +bfd_verify_pkt_auth_key (const bfd_pkt_t * pkt, u32 pkt_size, + bfd_session_t * bs, u8 bfd_key_id, + bfd_auth_key_t * auth_key) +{ + switch (auth_key->auth_type) + { + case BFD_AUTH_TYPE_reserved: + clib_warning ("Internal error, unexpected auth_type=%d:%s", + auth_key->auth_type, + bfd_auth_type_str (auth_key->auth_type)); + return 0; + case BFD_AUTH_TYPE_simple_password: + clib_warning + ("Internal error, not implemented, unexpected auth_type=%d:%s", + auth_key->auth_type, bfd_auth_type_str (auth_key->auth_type)); + return 0; + case BFD_AUTH_TYPE_keyed_md5: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_md5: + clib_warning + ("Internal error, not implemented, unexpected auth_type=%d:%s", + auth_key->auth_type, bfd_auth_type_str (auth_key->auth_type)); + return 0; + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: +#if WITH_LIBSSL > 0 + do + { + const u32 seq_num = clib_net_to_host_u32 (((bfd_pkt_with_sha1_auth_t + *) pkt)-> + sha1_auth.seq_num); + return bfd_verify_pkt_auth_seq_num (bs, seq_num, + bfd_auth_type_is_meticulous + (auth_key->auth_type)) + && bfd_verify_pkt_auth_key_sha1 (pkt, pkt_size, bs, bfd_key_id, + auth_key); + } + while (0); +#else + clib_warning + ("Internal error, attempt to use SHA1 without SSL support"); + return 0; +#endif + } + return 0; +} + /** * @brief verify bfd packet - authentication * @@ -839,30 +1160,81 @@ bfd_verify_pkt_common (const bfd_pkt_t * pkt) * @return 1 if bfd packet is valid */ int -bfd_verify_pkt_session (const bfd_pkt_t * pkt, u16 pkt_size, - const bfd_session_t * bs) +bfd_verify_pkt_auth (const bfd_pkt_t * pkt, u16 pkt_size, bfd_session_t * bs) { - const bfd_pkt_with_auth_t *with_auth = (bfd_pkt_with_auth_t *) pkt; - if (!bfd_pkt_get_auth_present (pkt)) + if (bfd_pkt_get_auth_present (pkt)) { - if (pkt_size > sizeof (*pkt)) + /* authentication present in packet */ + if (!bs->auth.curr_key) { - BFD_ERR ("BFD verification failed - unexpected packet size '%d' " - "(auth not present)", pkt_size); - return 0; + /* currently not using authentication - can we turn it on? */ + if (bs->auth.is_delayed && bs->auth.next_key) + { + /* yes, switch is scheduled - make sure the auth is valid */ + if (bfd_verify_pkt_auth_key (pkt, pkt_size, bs, + bs->auth.next_bfd_key_id, + bs->auth.next_key)) + { + /* auth matches next key, do the switch, packet is valid */ + bfd_session_switch_auth_to_next (bs); + return 1; + } + } + } + else + { + /* yes, using authentication, verify the key */ + if (bfd_verify_pkt_auth_key (pkt, pkt_size, bs, + bs->auth.curr_bfd_key_id, + bs->auth.curr_key)) + { + /* verification passed, packet is valid */ + return 1; + } + else + { + /* verification failed - but maybe we need to switch key */ + if (bs->auth.is_delayed && bs->auth.next_key) + { + /* delayed switch present, verify if that key works */ + if (bfd_verify_pkt_auth_key (pkt, pkt_size, bs, + bs->auth.next_bfd_key_id, + bs->auth.next_key)) + { + /* auth matches next key, switch key, packet is valid */ + bfd_session_switch_auth_to_next (bs); + return 1; + } + } + } } } else { - if (!with_auth->auth.type) + /* authentication in packet not present */ + if (pkt_size > sizeof (*pkt)) { - BFD_ERR ("BFD verification failed - unexpected auth type: '%d'", - with_auth->auth.type); + BFD_ERR ("BFD verification failed - unexpected packet size '%d' " + "(auth not present)", pkt_size); return 0; } - /* TODO FIXME - implement the actual verification */ + if (bs->auth.curr_key) + { + /* currently authenticating - could we turn it off? */ + if (bs->auth.is_delayed && !bs->auth.next_key) + { + /* yes, delayed switch to NULL key is scheduled */ + bfd_session_switch_auth_to_next (bs); + return 1; + } + } + else + { + /* no auth in packet, no auth in use - packet is valid */ + return 1; + } } - return 1; + return 0; } void @@ -879,6 +1251,38 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) bs->remote_demand = bfd_pkt_get_demand (pkt); u64 now = clib_cpu_time_now (); bs->last_rx_clocks = now; + if (bfd_pkt_get_auth_present (pkt)) + { + bfd_auth_type_e auth_type = + ((bfd_pkt_with_common_auth_t *) (pkt))->common_auth.type; + switch (auth_type) + { + case BFD_AUTH_TYPE_reserved: + /* fallthrough */ + case BFD_AUTH_TYPE_simple_password: + /* fallthrough */ + case BFD_AUTH_TYPE_keyed_md5: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_md5: + clib_warning ("Internal error, unexpected auth_type=%d:%s", + auth_type, bfd_auth_type_str (auth_type)); + break; + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: + do + { + bfd_pkt_with_sha1_auth_t *with_sha1 = + (bfd_pkt_with_sha1_auth_t *) pkt; + bs->auth.remote_seq_number = + clib_net_to_host_u32 (with_sha1->sha1_auth.seq_num); + bs->auth.remote_seq_number_known = 1; + BFD_DBG ("Received sequence number %u", + bs->auth.remote_seq_number); + } + while (0); + } + } bs->remote_desired_min_tx_us = clib_net_to_host_u32 (pkt->des_min_tx); bs->remote_detect_mult = pkt->head.detect_mult; bfd_set_remote_required_min_rx (bm, bs, now, @@ -933,25 +1337,129 @@ u8 * format_bfd_session (u8 * s, va_list * args) { const bfd_session_t *bs = va_arg (*args, bfd_session_t *); - return format (s, "BFD(%u): bfd.SessionState=%s, " - "bfd.RemoteSessionState=%s, " - "bfd.LocalDiscr=%u, " - "bfd.RemoteDiscr=%u, " - "bfd.LocalDiag=%s, " - "bfd.DesiredMinTxInterval=%u, " - "bfd.RequiredMinRxInterval=%u, " - "bfd.RequiredMinEchoRxInterval=%u, " - "bfd.RemoteMinRxInterval=%u, " - "bfd.DemandMode=%s, " - "bfd.RemoteDemandMode=%s, " - "bfd.DetectMult=%u, ", - bs->bs_idx, bfd_state_string (bs->local_state), - bfd_state_string (bs->remote_state), bs->local_discr, - bs->remote_discr, bfd_diag_code_string (bs->local_diag), - bs->desired_min_tx_us, bs->required_min_rx_us, - bs->required_min_echo_rx_us, bs->remote_min_rx_us, - (bs->local_demand ? "yes" : "no"), - (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult); + s = format (s, "BFD(%u): bfd.SessionState=%s, " + "bfd.RemoteSessionState=%s, " + "bfd.LocalDiscr=%u, " + "bfd.RemoteDiscr=%u, " + "bfd.LocalDiag=%s, " + "bfd.DesiredMinTxInterval=%u, " + "bfd.RequiredMinRxInterval=%u, " + "bfd.RequiredMinEchoRxInterval=%u, " + "bfd.RemoteMinRxInterval=%u, " + "bfd.DemandMode=%s, " + "bfd.RemoteDemandMode=%s, " + "bfd.DetectMult=%u, " + "Auth: {local-seq-num=%u, " + "remote-seq-num=%u, " + "is-delayed=%s, " + "curr-key=%U, " + "next-key=%U}", + bs->bs_idx, bfd_state_string (bs->local_state), + bfd_state_string (bs->remote_state), bs->local_discr, + bs->remote_discr, bfd_diag_code_string (bs->local_diag), + bs->desired_min_tx_us, bs->required_min_rx_us, + bs->required_min_echo_rx_us, bs->remote_min_rx_us, + (bs->local_demand ? "yes" : "no"), + (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult, + bs->auth.local_seq_number, bs->auth.remote_seq_number, + (bs->auth.is_delayed ? "yes" : "no"), format_bfd_auth_key, + bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key); + return s; +} + +unsigned +bfd_auth_type_supported (bfd_auth_type_e auth_type) +{ + if (auth_type == BFD_AUTH_TYPE_keyed_sha1 || + auth_type == BFD_AUTH_TYPE_meticulous_keyed_sha1) + { + return 1; + } + return 0; +} + +vnet_api_error_t +bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, + u8 bfd_key_id, u8 is_delayed) +{ + bfd_main_t *bm = &bfd_main; + const uword *key_idx_p = + hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (!key_idx_p) + { + clib_warning ("Authentication key with config ID %u doesn't exist)", + conf_key_id); + return VNET_API_ERROR_BFD_ENOENT; + } + const uword key_idx = *key_idx_p; + bfd_auth_key_t *key = pool_elt_at_index (bm->auth_keys, key_idx); + if (is_delayed) + { + if (bs->auth.next_key == key) + { + /* already using this key, no changes required */ + return 0; + } + bs->auth.next_key = key; + bs->auth.next_bfd_key_id = bfd_key_id; + bs->auth.is_delayed = 1; + } + else + { + if (bs->auth.curr_key == key) + { + /* already using this key, no changes required */ + return 0; + } + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + } + bs->auth.curr_key = key; + bs->auth.curr_bfd_key_id = bfd_key_id; + bs->auth.is_delayed = 0; + } + ++key->use_count; + BFD_DBG ("Session auth modified: %U", format_bfd_session, bs); + return 0; +} + +vnet_api_error_t +bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed) +{ +#if WITH_LIBSSL > 0 + if (!is_delayed) + { + /* not delayed - deactivate the current key right now */ + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + bs->auth.curr_key = NULL; + } + bs->auth.is_delayed = 0; + } + else + { + /* delayed - mark as so */ + bs->auth.is_delayed = 1; + } + /* + * clear the next key unconditionally - either the auth change is not delayed + * in which case the caller expects the session to not use authentication + * from this point forward, or it is delayed, in which case the next_key + * needs to be set to NULL to make it so in the future + */ + if (bs->auth.next_key) + { + --bs->auth.next_key->use_count; + bs->auth.next_key = NULL; + } + BFD_DBG ("Session auth modified: %U", format_bfd_session, bs); + return 0; +#else + clib_warning ("SSL missing, cannot deactivate BFD authentication"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif } bfd_main_t bfd_main; diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index 20da381a..b66b79e7 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -25,7 +25,7 @@ #include #define foreach_bfd_transport(F) \ - F (UDP4, "ip4-rewrite") \ + F (UDP4, "ip4-rewrite") \ F (UDP6, "ip6-rewrite") typedef enum @@ -46,6 +46,24 @@ typedef enum #undef F } bfd_mode_e; +typedef struct +{ + /* global configuration key ID */ + u32 conf_key_id; + + /* keeps track of how many sessions reference this key */ + u32 use_count; + + /* + * key data directly usable for bfd purposes - already padded with zeroes + * (so we don't need the actual length) + */ + u8 key[20]; + + /* authentication type for this key */ + bfd_auth_type_e auth_type; +} bfd_auth_key_t; + typedef struct { /* index in bfd_main.sessions pool */ @@ -120,6 +138,40 @@ typedef struct /* detection time */ u64 detection_time_clocks; + /* authentication information */ + struct + { + /* current key in use */ + bfd_auth_key_t *curr_key; + + /* + * set to next key to use if delayed switch is enabled - in that case + * the key is switched when first incoming packet is signed with next_key + */ + bfd_auth_key_t *next_key; + + /* sequence number incremented occasionally or always (if meticulous) */ + u32 local_seq_number; + + /* remote sequence number */ + u32 remote_seq_number; + + /* set to 1 if remote sequence number is known */ + u8 remote_seq_number_known; + + /* current key ID sent out in bfd packet */ + u8 curr_bfd_key_id; + + /* key ID to use when switched to next_key */ + u8 next_bfd_key_id; + + /* + * set to 1 if delayed action is pending, which might be activation + * of authentication, change of key or deactivation + */ + u8 is_delayed; + } auth; + /* transport type for this session */ bfd_transport_t transport; @@ -129,12 +181,6 @@ typedef struct }; } bfd_session_t; -typedef struct -{ - u32 client_index; - u32 client_pid; -} event_subscriber_t; - typedef struct { /* pool of bfd sessions context data */ @@ -162,6 +208,12 @@ typedef struct /* for generating random numbers */ u32 random_seed; + /* pool of authentication keys */ + bfd_auth_key_t *auth_keys; + + /* hashmap - index in pool auth_keys by conf_key_id */ + u32 *auth_key_by_conf_key_id; + } bfd_main_t; extern bfd_main_t bfd_main; @@ -202,12 +254,17 @@ bfd_session_t *bfd_find_session_by_disc (bfd_main_t * bm, u32 disc); void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs); void bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * bfd, u32 bs_idx); int bfd_verify_pkt_common (const bfd_pkt_t * pkt); -int bfd_verify_pkt_session (const bfd_pkt_t * pkt, u16 pkt_size, - const bfd_session_t * bs); +int bfd_verify_pkt_auth (const bfd_pkt_t * pkt, u16 pkt_size, + bfd_session_t * bs); void bfd_event (bfd_main_t * bm, bfd_session_t * bs); -void bfd_send_final (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs); +void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, + bfd_session_t * bs); u8 *format_bfd_session (u8 * s, va_list * args); - +void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down); +unsigned bfd_auth_type_supported (bfd_auth_type_e auth_type); +vnet_api_error_t bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, + u8 bfd_key_id, u8 is_delayed); +vnet_api_error_t bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed); #define USEC_PER_MS 1000LL #define USEC_PER_SECOND (1000 * USEC_PER_MS) diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c index ede9536f..180fc6df 100644 --- a/src/vnet/bfd/bfd_protocol.c +++ b/src/vnet/bfd/bfd_protocol.c @@ -1,74 +1,159 @@ +/* + * Copyright (c) 2011-2016 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. + */ #include -u8 bfd_pkt_get_version (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_version (const bfd_pkt_t * pkt) { return pkt->head.vers_diag >> 5; } -void bfd_pkt_set_version (bfd_pkt_t *pkt, int version) +void +bfd_pkt_set_version (bfd_pkt_t * pkt, int version) { pkt->head.vers_diag = - (version << 5) | (pkt->head.vers_diag & ((1 << 5) - 1)); + (version << 5) | (pkt->head.vers_diag & ((1 << 5) - 1)); } -u8 bfd_pkt_get_diag_code (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_diag_code (const bfd_pkt_t * pkt) { return pkt->head.vers_diag & ((1 << 5) - 1); } -void bfd_pkt_set_diag_code (bfd_pkt_t *pkt, int value) +void +bfd_pkt_set_diag_code (bfd_pkt_t * pkt, int value) { pkt->head.vers_diag = - (pkt->head.vers_diag & ~((1 << 5) - 1)) | (value & ((1 << 5) - 1)); + (pkt->head.vers_diag & ~((1 << 5) - 1)) | (value & ((1 << 5) - 1)); } -u8 bfd_pkt_get_state (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_state (const bfd_pkt_t * pkt) { return pkt->head.sta_flags >> 6; } -void bfd_pkt_set_state (bfd_pkt_t *pkt, int value) +void +bfd_pkt_set_state (bfd_pkt_t * pkt, int value) { pkt->head.sta_flags = (value << 6) | (pkt->head.sta_flags & ((1 << 6) - 1)); } -u8 bfd_pkt_get_poll (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_poll (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 5) & 1; } -void bfd_pkt_set_final (bfd_pkt_t *pkt) { pkt->head.sta_flags |= 1 << 5; } +void +bfd_pkt_set_poll (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 5; +} -u8 bfd_pkt_get_final (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_final (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 4) & 1; } -void bfd_pkt_set_poll (bfd_pkt_t *pkt); -u8 bfd_pkt_get_control_plane_independent (const bfd_pkt_t *pkt) +void +bfd_pkt_set_final (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 4; +} + +u8 +bfd_pkt_get_control_plane_independent (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 3) & 1; } -void bfd_pkt_set_control_plane_independent (bfd_pkt_t *pkt); +void +bfd_pkt_set_control_plane_independent (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 3; +} -u8 bfd_pkt_get_auth_present (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_auth_present (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 2) & 1; } -void bfd_pkt_set_auth_present (bfd_pkt_t *pkt); +void +bfd_pkt_set_auth_present (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 2; +} -u8 bfd_pkt_get_demand (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_demand (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 1) & 1; } -void bfd_pkt_set_demand (bfd_pkt_t *pkt) { pkt->head.sta_flags |= 1 << 1; } +void +bfd_pkt_set_demand (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 1; +} + +u8 +bfd_pkt_get_multipoint (const bfd_pkt_t * pkt) +{ + return (pkt->head.sta_flags >> 0) & 1; +} + +void +bfd_pkt_set_multipoint (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 0; +} + +u32 +bfd_max_len_for_auth_type (bfd_auth_type_e auth_type) +{ +#define F(t, l, n, s) \ + if (auth_type == t) \ + { \ + return l; \ + } + foreach_bfd_auth_type (F); +#undef F + return 0; +} -u8 bfd_pkt_get_multipoint (const bfd_pkt_t *pkt) +const char * +bfd_auth_type_str (bfd_auth_type_e auth_type) { - return pkt->head.sta_flags & 1; +#define F(t, l, n, s) \ + if (auth_type == t) \ + { \ + return s; \ + } + foreach_bfd_auth_type (F); +#undef F + return "UNKNOWN"; } -void bfd_pkt_set_multipoint (bfd_pkt_t *pkt); +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/bfd/bfd_protocol.h b/src/vnet/bfd/bfd_protocol.h index cf751b3b..cdbb8fa7 100644 --- a/src/vnet/bfd/bfd_protocol.h +++ b/src/vnet/bfd/bfd_protocol.h @@ -22,45 +22,93 @@ #include #include +/* auth type value, max key length, name, description */ +#define foreach_bfd_auth_type(F) \ + F (0, 0, reserved, "Reserved") \ + F (1, 16, simple_password, "Simple Password") \ + F (2, 16, keyed_md5, "Keyed MD5") \ + F (3, 16, meticulous_keyed_md5, "Meticulous Keyed MD5") \ + F (4, 20, keyed_sha1, "Keyed SHA1") \ + F (5, 20, meticulous_keyed_sha1, "Meticulous Keyed SHA1") + +#define BFD_AUTH_TYPE_NAME(t) BFD_AUTH_TYPE_##t + +typedef enum +{ +#define F(n, l, t, s) BFD_AUTH_TYPE_NAME (t) = n, + foreach_bfd_auth_type (F) +#undef F +} bfd_auth_type_e; + +u32 bfd_max_len_for_auth_type (bfd_auth_type_e auth_type); +const char *bfd_auth_type_str (bfd_auth_type_e auth_type); + /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { - /* - An optional Authentication Section MAY be present: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Auth Type | Auth Len | Authentication Data... | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ u8 type; u8 len; - u8 data[0]; -}) bfd_auth_t; +}) bfd_auth_common_t; /* *INDENT-ON* */ /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { /* - The Mandatory Section of a BFD Control packet has the following - format: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | My Discriminator | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Your Discriminator | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Desired Min TX Interval | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Required Min RX Interval | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Required Min Echo RX Interval | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ + * 4.4. Keyed SHA1 and Meticulous Keyed SHA1 Authentication Section Format + + * If the Authentication Present (A) bit is set in the header, and the + * Authentication Type field contains 4 (Keyed SHA1) or 5 (Meticulous + * Keyed SHA1), the Authentication Section has the following format: + + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Auth Type | Auth Len | Auth Key ID | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Auth Key/Hash... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | ... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + bfd_auth_common_t type_len; + u8 key_id; + u8 reserved; + u32 seq_num; + /* + * Auth Key/Hash + + * This field carries the 20-byte SHA1 hash for the packet. When the + * hash is calculated, the shared SHA1 key is stored in this field, + * padded to a length of 20 bytes with trailing zero bytes if needed. + * The shared key MUST be encoded and configured to section 6.7.4. + */ + u8 hash[20]; +}) bfd_auth_sha1_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + /* + * The Mandatory Section of a BFD Control packet has the following + * format: + + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | My Discriminator | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Your Discriminator | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Desired Min TX Interval | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Required Min RX Interval | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Required Min Echo RX Interval | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ struct { u8 vers_diag; @@ -79,8 +127,15 @@ typedef CLIB_PACKED (struct { /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { bfd_pkt_t pkt; - bfd_auth_t auth; -}) bfd_pkt_with_auth_t; + bfd_auth_common_t common_auth; +}) bfd_pkt_with_common_auth_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + bfd_pkt_t pkt; + bfd_auth_sha1_t sha1_auth; +}) bfd_pkt_with_sha1_auth_t; /* *INDENT-ON* */ u8 bfd_pkt_get_version (const bfd_pkt_t * pkt); diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index dfd030ae..443f4253 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -45,7 +45,7 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, ip4_header_t ip4; udp_header_t udp; } ip4_udp_headers; - ip4_udp_headers *headers = vlib_buffer_get_current (b); + ip4_udp_headers *headers = NULL; vlib_buffer_advance (b, -sizeof (*headers)); headers = vlib_buffer_get_current (b); memset (headers, 0, sizeof (*headers)); @@ -82,8 +82,9 @@ bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, ip6_header_t ip6; udp_header_t udp; } ip6_udp_headers; - vlib_buffer_advance (b, -sizeof (ip6_udp_headers)); - ip6_udp_headers *headers = vlib_buffer_get_current (b); + ip6_udp_headers *headers = NULL; + vlib_buffer_advance (b, -sizeof (*headers)); + headers = vlib_buffer_get_current (b); memset (headers, 0, sizeof (*headers)); headers->ip6.ip_version_traffic_class_and_flow_label = clib_host_to_net_u32 (0x6 << 28); @@ -125,16 +126,27 @@ bfd_lookup_session (bfd_udp_main_t * bum, const bfd_udp_key_t * key) return 0; } +static void +bfd_udp_key_init (bfd_udp_key_t * key, u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr) +{ + memset (key, 0, sizeof (*key)); + key->sw_if_index = sw_if_index; + key->local_addr.as_u64[0] = local_addr->as_u64[0]; + key->local_addr.as_u64[1] = local_addr->as_u64[1]; + key->peer_addr.as_u64[0] = peer_addr->as_u64[0]; + key->peer_addr.as_u64[1] = peer_addr->as_u64[1]; +} + static vnet_api_error_t bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, u32 desired_min_tx_us, u32 required_min_rx_us, u8 detect_mult, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, - u32 * bs_index) + bfd_session_t ** bs_out) { - vnet_sw_interface_t *sw_if = - vnet_get_sw_interface (vnet_get_main (), sw_if_index); /* get a pool entry and if we end up not needing it, give it back */ bfd_transport_t t = BFD_TRANSPORT_UDP4; if (!ip46_address_is_ip4 (local_addr)) @@ -145,19 +157,15 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, bfd_udp_session_t *bus = &bs->udp; memset (bus, 0, sizeof (*bus)); bfd_udp_key_t *key = &bus->key; - key->sw_if_index = sw_if->sw_if_index; - key->local_addr.as_u64[0] = local_addr->as_u64[0]; - key->local_addr.as_u64[1] = local_addr->as_u64[1]; - key->peer_addr.as_u64[0] = peer_addr->as_u64[0]; - key->peer_addr.as_u64[1] = peer_addr->as_u64[1]; + bfd_udp_key_init (key, sw_if_index, local_addr, peer_addr); const bfd_session_t *tmp = bfd_lookup_session (bum, key); if (tmp) { - BFD_ERR ("duplicate bfd-udp session, existing bs_idx=%d", tmp->bs_idx); + clib_warning ("duplicate bfd-udp session, existing bs_idx=%d", + tmp->bs_idx); bfd_put_session (bum->bfd_main, bs); return VNET_API_ERROR_BFD_EEXIST; } - key->sw_if_index = sw_if->sw_if_index; mhash_set (&bum->bfd_session_idx_by_bfd_key, key, bs->bs_idx, NULL); BFD_DBG ("session created, bs_idx=%u, sw_if_index=%d, local=%U, peer=%U", bs->bs_idx, key->sw_if_index, format_ip46_address, @@ -185,8 +193,7 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, bs->required_min_rx_us = required_min_rx_us; bs->required_min_echo_rx_us = required_min_rx_us; /* FIXME */ bs->local_detect_mult = detect_mult; - bfd_session_start (bum->bfd_main, bs); - *bs_index = bs->bs_idx; + *bs_out = bs; return 0; } @@ -201,14 +208,14 @@ bfd_udp_validate_api_input (u32 sw_if_index, ip_interface_address_t *ia = NULL; if (!sw_if) { - BFD_ERR ("got NULL sw_if"); + clib_warning ("got NULL sw_if"); return VNET_API_ERROR_INVALID_SW_IF_INDEX; } if (ip46_address_is_ip4 (local_addr)) { if (!ip46_address_is_ip4 (peer_addr)) { - BFD_ERR ("IP family mismatch"); + clib_warning ("IP family mismatch"); return VNET_API_ERROR_INVALID_ARGUMENT; } ip4_main_t *im = &ip4_main; @@ -231,7 +238,7 @@ bfd_udp_validate_api_input (u32 sw_if_index, { if (ip46_address_is_ip4 (peer_addr)) { - BFD_ERR ("IP family mismatch"); + clib_warning ("IP family mismatch"); return VNET_API_ERROR_INVALID_ARGUMENT; } ip6_main_t *im = &ip6_main; @@ -241,7 +248,7 @@ bfd_udp_validate_api_input (u32 sw_if_index, ip6_address_t *x = ip_interface_address_get_address (&im->lookup_main, ia); if (local_addr->ip6.as_u64[0] == x->as_u64[0] && - local_addr->ip6.as_u64[1] == x->as_u64[1]) + local_addr->ip6.as_u64[1] == x->as_u64[1]) { /* valid address for this interface */ local_ip_valid = 1; @@ -253,18 +260,48 @@ bfd_udp_validate_api_input (u32 sw_if_index, if (!local_ip_valid) { - BFD_ERR ("address not found on interface"); + clib_warning ("address not found on interface"); return VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE; } return 0; } -vnet_api_error_t -bfd_udp_add_session (u32 sw_if_index, u32 desired_min_tx_us, - u32 required_min_rx_us, u8 detect_mult, - const ip46_address_t * local_addr, - const ip46_address_t * peer_addr, u32 * bs_index) +static vnet_api_error_t +bfd_udp_find_session_by_api_input (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + bfd_session_t ** bs_out) +{ + vnet_api_error_t rv = + bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); + if (!rv) + { + bfd_udp_main_t *bum = &bfd_udp_main; + bfd_udp_key_t key; + bfd_udp_key_init (&key, sw_if_index, local_addr, peer_addr); + bfd_session_t *bs = bfd_lookup_session (bum, &key); + if (bs) + { + *bs_out = bs; + } + else + { + clib_warning + ("BFD session not found (sw_if_index=%u, local=%U, peer=%U", + sw_if_index, format_ip46_address, local_addr, IP46_TYPE_ANY, + format_ip46_address, peer_addr, IP46_TYPE_ANY); + return VNET_API_ERROR_BFD_ENOENT; + } + } + return rv; +} + +static vnet_api_error_t +bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_us, + u32 required_min_rx_us, u8 detect_mult, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr) { vnet_api_error_t rv = bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); @@ -274,18 +311,66 @@ bfd_udp_add_session (u32 sw_if_index, u32 desired_min_tx_us, } if (detect_mult < 1) { - BFD_ERR ("detect_mult < 1"); + clib_warning ("detect_mult < 1"); return VNET_API_ERROR_INVALID_ARGUMENT; } if (desired_min_tx_us < 1) { - BFD_ERR ("desired_min_tx_us < 1"); + clib_warning ("desired_min_tx_us < 1"); return VNET_API_ERROR_INVALID_ARGUMENT; } - return bfd_udp_add_session_internal (&bfd_udp_main, sw_if_index, - desired_min_tx_us, required_min_rx_us, - detect_mult, local_addr, peer_addr, - bs_index); + return 0; +} + +static void +bfd_udp_del_session_internal (bfd_session_t * bs) +{ + bfd_udp_main_t *bum = &bfd_udp_main; + BFD_DBG ("free bfd-udp session, bs_idx=%d", bs->bs_idx); + mhash_unset (&bum->bfd_session_idx_by_bfd_key, &bs->udp.key, NULL); + adj_unlock (bs->udp.adj_index); + bfd_put_session (bum->bfd_main, bs); +} + +vnet_api_error_t +bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u32 desired_min_tx_us, + u32 required_min_rx_us, u8 detect_mult, + u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id) +{ + vnet_api_error_t rv = bfd_api_verify_common (sw_if_index, desired_min_tx_us, + required_min_rx_us, + detect_mult, + local_addr, peer_addr); + bfd_session_t *bs = NULL; + if (!rv) + { + rv = + bfd_udp_add_session_internal (&bfd_udp_main, sw_if_index, + desired_min_tx_us, required_min_rx_us, + detect_mult, local_addr, peer_addr, + &bs); + } + if (!rv && is_authenticated) + { +#if WITH_LIBSSL > 0 + rv = bfd_auth_activate (bs, conf_key_id, bfd_key_id, + 0 /* is not delayed */ ); +#else + clib_warning ("SSL missing, cannot add authenticated BFD session"); + rv = VNET_API_ERROR_BFD_NOTSUPP; +#endif + if (rv) + { + bfd_udp_del_session_internal (bs); + } + } + if (!rv) + { + bfd_session_start (bfd_udp_main.bfd_main, bs); + } + + return rv; } vnet_api_error_t @@ -293,36 +378,162 @@ bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr) { + bfd_session_t *bs = NULL; vnet_api_error_t rv = - bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); if (rv) { return rv; } - bfd_udp_main_t *bum = &bfd_udp_main; - vnet_sw_interface_t *sw_if = - vnet_get_sw_interface (vnet_get_main (), sw_if_index); - bfd_udp_key_t key; - memset (&key, 0, sizeof (key)); - key.sw_if_index = sw_if->sw_if_index; - key.local_addr.as_u64[0] = local_addr->as_u64[0]; - key.local_addr.as_u64[1] = local_addr->as_u64[1]; - key.peer_addr.as_u64[0] = peer_addr->as_u64[0]; - key.peer_addr.as_u64[1] = peer_addr->as_u64[1]; - bfd_session_t *tmp = bfd_lookup_session (bum, &key); - if (tmp) + bfd_udp_del_session_internal (bs); + return 0; +} + +vnet_api_error_t +bfd_udp_session_set_flags (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u8 admin_up_down) +{ + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + bfd_session_set_flags (bs, admin_up_down); + return 0; +} + +vnet_api_error_t +bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, + const u8 * key_data) +{ +#if WITH_LIBSSL > 0 + bfd_auth_key_t *auth_key = NULL; + if (!key_len || key_len > bfd_max_len_for_auth_type (auth_type)) + { + clib_warning ("Invalid authentication key length for auth_type=%d:%s " + "(key_len=%u, must be " + "non-zero, expected max=%u)", + auth_type, bfd_auth_type_str (auth_type), key_len, + (u32) bfd_max_len_for_auth_type (auth_type)); + return VNET_API_ERROR_INVALID_VALUE; + } + if (!bfd_auth_type_supported (auth_type)) + { + clib_warning ("Unsupported auth type=%d:%s", auth_type, + bfd_auth_type_str (auth_type)); + return VNET_API_ERROR_BFD_NOTSUPP; + } + bfd_main_t *bm = bfd_udp_main.bfd_main; + uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (key_idx_p) + { + /* modifying existing key - must not be used */ + const uword key_idx = *key_idx_p; + auth_key = pool_elt_at_index (bm->auth_keys, key_idx); + if (auth_key->use_count > 0) + { + clib_warning ("Authentication key with conf ID %u in use by %u BFD " + "sessions - cannot modify", + conf_key_id, auth_key->use_count); + return VNET_API_ERROR_BFD_EINUSE; + } + } + else + { + /* adding new key */ + pool_get (bm->auth_keys, auth_key); + auth_key->conf_key_id = conf_key_id; + hash_set (bm->auth_key_by_conf_key_id, conf_key_id, + auth_key - bm->auth_keys); + } + auth_key->auth_type = auth_type; + memset (auth_key->key, 0, sizeof (auth_key->key)); + clib_memcpy (auth_key->key, key_data, key_len); + return 0; +#else + clib_warning ("SSL missing, cannot manipulate authentication keys"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_auth_del_key (u32 conf_key_id) +{ +#if WITH_LIBSSL > 0 + bfd_auth_key_t *auth_key = NULL; + bfd_main_t *bm = bfd_udp_main.bfd_main; + uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (key_idx_p) { - BFD_DBG ("free bfd-udp session, bs_idx=%d", tmp->bs_idx); - mhash_unset (&bum->bfd_session_idx_by_bfd_key, &key, NULL); - adj_unlock (tmp->udp.adj_index); - bfd_put_session (bum->bfd_main, tmp); + /* deleting existing key - must not be used */ + const uword key_idx = *key_idx_p; + auth_key = pool_elt_at_index (bm->auth_keys, key_idx); + if (auth_key->use_count > 0) + { + clib_warning ("Authentication key with conf ID %u in use by %u BFD " + "sessions - cannot delete", + conf_key_id, auth_key->use_count); + return VNET_API_ERROR_BFD_EINUSE; + } + hash_unset (bm->auth_key_by_conf_key_id, conf_key_id); + memset (auth_key, 0, sizeof (*auth_key)); + pool_put (bm->auth_keys, auth_key); } else { - BFD_ERR ("no such session"); - return VNET_API_ERROR_BFD_NOENT; + /* no such key */ + clib_warning ("Authentication key with conf ID %u does not exist", + conf_key_id); + return VNET_API_ERROR_BFD_ENOENT; } return 0; +#else + clib_warning ("SSL missing, cannot manipulate authentication keys"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_udp_auth_activate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 conf_key_id, u8 key_id, u8 is_delayed) +{ +#if WITH_LIBSSL > 0 + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + return bfd_auth_activate (bs, conf_key_id, key_id, is_delayed); +#else + clib_warning ("SSL missing, cannot activate BFD authentication"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_udp_auth_deactivate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u8 is_delayed) +{ + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + return bfd_auth_deactivate (bs, is_delayed); } typedef enum @@ -461,6 +672,14 @@ bfd_udp4_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, BFD_ERR ("Couldn't find ip4 or udp header"); return BFD_UDP_ERROR_BAD; } + const u32 udp_payload_length = udp->length - sizeof (*udp); + if (pkt->head.length > udp_payload_length) + { + BFD_ERR + ("BFD packet length is larger than udp payload length (%u > %u)", + pkt->head.length, udp_payload_length); + return BFD_UDP_ERROR_BAD; + } if (!bfd_verify_pkt_common (pkt)) { return BFD_UDP_ERROR_BAD; @@ -491,8 +710,9 @@ bfd_udp4_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, return BFD_UDP_ERROR_BAD; } BFD_DBG ("BFD session found, bs_idx=%u", bs->bs_idx); - if (!bfd_verify_pkt_session (pkt, b->current_length, bs)) + if (!bfd_verify_pkt_auth (pkt, b->current_length, bs)) { + BFD_ERR ("Packet verification failed, dropping packet"); return BFD_UDP_ERROR_BAD; } bfd_udp_error_t err; @@ -526,11 +746,10 @@ bfd_udp6_find_headers (vlib_buffer_t * b, const ip6_header_t ** ip6, *udp = NULL; return; } - /* FIXME skip extra headers when searching for UDP ? */ if ((*ip6)->protocol != IP_PROTOCOL_UDP) { BFD_ERR ("Unexpected protocol in IPv6 header '%u', expected '%u' (== " - "IP_PROTOCOL_UDP)" (*ip6)->protocol, IP_PROTOCOL_UDP); + "IP_PROTOCOL_UDP)", (*ip6)->protocol, IP_PROTOCOL_UDP); *ip6 = NULL; *udp = NULL; return; @@ -596,6 +815,14 @@ bfd_udp6_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, BFD_ERR ("Couldn't find ip6 or udp header"); return BFD_UDP_ERROR_BAD; } + const u32 udp_payload_length = udp->length - sizeof (*udp); + if (pkt->head.length > udp_payload_length) + { + BFD_ERR + ("BFD packet length is larger than udp payload length (%u > %u)", + pkt->head.length, udp_payload_length); + return BFD_UDP_ERROR_BAD; + } if (!bfd_verify_pkt_common (pkt)) { return BFD_UDP_ERROR_BAD; @@ -617,8 +844,9 @@ bfd_udp6_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, key.peer_addr.ip6.as_u64[0] = ip6->src_address.as_u64[0]; key.peer_addr.ip6.as_u64[1] = ip6->src_address.as_u64[1]; BFD_DBG ("Looking up BFD session using key (sw_if_index=%u, local=%U, " - "peer=%U)", key.sw_if_index, format_ip6_address, - &key.local_addr, format_ip6_address, &key.peer_addr); + "peer=%U)", + key.sw_if_index, format_ip6_address, &key.local_addr, + format_ip6_address, &key.peer_addr); bs = bfd_lookup_session (&bfd_udp_main, &key); } if (!bs) @@ -627,8 +855,9 @@ bfd_udp6_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, return BFD_UDP_ERROR_BAD; } BFD_DBG ("BFD session found, bs_idx=%u", bs->bs_idx); - if (!bfd_verify_pkt_session (pkt, b->current_length, bs)) + if (!bfd_verify_pkt_auth (pkt, b->current_length, bs)) { + BFD_ERR ("Packet verification failed, dropping packet"); return BFD_UDP_ERROR_BAD; } bfd_udp_error_t err; @@ -699,7 +928,7 @@ bfd_udp_input (vlib_main_t * vm, vlib_node_runtime_t * rt, const bfd_pkt_t *pkt = vlib_buffer_get_current (b0); if (bfd_pkt_get_poll (pkt)) { - bfd_send_final (vm, b0, bs); + bfd_init_final_control_frame (vm, b0, bs); if (is_ipv6) { vlib_node_increment_counter (vm, bfd_udp6_input_node.index, diff --git a/test/bfd.py b/test/bfd.py index 51716813..475a1707 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -1,3 +1,4 @@ +from random import randint from socket import AF_INET, AF_INET6 from scapy.all import * from scapy.packet import * @@ -53,11 +54,57 @@ class BFDState(NumericConstant): NumericConstant.__init__(self, value) +class BFDAuthType(NumericConstant): + """ BFD Authentication Type """ + no_auth = 0 + simple_pwd = 1 + keyed_md5 = 2 + meticulous_keyed_md5 = 3 + keyed_sha1 = 4 + meticulous_keyed_sha1 = 5 + + desc_dict = { + no_auth: "No authentication", + simple_pwd: "Simple Password", + keyed_md5: "Keyed MD5", + meticulous_keyed_md5: "Meticulous Keyed MD5", + keyed_sha1: "Keyed SHA1", + meticulous_keyed_sha1: "Meticulous Keyed SHA1", + } + + def __init__(self, value): + NumericConstant.__init__(self, value) + + +def bfd_is_auth_used(pkt): + return "A" in pkt.sprintf("%BFD.flags%") + + +def bfd_is_simple_pwd_used(pkt): + return bfd_is_auth_used(pkt) and pkt.auth_type == BFDAuthType.simple_pwd + + +def bfd_is_sha1_used(pkt): + return bfd_is_auth_used(pkt) and pkt.auth_type in \ + (BFDAuthType.keyed_sha1, BFDAuthType.meticulous_keyed_sha1) + + +def bfd_is_md5_used(pkt): + return bfd_is_auth_used(pkt) and pkt.auth_type in \ + (BFDAuthType.keyed_md5, BFDAuthType.meticulous_keyed_md5) + + +def bfd_is_md5_or_sha1_used(pkt): + return bfd_is_md5_used(pkt) or bfd_is_sha1_used(pkt) + + class BFD(Packet): udp_dport = 3784 #: BFD destination port per RFC 5881 udp_sport_min = 49152 #: BFD source port min value per RFC 5881 udp_sport_max = 65535 #: BFD source port max value per RFC 5881 + bfd_pkt_len = 24 # : length of BFD pkt without authentication section + sha1_auth_len = 28 # : length of authentication section if SHA1 used name = "BFD" @@ -65,14 +112,27 @@ class BFD(Packet): BitField("version", 1, 3), BitEnumField("diag", 0, 5, BFDDiagCode.desc_dict), BitEnumField("state", 0, 2, BFDState.desc_dict), - FlagsField("flags", 0, 6, ['P', 'F', 'C', 'A', 'D', 'M']), + FlagsField("flags", 0, 6, ['M', 'D', 'A', 'C', 'F', 'P']), XByteField("detect_mult", 0), - XByteField("length", 24), + BitField("length", bfd_pkt_len, 8), BitField("my_discriminator", 0, 32), BitField("your_discriminator", 0, 32), BitField("desired_min_tx_interval", 0, 32), BitField("required_min_rx_interval", 0, 32), - BitField("required_min_echo_rx_interval", 0, 32)] + BitField("required_min_echo_rx_interval", 0, 32), + ConditionalField( + BitEnumField("auth_type", 0, 8, BFDAuthType.desc_dict), + bfd_is_auth_used), + ConditionalField(BitField("auth_len", 0, 8), bfd_is_auth_used), + ConditionalField(BitField("auth_key_id", 0, 8), bfd_is_auth_used), + ConditionalField(BitField("auth_reserved", 0, 8), + bfd_is_md5_or_sha1_used), + ConditionalField( + BitField("auth_seq_num", 0, 32), bfd_is_md5_or_sha1_used), + ConditionalField(StrField("auth_key_hash", "0" * 16), bfd_is_md5_used), + ConditionalField( + StrField("auth_key_hash", "0" * 20), bfd_is_sha1_used), + ] def mysummary(self): return self.sprintf("BFD(my_disc=%BFD.my_discriminator%," @@ -82,9 +142,78 @@ class BFD(Packet): bind_layers(UDP, BFD, dport=BFD.udp_dport) +class VppBFDAuthKey(VppObject): + """ Represents BFD authentication key in VPP """ + + def __init__(self, test, conf_key_id, auth_type, key): + self._test = test + self._key = key + self._auth_type = auth_type + test.assertIn(auth_type, BFDAuthType.desc_dict) + self._conf_key_id = conf_key_id + + @property + def test(self): + """ Test which created this key """ + return self._test + + @property + def auth_type(self): + """ Authentication type for this key """ + return self._auth_type + + @property + def key(self): + return self._key + + @property + def conf_key_id(self): + return self._conf_key_id + + def add_vpp_config(self): + self.test.vapi.bfd_auth_set_key( + self._conf_key_id, self._auth_type, self._key) + self._test.registry.register(self, self.test.logger) + + def get_bfd_auth_keys_dump_entry(self): + """ get the entry in the auth keys dump corresponding to this key """ + result = self.test.vapi.bfd_auth_keys_dump() + for k in result: + if k.conf_key_id == self._conf_key_id: + return k + return None + + def query_vpp_config(self): + return self.get_bfd_auth_keys_dump_entry() is not None + + def remove_vpp_config(self): + self.test.vapi.bfd_auth_del_key(self._conf_key_id) + + def object_id(self): + return "bfd-auth-key-%s" % self._conf_key_id + + def __str__(self): + return self.object_id() + + class VppBFDUDPSession(VppObject): """ Represents BFD UDP session in VPP """ + def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET, + desired_min_tx=100000, required_min_rx=100000, detect_mult=3, + sha1_key=None, bfd_key_id=None): + self._test = test + self._interface = interface + self._af = af + self._local_addr = local_addr + self._peer_addr = peer_addr + self._peer_addr_n = socket.inet_pton(af, peer_addr) + self._desired_min_tx = desired_min_tx + self._required_min_rx = required_min_rx + self._detect_mult = detect_mult + self._sha1_key = sha1_key + self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255) + @property def test(self): """ Test which created this session """ @@ -100,13 +229,6 @@ class VppBFDUDPSession(VppObject): """ Address family - AF_INET or AF_INET6 """ return self._af - @property - def bs_index(self): - """ BFD session index from VPP """ - if self._bs_index is not None: - return self._bs_index - raise NotConfiguredException("not configured") - @property def local_addr(self): """ BFD session local address (VPP address) """ @@ -141,19 +263,27 @@ class VppBFDUDPSession(VppObject): """ BFD session peer address - raw, suitable for API """ return self._peer_addr_n - @property - def state(self): - """ BFD session state """ + def get_bfd_udp_session_dump_entry(self): result = self.test.vapi.bfd_udp_session_dump() - session = None for s in result: + self.test.logger.debug("session entry: %s" % str(s)) if s.sw_if_index == self.interface.sw_if_index: if self.af == AF_INET \ and s.is_ipv6 == 0 \ and self.interface.local_ip4n == s.local_addr[:4] \ and self.interface.remote_ip4n == s.peer_addr[:4]: - session = s - break + return s + if self.af == AF_INET6 \ + and s.is_ipv6 == 1 \ + and self.interface.local_ip6n == s.local_addr \ + and self.interface.remote_ip6n == s.peer_addr: + return s + return None + + @property + def state(self): + """ BFD session state """ + session = self.get_bfd_udp_session_dump_entry() if session is None: raise Exception("Could not find BFD session in VPP response: %s" % repr(result)) @@ -171,61 +301,78 @@ class VppBFDUDPSession(VppObject): def detect_mult(self): return self._detect_mult - def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET, - desired_min_tx=100000, required_min_rx=100000, detect_mult=3): - self._test = test - self._interface = interface - self._af = af - self._local_addr = local_addr - self._peer_addr = peer_addr - self._peer_addr_n = socket.inet_pton(af, peer_addr) - self._bs_index = None - self._desired_min_tx = desired_min_tx - self._required_min_rx = required_min_rx - self._detect_mult = detect_mult + @property + def sha1_key(self): + return self._sha1_key + + @property + def bfd_key_id(self): + return self._bfd_key_id + + def activate_auth(self, key, bfd_key_id=None, delayed=False): + self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255) + self._sha1_key = key + is_ipv6 = 1 if AF_INET6 == self.af else 0 + conf_key_id = self._sha1_key.conf_key_id + is_delayed = 1 if delayed else 0 + self.test.vapi.bfd_udp_auth_activate(self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6, + bfd_key_id=self._bfd_key_id, + conf_key_id=conf_key_id, + is_delayed=is_delayed) + + def deactivate_auth(self, delayed=False): + self._bfd_key_id = None + self._sha1_key = None + is_delayed = 1 if delayed else 0 + is_ipv6 = 1 if AF_INET6 == self.af else 0 + self.test.vapi.bfd_udp_auth_deactivate(self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6, + is_delayed=is_delayed) def add_vpp_config(self): is_ipv6 = 1 if AF_INET6 == self.af else 0 - result = self.test.vapi.bfd_udp_add( - self._interface.sw_if_index, - self.desired_min_tx, - self.required_min_rx, - self.detect_mult, - self.local_addr_n, - self.peer_addr_n, - is_ipv6=is_ipv6) - self._bs_index = result.bs_index + bfd_key_id = self._bfd_key_id if self._sha1_key else None + conf_key_id = self._sha1_key.conf_key_id if self._sha1_key else None + self.test.vapi.bfd_udp_add(self._interface.sw_if_index, + self.desired_min_tx, + self.required_min_rx, + self.detect_mult, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6, + bfd_key_id=bfd_key_id, + conf_key_id=conf_key_id) self._test.registry.register(self, self.test.logger) def query_vpp_config(self): - result = self.test.vapi.bfd_udp_session_dump() - session = None - for s in result: - if s.sw_if_index == self.interface.sw_if_index: - if self.af == AF_INET \ - and s.is_ipv6 == 0 \ - and self.interface.local_ip4n == s.local_addr[:4] \ - and self.interface.remote_ip4n == s.peer_addr[:4]: - session = s - break - if session is None: - return False - return True + session = self.get_bfd_udp_session_dump_entry() + return session is not None def remove_vpp_config(self): - if self._bs_index is not None: - is_ipv6 = 1 if AF_INET6 == self._af else 0 - self.test.vapi.bfd_udp_del( - self._interface.sw_if_index, - self.local_addr_n, - self.peer_addr_n, - is_ipv6=is_ipv6) + is_ipv6 = 1 if AF_INET6 == self._af else 0 + self.test.vapi.bfd_udp_del(self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) def object_id(self): - return "bfd-udp-%d" % self.bs_index + return "bfd-udp-%s-%s-%s-%s" % (self._interface.sw_if_index, + self.local_addr, + self.peer_addr, + self.af) def __str__(self): return self.object_id() def admin_up(self): - self.test.vapi.bfd_session_set_flags(self.bs_index, 1) + is_ipv6 = 1 if AF_INET6 == self._af else 0 + self.test.vapi.bfd_udp_session_set_flags(1, + self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) diff --git a/test/framework.py b/test/framework.py index 02935604..8ceb33c3 100644 --- a/test/framework.py +++ b/test/framework.py @@ -89,8 +89,8 @@ class VppTestCase(unittest.TestCase): if dl == "core": if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0: # give a heads up if this is actually useless - cls.logger.critical("WARNING: core size limit is set 0, core " - "files will NOT be created") + print(colorize("WARNING: core size limit is set 0, core files " + "will NOT be created", RED)) cls.debug_core = True elif dl == "gdb": cls.debug_gdb = True @@ -533,12 +533,11 @@ class VppTestCase(unittest.TestCase): self.assertEqual(real_value, expected_value, msg) - def assert_in_range( - self, - real_value, - expected_min, - expected_max, - name=None): + def assert_in_range(self, + real_value, + expected_min, + expected_max, + name=None): if name is None: msg = None else: diff --git a/test/test_bfd.py b/test/test_bfd.py index d047b5a3..5f861477 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -1,6 +1,8 @@ #!/usr/bin/env python import unittest +import hashlib +import binascii import time from random import randint from bfd import * @@ -10,6 +12,22 @@ from util import ppp us_in_sec = 1000000 +class AuthKeyFactory(object): + """Factory class for creating auth keys with unique conf key ID""" + + def __init__(self): + self._conf_key_ids = {} + + def create_random_key(self, test, auth_type=BFDAuthType.keyed_sha1): + conf_key_id = randint(0, 0xFFFFFFFF) + while conf_key_id in self._conf_key_ids: + conf_key_id = randint(0, 0xFFFFFFFF) + self._conf_key_ids[conf_key_id] = 1 + key = str(bytearray([randint(0, 255) for j in range(randint(1, 20))])) + return VppBFDAuthKey(test=test, auth_type=auth_type, + conf_key_id=conf_key_id, key=key) + + class BFDAPITestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) - API""" @@ -21,19 +39,23 @@ class BFDAPITestCase(VppTestCase): cls.create_pg_interfaces(range(2)) for i in cls.pg_interfaces: i.config_ip4() + i.config_ip6() i.resolve_arp() except Exception: super(BFDAPITestCase, cls).tearDownClass() raise + def setUp(self): + super(BFDAPITestCase, self).setUp() + self.factory = AuthKeyFactory() + def test_add_bfd(self): """ create a BFD session """ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() self.logger.debug("Session state is %s" % str(session.state)) session.remove_vpp_config() - session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() self.logger.debug("Session state is %s" % str(session.state)) session.remove_vpp_config() @@ -48,25 +70,155 @@ class BFDAPITestCase(VppTestCase): session.remove_vpp_config() - def test_add_two(self): - """ create two BFD sessions """ - session1 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) - session1.add_vpp_config() - session2 = VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4) - session2.add_vpp_config() - self.assertNotEqual(session1.bs_index, session2.bs_index, - "Different BFD sessions share bs_index (%s)" % - session1.bs_index) + def test_add_bfd6(self): + """ create IPv6 BFD session """ + session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip6, af=AF_INET6) + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + + def test_add_sha1_keys(self): + """ add SHA1 keys """ + key_count = 10 + keys = [self.factory.create_random_key( + self) for i in range(0, key_count)] + for key in keys: + self.assertFalse(key.query_vpp_config()) + for key in keys: + key.add_vpp_config() + for key in keys: + self.assertTrue(key.query_vpp_config()) + # remove randomly + indexes = range(key_count) + random.shuffle(indexes) + removed = [] + for i in indexes: + key = keys[i] + key.remove_vpp_config() + removed.append(i) + for j in range(key_count): + key = keys[j] + if j in removed: + self.assertFalse(key.query_vpp_config()) + else: + self.assertTrue(key.query_vpp_config()) + # should be removed now + for key in keys: + self.assertFalse(key.query_vpp_config()) + # add back and remove again + for key in keys: + key.add_vpp_config() + for key in keys: + self.assertTrue(key.query_vpp_config()) + for key in keys: + key.remove_vpp_config() + for key in keys: + self.assertFalse(key.query_vpp_config()) + + def test_add_bfd_sha1(self): + """ create a BFD session (SHA1) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key) + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + + def test_double_add_sha1(self): + """ create the same BFD session twice (negative case) (SHA1) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key) + session.add_vpp_config() + with self.assertRaises(Exception): + session.add_vpp_config() + + def test_add_authenticated_with_nonexistent_key(self): + """ create BFD session using non-existent SHA1 (negative case) """ + session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, + sha1_key=self.factory.create_random_key(self)) + with self.assertRaises(Exception): + session.add_vpp_config() + + def test_shared_sha1_key(self): + """ share single SHA1 key between multiple BFD sessions """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + sessions = [ + VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key), + VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, + sha1_key=key, af=AF_INET6), + VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4, + sha1_key=key), + VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6, + sha1_key=key, af=AF_INET6)] + for s in sessions: + s.add_vpp_config() + removed = 0 + for s in sessions: + e = key.get_bfd_auth_keys_dump_entry() + self.assert_equal(e.use_count, len(sessions) - removed, + "Use count for shared key") + s.remove_vpp_config() + removed += 1 + e = key.get_bfd_auth_keys_dump_entry() + self.assert_equal(e.use_count, len(sessions) - removed, + "Use count for shared key") + + def test_activate_auth(self): + """ activate SHA1 authentication """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + session.activate_auth(key) + + def test_deactivate_auth(self): + """ deactivate SHA1 authentication """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + session.activate_auth(key) + session.deactivate_auth() + + def test_change_key(self): + key1 = self.factory.create_random_key(self) + key2 = self.factory.create_random_key(self) + while key2.conf_key_id == key1.conf_key_id: + key2 = self.factory.create_random_key(self) + key1.add_vpp_config() + key2.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key1) + session.add_vpp_config() + session.activate_auth(key2) class BFDTestSession(object): """ BFD session as seen from test framework side """ - def __init__(self, test, interface, af, detect_mult=3): + def __init__(self, test, interface, af, detect_mult=3, sha1_key=None, + bfd_key_id=None, our_seq_number=0xFFFFFFFF - 4): self.test = test self.af = af + self.sha1_key = sha1_key + self.bfd_key_id = bfd_key_id self.interface = interface self.udp_sport = 50000 + self.our_seq_number = our_seq_number + self.vpp_seq_number = None self.bfd_values = { 'my_discriminator': 0, 'desired_min_tx_interval': 100000, @@ -74,10 +226,25 @@ class BFDTestSession(object): 'diag': BFDDiagCode.no_diagnostic, } + def inc_seq_num(self): + if self.our_seq_number == 0xFFFFFFFF: + self.our_seq_number = 0 + else: + self.our_seq_number += 1 + def update(self, **kwargs): self.bfd_values.update(kwargs) def create_packet(self): + if self.sha1_key: + bfd = BFD(flags="A") + bfd.auth_type = self.sha1_key.auth_type + bfd.auth_len = BFD.sha1_auth_len + bfd.auth_key_id = self.bfd_key_id + bfd.auth_seq_num = self.our_seq_number + bfd.length = BFD.sha1_auth_len + BFD.bfd_pkt_len + else: + bfd = BFD() if self.af == AF_INET6: packet = (Ether(src=self.interface.remote_mac, dst=self.interface.local_mac) / @@ -85,7 +252,7 @@ class BFDTestSession(object): dst=self.interface.local_ip6, hlim=255) / UDP(sport=self.udp_sport, dport=BFD.udp_dport) / - BFD()) + bfd) else: packet = (Ether(src=self.interface.remote_mac, dst=self.interface.local_mac) / @@ -93,11 +260,17 @@ class BFDTestSession(object): dst=self.interface.local_ip4, ttl=255) / UDP(sport=self.udp_sport, dport=BFD.udp_dport) / - BFD()) + bfd) self.test.logger.debug("BFD: Creating packet") for name, value in self.bfd_values.iteritems(): self.test.logger.debug("BFD: setting packet.%s=%s", name, value) packet[BFD].setfieldval(name, value) + if self.sha1_key: + hash_material = str(packet[BFD])[:32] + self.sha1_key.key + \ + "\0" * (20 - len(self.sha1_key.key)) + self.test.logger.debug("BFD: Calculated SHA1 hash: %s" % + hashlib.sha1(hash_material).hexdigest()) + packet[BFD].auth_key_hash = hashlib.sha1(hash_material).digest() return packet def send_packet(self): @@ -106,13 +279,60 @@ class BFDTestSession(object): self.test.pg0.add_stream([p]) self.test.pg_start() - def verify_packet(self, packet): + def verify_sha1_auth(self, packet): + """ Verify correctness of authentication in BFD layer. """ + bfd = packet[BFD] + self.test.assert_equal(bfd.auth_len, 28, "Auth section length") + self.test.assert_equal(bfd.auth_type, self.sha1_key.auth_type, + BFDAuthType) + self.test.assert_equal(bfd.auth_key_id, self.bfd_key_id, "Key ID") + self.test.assert_equal(bfd.auth_reserved, 0, "Reserved") + if self.vpp_seq_number is None: + self.vpp_seq_number = bfd.auth_seq_num + self.test.logger.debug("Received initial sequence number: %s" % + self.vpp_seq_number) + else: + recvd_seq_num = bfd.auth_seq_num + self.test.logger.debug("Received followup sequence number: %s" % + recvd_seq_num) + if self.vpp_seq_number < 0xffffffff: + if self.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + self.test.assert_equal(recvd_seq_num, + self.vpp_seq_number + 1, + "BFD sequence number") + else: + self.test.assert_in_range(recvd_seq_num, + self.vpp_seq_number, + self.vpp_seq_number + 1, + "BFD sequence number") + else: + if self.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + self.test.assert_equal(recvd_seq_num, 0, + "BFD sequence number") + else: + self.test.assertIn(recvd_seq_num, (self.vpp_seq_number, 0), + "BFD sequence number not one of " + "(%s, 0)" % self.vpp_seq_number) + self.vpp_seq_number = recvd_seq_num + # last 20 bytes represent the hash - so replace them with the key, + # pad the result with zeros and hash the result + hash_material = bfd.original[:-20] + self.sha1_key.key + \ + "\0" * (20 - len(self.sha1_key.key)) + expected_hash = hashlib.sha1(hash_material).hexdigest() + self.test.assert_equal(binascii.hexlify(bfd.auth_key_hash), + expected_hash, "Auth key hash") + + def verify_bfd(self, packet): """ Verify correctness of BFD layer. """ bfd = packet[BFD] self.test.assert_equal(bfd.version, 1, "BFD version") self.test.assert_equal(bfd.your_discriminator, self.bfd_values['my_discriminator'], "BFD - your discriminator") + if self.sha1_key: + self.verify_sha1_auth(packet) class BFDCommonCode: @@ -122,9 +342,9 @@ class BFDCommonCode: self.vapi.collect_events() # clear the event queue if not self.vpp_dead: self.vapi.want_bfd_events(enable_disable=0) - self.vpp_session.remove_vpp_config() def bfd_session_up(self): + """ Bring BFD session up """ self.pg_enable_capture([self.pg0]) self.logger.info("BFD: Waiting for slow hello") p, timeout = self.wait_for_bfd_packet(2) @@ -139,6 +359,18 @@ class BFDCommonCode: self.verify_event(e, expected_state=BFDState.up) self.logger.info("BFD: Session is Up") self.test_session.update(state=BFDState.up) + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def bfd_session_down(self): + """ Bring BFD session down """ + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.test_session.update(state=BFDState.down) + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.down) + self.logger.info("BFD: Session is Down") + self.assert_equal(self.vpp_session.state, BFDState.down, BFDState) def verify_ip(self, packet): """ Verify correctness of IP layer. """ @@ -166,12 +398,9 @@ class BFDCommonCode: """ Verify correctness of event values. """ e = event self.logger.debug("BFD: Event: %s" % repr(e)) - self.assert_equal(e.bs_index, self.vpp_session.bs_index, - "BFD session index") - self.assert_equal( - e.sw_if_index, - self.vpp_session.interface.sw_if_index, - "BFD interface index") + self.assert_equal(e.sw_if_index, + self.vpp_session.interface.sw_if_index, + "BFD interface index") is_ipv6 = 0 if self.vpp_session.af == AF_INET6: is_ipv6 = 1 @@ -199,7 +428,7 @@ class BFDCommonCode: before = time.time() p = self.pg0.wait_for_packet(timeout=timeout) after = time.time() - self.logger.debug(ppp("Got packet:", p)) + self.logger.debug(ppp("BFD: Got packet:", p)) bfd = p[BFD] if bfd is None: raise Exception(ppp("Unexpected or invalid BFD packet:", p)) @@ -207,20 +436,9 @@ class BFDCommonCode: raise Exception(ppp("Unexpected payload in BFD packet:", bfd)) self.verify_ip(p) self.verify_udp(p) - self.test_session.verify_packet(p) + self.test_session.verify_bfd(p) return p, after - before - def test_session_up(self): - """ bring BFD session up """ - self.bfd_session_up() - - def test_hold_up(self): - """ hold BFD session up """ - self.bfd_session_up() - for i in range(5): - self.wait_for_bfd_packet() - self.test_session.send_packet() - class BFD4TestCase(VppTestCase, BFDCommonCode): """Bidirectional Forwarding Detection (BFD)""" @@ -231,7 +449,6 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): try: cls.create_pg_interfaces([0]) cls.pg0.config_ip4() - cls.pg0.generate_remote_hosts() cls.pg0.configure_ipv4_neighbors() cls.pg0.admin_up() cls.pg0.resolve_arp() @@ -242,6 +459,7 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): def setUp(self): super(BFD4TestCase, self).setUp() + self.factory = AuthKeyFactory() self.vapi.want_bfd_events() try: self.vpp_session = VppBFDUDPSession(self, self.pg0, @@ -255,7 +473,25 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): def tearDown(self): BFDCommonCode.tearDown(self) - super(BFD4TestCase, self).tearDown() + VppTestCase.tearDown(self) + + def test_session_up(self): + """ bring BFD session up """ + self.bfd_session_up() + + def test_session_down(self): + """ bring BFD session down """ + self.bfd_session_up() + self.bfd_session_down() + + def test_hold_up(self): + """ hold BFD session up """ + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") def test_slow_timer(self): """ verify slow periodic control frames while session down """ @@ -367,6 +603,7 @@ class BFD6TestCase(VppTestCase, BFDCommonCode): def setUp(self): super(BFD6TestCase, self).setUp() + self.factory = AuthKeyFactory() self.vapi.want_bfd_events() try: self.vpp_session = VppBFDUDPSession(self, self.pg0, @@ -382,7 +619,429 @@ class BFD6TestCase(VppTestCase, BFDCommonCode): def tearDown(self): BFDCommonCode.tearDown(self) - super(BFD6TestCase, self).tearDown() + VppTestCase.tearDown(self) + + def test_session_up(self): + """ bring BFD session up """ + self.bfd_session_up() + + def test_hold_up(self): + """ hold BFD session up """ + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + +class BFDSHA1TestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """ + + @classmethod + def setUpClass(cls): + super(BFDSHA1TestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFDSHA1TestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFDSHA1TestCase, self).setUp() + self.factory = AuthKeyFactory() + self.vapi.want_bfd_events() + + def tearDown(self): + BFDCommonCode.tearDown(self) + VppTestCase.tearDown(self) + + def test_session_up(self): + """ bring BFD session up """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, + sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + + def test_hold_up(self): + """ hold BFD session up """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, + sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def test_hold_up_meticulous(self): + """ hold BFD session up - meticulous auth """ + key = self.factory.create_random_key( + self, BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.inc_seq_num() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def test_send_bad_seq_number(self): + """ session is not kept alive by msgs with bad seq numbers""" + key = self.factory.create_random_key( + self, BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.wait_for_bfd_packet() + self.test_session.send_packet() + e = self.vapi.collect_events() + # session should be down now, because the sequence numbers weren't + # updated + self.assert_equal(len(e), 1, "number of bfd events") + self.verify_event(e[0], expected_state=BFDState.down) + + def execute_rogue_session_scenario(self, vpp_bfd_udp_session, + legitimate_test_session, + rogue_test_session, + rogue_bfd_values=None): + """ execute a rogue session interaction scenario + + 1. create vpp session, add config + 2. bring the legitimate session up + 3. copy the bfd values from legitimate session to rogue session + 4. apply rogue_bfd_values to rogue session + 5. set rogue session state to down + 6. send message to take the session down from the rogue session + 7. assert that the legitimate session is unaffected + """ + + self.vpp_session = vpp_bfd_udp_session + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = legitimate_test_session + # bring vpp session up + self.bfd_session_up() + # send packet from rogue session + rogue_test_session.bfd_values = self.test_session.bfd_values.copy() + if rogue_bfd_values: + rogue_test_session.update(**rogue_bfd_values) + rogue_test_session.update(state=BFDState.down) + rogue_test_session.send_packet() + self.wait_for_bfd_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def test_mismatch_auth(self): + """ session is not brought down by unauthenticated msg """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + legitimate_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + rogue_test_session = BFDTestSession(self, self.pg0, AF_INET) + self.execute_rogue_session_scenario(vpp_session, + legitimate_test_session, + rogue_test_session) + + def test_mismatch_bfd_key_id(self): + """ session is not brought down by msg with non-existent key-id """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + # pick a different random bfd key id + x = randint(0, 255) + while x == vpp_session.bfd_key_id: + x = randint(0, 255) + legitimate_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + rogue_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=x) + self.execute_rogue_session_scenario(vpp_session, + legitimate_test_session, + rogue_test_session) + + def test_mismatched_auth_type(self): + """ session is not brought down by msg with wrong auth type """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + legitimate_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + rogue_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + self.execute_rogue_session_scenario( + vpp_session, legitimate_test_session, rogue_test_session, + {'auth_type': BFDAuthType.keyed_md5}) + + def test_restart(self): + """ simulate remote peer restart and resynchronization """ + key = self.factory.create_random_key( + self, BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0) + self.bfd_session_up() + # now we need to not respond for 2*detection_time (4 packets) + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.down) + self.test_session.update(state=BFDState.down) + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + # reset sequence number + self.test_session.our_seq_number = 0 + self.bfd_session_up() + + +class BFDAuthOnOffTestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD) (changing auth) """ + + @classmethod + def setUpClass(cls): + super(BFDAuthOnOffTestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFDAuthOnOffTestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFDAuthOnOffTestCase, self).setUp() + self.factory = AuthKeyFactory() + self.vapi.want_bfd_events() + + def tearDown(self): + BFDCommonCode.tearDown(self) + VppTestCase.tearDown(self) + + def test_auth_on_immediate(self): + """ turn auth on without disturbing session state (immediate) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key) + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_off_immediate(self): + """ turn auth off without disturbing session state (immediate) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.deactivate_auth() + self.test_session.bfd_key_id = None + self.test_session.sha1_key = None + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_change_key_immediate(self): + """ change auth key without disturbing session state (immediate) """ + key1 = self.factory.create_random_key(self) + key1.add_vpp_config() + key2 = self.factory.create_random_key(self) + key2.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key1) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key1, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key2) + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key2 + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_on_delayed(self): + """ turn auth on without disturbing session state (delayed) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key, delayed=True) + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key + self.test_session.send_packet() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_off_delayed(self): + """ turn auth off without disturbing session state (delayed) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.deactivate_auth(delayed=True) + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.test_session.bfd_key_id = None + self.test_session.sha1_key = None + self.test_session.send_packet() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_change_key_delayed(self): + """ change auth key without disturbing session state (delayed) """ + key1 = self.factory.create_random_key(self) + key1.add_vpp_config() + key2 = self.factory.create_random_key(self) + key2.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key1) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key1, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key2, delayed=True) + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key2 + self.test_session.send_packet() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 90c954dc..72c18e6c 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -992,16 +992,57 @@ class VppPapiProvider(object): self.api(self.papi.control_ping) def bfd_udp_add(self, sw_if_index, desired_min_tx, required_min_rx, - detect_mult, local_addr, peer_addr, is_ipv6=0): - return self.api(self.papi.bfd_udp_add, + detect_mult, local_addr, peer_addr, is_ipv6=0, + bfd_key_id=None, conf_key_id=None): + if bfd_key_id is None: + return self.api(self.papi.bfd_udp_add, + { + 'sw_if_index': sw_if_index, + 'desired_min_tx': desired_min_tx, + 'required_min_rx': required_min_rx, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'detect_mult': detect_mult, + }) + else: + return self.api(self.papi.bfd_udp_add, + { + 'sw_if_index': sw_if_index, + 'desired_min_tx': desired_min_tx, + 'required_min_rx': required_min_rx, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'detect_mult': detect_mult, + 'is_authenticated': 1, + 'bfd_key_id': bfd_key_id, + 'conf_key_id': conf_key_id, + }) + + def bfd_udp_auth_activate(self, sw_if_index, local_addr, peer_addr, + is_ipv6=0, bfd_key_id=None, conf_key_id=None, + is_delayed=False): + return self.api(self.papi.bfd_udp_auth_activate, { 'sw_if_index': sw_if_index, - 'desired_min_tx': desired_min_tx, - 'required_min_rx': required_min_rx, 'local_addr': local_addr, 'peer_addr': peer_addr, 'is_ipv6': is_ipv6, - 'detect_mult': detect_mult, + 'is_delayed': 1 if is_delayed else 0, + 'bfd_key_id': bfd_key_id, + 'conf_key_id': conf_key_id, + }) + + def bfd_udp_auth_deactivate(self, sw_if_index, local_addr, peer_addr, + is_ipv6=0, is_delayed=False): + return self.api(self.papi.bfd_udp_auth_deactivate, + { + 'sw_if_index': sw_if_index, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'is_delayed': 1 if is_delayed else 0, }) def bfd_udp_del(self, sw_if_index, local_addr, peer_addr, is_ipv6=0): @@ -1016,10 +1057,14 @@ class VppPapiProvider(object): def bfd_udp_session_dump(self): return self.api(self.papi.bfd_udp_session_dump, {}) - def bfd_session_set_flags(self, bs_idx, admin_up_down): - return self.api(self.papi.bfd_session_set_flags, { - 'bs_index': bs_idx, + def bfd_udp_session_set_flags(self, admin_up_down, sw_if_index, local_addr, + peer_addr, is_ipv6=0): + return self.api(self.papi.bfd_udp_session_set_flags, { 'admin_up_down': admin_up_down, + 'sw_if_index': sw_if_index, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, }) def want_bfd_events(self, enable_disable=1): @@ -1028,6 +1073,22 @@ class VppPapiProvider(object): 'pid': os.getpid(), }) + def bfd_auth_set_key(self, conf_key_id, auth_type, key): + return self.api(self.papi.bfd_auth_set_key, { + 'conf_key_id': conf_key_id, + 'auth_type': auth_type, + 'key': key, + 'key_len': len(key), + }) + + def bfd_auth_del_key(self, conf_key_id): + return self.api(self.papi.bfd_auth_del_key, { + 'conf_key_id': conf_key_id, + }) + + def bfd_auth_keys_dump(self): + return self.api(self.papi.bfd_auth_keys_dump, {}) + def classify_add_del_table( self, is_add, -- cgit 1.2.3-korg From a57a970952be2c3403c57dd7a16cd3d73660ef79 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 2 Feb 2017 06:58:07 +0100 Subject: BFD: modify session parameters Change-Id: I666e5c0cc71a3693640960c93cdd1907f84fbe23 Signed-off-by: Klement Sekera --- src/vnet/api_errno.h | 3 +- src/vnet/bfd/bfd.api | 40 ++++++ src/vnet/bfd/bfd_api.c | 24 ++++ src/vnet/bfd/bfd_api.h | 14 ++- src/vnet/bfd/bfd_debug.h | 7 ++ src/vnet/bfd/bfd_main.c | 296 +++++++++++++++++++++++++++++++++----------- src/vnet/bfd/bfd_main.h | 57 ++++++--- src/vnet/bfd/bfd_protocol.c | 26 ++++ src/vnet/bfd/bfd_udp.c | 61 +++++---- test/bfd.py | 19 +++ test/framework.py | 4 + test/test_bfd.py | 164 ++++++++++++++++++++++-- test/vpp_papi_provider.py | 13 ++ 13 files changed, 601 insertions(+), 127 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/api_errno.h b/src/vnet/api_errno.h index 32880232..0daba169 100644 --- a/src/vnet/api_errno.h +++ b/src/vnet/api_errno.h @@ -95,7 +95,8 @@ _(BFD_EEXIST, -101, "Duplicate BFD object") \ _(BFD_ENOENT, -102, "No such BFD object") \ _(BFD_EINUSE, -103, "BFD object in use") \ _(BFD_NOTSUPP, -104, "BFD feature not supported") \ -_(LISP_RLOC_LOCAL, -105, "RLOC address is local") +_(LISP_RLOC_LOCAL, -105, "RLOC address is local") \ +_(BFD_EAGAIN, -106, "BFD object cannot be manipulated at this time") typedef enum { diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api index 17ca35b6..f307ed2a 100644 --- a/src/vnet/bfd/bfd.api +++ b/src/vnet/bfd/bfd.api @@ -107,6 +107,40 @@ define bfd_udp_add_reply i32 retval; }; +/** \brief Modify UDP BFD session on interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - sw index of the interface + @param desired_min_tx - desired min transmit interval (microseconds) + @param required_min_rx - required min receive interval (microseconds) + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param detect_mult - detect multiplier (# of packets missed before connection goes down) +*/ +define bfd_udp_mod +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u32 desired_min_tx; + u32 required_min_rx; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; + u8 detect_mult; +}; + +/** \brief Modify UDP BFD session response + @param context - sender context, to match reply w/ request + @param retval - return code for the request +*/ +define bfd_udp_mod_reply +{ + u32 context; + i32 retval; +}; + /** \brief Delete UDP BFD session on interface @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @@ -155,6 +189,9 @@ define bfd_udp_session_dump @param is_authenticated - non-zero if authentication in-use, zero otherwise @param bfd_key_id - ID of key currently in-use if auth is on @param conf_key_id - configured key ID for this session + @param required_min_rx - required min receive interval (microseconds) + @param desired_min_tx - desired min transmit interval (microseconds) + @param detect_mult - detect multiplier (# of packets missed before connection goes down) */ define bfd_udp_session_details { @@ -167,6 +204,9 @@ define bfd_udp_session_details u8 is_authenticated; u8 bfd_key_id; u32 conf_key_id; + u32 required_min_rx; + u32 desired_min_tx; + u8 detect_mult; }; /** \brief Set flags of BFD UDP session diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c index cfc3a38d..af70f0ec 100644 --- a/src/vnet/bfd/bfd_api.c +++ b/src/vnet/bfd/bfd_api.c @@ -45,6 +45,7 @@ #define foreach_vpe_api_msg \ _ (BFD_UDP_ADD, bfd_udp_add) \ + _ (BFD_UDP_MOD, bfd_udp_mod) \ _ (BFD_UDP_DEL, bfd_udp_del) \ _ (BFD_UDP_SESSION_DUMP, bfd_udp_session_dump) \ _ (BFD_UDP_SESSION_SET_FLAGS, bfd_udp_session_set_flags) \ @@ -97,6 +98,25 @@ vl_api_bfd_udp_add_t_handler (vl_api_bfd_udp_add_t * mp) REPLY_MACRO (VL_API_BFD_UDP_ADD_REPLY); } +static void +vl_api_bfd_udp_mod_t_handler (vl_api_bfd_udp_mod_t * mp) +{ + vl_api_bfd_udp_mod_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = bfd_udp_mod_session (BFD_UDP_API_PARAM_FROM_MP (mp), + clib_net_to_host_u32 (mp->desired_min_tx), + clib_net_to_host_u32 (mp->required_min_rx), + mp->detect_mult); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_MOD_REPLY); +} + static void vl_api_bfd_udp_del_t_handler (vl_api_bfd_udp_del_t * mp) { @@ -146,6 +166,10 @@ send_bfd_udp_session_details (unix_shared_memory_queue_t * q, u32 context, sizeof (key->peer_addr.ip4.data)); } + mp->required_min_rx = + clib_host_to_net_u32 (bs->config_required_min_rx_usec); + mp->desired_min_tx = clib_host_to_net_u32 (bs->config_desired_min_tx_usec); + mp->detect_mult = bs->local_detect_mult; vl_msg_api_send_shmem (q, (u8 *) & mp); } diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h index 128a3dc4..f4486a79 100644 --- a/src/vnet/bfd/bfd_api.h +++ b/src/vnet/bfd/bfd_api.h @@ -26,9 +26,17 @@ vnet_api_error_t bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, - const ip46_address_t * peer_addr, u32 desired_min_tx_us, - u32 required_min_rx_us, u8 detect_mult, - u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id); + const ip46_address_t * peer_addr, + u32 desired_min_tx_usec, u32 required_min_rx_usec, + u8 detect_mult, u8 is_authenticated, u32 conf_key_id, + u8 bfd_key_id); + +vnet_api_error_t bfd_udp_mod_session (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 desired_min_tx_usec, + u32 required_min_rx_usec, + u8 detect_mult); vnet_api_error_t bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, diff --git a/src/vnet/bfd/bfd_debug.h b/src/vnet/bfd/bfd_debug.h index 707ebab2..a06e934f 100644 --- a/src/vnet/bfd/bfd_debug.h +++ b/src/vnet/bfd/bfd_debug.h @@ -63,6 +63,13 @@ } \ while (0); +#define BFD_CLK_FMT "%luus/%lu clocks/%.2fs" +#define BFD_CLK_PRN(clocks) \ + (u64) ((((f64)clocks) / vlib_get_main ()->clib_time.clocks_per_second) * \ + USEC_PER_SECOND), \ + (clocks), \ + (((f64)clocks) / vlib_get_main ()->clib_time.clocks_per_second) + #else #define BFD_DBG(...) #define BFD_ERR(...) diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 8f2fae2b..798d0631 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -30,11 +30,16 @@ #endif static u64 -bfd_us_to_clocks (bfd_main_t * bm, u64 us) +bfd_usec_to_clocks (const bfd_main_t * bm, u64 us) { return bm->cpu_cps * ((f64) us / USEC_PER_SECOND); } +// static u64 bfd_clocks_to_usec (const bfd_main_t *bm, u64 clocks) +//{ +// return (clocks / bm->cpu_cps) * USEC_PER_SECOND; +//} + static vlib_node_registration_t bfd_process_node; /* set to 0 here, real values filled at startup */ @@ -83,9 +88,11 @@ bfd_set_defaults (bfd_main_t * bm, bfd_session_t * bs) bs->remote_state = BFD_STATE_down; bs->local_demand = 0; bs->remote_discr = 0; - bs->desired_min_tx_us = BFD_DEFAULT_DESIRED_MIN_TX_US; - bs->desired_min_tx_clocks = bfd_us_to_clocks (bm, bs->desired_min_tx_us); - bs->remote_min_rx_us = 1; + bs->config_desired_min_tx_usec = BFD_DEFAULT_DESIRED_MIN_TX_US; + bs->config_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; + bs->effective_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; + bs->remote_min_rx_usec = 1; + bs->remote_min_rx_clocks = bfd_usec_to_clocks (bm, bs->remote_min_rx_usec); bs->remote_demand = 0; bs->auth.remote_seq_number = 0; bs->auth.remote_seq_number_known = 0; @@ -123,7 +130,8 @@ bfd_recalc_tx_interval (bfd_main_t * bm, bfd_session_t * bs) if (!bs->local_demand) { bs->transmit_interval_clocks = - clib_max (bs->desired_min_tx_clocks, bs->remote_min_rx_clocks); + clib_max (bs->effective_desired_min_tx_clocks, + bs->remote_min_rx_clocks); } else { @@ -193,18 +201,18 @@ bfd_recalc_detection_time (bfd_main_t * bm, bfd_session_t * bs) { if (!bs->local_demand) { + /* asynchronous mode */ bs->detection_time_clocks = bs->remote_detect_mult * - bfd_us_to_clocks (bm, clib_max (bs->required_min_rx_us, - bs->remote_desired_min_tx_us)); + clib_max (bs->effective_required_min_rx_clocks, + bs->remote_desired_min_tx_clocks); } else { + /* demand mode */ bs->detection_time_clocks = - bs->local_detect_mult * - bfd_us_to_clocks (bm, - clib_max (bs->desired_min_tx_us, - bs->remote_min_rx_us)); + bs->local_detect_mult * clib_max (bs->config_desired_min_tx_clocks, + bs->remote_min_rx_clocks); } BFD_DBG ("Recalculated detection time %lu clocks/%.2fs", bs->detection_time_clocks, @@ -259,30 +267,44 @@ bfd_set_timer (bfd_main_t * bm, bfd_session_t * bs, u64 now, } static void -bfd_set_desired_min_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now, - u32 desired_min_tx_us, int handling_wakeup) +bfd_set_effective_desired_min_tx (bfd_main_t * bm, + bfd_session_t * bs, u64 now, + u64 desired_min_tx_clocks, + int handling_wakeup) { - bs->desired_min_tx_us = desired_min_tx_us; - bs->desired_min_tx_clocks = bfd_us_to_clocks (bm, bs->desired_min_tx_us); - BFD_DBG ("Set desired min tx to %uus/%lu clocks/%.2fs", - bs->desired_min_tx_us, bs->desired_min_tx_clocks, - bs->desired_min_tx_clocks / bm->cpu_cps); + bs->effective_desired_min_tx_clocks = desired_min_tx_clocks; + BFD_DBG ("Set effective desired min tx to " BFD_CLK_FMT, + BFD_CLK_PRN (bs->effective_desired_min_tx_clocks)); bfd_recalc_detection_time (bm, bs); bfd_recalc_tx_interval (bm, bs); bfd_calc_next_tx (bm, bs, now); bfd_set_timer (bm, bs, now, handling_wakeup); } +static void +bfd_set_effective_required_min_rx (bfd_main_t * bm, + bfd_session_t * bs, u64 now, + u64 required_min_rx_clocks, + int handling_wakeup) +{ + bs->effective_required_min_rx_clocks = required_min_rx_clocks; + BFD_DBG ("Set effective required min rx to " BFD_CLK_FMT, + BFD_CLK_PRN (bs->effective_required_min_rx_clocks)); + bfd_recalc_detection_time (bm, bs); + bfd_set_timer (bm, bs, now, handling_wakeup); +} + static void bfd_set_remote_required_min_rx (bfd_main_t * bm, bfd_session_t * bs, u64 now, - u32 remote_required_min_rx_us, + u32 remote_required_min_rx_usec, int handling_wakeup) { - bs->remote_min_rx_us = remote_required_min_rx_us; - bs->remote_min_rx_clocks = bfd_us_to_clocks (bm, bs->remote_min_rx_us); - BFD_DBG ("Set remote min rx to %uus/%lu clocks/%.2fs", bs->remote_min_rx_us, - bs->remote_min_rx_clocks, bs->remote_min_rx_clocks / bm->cpu_cps); + bs->remote_min_rx_usec = remote_required_min_rx_usec; + bs->remote_min_rx_clocks = + bfd_usec_to_clocks (bm, remote_required_min_rx_usec); + BFD_DBG ("Set remote min rx to " BFD_CLK_FMT, + BFD_CLK_PRN (bs->remote_min_rx_clocks)); bfd_recalc_detection_time (bm, bs); bfd_recalc_tx_interval (bm, bs); bfd_calc_next_tx (bm, bs, now); @@ -316,32 +338,6 @@ bfd_del_session (uword bs_idx) return 0; } -const char * -bfd_diag_code_string (bfd_diag_code_e diag) -{ -#define F(n, t, s) \ - case BFD_DIAG_CODE_NAME (t): \ - return s; - switch (diag) - { - foreach_bfd_diag_code (F)} - return "UNKNOWN"; -#undef F -} - -const char * -bfd_state_string (bfd_state_e state) -{ -#define F(n, t, s) \ - case BFD_STATE_NAME (t): \ - return s; - switch (state) - { - foreach_bfd_state (F)} - return "UNKNOWN"; -#undef F -} - void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down) { @@ -404,30 +400,62 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, switch (bs->local_state) { case BFD_STATE_admin_down: - bfd_set_desired_min_tx (bm, bs, now, - clib_max (bs->config_desired_min_tx_us, - BFD_DEFAULT_DESIRED_MIN_TX_US), - handling_wakeup); + bfd_set_effective_required_min_rx (bm, bs, now, + bs->config_required_min_rx_clocks, + handling_wakeup); + bfd_set_effective_desired_min_tx (bm, bs, now, + clib_max + (bs->config_desired_min_tx_clocks, + bm->default_desired_min_tx_clocks), + handling_wakeup); break; case BFD_STATE_down: - bfd_set_desired_min_tx (bm, bs, now, - clib_max (bs->config_desired_min_tx_us, - BFD_DEFAULT_DESIRED_MIN_TX_US), - handling_wakeup); + bfd_set_effective_required_min_rx (bm, bs, now, + bs->config_required_min_rx_clocks, + handling_wakeup); + bfd_set_effective_desired_min_tx (bm, bs, now, + clib_max + (bs->config_desired_min_tx_clocks, + bm->default_desired_min_tx_clocks), + handling_wakeup); break; case BFD_STATE_init: - bfd_set_desired_min_tx (bm, bs, now, - clib_max (bs->config_desired_min_tx_us, - BFD_DEFAULT_DESIRED_MIN_TX_US), - handling_wakeup); + bfd_set_effective_desired_min_tx (bm, bs, now, + clib_max + (bs->config_desired_min_tx_clocks, + bm->default_desired_min_tx_clocks), + handling_wakeup); break; case BFD_STATE_up: - bfd_set_desired_min_tx (bm, bs, now, bs->config_desired_min_tx_us, - handling_wakeup); + if (POLL_NOT_NEEDED == bs->poll_state) + { + bfd_set_effective_required_min_rx (bm, bs, now, + bs->config_required_min_rx_clocks, + handling_wakeup); + } + bfd_set_effective_desired_min_tx (bm, bs, now, + bs->config_desired_min_tx_clocks, + handling_wakeup); break; } } +static void +bfd_on_config_change (vlib_main_t * vm, vlib_node_runtime_t * rt, + bfd_main_t * bm, bfd_session_t * bs, u64 now) +{ + if (bs->remote_demand) + { + /* TODO - initiate poll sequence here */ + } + else + { + /* asynchronous - poll is part of periodic - nothing to do here */ + } + bfd_recalc_detection_time (bm, bs); + bfd_set_timer (bm, bs, now, 0); +} + static void bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs) @@ -557,11 +585,10 @@ bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) pkt->head.length = clib_host_to_net_u32 (bfd_length); pkt->my_disc = bs->local_discr; pkt->your_disc = bs->remote_discr; - pkt->des_min_tx = clib_host_to_net_u32 (bs->desired_min_tx_us); - pkt->req_min_rx = clib_host_to_net_u32 (bs->required_min_rx_us); - pkt->req_min_echo_rx = clib_host_to_net_u32 (bs->required_min_echo_rx_us); + pkt->des_min_tx = clib_host_to_net_u32 (bs->config_desired_min_tx_usec); + pkt->req_min_rx = clib_host_to_net_u32 (bs->config_required_min_rx_usec); + pkt->req_min_echo_rx = clib_host_to_net_u32 (1); b->current_length = bfd_length; - bfd_add_auth_section (b, bs); } static void @@ -569,7 +596,7 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, bfd_session_t * bs, u64 now, int handling_wakeup) { - if (!bs->remote_min_rx_us) + if (!bs->remote_min_rx_usec) { BFD_DBG ("bfd.RemoteMinRxInterval is zero, not sending periodic control " @@ -593,6 +620,14 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, return; } bfd_init_control_frame (b, bs); + if (POLL_NOT_NEEDED != bs->poll_state) + { + /* here we are either beginning a new poll sequence or retrying .. */ + bfd_pkt_set_poll (vlib_buffer_get_current (b)); + bs->poll_state = POLL_IN_PROGRESS; + BFD_DBG ("Setting poll bit in packet, bs_idx=%u", bs->bs_idx); + } + bfd_add_auth_section (b, bs); bfd_add_transport_layer (vm, b, bs); bs->last_tx_clocks = now; bfd_calc_next_tx (bm, bs, now); @@ -613,8 +648,14 @@ bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, BFD_DBG ("Send final control frame for bs_idx=%lu", bs->bs_idx); bfd_init_control_frame (b, bs); bfd_pkt_set_final (vlib_buffer_get_current (b)); + bfd_add_auth_section (b, bs); bfd_add_transport_layer (vm, b, bs); bs->last_tx_clocks = clib_cpu_time_now (); + /* + * RFC allows to include changes in final frame, so if there were any + * pending, we already did that, thus we can clear any pending poll needs + */ + bs->poll_state = POLL_NOT_NEEDED; } static void @@ -629,6 +670,14 @@ bfd_check_rx_timeout (bfd_main_t * bm, bfd_session_t * bs, u64 now, BFD_DBG ("Rx timeout, session goes down"); bfd_set_diag (bs, BFD_DIAG_CODE_det_time_exp); bfd_set_state (bm, bs, BFD_STATE_down, handling_wakeup); + /* + * If the remote system does not receive any + * BFD Control packets for a Detection Time, it SHOULD reset + * bfd.RemoteMinRxInterval to its initial value of 1 (per section 6.8.1, + * since it is no longer required to maintain previous session state) + * and then can transmit at its own rate. + */ + bfd_set_remote_required_min_rx (bm, bs, now, 1, handling_wakeup); } } @@ -722,6 +771,19 @@ bfd_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) (u32) * event_data); } break; + case BFD_EVENT_CONFIG_CHANGED: + if (!pool_is_free_index (bm->sessions, *event_data)) + { + bfd_session_t *bs = + pool_elt_at_index (bm->sessions, *event_data); + bfd_on_config_change (vm, rt, bm, bs, now); + } + else + { + BFD_DBG ("Ignoring event for non-existent session index %u", + (u32) * event_data); + } + break; default: clib_warning ("BUG: event type 0x%wx", event_type); break; @@ -810,6 +872,8 @@ bfd_main_init (vlib_main_t * vm) memset (&bm->wheel, 0, sizeof (bm->wheel)); bm->cpu_cps = vm->clib_time.clocks_per_second; BFD_DBG ("cps is %.2f", bm->cpu_cps); + bm->default_desired_min_tx_clocks = + bfd_usec_to_clocks (bm, BFD_DEFAULT_DESIRED_MIN_TX_US); const u64 now = clib_cpu_time_now (); timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; @@ -1283,7 +1347,8 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) while (0); } } - bs->remote_desired_min_tx_us = clib_net_to_host_u32 (pkt->des_min_tx); + bs->remote_desired_min_tx_clocks = + bfd_usec_to_clocks (bm, clib_net_to_host_u32 (pkt->des_min_tx)); bs->remote_detect_mult = pkt->head.detect_mult; bfd_set_remote_required_min_rx (bm, bs, now, clib_net_to_host_u32 (pkt->req_min_rx), 0); @@ -1297,6 +1362,18 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) */ /* FIXME 6.8.2 */ /* FIXME 6.8.4 */ + if (bs->poll_state == POLL_IN_PROGRESS && bfd_pkt_get_final (pkt)) + { + bs->poll_state = POLL_NOT_NEEDED; + BFD_DBG ("Poll sequence terminated, bs_idx=%u", bs->bs_idx); + if (BFD_STATE_up == bs->local_state) + { + bfd_set_effective_required_min_rx (bm, bs, now, + bs->config_required_min_rx_clocks, + 0); + bfd_recalc_detection_time (bm, bs); + } + } if (BFD_STATE_admin_down == bs->local_state) return; if (BFD_STATE_admin_down == bs->remote_state) @@ -1333,6 +1410,20 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) } } +static const char * +bfd_poll_state_string (bfd_poll_state_e state) +{ + switch (state) + { +#define F(x) \ + case POLL_##x: \ + return "POLL_" #x; + foreach_bfd_poll_state (F) +#undef F + } + return "UNKNOWN"; +} + u8 * format_bfd_session (u8 * s, va_list * args) { @@ -1353,17 +1444,18 @@ format_bfd_session (u8 * s, va_list * args) "remote-seq-num=%u, " "is-delayed=%s, " "curr-key=%U, " - "next-key=%U}", + "next-key=%U}," + "poll-state: %s", bs->bs_idx, bfd_state_string (bs->local_state), bfd_state_string (bs->remote_state), bs->local_discr, bs->remote_discr, bfd_diag_code_string (bs->local_diag), - bs->desired_min_tx_us, bs->required_min_rx_us, - bs->required_min_echo_rx_us, bs->remote_min_rx_us, - (bs->local_demand ? "yes" : "no"), + bs->config_desired_min_tx_usec, bs->config_required_min_rx_usec, + 1, bs->remote_min_rx_usec, (bs->local_demand ? "yes" : "no"), (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult, bs->auth.local_seq_number, bs->auth.remote_seq_number, (bs->auth.is_delayed ? "yes" : "no"), format_bfd_auth_key, - bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key); + bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key, + bfd_poll_state_string (bs->poll_state)); return s; } @@ -1462,6 +1554,62 @@ bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed) #endif } +vnet_api_error_t +bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, + u32 desired_min_tx_usec, + u32 required_min_rx_usec, u8 detect_mult) +{ + if (bs->local_detect_mult != detect_mult || + bs->config_desired_min_tx_usec != desired_min_tx_usec || + bs->config_required_min_rx_usec != required_min_rx_usec) + { + BFD_DBG ("Changing session params: %U", format_bfd_session, bs); + switch (bs->poll_state) + { + case POLL_NOT_NEEDED: + if (BFD_STATE_up == bs->local_state || + BFD_STATE_init == bs->local_state) + { + /* poll sequence is not needed for detect multiplier change */ + if (bs->config_desired_min_tx_usec != desired_min_tx_usec || + bs->config_required_min_rx_usec != required_min_rx_usec) + { + bs->poll_state = POLL_NEEDED; + BFD_DBG ("Set poll state=%s, bs_idx=%u", + bfd_poll_state_string (bs->poll_state), + bs->bs_idx); + } + } + break; + case POLL_NEEDED: + /* nothing to do */ + break; + case POLL_IN_PROGRESS: + /* can't change params now ... */ + BFD_ERR ("Poll in progress, cannot change params for session with " + "bs_idx=%u", bs->bs_idx); + return VNET_API_ERROR_BFD_EAGAIN; + } + + bs->local_detect_mult = detect_mult; + bs->config_desired_min_tx_usec = desired_min_tx_usec; + bs->config_desired_min_tx_clocks = + bfd_usec_to_clocks (bm, desired_min_tx_usec); + bs->config_required_min_rx_usec = required_min_rx_usec; + bs->config_required_min_rx_clocks = + bfd_usec_to_clocks (bm, required_min_rx_usec); + BFD_DBG ("Changed session params: %U", format_bfd_session, bs); + + vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index, + BFD_EVENT_CONFIG_CHANGED, bs->bs_idx); + } + else + { + BFD_DBG ("Ignore parameter change - no change, bs_idx=%u", bs->bs_idx); + } + return 0; +} + bfd_main_t bfd_main; /* diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index b66b79e7..361ff0b7 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -64,6 +64,18 @@ typedef struct bfd_auth_type_e auth_type; } bfd_auth_key_t; +#define foreach_bfd_poll_state(F)\ + F(NOT_NEEDED)\ +F(NEEDED)\ +F(IN_PROGRESS) + +typedef enum +{ +#define F(x) POLL_##x, + foreach_bfd_poll_state (F) +#undef F +} bfd_poll_state_e; + typedef struct { /* index in bfd_main.sessions pool */ @@ -85,28 +97,34 @@ typedef struct u32 remote_discr; /* configured desired min tx interval (microseconds) */ - u32 config_desired_min_tx_us; + u32 config_desired_min_tx_usec; + + /* configured desired min tx interval (clocks) */ + u64 config_desired_min_tx_clocks; - /* desired min tx interval (microseconds) */ - u32 desired_min_tx_us; + /* effective desired min tx interval (clocks) */ + u64 effective_desired_min_tx_clocks; - /* desired min tx interval (clocks) */ - u64 desired_min_tx_clocks; + /* configured required min rx interval (microseconds) */ + u32 config_required_min_rx_usec; - /* required min rx interval (microseconds) */ - u32 required_min_rx_us; + /* configured required min rx interval (clocks) */ + u64 config_required_min_rx_clocks; - /* required min echo rx interval (microseconds) */ - u32 required_min_echo_rx_us; + /* effective required min rx interval (clocks) */ + u64 effective_required_min_rx_clocks; /* remote min rx interval (microseconds) */ - u32 remote_min_rx_us; + u64 remote_min_rx_usec; /* remote min rx interval (clocks) */ u64 remote_min_rx_clocks; - /* remote desired min tx interval (microseconds) */ - u32 remote_desired_min_tx_us; + /* remote desired min tx interval (clocks) */ + u64 remote_desired_min_tx_clocks; + + /* configured detect multiplier */ + u8 local_detect_mult; /* 1 if in demand mode, 0 otherwise */ u8 local_demand; @@ -114,9 +132,6 @@ typedef struct /* 1 if remote system sets demand mode, 0 otherwise */ u8 remote_demand; - /* local detect multiplier */ - u8 local_detect_mult; - /* remote detect multiplier */ u8 remote_detect_mult; @@ -138,6 +153,9 @@ typedef struct /* detection time */ u64 detection_time_clocks; + /* state info regarding poll sequence */ + bfd_poll_state_e poll_state; + /* authentication information */ struct { @@ -175,6 +193,7 @@ typedef struct /* transport type for this session */ bfd_transport_t transport; + /* union of transport-specific data */ union { bfd_udp_session_t udp; @@ -205,6 +224,9 @@ typedef struct /* cpu clocks per second */ f64 cpu_cps; + /* default desired min tx in clocks */ + u64 default_desired_min_tx_clocks; + /* for generating random numbers */ u32 random_seed; @@ -243,6 +265,7 @@ enum { BFD_EVENT_RESCHEDULE = 1, BFD_EVENT_NEW_SESSION, + BFD_EVENT_CONFIG_CHANGED, } bfd_process_event_e; u8 *bfd_input_format_trace (u8 * s, va_list * args); @@ -265,6 +288,10 @@ unsigned bfd_auth_type_supported (bfd_auth_type_e auth_type); vnet_api_error_t bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, u8 bfd_key_id, u8 is_delayed); vnet_api_error_t bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed); +vnet_api_error_t +bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, + u32 desired_min_tx_usec, + u32 required_min_rx_usec, u8 detect_mult); #define USEC_PER_MS 1000LL #define USEC_PER_SECOND (1000 * USEC_PER_MS) diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c index 180fc6df..92b226bd 100644 --- a/src/vnet/bfd/bfd_protocol.c +++ b/src/vnet/bfd/bfd_protocol.c @@ -150,6 +150,32 @@ bfd_auth_type_str (bfd_auth_type_e auth_type) return "UNKNOWN"; } +const char * +bfd_diag_code_string (bfd_diag_code_e diag) +{ +#define F(n, t, s) \ + case BFD_DIAG_CODE_NAME (t): \ + return s; + switch (diag) + { + foreach_bfd_diag_code (F)} + return "UNKNOWN"; +#undef F +} + +const char * +bfd_state_string (bfd_state_e state) +{ +#define F(n, t, s) \ + case BFD_STATE_NAME (t): \ + return s; + switch (state) + { + foreach_bfd_state (F)} + return "UNKNOWN"; +#undef F +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index 443f4253..e21b887c 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -141,8 +141,8 @@ bfd_udp_key_init (bfd_udp_key_t * key, u32 sw_if_index, static vnet_api_error_t bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, - u32 desired_min_tx_us, u32 required_min_rx_us, - u8 detect_mult, + u32 desired_min_tx_usec, + u32 required_min_rx_usec, u8 detect_mult, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, bfd_session_t ** bs_out) @@ -189,12 +189,9 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, "returns %d", format_ip46_address, &key->peer_addr, IP46_TYPE_ANY, key->sw_if_index, bus->adj_index); } - bs->config_desired_min_tx_us = desired_min_tx_us; - bs->required_min_rx_us = required_min_rx_us; - bs->required_min_echo_rx_us = required_min_rx_us; /* FIXME */ - bs->local_detect_mult = detect_mult; *bs_out = bs; - return 0; + return bfd_session_set_params (bum->bfd_main, bs, desired_min_tx_usec, + required_min_rx_usec, detect_mult); } static vnet_api_error_t @@ -298,8 +295,8 @@ bfd_udp_find_session_by_api_input (u32 sw_if_index, } static vnet_api_error_t -bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_us, - u32 required_min_rx_us, u8 detect_mult, +bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_usec, + u32 required_min_rx_usec, u8 detect_mult, const ip46_address_t * local_addr, const ip46_address_t * peer_addr) { @@ -314,9 +311,9 @@ bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_us, clib_warning ("detect_mult < 1"); return VNET_API_ERROR_INVALID_ARGUMENT; } - if (desired_min_tx_us < 1) + if (desired_min_tx_usec < 1) { - clib_warning ("desired_min_tx_us < 1"); + clib_warning ("desired_min_tx_usec < 1"); return VNET_API_ERROR_INVALID_ARGUMENT; } return 0; @@ -334,22 +331,23 @@ bfd_udp_del_session_internal (bfd_session_t * bs) vnet_api_error_t bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, - const ip46_address_t * peer_addr, u32 desired_min_tx_us, - u32 required_min_rx_us, u8 detect_mult, - u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id) + const ip46_address_t * peer_addr, + u32 desired_min_tx_usec, u32 required_min_rx_usec, + u8 detect_mult, u8 is_authenticated, u32 conf_key_id, + u8 bfd_key_id) { - vnet_api_error_t rv = bfd_api_verify_common (sw_if_index, desired_min_tx_us, - required_min_rx_us, - detect_mult, - local_addr, peer_addr); + vnet_api_error_t rv = + bfd_api_verify_common (sw_if_index, desired_min_tx_usec, + required_min_rx_usec, detect_mult, + local_addr, peer_addr); bfd_session_t *bs = NULL; if (!rv) { rv = bfd_udp_add_session_internal (&bfd_udp_main, sw_if_index, - desired_min_tx_us, required_min_rx_us, - detect_mult, local_addr, peer_addr, - &bs); + desired_min_tx_usec, + required_min_rx_usec, detect_mult, + local_addr, peer_addr, &bs); } if (!rv && is_authenticated) { @@ -373,6 +371,27 @@ bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, return rv; } +vnet_api_error_t +bfd_udp_mod_session (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 desired_min_tx_usec, + u32 required_min_rx_usec, u8 detect_mult) +{ + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + + return bfd_session_set_params (bfd_udp_main.bfd_main, bs, + desired_min_tx_usec, required_min_rx_usec, + detect_mult); +} + vnet_api_error_t bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, diff --git a/test/bfd.py b/test/bfd.py index 475a1707..dc6f9674 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -334,6 +334,25 @@ class VppBFDUDPSession(VppObject): is_ipv6=is_ipv6, is_delayed=is_delayed) + def modify_parameters(self, + detect_mult=None, + desired_min_tx=None, + required_min_rx=None): + if detect_mult: + self._detect_mult = detect_mult + if desired_min_tx: + self._desired_min_tx = desired_min_tx + if required_min_rx: + self._required_min_rx = required_min_rx + is_ipv6 = 1 if AF_INET6 == self.af else 0 + self.test.vapi.bfd_udp_mod(self._interface.sw_if_index, + self.desired_min_tx, + self.required_min_rx, + self.detect_mult, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) + def add_vpp_config(self): is_ipv6 = 1 if AF_INET6 == self.af else 0 bfd_key_id = self._bfd_key_id if self._sha1_key else None diff --git a/test/framework.py b/test/framework.py index 889a3046..4185dbfb 100644 --- a/test/framework.py +++ b/test/framework.py @@ -545,6 +545,10 @@ class VppTestCase(unittest.TestCase): name, real_value, expected_min, expected_max) self.assertTrue(expected_min <= real_value <= expected_max, msg) + def sleep(self, timeout): + self.logger.debug("Sleeping for %ss" % timeout) + time.sleep(timeout) + class VppTestResult(unittest.TestResult): """ diff --git a/test/test_bfd.py b/test/test_bfd.py index 5f861477..aedd56e4 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +from __future__ import division import unittest import hashlib import binascii @@ -81,6 +82,33 @@ class BFDAPITestCase(VppTestCase): self.logger.debug("Session state is %s" % str(session.state)) session.remove_vpp_config() + def test_mod_bfd(self): + """ modify BFD session parameters """ + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + desired_min_tx=50000, + required_min_rx=10000, + detect_mult=1) + session.add_vpp_config() + e = session.get_bfd_udp_session_dump_entry() + self.assert_equal(session.desired_min_tx, + e.desired_min_tx, + "desired min transmit interval") + self.assert_equal(session.required_min_rx, + e.required_min_rx, + "required min receive interval") + self.assert_equal(session.detect_mult, e.detect_mult, "detect mult") + session.modify_parameters(desired_min_tx=session.desired_min_tx * 2, + required_min_rx=session.required_min_rx * 2, + detect_mult=session.detect_mult * 2) + e = session.get_bfd_udp_session_dump_entry() + self.assert_equal(session.desired_min_tx, + e.desired_min_tx, + "desired min transmit interval") + self.assert_equal(session.required_min_rx, + e.required_min_rx, + "required min receive interval") + self.assert_equal(session.detect_mult, e.detect_mult, "detect mult") + def test_add_sha1_keys(self): """ add SHA1 keys """ key_count = 10 @@ -194,6 +222,7 @@ class BFDAPITestCase(VppTestCase): session.deactivate_auth() def test_change_key(self): + """ change SHA1 key """ key1 = self.factory.create_random_key(self) key2 = self.factory.create_random_key(self) while key2.conf_key_id == key1.conf_key_id: @@ -273,10 +302,11 @@ class BFDTestSession(object): packet[BFD].auth_key_hash = hashlib.sha1(hash_material).digest() return packet - def send_packet(self): - p = self.create_packet() - self.test.logger.debug(ppp("Sending packet:", p)) - self.test.pg0.add_stream([p]) + def send_packet(self, packet=None): + if packet is None: + packet = self.create_packet() + self.test.logger.debug(ppp("Sending packet:", packet)) + self.test.pg0.add_stream([packet]) self.test.pg_start() def verify_sha1_auth(self, packet): @@ -521,11 +551,25 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): e = self.vapi.wait_for_event(1, "bfd_udp_session_details") self.verify_event(e, expected_state=BFDState.up) - try: - p = self.pg0.wait_for_packet(timeout=1) - except: - return - raise Exception(ppp("Received unexpected BFD packet:", p)) + cap = 2 * self.vpp_session.desired_min_tx *\ + self.vpp_session.detect_mult + now = time.time() + count = 0 + # busy wait here, trying to collect a packet or event, vpp is not + # allowed to send packets and the session will timeout first - so the + # Up->Down event must arrive before any packets do + while time.time() < now + cap / us_in_sec: + try: + p, ttp = self.wait_for_bfd_packet(timeout=0) + self.logger.error(ppp("Received unexpected packet:", p)) + count += 1 + except: + pass + events = self.vapi.collect_events() + if len(events) > 0: + self.verify_event(events[0], BFDState.down) + break + self.assert_equal(count, 0, "number of packets received") def test_conn_down(self): """ verify session goes down after inactivity """ @@ -542,20 +586,27 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): def test_large_required_min_rx(self): """ large remote RequiredMinRxInterval """ self.bfd_session_up() + self.wait_for_bfd_packet() interval = 3000000 self.test_session.update(required_min_rx_interval=interval) self.test_session.send_packet() now = time.time() count = 0 + # busy wait here, trying to collect a packet or event, vpp is not + # allowed to send packets and the session will timeout first - so the + # Up->Down event must arrive before any packets do while time.time() < now + interval / us_in_sec: try: - p = self.wait_for_bfd_packet() - if count > 1: - self.logger.error(ppp("Received unexpected packet:", p)) + p, ttp = self.wait_for_bfd_packet(timeout=0) + self.logger.error(ppp("Received unexpected packet:", p)) count += 1 except: pass - self.assert_in_range(count, 0, 1, "number of packets received") + events = self.vapi.collect_events() + if len(events) > 0: + self.verify_event(events[0], BFDState.down) + break + self.assert_equal(count, 0, "number of packets received") def test_immediate_remote_min_rx_reduce(self): """ immediately honor remote min rx reduction """ @@ -583,6 +634,93 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): 1.10 * interval / us_in_sec, "time between BFD packets") + def test_modify_req_min_rx_double(self): + """ modify session - double required min rx """ + self.bfd_session_up() + self.wait_for_bfd_packet() + self.test_session.update(desired_min_tx_interval=10000, + required_min_rx_interval=10000) + self.test_session.send_packet() + # first double required min rx + self.vpp_session.modify_parameters( + required_min_rx=2 * self.vpp_session.required_min_rx) + p, ttp = self.wait_for_bfd_packet() + # poll bit needs to be set + self.assertIn("P", p.sprintf("%BFD.flags%"), + "Poll bit not set in BFD packet") + # finish poll sequence with final packet + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + # now we can wait 0.9*3*req-min-rx and the session should still be up + self.sleep(0.9 * self.vpp_session.detect_mult * + self.vpp_session.required_min_rx / us_in_sec) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_modify_req_min_rx_halve(self): + """ modify session - halve required min rx """ + self.vpp_session.modify_parameters( + required_min_rx=2 * self.vpp_session.required_min_rx) + self.bfd_session_up() + self.wait_for_bfd_packet() + self.test_session.update(desired_min_tx_interval=10000, + required_min_rx_interval=10000) + self.test_session.send_packet() + p, ttp = self.wait_for_bfd_packet() + # halve required min rx + old_required_min_rx = self.vpp_session.required_min_rx + self.vpp_session.modify_parameters( + required_min_rx=0.5 * self.vpp_session.required_min_rx) + # now we wait 0.8*3*old-req-min-rx and the session should still be up + self.sleep(0.8 * self.vpp_session.detect_mult * + old_required_min_rx / us_in_sec) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + p, ttp = self.wait_for_bfd_packet() + # poll bit needs to be set + self.assertIn("P", p.sprintf("%BFD.flags%"), + "Poll bit not set in BFD packet") + # finish poll sequence with final packet + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + # now the session should time out under new conditions + before = time.time() + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + after = time.time() + detection_time = self.vpp_session.detect_mult *\ + self.vpp_session.required_min_rx / us_in_sec + self.assert_in_range(after - before, + 0.9 * detection_time, + 1.1 * detection_time, + "time before bfd session goes down") + self.verify_event(e, expected_state=BFDState.down) + + def test_modify_des_min_tx(self): + """ modify desired min tx interval """ + pass + + def test_modify_detect_mult(self): + """ modify detect multiplier """ + self.bfd_session_up() + self.vpp_session.modify_parameters(detect_mult=1) + p, ttp = self.wait_for_bfd_packet() + self.assert_equal(self.vpp_session.detect_mult, + p[BFD].detect_mult, + "detect mult") + # poll bit must not be set + self.assertNotIn("P", p.sprintf("%BFD.flags%"), + "Poll bit not set in BFD packet") + self.vpp_session.modify_parameters(detect_mult=10) + p, ttp = self.wait_for_bfd_packet() + self.assert_equal(self.vpp_session.detect_mult, + p[BFD].detect_mult, + "detect mult") + # poll bit must not be set + self.assertNotIn("P", p.sprintf("%BFD.flags%"), + "Poll bit not set in BFD packet") + class BFD6TestCase(VppTestCase, BFDCommonCode): """Bidirectional Forwarding Detection (BFD) (IPv6) """ diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 2cd02cc7..39efa9e4 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1023,6 +1023,19 @@ class VppPapiProvider(object): 'conf_key_id': conf_key_id, }) + def bfd_udp_mod(self, sw_if_index, desired_min_tx, required_min_rx, + detect_mult, local_addr, peer_addr, is_ipv6=0): + return self.api(self.papi.bfd_udp_mod, + { + 'sw_if_index': sw_if_index, + 'desired_min_tx': desired_min_tx, + 'required_min_rx': required_min_rx, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'detect_mult': detect_mult, + }) + def bfd_udp_auth_activate(self, sw_if_index, local_addr, peer_addr, is_ipv6=0, bfd_key_id=None, conf_key_id=None, is_delayed=False): -- cgit 1.2.3-korg From 2bce0332d368901ea66c7e582119719757e37e42 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 9 Feb 2017 06:03:46 +0100 Subject: BFD: set per session UDP source port per RFC Change-Id: Id294dbbd6499ae8221cc8143e1027adc08866ae6 Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd_main.c | 9 ++------- src/vnet/bfd/bfd_main.h | 2 +- src/vnet/bfd/bfd_udp.c | 40 ++++++++++++++++++++++++++++++++++++---- src/vnet/bfd/bfd_udp.h | 8 ++++---- 4 files changed, 43 insertions(+), 16 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 0c5f1986..7e06962a 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -35,11 +35,6 @@ bfd_usec_to_clocks (const bfd_main_t * bm, u64 us) return bm->cpu_cps * ((f64) us / USEC_PER_SECOND); } -// static u64 bfd_clocks_to_usec (const bfd_main_t *bm, u64 clocks) -//{ -// return (clocks / bm->cpu_cps) * USEC_PER_SECOND; -//} - static vlib_node_registration_t bfd_process_node; /* set to 0 here, real values filled at startup */ @@ -464,11 +459,11 @@ bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, { case BFD_TRANSPORT_UDP4: BFD_DBG ("Transport bfd via udp4, bs_idx=%u", bs->bs_idx); - bfd_add_udp4_transport (vm, b, &bs->udp); + bfd_add_udp4_transport (vm, b, bs); break; case BFD_TRANSPORT_UDP6: BFD_DBG ("Transport bfd via udp6, bs_idx=%u", bs->bs_idx); - bfd_add_udp6_transport (vm, b, &bs->udp); + bfd_add_udp6_transport (vm, b, bs); break; } } diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index 361ff0b7..14a54d6f 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -76,7 +76,7 @@ typedef enum #undef F } bfd_poll_state_e; -typedef struct +typedef struct bfd_session_s { /* index in bfd_main.sessions pool */ u32 bs_idx; diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index e1ff8a9d..75b35974 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2011-2016 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. + */ #include #include #include @@ -31,10 +45,25 @@ static vlib_node_registration_t bfd_udp6_input_node; bfd_udp_main_t bfd_udp_main; +static u16 +bfd_udp_bs_idx_to_sport (u32 bs_idx) +{ + /* The source port MUST be in the range 49152 through 65535. The same UDP + * source port number MUST be used for all BFD Control packets associated + * with a particular session. The source port number SHOULD be unique among + * all BFD sessions on the system. If more than 16384 BFD sessions are + * simultaneously active, UDP source port numbers MAY be reused on + * multiple sessions, but the number of distinct uses of the same UDP + * source port number SHOULD be minimized. + */ + return 49152 + bs_idx % (65535 - 49152 + 1); +} + void bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, - bfd_udp_session_t * bus) + const bfd_session_t * bs) { + const bfd_udp_session_t *bus = &bs->udp; const bfd_udp_key_t *key = &bus->key; b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; @@ -55,7 +84,8 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, headers->ip4.src_address.as_u32 = key->local_addr.ip4.as_u32; headers->ip4.dst_address.as_u32 = key->peer_addr.ip4.as_u32; - headers->udp.src_port = clib_host_to_net_u16 (50000); /* FIXME */ + headers->udp.src_port = + clib_host_to_net_u16 (bfd_udp_bs_idx_to_sport (bs->bs_idx)); headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); /* fix ip length, checksum and udp length */ @@ -70,8 +100,9 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, void bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, - bfd_udp_session_t * bus) + const bfd_session_t * bs) { + const bfd_udp_session_t *bus = &bs->udp; const bfd_udp_key_t *key = &bus->key; b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; @@ -95,7 +126,8 @@ bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, clib_memcpy (&headers->ip6.dst_address, &key->peer_addr.ip6, sizeof (headers->ip6.dst_address)); - headers->udp.src_port = clib_host_to_net_u16 (50000); /* FIXME */ + headers->udp.src_port = + clib_host_to_net_u16 (bfd_udp_bs_idx_to_sport (bs->bs_idx)); headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd6); /* fix ip payload length and udp length */ diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index 2cd89ca2..26e89851 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -23,8 +23,6 @@ #include #include -#define BFD_UDP_KEY_BODY - /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { @@ -42,10 +40,12 @@ typedef struct adj_index_t adj_index; } bfd_udp_session_t; +struct bfd_session_s; + void bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, - bfd_udp_session_t * bs); + const struct bfd_session_s *bs); void bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, - bfd_udp_session_t * bs); + const struct bfd_session_s *bs); #endif /* __included_bfd_udp_h__ */ -- cgit 1.2.3-korg From 239790fd91b3f62e5eda1042a97f9216fe59856e Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 16 Feb 2017 10:53:53 +0100 Subject: BFD: echo function Change-Id: Ib1e301d62b687d4e42434239e7cd412065c28da0 Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd.api | 38 ++- src/vnet/bfd/bfd_api.c | 31 ++- src/vnet/bfd/bfd_api.h | 26 +- src/vnet/bfd/bfd_debug.h | 2 +- src/vnet/bfd/bfd_main.c | 689 ++++++++++++++++++++++++++++++++-------------- src/vnet/bfd/bfd_main.h | 94 +++++-- src/vnet/bfd/bfd_udp.c | 286 +++++++++++++++---- src/vnet/bfd/bfd_udp.h | 16 +- test/bfd.py | 21 ++ test/framework.py | 2 +- test/test_bfd.py | 477 ++++++++++++++++++++++++++++---- test/vpp_papi_provider.py | 4 + 12 files changed, 1319 insertions(+), 367 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api index f307ed2a..93bf0fb9 100644 --- a/src/vnet/bfd/bfd.api +++ b/src/vnet/bfd/bfd.api @@ -13,29 +13,43 @@ * limitations under the License. */ -/** \brief Configure BFD feature +/** \brief Set BFD echo source @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request - @param slow_timer - slow timer (seconds) - @param min_tx - desired min tx interval - @param min_rx - desired min rx interval - @param detect_mult - desired detection multiplier + @param sw_if_index - interface to use as echo source */ -define bfd_set_config +define bfd_udp_set_echo_source +{ + u32 client_index; + u32 context; + u32 sw_if_index; +}; + +/** \brief Set BFD feature response + @param context - sender context, to match reply w/ request + @param retval - return code for the request +*/ +define bfd_udp_set_echo_source_reply +{ + u32 context; + i32 retval; +}; + +/** \brief Delete BFD echo source + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define bfd_udp_del_echo_source { u32 client_index; u32 context; - u32 slow_timer; - u32 min_tx; - u32 min_rx; - u8 detect_mult; }; -/** \brief Configure BFD feature response +/** \brief Delete BFD feature response @param context - sender context, to match reply w/ request @param retval - return code for the request */ -define bfd_set_config_reply +define bfd_udp_del_echo_source_reply { u32 context; i32 retval; diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c index af70f0ec..6632eae4 100644 --- a/src/vnet/bfd/bfd_api.c +++ b/src/vnet/bfd/bfd_api.c @@ -54,7 +54,9 @@ _ (BFD_AUTH_DEL_KEY, bfd_auth_del_key) \ _ (BFD_AUTH_KEYS_DUMP, bfd_auth_keys_dump) \ _ (BFD_UDP_AUTH_ACTIVATE, bfd_udp_auth_activate) \ - _ (BFD_UDP_AUTH_DEACTIVATE, bfd_udp_auth_deactivate) + _ (BFD_UDP_AUTH_DEACTIVATE, bfd_udp_auth_deactivate) \ + _ (BFD_UDP_SET_ECHO_SOURCE, bfd_udp_set_echo_source) \ + _ (BFD_UDP_DEL_ECHO_SOURCE, bfd_udp_del_echo_source) pub_sub_handler (bfd_events, BFD_EVENTS); @@ -314,6 +316,33 @@ vl_api_bfd_udp_auth_deactivate_t_handler (vl_api_bfd_udp_auth_deactivate_t * REPLY_MACRO (VL_API_BFD_UDP_AUTH_DEACTIVATE_REPLY); } +static void +vl_api_bfd_udp_set_echo_source_t_handler (vl_api_bfd_udp_set_echo_source_t * + mp) +{ + vl_api_bfd_udp_set_echo_source_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + rv = bfd_udp_set_echo_source (clib_net_to_host_u32 (mp->sw_if_index)); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_SET_ECHO_SOURCE_REPLY); +} + +static void +vl_api_bfd_udp_del_echo_source_t_handler (vl_api_bfd_udp_del_echo_source_t * + mp) +{ + vl_api_bfd_udp_del_echo_source_reply_t *rmp; + int rv; + + rv = bfd_udp_del_echo_source (); + + REPLY_MACRO (VL_API_BFD_UDP_DEL_ECHO_SOURCE_REPLY); +} + /* * bfd_api_hookup * Add vpe's API message handlers to the table. diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h index f4486a79..63d4a62e 100644 --- a/src/vnet/bfd/bfd_api.h +++ b/src/vnet/bfd/bfd_api.h @@ -24,6 +24,17 @@ #include #include +#define foreach_bfd_transport(F) \ + F (UDP4, "ip4-rewrite") \ + F (UDP6, "ip6-rewrite") + +typedef enum +{ +#define F(t, n) BFD_TRANSPORT_##t, + foreach_bfd_transport (F) +#undef F +} bfd_transport_e; + vnet_api_error_t bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, @@ -31,12 +42,11 @@ bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, u8 detect_mult, u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id); -vnet_api_error_t bfd_udp_mod_session (u32 sw_if_index, - const ip46_address_t * local_addr, - const ip46_address_t * peer_addr, - u32 desired_min_tx_usec, - u32 required_min_rx_usec, - u8 detect_mult); +vnet_api_error_t +bfd_udp_mod_session (u32 sw_if_index, const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 desired_min_tx_usec, u32 required_min_rx_usec, + u8 detect_mult); vnet_api_error_t bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, @@ -63,6 +73,10 @@ vnet_api_error_t bfd_udp_auth_deactivate (u32 sw_if_index, const ip46_address_t * peer_addr, u8 is_delayed); +vnet_api_error_t bfd_udp_set_echo_source (u32 loopback_sw_if_index); + +vnet_api_error_t bfd_udp_del_echo_source (); + #endif /* __included_bfd_api_h__ */ /* diff --git a/src/vnet/bfd/bfd_debug.h b/src/vnet/bfd/bfd_debug.h index a06e934f..3017352e 100644 --- a/src/vnet/bfd/bfd_debug.h +++ b/src/vnet/bfd/bfd_debug.h @@ -20,7 +20,7 @@ #define __included_bfd_debug_h__ /* controls debug prints */ -#define BFD_DEBUG (0) +#define BFD_DEBUG (1) #if BFD_DEBUG #define BFD_DEBUG_FILE_DEF \ diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index c0fd18df..29c40458 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -17,17 +17,37 @@ * @brief BFD nodes implementation */ +#if WITH_LIBSSL > 0 +#include +#endif + +#if __SSE4_2__ +#include +#endif + #include #include #include +#include #include #include #include #include #include -#if WITH_LIBSSL > 0 -#include + +static u64 +bfd_calc_echo_checksum (u32 discriminator, u64 expire_time, u32 secret) +{ + u64 checksum = 0; +#if __SSE4_2__ + checksum = _mm_crc32_u64 (0, discriminator); + checksum = _mm_crc32_u64 (checksum, expire_time); + checksum = _mm_crc32_u64 (checksum, secret); +#else + checksum = clib_xxhash (discriminator ^ expire_time ^ secret); #endif + return checksum; +} static u64 bfd_usec_to_clocks (const bfd_main_t * bm, u64 us) @@ -35,6 +55,12 @@ bfd_usec_to_clocks (const bfd_main_t * bm, u64 us) return bm->cpu_cps * ((f64) us / USEC_PER_SECOND); } +static u32 +bfd_clocks_to_usec (const bfd_main_t * bm, u64 clocks) +{ + return (clocks / bm->cpu_cps) * USEC_PER_SECOND; +} + static vlib_node_registration_t bfd_process_node; /* set to 0 here, real values filled at startup */ @@ -81,17 +107,19 @@ bfd_set_defaults (bfd_main_t * bm, bfd_session_t * bs) bs->local_state = BFD_STATE_down; bs->local_diag = BFD_DIAG_CODE_no_diag; bs->remote_state = BFD_STATE_down; - bs->local_demand = 0; bs->remote_discr = 0; - bs->config_desired_min_tx_usec = BFD_DEFAULT_DESIRED_MIN_TX_US; + bs->config_desired_min_tx_usec = BFD_DEFAULT_DESIRED_MIN_TX_USEC; bs->config_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; bs->effective_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; bs->remote_min_rx_usec = 1; bs->remote_min_rx_clocks = bfd_usec_to_clocks (bm, bs->remote_min_rx_usec); + bs->remote_min_echo_rx_usec = 0; + bs->remote_min_echo_rx_clocks = 0; bs->remote_demand = 0; bs->auth.remote_seq_number = 0; bs->auth.remote_seq_number_known = 0; bs->auth.local_seq_number = random_u32 (&bm->random_seed); + bs->echo_secret = random_u32 (&bm->random_seed); } static void @@ -119,68 +147,90 @@ bfd_set_state (bfd_main_t * bm, bfd_session_t * bs, } } -static void -bfd_recalc_tx_interval (bfd_main_t * bm, bfd_session_t * bs) +static const char * +bfd_poll_state_string (bfd_poll_state_e state) { - if (!bs->local_demand) + switch (state) { - bs->transmit_interval_clocks = - clib_max (bs->effective_desired_min_tx_clocks, - bs->remote_min_rx_clocks); +#define F(x) \ + case BFD_POLL_##x: \ + return "BFD_POLL_" #x; + foreach_bfd_poll_state (F) +#undef F } - else + return "UNKNOWN"; +} + +static void +bfd_set_poll_state (bfd_session_t * bs, bfd_poll_state_e state) +{ + if (bs->poll_state != state) { - /* TODO */ + BFD_DBG ("Setting poll state=%s, bs_idx=%u", + bfd_poll_state_string (state), bs->bs_idx); + bs->poll_state = state; } - BFD_DBG ("Recalculated transmit interval %lu clocks/%.2fs", - bs->transmit_interval_clocks, - bs->transmit_interval_clocks / bm->cpu_cps); +} + +static void +bfd_recalc_tx_interval (bfd_main_t * bm, bfd_session_t * bs) +{ + bs->transmit_interval_clocks = + clib_max (bs->effective_desired_min_tx_clocks, bs->remote_min_rx_clocks); + BFD_DBG ("Recalculated transmit interval " BFD_CLK_FMT, + BFD_CLK_PRN (bs->transmit_interval_clocks)); +} + +static void +bfd_recalc_echo_tx_interval (bfd_main_t * bm, bfd_session_t * bs) +{ + bs->echo_transmit_interval_clocks = + clib_max (bs->effective_desired_min_tx_clocks, + bs->remote_min_echo_rx_clocks); + BFD_DBG ("Recalculated echo transmit interval " BFD_CLK_FMT, + BFD_CLK_PRN (bs->echo_transmit_interval_clocks)); } static void bfd_calc_next_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now) { - if (!bs->local_demand) + if (bs->local_detect_mult > 1) { - if (bs->local_detect_mult > 1) - { - /* common case - 75-100% of transmit interval */ - bs->tx_timeout_clocks = bs->last_tx_clocks + - (1 - .25 * (random_f64 (&bm->random_seed))) * - bs->transmit_interval_clocks; - if (bs->tx_timeout_clocks < now) - { - /* huh, we've missed it already, transmit now */ - BFD_DBG ("Missed %lu transmit events (now is %lu, calc " - "tx_timeout is %lu)", - (now - bs->tx_timeout_clocks) / - bs->transmit_interval_clocks, - now, bs->tx_timeout_clocks); - bs->tx_timeout_clocks = now; - } - } - else + /* common case - 75-100% of transmit interval */ + bs->tx_timeout_clocks = bs->last_tx_clocks + + (1 - .25 * (random_f64 (&bm->random_seed))) * + bs->transmit_interval_clocks; + if (bs->tx_timeout_clocks < now) { - /* special case - 75-90% of transmit interval */ - bs->tx_timeout_clocks = - bs->last_tx_clocks + - (.9 - .15 * (random_f64 (&bm->random_seed))) * - bs->transmit_interval_clocks; - if (bs->tx_timeout_clocks < now) - { - /* huh, we've missed it already, transmit now */ - BFD_DBG ("Missed %lu transmit events (now is %lu, calc " - "tx_timeout is %lu)", - (now - bs->tx_timeout_clocks) / - bs->transmit_interval_clocks, - now, bs->tx_timeout_clocks); - bs->tx_timeout_clocks = now; - } + /* + * the timeout is in the past, which means that either remote + * demand mode was set or performance/clock issues ... + */ + BFD_DBG ("Missed %lu transmit events (now is %lu, calc " + "tx_timeout is %lu)", + (now - bs->tx_timeout_clocks) / + bs->transmit_interval_clocks, now, bs->tx_timeout_clocks); + bs->tx_timeout_clocks = now; } } else { - /* TODO */ + /* special case - 75-90% of transmit interval */ + bs->tx_timeout_clocks = bs->last_tx_clocks + + (.9 - .15 * (random_f64 (&bm->random_seed))) * + bs->transmit_interval_clocks; + if (bs->tx_timeout_clocks < now) + { + /* + * the timeout is in the past, which means that either remote + * demand mode was set or performance/clock issues ... + */ + BFD_DBG ("Missed %lu transmit events (now is %lu, calc " + "tx_timeout is %lu)", + (now - bs->tx_timeout_clocks) / + bs->transmit_interval_clocks, now, bs->tx_timeout_clocks); + bs->tx_timeout_clocks = now; + } } if (bs->tx_timeout_clocks) { @@ -191,24 +241,33 @@ bfd_calc_next_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now) } } +static void +bfd_calc_next_echo_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now) +{ + bs->echo_tx_timeout_clocks = + bs->echo_last_tx_clocks + bs->echo_transmit_interval_clocks; + if (bs->echo_tx_timeout_clocks < now) + { + /* huh, we've missed it already, transmit now */ + BFD_DBG ("Missed %lu echo transmit events (now is %lu, calc tx_timeout " + "is %lu)", + (now - bs->echo_tx_timeout_clocks) / + bs->echo_transmit_interval_clocks, + now, bs->echo_tx_timeout_clocks); + bs->echo_tx_timeout_clocks = now; + } + BFD_DBG ("Next echo transmit in %lu clocks/%.02fs@%lu", + bs->echo_tx_timeout_clocks - now, + (bs->echo_tx_timeout_clocks - now) / bm->cpu_cps, + bs->echo_tx_timeout_clocks); +} + static void bfd_recalc_detection_time (bfd_main_t * bm, bfd_session_t * bs) { - if (!bs->local_demand) - { - /* asynchronous mode */ - bs->detection_time_clocks = - bs->remote_detect_mult * - clib_max (bs->effective_required_min_rx_clocks, - bs->remote_desired_min_tx_clocks); - } - else - { - /* demand mode */ - bs->detection_time_clocks = - bs->local_detect_mult * clib_max (bs->config_desired_min_tx_clocks, - bs->remote_min_rx_clocks); - } + bs->detection_time_clocks = + bs->remote_detect_mult * clib_max (bs->effective_required_min_rx_clocks, + bs->remote_desired_min_tx_clocks); BFD_DBG ("Recalculated detection time %lu clocks/%.2fs", bs->detection_time_clocks, bs->detection_time_clocks / bm->cpu_cps); @@ -220,25 +279,37 @@ bfd_set_timer (bfd_main_t * bm, bfd_session_t * bs, u64 now, { u64 next = 0; u64 rx_timeout = 0; + u64 tx_timeout = 0; if (BFD_STATE_up == bs->local_state) { rx_timeout = bs->last_rx_clocks + bs->detection_time_clocks; } - if (bs->tx_timeout_clocks && rx_timeout) + if (BFD_STATE_up != bs->local_state || !bs->remote_demand || + BFD_POLL_NOT_NEEDED != bs->poll_state) { - next = clib_min (bs->tx_timeout_clocks, rx_timeout); + tx_timeout = bs->tx_timeout_clocks; } - else if (bs->tx_timeout_clocks) + if (tx_timeout && rx_timeout) { - next = bs->tx_timeout_clocks; + next = clib_min (tx_timeout, rx_timeout); + } + else if (tx_timeout) + { + next = tx_timeout; } else if (rx_timeout) { next = rx_timeout; } - BFD_DBG ("bs_idx=%u, tx_timeout=%lu, rx_timeout=%lu, next=%s", bs->bs_idx, - bs->tx_timeout_clocks, rx_timeout, - next == bs->tx_timeout_clocks ? "tx" : "rx"); + if (bs->echo && next > bs->echo_tx_timeout_clocks) + { + next = bs->echo_tx_timeout_clocks; + } + BFD_DBG ("bs_idx=%u, tx_timeout=%lu, echo_tx_timeout=%lu, rx_timeout=%lu, " + "next=%s", + bs->bs_idx, tx_timeout, bs->echo_tx_timeout_clocks, rx_timeout, + next == tx_timeout + ? "tx" : (next == bs->echo_tx_timeout_clocks ? "echo tx" : "rx")); /* sometimes the wheel expires an event a bit sooner than requested, account for that here */ if (next && (now + bm->wheel_inaccuracy > bs->wheel_time_clocks || @@ -271,6 +342,7 @@ bfd_set_effective_desired_min_tx (bfd_main_t * bm, BFD_CLK_PRN (bs->effective_desired_min_tx_clocks)); bfd_recalc_detection_time (bm, bs); bfd_recalc_tx_interval (bm, bs); + bfd_recalc_echo_tx_interval (bm, bs); bfd_calc_next_tx (bm, bs, now); } @@ -287,25 +359,40 @@ bfd_set_effective_required_min_rx (bfd_main_t * bm, static void bfd_set_remote_required_min_rx (bfd_main_t * bm, bfd_session_t * bs, - u64 now, - u32 remote_required_min_rx_usec, - int handling_wakeup) + u64 now, u32 remote_required_min_rx_usec) { - bs->remote_min_rx_usec = remote_required_min_rx_usec; - bs->remote_min_rx_clocks = - bfd_usec_to_clocks (bm, remote_required_min_rx_usec); - BFD_DBG ("Set remote min rx to " BFD_CLK_FMT, - BFD_CLK_PRN (bs->remote_min_rx_clocks)); - bfd_recalc_detection_time (bm, bs); - bfd_recalc_tx_interval (bm, bs); - bfd_calc_next_tx (bm, bs, now); - bfd_set_timer (bm, bs, now, handling_wakeup); + if (bs->remote_min_rx_usec != remote_required_min_rx_usec) + { + bs->remote_min_rx_usec = remote_required_min_rx_usec; + bs->remote_min_rx_clocks = + bfd_usec_to_clocks (bm, remote_required_min_rx_usec); + BFD_DBG ("Set remote min rx to " BFD_CLK_FMT, + BFD_CLK_PRN (bs->remote_min_rx_clocks)); + bfd_recalc_detection_time (bm, bs); + bfd_recalc_tx_interval (bm, bs); + } +} + +static void +bfd_set_remote_required_min_echo_rx (bfd_main_t * bm, bfd_session_t * bs, + u64 now, + u32 remote_required_min_echo_rx_usec) +{ + if (bs->remote_min_echo_rx_usec != remote_required_min_echo_rx_usec) + { + bs->remote_min_echo_rx_usec = remote_required_min_echo_rx_usec; + bs->remote_min_echo_rx_clocks = + bfd_usec_to_clocks (bm, bs->remote_min_echo_rx_usec); + BFD_DBG ("Set remote min echo rx to " BFD_CLK_FMT, + BFD_CLK_PRN (bs->remote_min_echo_rx_clocks)); + bfd_recalc_echo_tx_interval (bm, bs); + } } void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) { - BFD_DBG ("%U", format_bfd_session, bs); + BFD_DBG ("\nStarting session: %U", format_bfd_session, bs); bfd_recalc_tx_interval (bm, bs); vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index, BFD_EVENT_NEW_SESSION, bs->bs_idx); @@ -418,11 +505,12 @@ static void bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, int handling_wakeup) { - BFD_DBG ("State changed: %U", format_bfd_session, bs); + BFD_DBG ("\nState changed: %U", format_bfd_session, bs); bfd_event (bm, bs); switch (bs->local_state) { case BFD_STATE_admin_down: + bs->echo = 0; bfd_set_effective_desired_min_tx (bm, bs, now, clib_max (bs->config_desired_min_tx_clocks, @@ -432,6 +520,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, bfd_set_timer (bm, bs, now, handling_wakeup); break; case BFD_STATE_down: + bs->echo = 0; bfd_set_effective_desired_min_tx (bm, bs, now, clib_max (bs->config_desired_min_tx_clocks, @@ -441,6 +530,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, bfd_set_timer (bm, bs, now, handling_wakeup); break; case BFD_STATE_init: + bs->echo = 0; bfd_set_effective_desired_min_tx (bm, bs, now, bs->config_desired_min_tx_clocks); bfd_set_timer (bm, bs, now, handling_wakeup); @@ -448,7 +538,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, case BFD_STATE_up: bfd_set_effective_desired_min_tx (bm, bs, now, bs->config_desired_min_tx_clocks); - if (POLL_NOT_NEEDED == bs->poll_state) + if (BFD_POLL_NOT_NEEDED == bs->poll_state) { bfd_set_effective_required_min_rx (bm, bs, now, bs->config_required_min_rx_clocks); @@ -462,13 +552,14 @@ static void bfd_on_config_change (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, bfd_session_t * bs, u64 now) { - if (bs->remote_demand) - { - /* TODO - initiate poll sequence here */ - } - else + /* + * if remote demand mode is set and we need to do a poll, set the next + * timeout so that the session wakes up immediately + */ + if (bs->remote_demand && BFD_POLL_NEEDED == bs->poll_state && + bs->poll_state_start_or_timeout_clocks < now) { - /* asynchronous - poll is part of periodic - nothing to do here */ + bs->tx_timeout_clocks = now; } bfd_recalc_detection_time (bm, bs); bfd_set_timer (bm, bs, now, 0); @@ -482,27 +573,36 @@ bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, { case BFD_TRANSPORT_UDP4: BFD_DBG ("Transport bfd via udp4, bs_idx=%u", bs->bs_idx); - bfd_add_udp4_transport (vm, b, bs); + bfd_add_udp4_transport (vm, b, bs, 0 /* is_echo */ ); break; case BFD_TRANSPORT_UDP6: BFD_DBG ("Transport bfd via udp6, bs_idx=%u", bs->bs_idx); - bfd_add_udp6_transport (vm, b, bs); + bfd_add_udp6_transport (vm, b, bs, 0 /* is_echo */ ); break; } } -static vlib_buffer_t * -bfd_create_frame_to_next_node (vlib_main_t * vm, bfd_session_t * bs) +static int +bfd_echo_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, + bfd_session_t * bs) { - u32 bi; - if (vlib_buffer_alloc (vm, &bi, 1) != 1) + switch (bs->transport) { - clib_warning ("buffer allocation failure"); - return NULL; + case BFD_TRANSPORT_UDP4: + BFD_DBG ("Transport bfd echo via udp4, bs_idx=%u", bs->bs_idx); + return bfd_add_udp4_transport (vm, b, bs, 1 /* is_echo */ ); + break; + case BFD_TRANSPORT_UDP6: + BFD_DBG ("Transport bfd echo via udp6, bs_idx=%u", bs->bs_idx); + return bfd_add_udp6_transport (vm, b, bs, 1 /* is_echo */ ); + break; } + return 0; +} - vlib_buffer_t *b = vlib_get_buffer (vm, bi); - ASSERT (b->current_data == 0); +static void +bfd_create_frame_to_next_node (vlib_main_t * vm, bfd_session_t * bs, u32 bi) +{ vlib_frame_t *f = vlib_get_frame_to_node (vm, bfd_node_index_by_transport[bs->transport]); @@ -510,9 +610,7 @@ bfd_create_frame_to_next_node (vlib_main_t * vm, bfd_session_t * bs) u32 *to_next = vlib_frame_vector_args (f); to_next[0] = bi; f->n_vectors = 1; - vlib_put_frame_to_node (vm, bfd_node_index_by_transport[bs->transport], f); - return b; } #if WITH_LIBSSL > 0 @@ -583,45 +681,118 @@ bfd_add_auth_section (vlib_buffer_t * b, bfd_session_t * bs) } } +static int +bfd_is_echo_possible (bfd_session_t * bs) +{ + if (BFD_STATE_up == bs->local_state && BFD_STATE_up == bs->remote_state && + bs->remote_min_echo_rx_usec > 0) + { + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + return bfd_udp_is_echo_available (BFD_TRANSPORT_UDP4); + case BFD_TRANSPORT_UDP6: + return bfd_udp_is_echo_available (BFD_TRANSPORT_UDP6); + } + } + return 0; +} + static void -bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) +bfd_init_control_frame (bfd_main_t * bm, bfd_session_t * bs, + vlib_buffer_t * b) { bfd_pkt_t *pkt = vlib_buffer_get_current (b); - u32 bfd_length = 0; bfd_length = sizeof (bfd_pkt_t); memset (pkt, 0, sizeof (*pkt)); bfd_pkt_set_version (pkt, 1); bfd_pkt_set_diag_code (pkt, bs->local_diag); bfd_pkt_set_state (pkt, bs->local_state); - if (bs->local_demand && BFD_STATE_up == bs->local_state && - BFD_STATE_up == bs->remote_state) - { - bfd_pkt_set_demand (pkt); - } pkt->head.detect_mult = bs->local_detect_mult; pkt->head.length = clib_host_to_net_u32 (bfd_length); pkt->my_disc = bs->local_discr; pkt->your_disc = bs->remote_discr; pkt->des_min_tx = clib_host_to_net_u32 (bs->config_desired_min_tx_usec); - pkt->req_min_rx = clib_host_to_net_u32 (bs->config_required_min_rx_usec); + if (bs->echo) + { + pkt->req_min_rx = + clib_host_to_net_u32 (bfd_clocks_to_usec + (bm, bs->effective_required_min_rx_clocks)); + } + else + { + pkt->req_min_rx = + clib_host_to_net_u32 (bs->config_required_min_rx_usec); + } pkt->req_min_echo_rx = clib_host_to_net_u32 (1); b->current_length = bfd_length; } +static void +bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, + bfd_main_t * bm, bfd_session_t * bs, u64 now, + int handling_wakeup) +{ + if (!bfd_is_echo_possible (bs)) + { + BFD_DBG ("\nSwitching off echo function: %U", format_bfd_session, bs); + bs->echo = 0; + return; + } + /* sometimes the wheel expires an event a bit sooner than requested, account + for that here */ + if (now + bm->wheel_inaccuracy >= bs->echo_tx_timeout_clocks) + { + BFD_DBG ("\nSending echo packet: %U", format_bfd_session, bs); + u32 bi; + if (vlib_buffer_alloc (vm, &bi, 1) != 1) + { + clib_warning ("buffer allocation failure"); + return; + } + vlib_buffer_t *b = vlib_get_buffer (vm, bi); + ASSERT (b->current_data == 0); + bfd_echo_pkt_t *pkt = vlib_buffer_get_current (b); + memset (pkt, 0, sizeof (*pkt)); + pkt->discriminator = bs->local_discr; + pkt->expire_time_clocks = + now + bs->echo_transmit_interval_clocks * bs->local_detect_mult; + pkt->checksum = + bfd_calc_echo_checksum (bs->local_discr, pkt->expire_time_clocks, + bs->echo_secret); + b->current_length = sizeof (*pkt); + if (!bfd_echo_add_transport_layer (vm, b, bs)) + { + BFD_ERR ("cannot send echo packet out, turning echo off"); + bs->echo = 0; + vlib_buffer_free_one (vm, bi); + return; + } + bs->echo_last_tx_clocks = now; + bfd_calc_next_echo_tx (bm, bs, now); + bfd_create_frame_to_next_node (vm, bs, bi); + } + else + { + BFD_DBG + ("No need to send echo packet now, now is %lu, tx_timeout is %lu", + now, bs->echo_tx_timeout_clocks); + } +} + static void bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, bfd_session_t * bs, u64 now, int handling_wakeup) { - if (!bs->remote_min_rx_usec) + if (!bs->remote_min_rx_usec && BFD_POLL_NOT_NEEDED == bs->poll_state) { - BFD_DBG - ("bfd.RemoteMinRxInterval is zero, not sending periodic control " - "frame"); + BFD_DBG ("Remote min rx interval is zero, not sending periodic control " + "frame"); return; } - if (POLL_NOT_NEEDED == bs->poll_state && bs->remote_demand && + if (BFD_POLL_NOT_NEEDED == bs->poll_state && bs->remote_demand && BFD_STATE_up == bs->local_state && BFD_STATE_up == bs->remote_state) { /* @@ -630,33 +801,52 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, * bfd.SessionState is Up, and bfd.RemoteSessionState is Up) and a Poll * Sequence is not being transmitted. */ - BFD_DBG ("bfd.RemoteDemand is non-zero, not sending periodic control " - "frame"); + BFD_DBG ("Remote demand is set, not sending periodic control frame"); return; } /* sometimes the wheel expires an event a bit sooner than requested, account for that here */ if (now + bm->wheel_inaccuracy >= bs->tx_timeout_clocks) { - BFD_DBG ("Send periodic control frame for bs_idx=%lu: %U", bs->bs_idx, - format_bfd_session, bs); - vlib_buffer_t *b = bfd_create_frame_to_next_node (vm, bs); - if (!b) + BFD_DBG ("\nSending periodic control frame: %U", format_bfd_session, + bs); + u32 bi; + if (vlib_buffer_alloc (vm, &bi, 1) != 1) { + clib_warning ("buffer allocation failure"); return; } - bfd_init_control_frame (b, bs); - if (POLL_NOT_NEEDED != bs->poll_state) + vlib_buffer_t *b = vlib_get_buffer (vm, bi); + ASSERT (b->current_data == 0); + bfd_init_control_frame (bm, bs, b); + switch (bs->poll_state) { - /* here we are either beginning a new poll sequence or retrying .. */ + case BFD_POLL_NEEDED: + if (now < bs->poll_state_start_or_timeout_clocks) + { + BFD_DBG ("Cannot start a poll sequence yet, need to wait " + "for " BFD_CLK_FMT, + BFD_CLK_PRN (bs->poll_state_start_or_timeout_clocks - + now)); + break; + } + bs->poll_state_start_or_timeout_clocks = now; + bfd_set_poll_state (bs, BFD_POLL_IN_PROGRESS); + /* fallthrough */ + case BFD_POLL_IN_PROGRESS: + case BFD_POLL_IN_PROGRESS_AND_QUEUED: bfd_pkt_set_poll (vlib_buffer_get_current (b)); - bs->poll_state = POLL_IN_PROGRESS; BFD_DBG ("Setting poll bit in packet, bs_idx=%u", bs->bs_idx); + break; + case BFD_POLL_NOT_NEEDED: + /* fallthrough */ + break; } bfd_add_auth_section (b, bs); bfd_add_transport_layer (vm, b, bs); bs->last_tx_clocks = now; bfd_calc_next_tx (bm, bs, now); + bfd_create_frame_to_next_node (vm, bs, bi); } else { @@ -664,15 +854,14 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, ("No need to send control frame now, now is %lu, tx_timeout is %lu", now, bs->tx_timeout_clocks); } - bfd_set_timer (bm, bs, now, handling_wakeup); } void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, - bfd_session_t * bs) + bfd_main_t * bm, bfd_session_t * bs) { BFD_DBG ("Send final control frame for bs_idx=%lu", bs->bs_idx); - bfd_init_control_frame (b, bs); + bfd_init_control_frame (bm, bs, b); bfd_pkt_set_final (vlib_buffer_get_current (b)); bfd_add_auth_section (b, bs); bfd_add_transport_layer (vm, b, bs); @@ -681,7 +870,7 @@ bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, * RFC allows to include changes in final frame, so if there were any * pending, we already did that, thus we can clear any pending poll needs */ - bs->poll_state = POLL_NOT_NEEDED; + bfd_set_poll_state (bs, BFD_POLL_NOT_NEEDED); } static void @@ -703,7 +892,16 @@ bfd_check_rx_timeout (bfd_main_t * bm, bfd_session_t * bs, u64 now, * since it is no longer required to maintain previous session state) * and then can transmit at its own rate. */ - bfd_set_remote_required_min_rx (bm, bs, now, 1, handling_wakeup); + bfd_set_remote_required_min_rx (bm, bs, now, 1); + } + else if (bs->echo && + bs->echo_last_rx_clocks + + bs->echo_transmit_interval_clocks * bs->local_detect_mult <= + now + bm->wheel_inaccuracy) + { + BFD_DBG ("Echo rx timeout, session goes down"); + bfd_set_diag (bs, BFD_DIAG_CODE_echo_failed); + bfd_set_state (bm, bs, BFD_STATE_down, handling_wakeup); } } @@ -721,10 +919,30 @@ bfd_on_timeout (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, bfd_send_periodic (vm, rt, bm, bs, now, 1); break; case BFD_STATE_init: - /* fallthrough */ + bfd_check_rx_timeout (bm, bs, now, 1); + bfd_send_periodic (vm, rt, bm, bs, now, 1); + break; case BFD_STATE_up: bfd_check_rx_timeout (bm, bs, now, 1); + if (BFD_POLL_NOT_NEEDED == bs->poll_state && !bs->echo && + bfd_is_echo_possible (bs)) + { + /* switch on echo function as main detection method now */ + BFD_DBG ("Switching on echo function, bs_idx=%u", bs->bs_idx); + bs->echo = 1; + bs->echo_last_rx_clocks = now; + bs->echo_tx_timeout_clocks = now; + bfd_set_effective_required_min_rx (bm, bs, now, + clib_max + (bm->min_required_min_rx_while_echo_clocks, + bs->config_required_min_rx_clocks)); + bfd_set_poll_state (bs, BFD_POLL_NEEDED); + } bfd_send_periodic (vm, rt, bm, bs, now, 1); + if (bs->echo) + { + bfd_send_echo (vm, rt, bm, bs, now, 1); + } break; } } @@ -822,6 +1040,7 @@ bfd_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) { bfd_session_t *bs = pool_elt_at_index (bm->sessions, bs_idx); bfd_on_timeout (vm, rt, bm, bs, now); + bfd_set_timer (bm, bs, now, 1); } } if (expired) @@ -894,7 +1113,9 @@ bfd_main_init (vlib_main_t * vm) bm->cpu_cps = vm->clib_time.clocks_per_second; BFD_DBG ("cps is %.2f", bm->cpu_cps); bm->default_desired_min_tx_clocks = - bfd_usec_to_clocks (bm, BFD_DEFAULT_DESIRED_MIN_TX_US); + bfd_usec_to_clocks (bm, BFD_DEFAULT_DESIRED_MIN_TX_USEC); + bm->min_required_min_rx_while_echo_clocks = + bfd_usec_to_clocks (bm, BFD_REQUIRED_MIN_RX_USEC_WHILE_ECHO); const u64 now = clib_cpu_time_now (); timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; @@ -912,14 +1133,28 @@ bfd_main_init (vlib_main_t * vm) VLIB_INIT_FUNCTION (bfd_main_init); bfd_session_t * -bfd_get_session (bfd_main_t * bm, bfd_transport_t t) +bfd_get_session (bfd_main_t * bm, bfd_transport_e t) { bfd_session_t *result; pool_get (bm->sessions, result); memset (result, 0, sizeof (*result)); result->bs_idx = result - bm->sessions; result->transport = t; - result->local_discr = random_u32 (&bm->random_seed); + const unsigned limit = 1000; + unsigned counter = 0; + do + { + result->local_discr = random_u32 (&bm->random_seed); + if (counter > limit) + { + clib_warning ("Couldn't allocate unused session discriminator even " + "after %u tries!", limit); + pool_put (bm->sessions, result); + return NULL; + } + ++counter; + } + while (hash_get (bm->session_by_disc, result->local_discr)); bfd_set_defaults (bm, result); hash_set (bm->session_by_disc, result->local_discr, result->bs_idx); return result; @@ -1372,29 +1607,45 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) bfd_usec_to_clocks (bm, clib_net_to_host_u32 (pkt->des_min_tx)); bs->remote_detect_mult = pkt->head.detect_mult; bfd_set_remote_required_min_rx (bm, bs, now, - clib_net_to_host_u32 (pkt->req_min_rx), 0); - /* FIXME - If the Required Min Echo RX Interval field is zero, the - transmission of Echo packets, if any, MUST cease. - - If a Poll Sequence is being transmitted by the local system and - the Final (F) bit in the received packet is set, the Poll Sequence - MUST be terminated. - */ + clib_net_to_host_u32 (pkt->req_min_rx)); + bfd_set_remote_required_min_echo_rx (bm, bs, now, + clib_net_to_host_u32 + (pkt->req_min_echo_rx)); /* FIXME 6.8.2 */ /* FIXME 6.8.4 */ - if (bs->poll_state == POLL_IN_PROGRESS && bfd_pkt_get_final (pkt)) + if (bfd_pkt_get_final (pkt)) { - bs->poll_state = POLL_NOT_NEEDED; - BFD_DBG ("Poll sequence terminated, bs_idx=%u", bs->bs_idx); - if (BFD_STATE_up == bs->local_state) + if (BFD_POLL_IN_PROGRESS == bs->poll_state) { - bfd_set_effective_required_min_rx (bm, bs, now, - bs->config_required_min_rx_clocks); - bfd_recalc_detection_time (bm, bs); - bfd_set_timer (bm, bs, now, 0); + BFD_DBG ("Poll sequence terminated, bs_idx=%u", bs->bs_idx); + bfd_set_poll_state (bs, BFD_POLL_NOT_NEEDED); + if (BFD_STATE_up == bs->local_state) + { + bfd_set_effective_required_min_rx (bm, bs, now, + clib_max (bs->echo * + bm->min_required_min_rx_while_echo_clocks, + bs->config_required_min_rx_clocks)); + } + } + else if (BFD_POLL_IN_PROGRESS_AND_QUEUED == bs->poll_state) + { + /* + * next poll sequence must be delayed by at least the round trip + * time, so calculate that here + */ + BFD_DBG ("Next poll sequence can commence in " BFD_CLK_FMT, + BFD_CLK_PRN (now - + bs->poll_state_start_or_timeout_clocks)); + bs->poll_state_start_or_timeout_clocks = + now + (now - bs->poll_state_start_or_timeout_clocks); + BFD_DBG + ("Poll sequence terminated, but another is needed, bs_idx=%u", + bs->bs_idx); + bfd_set_poll_state (bs, BFD_POLL_NEEDED); } } + bfd_calc_next_tx (bm, bs, now); + bfd_set_timer (bm, bs, now, 0); if (BFD_STATE_admin_down == bs->local_state) { BFD_DBG ("Session is admin-down, ignoring packet, bs_idx=%u", @@ -1435,52 +1686,75 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) } } -static const char * -bfd_poll_state_string (bfd_poll_state_e state) +int +bfd_consume_echo_pkt (bfd_main_t * bm, vlib_buffer_t * b) { - switch (state) + bfd_echo_pkt_t *pkt = NULL; + if (b->current_length != sizeof (*pkt)) { -#define F(x) \ - case POLL_##x: \ - return "POLL_" #x; - foreach_bfd_poll_state (F) -#undef F + return 0; } - return "UNKNOWN"; + pkt = vlib_buffer_get_current (b); + bfd_session_t *bs = bfd_find_session_by_disc (bm, pkt->discriminator); + if (!bs) + { + return 0; + } + BFD_DBG ("Scanning bfd echo packet, bs_idx=%d", bs->bs_idx); + u64 checksum = + bfd_calc_echo_checksum (bs->local_discr, pkt->expire_time_clocks, + bs->echo_secret); + if (checksum != pkt->checksum) + { + BFD_DBG ("Invalid echo packet, checksum mismatch"); + return 1; + } + u64 now = clib_cpu_time_now (); + if (pkt->expire_time_clocks < now) + { + BFD_DBG ("Stale packet received, expire time %lu < now %lu", + pkt->expire_time_clocks, now); + } + else + { + bs->echo_last_rx_clocks = now; + } + return 1; } u8 * format_bfd_session (u8 * s, va_list * args) { const bfd_session_t *bs = va_arg (*args, bfd_session_t *); - s = format (s, "BFD(%u): bfd.SessionState=%s, " - "bfd.RemoteSessionState=%s, " - "bfd.LocalDiscr=%u, " - "bfd.RemoteDiscr=%u, " - "bfd.LocalDiag=%s, " - "bfd.DesiredMinTxInterval=%u, " - "bfd.RequiredMinRxInterval=%u, " - "bfd.RequiredMinEchoRxInterval=%u, " - "bfd.RemoteMinRxInterval=%u, " - "bfd.DemandMode=%s, " - "bfd.RemoteDemandMode=%s, " - "bfd.DetectMult=%u, " - "Auth: {local-seq-num=%u, " - "remote-seq-num=%u, " - "is-delayed=%s, " - "curr-key=%U, " - "next-key=%U}," - "poll-state: %s", + uword indent = format_get_indent (s); + s = format (s, "bs_idx=%u local-state=%s remote-state=%s\n" + "%Ulocal-discriminator=%u remote-discriminator=%u\n" + "%Ulocal-diag=%s echo-active=%s\n" + "%Udesired-min-tx=%u required-min-rx=%u\n" + "%Urequired-min-echo-rx=%u detect-mult=%u\n" + "%Uremote-min-rx=%u remote-min-echo-rx=%u\n" + "%Uremote-demand=%s poll-state=%s\n" + "%Uauth: local-seq-num=%u remote-seq-num=%u\n" + "%U is-delayed=%s\n" + "%U curr-key=%U\n" + "%U next-key=%U", bs->bs_idx, bfd_state_string (bs->local_state), - bfd_state_string (bs->remote_state), bs->local_discr, - bs->remote_discr, bfd_diag_code_string (bs->local_diag), + bfd_state_string (bs->remote_state), format_white_space, indent, + bs->local_discr, bs->remote_discr, format_white_space, indent, + bfd_diag_code_string (bs->local_diag), + (bs->echo ? "yes" : "no"), format_white_space, indent, bs->config_desired_min_tx_usec, bs->config_required_min_rx_usec, - 1, bs->remote_min_rx_usec, (bs->local_demand ? "yes" : "no"), - (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult, - bs->auth.local_seq_number, bs->auth.remote_seq_number, - (bs->auth.is_delayed ? "yes" : "no"), format_bfd_auth_key, - bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key, - bfd_poll_state_string (bs->poll_state)); + format_white_space, indent, 1, bs->local_detect_mult, + format_white_space, indent, bs->remote_min_rx_usec, + bs->remote_min_echo_rx_usec, format_white_space, indent, + (bs->remote_demand ? "yes" : "no"), + bfd_poll_state_string (bs->poll_state), format_white_space, + indent, bs->auth.local_seq_number, bs->auth.remote_seq_number, + format_white_space, indent, + (bs->auth.is_delayed ? "yes" : "no"), format_white_space, + indent, format_bfd_auth_key, bs->auth.curr_key, + format_white_space, indent, format_bfd_auth_key, + bs->auth.next_key); return s; } @@ -1537,7 +1811,7 @@ bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, bs->auth.is_delayed = 0; } ++key->use_count; - BFD_DBG ("Session auth modified: %U", format_bfd_session, bs); + BFD_DBG ("\nSession auth modified: %U", format_bfd_session, bs); return 0; } @@ -1571,7 +1845,7 @@ bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed) --bs->auth.next_key->use_count; bs->auth.next_key = NULL; } - BFD_DBG ("Session auth modified: %U", format_bfd_session, bs); + BFD_DBG ("\nSession auth modified: %U", format_bfd_session, bs); return 0; #else clib_warning ("SSL missing, cannot deactivate BFD authentication"); @@ -1588,10 +1862,10 @@ bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, bs->config_desired_min_tx_usec != desired_min_tx_usec || bs->config_required_min_rx_usec != required_min_rx_usec) { - BFD_DBG ("Changing session params: %U", format_bfd_session, bs); + BFD_DBG ("\nChanging session params: %U", format_bfd_session, bs); switch (bs->poll_state) { - case POLL_NOT_NEEDED: + case BFD_POLL_NOT_NEEDED: if (BFD_STATE_up == bs->local_state || BFD_STATE_init == bs->local_state) { @@ -1599,21 +1873,26 @@ bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, if (bs->config_desired_min_tx_usec != desired_min_tx_usec || bs->config_required_min_rx_usec != required_min_rx_usec) { - bs->poll_state = POLL_NEEDED; - BFD_DBG ("Set poll state=%s, bs_idx=%u", - bfd_poll_state_string (bs->poll_state), - bs->bs_idx); + bfd_set_poll_state (bs, BFD_POLL_NEEDED); } } break; - case POLL_NEEDED: - /* nothing to do */ + case BFD_POLL_NEEDED: + case BFD_POLL_IN_PROGRESS_AND_QUEUED: + /* + * nothing to do - will be handled in the future poll which is + * already scheduled for execution + */ break; - case POLL_IN_PROGRESS: - /* can't change params now ... */ - BFD_ERR ("Poll in progress, cannot change params for session with " - "bs_idx=%u", bs->bs_idx); - return VNET_API_ERROR_BFD_EAGAIN; + case BFD_POLL_IN_PROGRESS: + /* poll sequence is not needed for detect multiplier change */ + if (bs->config_desired_min_tx_usec != desired_min_tx_usec || + bs->config_required_min_rx_usec != required_min_rx_usec) + { + BFD_DBG ("Poll in progress, queueing extra poll, bs_idx=%u", + bs->bs_idx); + bfd_set_poll_state (bs, BFD_POLL_IN_PROGRESS_AND_QUEUED); + } } bs->local_detect_mult = detect_mult; @@ -1623,7 +1902,7 @@ bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, bs->config_required_min_rx_usec = required_min_rx_usec; bs->config_required_min_rx_clocks = bfd_usec_to_clocks (bm, required_min_rx_usec); - BFD_DBG ("Changed session params: %U", format_bfd_session, bs); + BFD_DBG ("\nChanged session params: %U", format_bfd_session, bs); vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index, BFD_EVENT_CONFIG_CHANGED, bs->bs_idx); diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index 14a54d6f..d8063f9d 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -24,17 +24,6 @@ #include #include -#define foreach_bfd_transport(F) \ - F (UDP4, "ip4-rewrite") \ - F (UDP6, "ip6-rewrite") - -typedef enum -{ -#define F(t, n) BFD_TRANSPORT_##t, - foreach_bfd_transport (F) -#undef F -} bfd_transport_t; - #define foreach_bfd_mode(F) \ F (asynchronous) \ F (demand) @@ -64,14 +53,15 @@ typedef struct bfd_auth_type_e auth_type; } bfd_auth_key_t; -#define foreach_bfd_poll_state(F)\ - F(NOT_NEEDED)\ -F(NEEDED)\ -F(IN_PROGRESS) +#define foreach_bfd_poll_state(F) \ + F (NOT_NEEDED) \ + F (NEEDED) \ + F (IN_PROGRESS) \ + F (IN_PROGRESS_AND_QUEUED) typedef enum { -#define F(x) POLL_##x, +#define F(x) BFD_POLL_##x, foreach_bfd_poll_state (F) #undef F } bfd_poll_state_e; @@ -120,21 +110,27 @@ typedef struct bfd_session_s /* remote min rx interval (clocks) */ u64 remote_min_rx_clocks; + /* remote min echo rx interval (microseconds) */ + u64 remote_min_echo_rx_usec; + + /* remote min echo rx interval (clocks) */ + u64 remote_min_echo_rx_clocks; + /* remote desired min tx interval (clocks) */ u64 remote_desired_min_tx_clocks; /* configured detect multiplier */ u8 local_detect_mult; - /* 1 if in demand mode, 0 otherwise */ - u8 local_demand; - /* 1 if remote system sets demand mode, 0 otherwise */ u8 remote_demand; /* remote detect multiplier */ u8 remote_detect_mult; + /* 1 is echo function is active, 0 otherwise */ + u8 echo; + /* set to value of timer in timing wheel, 0 if never set */ u64 wheel_time_clocks; @@ -150,12 +146,33 @@ typedef struct bfd_session_s /* timestamp of last packet received */ u64 last_rx_clocks; + /* transmit interval for echo packets */ + u64 echo_transmit_interval_clocks; + + /* next time at which to transmit echo packet */ + u64 echo_tx_timeout_clocks; + + /* timestamp of last echo packet transmitted */ + u64 echo_last_tx_clocks; + + /* timestamp of last echo packet received */ + u64 echo_last_rx_clocks; + + /* secret used for calculating/checking checksum of echo packets */ + u32 echo_secret; + /* detection time */ u64 detection_time_clocks; /* state info regarding poll sequence */ bfd_poll_state_e poll_state; + /* + * helper for delayed poll sequence - marks either start of running poll + * sequence or timeout, after which we can start the next poll sequnce + */ + u64 poll_state_start_or_timeout_clocks; + /* authentication information */ struct { @@ -191,7 +208,7 @@ typedef struct bfd_session_s } auth; /* transport type for this session */ - bfd_transport_t transport; + bfd_transport_e transport; /* union of transport-specific data */ union @@ -227,6 +244,9 @@ typedef struct /* default desired min tx in clocks */ u64 default_desired_min_tx_clocks; + /* minimum required min rx while echo function is active - clocks */ + u64 min_required_min_rx_while_echo_clocks; + /* for generating random numbers */ u32 random_seed; @@ -268,36 +288,54 @@ enum BFD_EVENT_CONFIG_CHANGED, } bfd_process_event_e; -u8 *bfd_input_format_trace (u8 * s, va_list * args); +/* echo packet structure */ +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + /* local discriminator */ + u32 discriminator; + /* expire time of this packet - clocks */ + u64 expire_time_clocks; + /* checksum - based on discriminator, local secret and expire time */ + u64 checksum; +}) bfd_echo_pkt_t; +/* *INDENT-ON* */ -bfd_session_t *bfd_get_session (bfd_main_t * bm, bfd_transport_t t); +u8 *bfd_input_format_trace (u8 * s, va_list * args); +bfd_session_t *bfd_get_session (bfd_main_t * bm, bfd_transport_e t); void bfd_put_session (bfd_main_t * bm, bfd_session_t * bs); bfd_session_t *bfd_find_session_by_idx (bfd_main_t * bm, uword bs_idx); bfd_session_t *bfd_find_session_by_disc (bfd_main_t * bm, u32 disc); void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs); void bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * bfd, u32 bs_idx); +int bfd_consume_echo_pkt (bfd_main_t * bm, vlib_buffer_t * b); int bfd_verify_pkt_common (const bfd_pkt_t * pkt); int bfd_verify_pkt_auth (const bfd_pkt_t * pkt, u16 pkt_size, bfd_session_t * bs); void bfd_event (bfd_main_t * bm, bfd_session_t * bs); void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, - bfd_session_t * bs); + bfd_main_t * bm, bfd_session_t * bs); u8 *format_bfd_session (u8 * s, va_list * args); void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down); unsigned bfd_auth_type_supported (bfd_auth_type_e auth_type); vnet_api_error_t bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, u8 bfd_key_id, u8 is_delayed); vnet_api_error_t bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed); -vnet_api_error_t -bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, - u32 desired_min_tx_usec, - u32 required_min_rx_usec, u8 detect_mult); +vnet_api_error_t bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, + u32 desired_min_tx_usec, + u32 required_min_rx_usec, + u8 detect_mult); #define USEC_PER_MS 1000LL #define USEC_PER_SECOND (1000 * USEC_PER_MS) /* default, slow transmission interval for BFD packets, per spec at least 1s */ -#define BFD_DEFAULT_DESIRED_MIN_TX_US USEC_PER_SECOND +#define BFD_DEFAULT_DESIRED_MIN_TX_USEC USEC_PER_SECOND + +/* + * minimum required min rx set locally when echo function is used, per spec + * should be set to at least 1s + */ +#define BFD_REQUIRED_MIN_RX_USEC_WHILE_ECHO USEC_PER_SECOND #endif /* __included_bfd_main_h__ */ diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index 8519009d..146faad6 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -38,6 +41,12 @@ typedef struct /* hashmap - bfd session index by bfd key - used for CLI/API lookup, where * discriminator is unknown */ mhash_t bfd_session_idx_by_bfd_key; + /* convenience variable */ + vnet_main_t *vnet_main; + /* flag indicating whether echo_source_sw_if_index holds a valid value */ + int echo_source_is_set; + /* loopback interface used to get echo source ip */ + u32 echo_source_sw_if_index; } bfd_udp_main_t; static vlib_node_registration_t bfd_udp4_input_node; @@ -47,6 +56,80 @@ static vlib_node_registration_t bfd_udp_echo6_input_node; bfd_udp_main_t bfd_udp_main; +vnet_api_error_t +bfd_udp_set_echo_source (u32 sw_if_index) +{ + vnet_sw_interface_t *sw_if = + vnet_get_sw_interface_safe (bfd_udp_main.vnet_main, + bfd_udp_main.echo_source_sw_if_index); + if (sw_if) + { + bfd_udp_main.echo_source_sw_if_index = sw_if_index; + bfd_udp_main.echo_source_is_set = 1; + return 0; + } + return VNET_API_ERROR_BFD_ENOENT; +} + +vnet_api_error_t +bfd_udp_del_echo_source (u32 sw_if_index) +{ + bfd_udp_main.echo_source_sw_if_index = ~0; + bfd_udp_main.echo_source_is_set = 0; + return 0; +} + +int +bfd_udp_is_echo_available (bfd_transport_e transport) +{ + if (!bfd_udp_main.echo_source_is_set) + { + return 0; + } + /* + * for the echo to work, we need a loopback interface with at least one + * address with netmask length at most 31 (ip4) or 127 (ip6) so that we can + * pick an unused address from that subnet + */ + vnet_sw_interface_t *sw_if = + vnet_get_sw_interface_safe (bfd_udp_main.vnet_main, + bfd_udp_main.echo_source_sw_if_index); + if (sw_if && sw_if->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) + { + if (BFD_TRANSPORT_UDP4 == transport) + { + ip4_main_t *im = &ip4_main; + ip_interface_address_t *ia = NULL; + /* *INDENT-OFF* */ + foreach_ip_interface_address (&im->lookup_main, ia, + bfd_udp_main.echo_source_sw_if_index, + 0 /* honor unnumbered */, ({ + if (ia->address_length <= 31) + { + return 1; + } + })); + /* *INDENT-ON* */ + } + else if (BFD_TRANSPORT_UDP6 == transport) + { + ip6_main_t *im = &ip6_main; + ip_interface_address_t *ia = NULL; + /* *INDENT-OFF* */ + foreach_ip_interface_address (&im->lookup_main, ia, + bfd_udp_main.echo_source_sw_if_index, + 0 /* honor unnumbered */, ({ + if (ia->address_length <= 127) + { + return 1; + } + })); + /* *INDENT-ON* */ + } + } + return 0; +} + static u16 bfd_udp_bs_idx_to_sport (u32 bs_idx) { @@ -61,9 +144,78 @@ bfd_udp_bs_idx_to_sport (u32 bs_idx) return 49152 + bs_idx % (65535 - 49152 + 1); } -void +static void +lol () +{ +} + +int +bfd_udp_get_echo_src_ip4 (ip4_address_t * addr) +{ + if (!bfd_udp_main.echo_source_is_set) + { + BFD_ERR ("cannot find ip4 address, echo source not set"); + return 0; + } + ip_interface_address_t *ia = NULL; + ip4_main_t *im = &ip4_main; + + /* *INDENT-OFF* */ + foreach_ip_interface_address ( + &im->lookup_main, ia, bfd_udp_main.echo_source_sw_if_index, + 0 /* honor unnumbered */, ({ + ip4_address_t *x = + ip_interface_address_get_address (&im->lookup_main, ia); + if (ia->address_length <= 31) + { + addr->as_u32 = clib_host_to_net_u32 (x->as_u32); + /* + * flip the last bit to get a different address, might be network, + * we don't care ... + */ + addr->as_u32 ^= 1; + addr->as_u32 = clib_net_to_host_u32 (addr->as_u32); + return 1; + } + })); + /* *INDENT-ON* */ + BFD_ERR ("cannot find ip4 address, no usable address found"); + return 0; +} + +int +bfd_udp_get_echo_src_ip6 (ip6_address_t * addr) +{ + if (!bfd_udp_main.echo_source_is_set) + { + BFD_ERR ("cannot find ip6 address, echo source not set"); + return 0; + } + ip_interface_address_t *ia = NULL; + ip6_main_t *im = &ip6_main; + + /* *INDENT-OFF* */ + foreach_ip_interface_address ( + &im->lookup_main, ia, bfd_udp_main.echo_source_sw_if_index, + 0 /* honor unnumbered */, ({ + ip6_address_t *x = + ip_interface_address_get_address (&im->lookup_main, ia); + if (ia->address_length <= 127) + { + *addr = *x; + addr->as_u8[15] ^= 1; /* flip the last bit of the address */ + lol (); + return 1; + } + })); + /* *INDENT-ON* */ + BFD_ERR ("cannot find ip6 address, no usable address found"); + return 0; +} + +int bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, - const bfd_session_t * bs) + const bfd_session_t * bs, int is_echo) { const bfd_udp_session_t *bus = &bs->udp; const bfd_udp_key_t *key = &bus->key; @@ -83,12 +235,24 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, headers->ip4.ip_version_and_header_length = 0x45; headers->ip4.ttl = 255; headers->ip4.protocol = IP_PROTOCOL_UDP; - headers->ip4.src_address.as_u32 = key->local_addr.ip4.as_u32; - headers->ip4.dst_address.as_u32 = key->peer_addr.ip4.as_u32; - headers->udp.src_port = clib_host_to_net_u16 (bfd_udp_bs_idx_to_sport (bs->bs_idx)); - headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); + if (is_echo) + { + int rv; + if (!(rv = bfd_udp_get_echo_src_ip4 (&headers->ip4.src_address))) + { + return rv; + } + headers->ip4.dst_address.as_u32 = key->local_addr.ip4.as_u32; + headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd_echo4); + } + else + { + headers->ip4.src_address.as_u32 = key->local_addr.ip4.as_u32; + headers->ip4.dst_address.as_u32 = key->peer_addr.ip4.as_u32; + headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); + } /* fix ip length, checksum and udp length */ const u16 ip_length = vlib_buffer_length_in_chain (vm, b); @@ -98,11 +262,12 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, const u16 udp_length = ip_length - (sizeof (headers->ip4)); headers->udp.length = clib_host_to_net_u16 (udp_length); + return 1; } -void +int bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, - const bfd_session_t * bs) + const bfd_session_t * bs, int is_echo) { const bfd_udp_session_t *bus = &bs->udp; const bfd_udp_key_t *key = &bus->key; @@ -123,14 +288,28 @@ bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, clib_host_to_net_u32 (0x6 << 28); headers->ip6.hop_limit = 255; headers->ip6.protocol = IP_PROTOCOL_UDP; - clib_memcpy (&headers->ip6.src_address, &key->local_addr.ip6, - sizeof (headers->ip6.src_address)); - clib_memcpy (&headers->ip6.dst_address, &key->peer_addr.ip6, - sizeof (headers->ip6.dst_address)); - headers->udp.src_port = clib_host_to_net_u16 (bfd_udp_bs_idx_to_sport (bs->bs_idx)); - headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd6); + if (is_echo) + { + int rv; + if (!(rv = bfd_udp_get_echo_src_ip6 (&headers->ip6.src_address))) + { + return rv; + } + clib_memcpy (&headers->ip6.dst_address, &key->local_addr.ip6, + sizeof (headers->ip6.dst_address)); + + headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd_echo6); + } + else + { + clib_memcpy (&headers->ip6.src_address, &key->local_addr.ip6, + sizeof (headers->ip6.src_address)); + clib_memcpy (&headers->ip6.dst_address, &key->peer_addr.ip6, + sizeof (headers->ip6.dst_address)); + headers->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd6); + } /* fix ip payload length and udp length */ const u16 udp_length = @@ -147,6 +326,7 @@ bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, { headers->udp.checksum = 0xffff; } + return 1; } static bfd_session_t * @@ -182,12 +362,17 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, bfd_session_t ** bs_out) { /* get a pool entry and if we end up not needing it, give it back */ - bfd_transport_t t = BFD_TRANSPORT_UDP4; + bfd_transport_e t = BFD_TRANSPORT_UDP4; if (!ip46_address_is_ip4 (local_addr)) { t = BFD_TRANSPORT_UDP6; } bfd_session_t *bs = bfd_get_session (bum->bfd_main, t); + if (!bs) + { + bfd_put_session (bum->bfd_main, bs); + return VNET_API_ERROR_BFD_EAGAIN; + } bfd_udp_session_t *bus = &bs->udp; memset (bus, 0, sizeof (*bus)); bfd_udp_key_t *key = &bus->key; @@ -213,6 +398,21 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, BFD_DBG ("adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, VNET_LINK_IP4, %U, %d) " "returns %d", format_ip46_address, &key->peer_addr, IP46_TYPE_ANY, key->sw_if_index, bus->adj_index); + + fib_prefix_t fib_prefix; + memset (&fib_prefix, 0, sizeof (fib_prefix)); + fib_prefix.fp_len = 0; + fib_prefix.fp_proto = FIB_PROTOCOL_IP4; + fib_prefix.fp_addr = key->local_addr; + u32 fib_index = fib_table_find (FIB_PROTOCOL_IP4, 0); /* FIXME table id 0? */ + dpo_id_t dpo = DPO_INVALID; + dpo_proto_t dproto; + dproto = fib_proto_to_dpo (fib_prefix.fp_proto); + receive_dpo_add_or_lock (dproto, ~0, NULL, &dpo); + fib_table_entry_special_dpo_update (fib_index, &fib_prefix, + FIB_SOURCE_API, + FIB_ENTRY_FLAG_LOCAL, &dpo); + dpo_reset (&dpo); } else { @@ -234,7 +434,7 @@ bfd_udp_validate_api_input (u32 sw_if_index, const ip46_address_t * peer_addr) { vnet_sw_interface_t *sw_if = - vnet_get_sw_interface (vnet_get_main (), sw_if_index); + vnet_get_sw_interface_safe (bfd_udp_main.vnet_main, sw_if_index); u8 local_ip_valid = 0; ip_interface_address_t *ia = NULL; if (!sw_if) @@ -1001,7 +1201,8 @@ bfd_udp_input (vlib_main_t * vm, vlib_node_runtime_t * rt, const bfd_pkt_t *pkt = vlib_buffer_get_current (b0); if (bfd_pkt_get_poll (pkt)) { - bfd_init_final_control_frame (vm, b0, bs); + bfd_init_final_control_frame (vm, b0, bfd_udp_main.bfd_main, + bs); if (is_ipv6) { vlib_node_increment_counter (vm, bfd_udp6_input_node.index, @@ -1081,44 +1282,6 @@ VLIB_REGISTER_NODE (bfd_udp6_input_node, static) = { }; /* *INDENT-ON* */ -/** - * @brief swap the source and destination IP addresses in the packet - */ -static int -bfd_echo_address_swap (vlib_buffer_t * b, int is_ipv6) -{ - udp_header_t *dummy = NULL; - if (is_ipv6) - { - ip6_header_t *ip6 = NULL; - bfd_udp6_find_headers (b, &ip6, &dummy); - if (!ip6) - { - return 0; - } - ip6_address_t tmp = ip6->dst_address; - ip6->dst_address = ip6->src_address; - ip6->src_address = tmp; - vlib_buffer_advance (b, - (u8 *) ip6 - (u8 *) vlib_buffer_get_current (b)); - } - else - { - ip4_header_t *ip4 = NULL; - bfd_udp4_find_headers (b, &ip4, &dummy); - if (!ip4) - { - return 0; - } - ip4_address_t tmp = ip4->dst_address; - ip4->dst_address = ip4->src_address; - ip4->src_address = tmp; - vlib_buffer_advance (b, - (u8 *) ip4 - (u8 *) vlib_buffer_get_current (b)); - } - return 1; -} - /* * Process a frame of bfd echo packets * Expect 1 packet / frame @@ -1153,7 +1316,12 @@ bfd_udp_echo_input (vlib_main_t * vm, vlib_node_runtime_t * rt, clib_memcpy (t0->data, vlib_buffer_get_current (b0), len); } - if (bfd_echo_address_swap (b0, is_ipv6)) + if (bfd_consume_echo_pkt (bfd_udp_main.bfd_main, b0)) + { + b0->error = rt->errors[BFD_UDP_ERROR_NONE]; + next0 = BFD_UDP_INPUT_NEXT_NORMAL; + } + else { /* loop back the packet */ b0->error = rt->errors[BFD_UDP_ERROR_NONE]; @@ -1169,11 +1337,6 @@ bfd_udp_echo_input (vlib_main_t * vm, vlib_node_runtime_t * rt, } next0 = BFD_UDP_INPUT_NEXT_REPLY; } - else - { - b0->error = rt->errors[BFD_UDP_ERROR_BAD]; - next0 = BFD_UDP_INPUT_NEXT_NORMAL; - } vlib_set_next_frame_buffer (vm, rt, next0, bi0); @@ -1294,6 +1457,7 @@ bfd_udp_init (vlib_main_t * vm) mhash_init (&bfd_udp_main.bfd_session_idx_by_bfd_key, sizeof (uword), sizeof (bfd_udp_key_t)); bfd_udp_main.bfd_main = &bfd_main; + bfd_udp_main.vnet_main = vnet_get_main (); udp_register_dst_port (vm, UDP_DST_PORT_bfd4, bfd_udp4_input_node.index, 1); udp_register_dst_port (vm, UDP_DST_PORT_bfd6, bfd_udp6_input_node.index, 0); udp_register_dst_port (vm, UDP_DST_PORT_bfd_echo4, diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index 502e2314..ce2ee3cb 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -22,6 +22,7 @@ #include #include #include +#include /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { @@ -49,10 +50,17 @@ typedef struct struct bfd_session_s; -void bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, - const struct bfd_session_s *bs); -void bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, - const struct bfd_session_s *bs); +int bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, + const struct bfd_session_s *bs, int is_echo); +int bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, + const struct bfd_session_s *bs, int is_echo); + +/** + * @brief check if the bfd udp layer is echo-capable at this time + * + * @return 1 if available, 0 otherwise + */ +int bfd_udp_is_echo_available (bfd_transport_e transport); #endif /* __included_bfd_udp_h__ */ diff --git a/test/bfd.py b/test/bfd.py index 8bd9f9a3..b467cc79 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -152,6 +152,27 @@ class BFD(Packet): bind_layers(UDP, BFD, dport=BFD.udp_dport) +class BFD_vpp_echo(Packet): + """ BFD echo packet as used by VPP (non-rfc, as rfc doesn't define one) """ + + udp_dport = 3785 #: BFD echo destination port per RFC 5881 + name = "BFD_VPP_ECHO" + + fields_desc = [ + BitField("discriminator", 0, 32), + BitField("expire_time_clocks", 0, 64), + BitField("checksum", 0, 64) + ] + + def mysummary(self): + return self.sprintf( + "BFD_VPP_ECHO(disc=%BFD_VPP_ECHO.discriminator%," + "expire_time_clocks=%BFD_VPP_ECHO.expire_time_clocks%)") + +# glue the BFD echo packet class to scapy parser +bind_layers(UDP, BFD_vpp_echo, dport=BFD_vpp_echo.udp_dport) + + class VppBFDAuthKey(VppObject): """ Represents BFD authentication key in VPP """ diff --git a/test/framework.py b/test/framework.py index 90e0574a..3bbd37d5 100644 --- a/test/framework.py +++ b/test/framework.py @@ -574,7 +574,7 @@ class VppTestCase(unittest.TestCase): def assert_equal(self, real_value, expected_value, name_or_class=None): if name_or_class is None: - self.assertEqual(real_value, expected_value, msg) + self.assertEqual(real_value, expected_value) return try: msg = "Invalid %s: %d('%s') does not match expected value %d('%s')" diff --git a/test/test_bfd.py b/test/test_bfd.py index 68baf837..ce0cca55 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -6,14 +6,14 @@ import unittest import hashlib import binascii import time -from random import randint, shuffle +from random import randint, shuffle, getrandbits from socket import AF_INET, AF_INET6 from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import UDP, IP from scapy.layers.inet6 import IPv6 from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \ - BFDDiagCode, BFDState + BFDDiagCode, BFDState, BFD_vpp_echo from framework import VppTestCase, VppTestRunner from vpp_pg_interface import CaptureTimeoutError from util import ppp @@ -266,6 +266,7 @@ class BFDTestSession(object): self.my_discriminator = 0 self.desired_min_tx = 100000 self.required_min_rx = 100000 + self.required_min_echo_rx = None self.detect_mult = detect_mult self.diag = BFDDiagCode.no_diagnostic self.your_discriminator = None @@ -280,24 +281,27 @@ class BFDTestSession(object): self.our_seq_number += 1 def update(self, my_discriminator=None, your_discriminator=None, - desired_min_tx=None, required_min_rx=None, detect_mult=None, + desired_min_tx=None, required_min_rx=None, + required_min_echo_rx=None, detect_mult=None, diag=None, state=None, auth_type=None): """ update BFD parameters associated with session """ - if my_discriminator: + if my_discriminator is not None: self.my_discriminator = my_discriminator - if your_discriminator: + if your_discriminator is not None: self.your_discriminator = your_discriminator - if required_min_rx: + if required_min_rx is not None: self.required_min_rx = required_min_rx - if desired_min_tx: + if required_min_echo_rx is not None: + self.required_min_echo_rx = required_min_echo_rx + if desired_min_tx is not None: self.desired_min_tx = desired_min_tx - if detect_mult: + if detect_mult is not None: self.detect_mult = detect_mult - if diag: + if diag is not None: self.diag = diag - if state: + if state is not None: self.state = state - if auth_type: + if auth_type is not None: self.auth_type = auth_type def fill_packet_fields(self, packet): @@ -316,6 +320,11 @@ class BFDTestSession(object): "BFD: setting packet.required_min_rx_interval=%s", self.required_min_rx) bfd.required_min_rx_interval = self.required_min_rx + if self.required_min_echo_rx: + self.test.logger.debug( + "BFD: setting packet.required_min_echo_rx=%s", + self.required_min_echo_rx) + bfd.required_min_echo_rx_interval = self.required_min_echo_rx if self.desired_min_tx: self.test.logger.debug( "BFD: setting packet.desired_min_tx_interval=%s", @@ -579,6 +588,10 @@ class BFD4TestCase(VppTestCase): super(BFD4TestCase, cls).setUpClass() try: cls.create_pg_interfaces([0]) + cls.create_loopback_interfaces([0]) + cls.loopback0 = cls.lo_interfaces[0] + cls.loopback0.config_ip4() + cls.loopback0.admin_up() cls.pg0.config_ip4() cls.pg0.configure_ipv4_neighbors() cls.pg0.admin_up() @@ -646,32 +659,29 @@ class BFD4TestCase(VppTestCase): bfd_session_up(self) self.test_session.update(required_min_rx=0) self.test_session.send_packet() - cap = 2 * self.vpp_session.desired_min_tx *\ - self.test_session.detect_mult - time_mark = time.time() - count = 0 - # busy wait here, trying to collect a packet or event, vpp is not - # allowed to send packets and the session will timeout first - so the - # Up->Down event must arrive before any packets do - while time.time() < time_mark + cap / USEC_IN_SEC: + for dummy in range(self.test_session.detect_mult): + self.sleep(self.vpp_session.required_min_rx / USEC_IN_SEC, + "sleep before transmitting bfd packet") + self.test_session.send_packet() try: - p = wait_for_bfd_packet( - self, timeout=0, - pcap_time_min=time_mark - self.vpp_clock_offset) + p = wait_for_bfd_packet(self, timeout=0) self.logger.error(ppp("Received unexpected packet:", p)) - count += 1 except CaptureTimeoutError: pass - events = self.vapi.collect_events() - if len(events) > 0: - verify_event(self, events[0], BFDState.down) - break - self.assert_equal(count, 0, "number of packets received") + self.assert_equal( + len(self.vapi.collect_events()), 0, "number of bfd events") + self.test_session.update(required_min_rx=100000) + for dummy in range(3): + self.test_session.send_packet() + wait_for_bfd_packet( + self, timeout=self.test_session.required_min_rx / USEC_IN_SEC) + self.assert_equal( + len(self.vapi.collect_events()), 0, "number of bfd events") def test_conn_down(self): """ verify session goes down after inactivity """ bfd_session_up(self) - detection_time = self.vpp_session.detect_mult *\ + detection_time = self.test_session.detect_mult *\ self.vpp_session.required_min_rx / USEC_IN_SEC self.sleep(detection_time, "waiting for BFD session time-out") e = self.vapi.wait_for_event(1, "bfd_udp_session_details") @@ -799,7 +809,7 @@ class BFD4TestCase(VppTestCase): before = time.time() e = self.vapi.wait_for_event(1, "bfd_udp_session_details") after = time.time() - detection_time = self.vpp_session.detect_mult *\ + detection_time = self.test_session.detect_mult *\ self.vpp_session.required_min_rx / USEC_IN_SEC self.assert_in_range(after - before, 0.9 * detection_time, @@ -830,6 +840,71 @@ class BFD4TestCase(VppTestCase): self.assertNotIn("P", p.sprintf("%BFD.flags%"), "Poll bit not set in BFD packet") + def test_queued_poll(self): + """ test poll sequence queueing """ + bfd_session_up(self) + p = wait_for_bfd_packet(self) + self.vpp_session.modify_parameters( + required_min_rx=2 * self.vpp_session.required_min_rx) + p = wait_for_bfd_packet(self) + poll_sequence_start = time.time() + poll_sequence_length_min = 0.5 + send_final_after = time.time() + poll_sequence_length_min + # poll bit needs to be set + self.assertIn("P", p.sprintf("%BFD.flags%"), + "Poll bit not set in BFD packet") + self.assert_equal(p[BFD].required_min_rx_interval, + self.vpp_session.required_min_rx, + "BFD required min rx interval") + self.vpp_session.modify_parameters( + required_min_rx=2 * self.vpp_session.required_min_rx) + # 2nd poll sequence should be queued now + # don't send the reply back yet, wait for some time to emulate + # longer round-trip time + packet_count = 0 + while time.time() < send_final_after: + self.test_session.send_packet() + p = wait_for_bfd_packet(self) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.assert_equal(p[BFD].required_min_rx_interval, + self.vpp_session.required_min_rx, + "BFD required min rx interval") + packet_count += 1 + # poll bit must be set + self.assertIn("P", p.sprintf("%BFD.flags%"), + "Poll bit not set in BFD packet") + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + # finish 1st with final + poll_sequence_length = time.time() - poll_sequence_start + # vpp must wait for some time before starting new poll sequence + poll_no_2_started = False + for dummy in range(2 * packet_count): + p = wait_for_bfd_packet(self) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + if "P" in p.sprintf("%BFD.flags%"): + poll_no_2_started = True + if time.time() < poll_sequence_start + poll_sequence_length: + raise Exception("VPP started 2nd poll sequence too soon") + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + break + else: + self.test_session.send_packet() + self.assertTrue(poll_no_2_started, "2nd poll sequence not performed") + # finish 2nd with final + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + p = wait_for_bfd_packet(self) + # poll bit must not be set + self.assertNotIn("P", p.sprintf("%BFD.flags%"), + "Poll bit set in BFD packet") + def test_no_periodic_if_remote_demand(self): """ no periodic frames outside poll sequence if remote demand set """ bfd_session_up(self) @@ -868,7 +943,7 @@ class BFD4TestCase(VppTestCase): echo_packet = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / IP(src=self.pg0.remote_ip4, - dst=self.pg0.local_ip4) / + dst=self.pg0.remote_ip4) / UDP(dport=BFD.udp_dport_echo) / Raw("this should be looped back")) for dummy in range(echo_packet_count): @@ -887,18 +962,236 @@ class BFD4TestCase(VppTestCase): self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC") ip = p[IP] self.assert_equal(self.pg0.remote_ip4, ip.dst, "Destination IP") - self.assert_equal(self.pg0.local_ip4, ip.src, "Destination IP") + self.assert_equal(self.pg0.remote_ip4, ip.src, "Destination IP") udp = p[UDP] self.assert_equal(udp.dport, BFD.udp_dport_echo, "UDP destination port") self.assert_equal(udp.sport, udp_sport_rx, "UDP source port") udp_sport_rx += 1 - self.assertTrue(p.haslayer(Raw) and p[Raw] == echo_packet[Raw], - "Received packet is not the echo packet sent") + # need to compare the hex payload here, otherwise BFD_vpp_echo + # gets in way + self.assertEqual(str(p[UDP].payload), + str(echo_packet[UDP].payload), + "Received packet is not the echo packet sent") self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== " "ECHO packet identifier for test purposes)") + def test_echo(self): + """ echo function """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.test_session.send_packet() + detection_time = self.test_session.detect_mult *\ + self.vpp_session.required_min_rx / USEC_IN_SEC + # echo shouldn't work without echo source set + for dummy in range(3): + sleep = 0.75 * detection_time + self.sleep(sleep, "delay before sending bfd packet") + self.test_session.send_packet() + p = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.assert_equal(p[BFD].required_min_rx_interval, + self.vpp_session.required_min_rx, + "BFD required min rx interval") + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + # should be turned on - loopback echo packets + for dummy in range(3): + loop_until = time.time() + 0.75 * detection_time + while time.time() < loop_until: + p = self.pg0.wait_for_packet(1) + self.logger.debug(ppp("Got packet:", p)) + if p[UDP].dport == BFD.udp_dport_echo: + self.assert_equal( + p[IP].dst, self.pg0.local_ip4, "BFD ECHO dst IP") + self.assertNotEqual(p[IP].src, self.loopback0.local_ip4, + "BFD ECHO src IP equal to loopback IP") + self.logger.debug(ppp("Looping back packet:", p)) + self.pg0.add_stream(p) + self.pg_start() + elif p.haslayer(BFD): + self.assertGreaterEqual(p[BFD].required_min_rx_interval, + 1000000) + if "P" in p.sprintf("%BFD.flags%"): + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + else: + raise Exception(ppp("Received unknown packet:", p)) + + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.test_session.send_packet() + + def test_echo_fail(self): + """ session goes down if echo function fails """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.test_session.send_packet() + detection_time = self.test_session.detect_mult *\ + self.vpp_session.required_min_rx / USEC_IN_SEC + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + # echo function should be used now, but we will drop the echo packets + verified_diag = False + for dummy in range(3): + loop_until = time.time() + 0.75 * detection_time + while time.time() < loop_until: + p = self.pg0.wait_for_packet(1) + self.logger.debug(ppp("Got packet:", p)) + if p[UDP].dport == BFD.udp_dport_echo: + # dropped + pass + elif p.haslayer(BFD): + if "P" in p.sprintf("%BFD.flags%"): + self.assertGreaterEqual( + p[BFD].required_min_rx_interval, + 1000000) + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + if p[BFD].state == BFDState.down: + self.assert_equal(p[BFD].diag, + BFDDiagCode.echo_function_failed, + BFDDiagCode) + verified_diag = True + else: + raise Exception(ppp("Received unknown packet:", p)) + self.test_session.send_packet() + events = self.vapi.collect_events() + self.assert_equal(len(events), 1, "number of bfd events") + self.assert_equal(events[0].state, BFDState.down, BFDState) + self.assertTrue(verified_diag, "Incorrect diagnostics code received") + + def test_echo_stop(self): + """ echo function stops if peer sets required min echo rx zero """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.test_session.send_packet() + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + # wait for first echo packet + while True: + p = self.pg0.wait_for_packet(1) + self.logger.debug(ppp("Got packet:", p)) + if p[UDP].dport == BFD.udp_dport_echo: + self.logger.debug(ppp("Looping back packet:", p)) + self.pg0.add_stream(p) + self.pg_start() + break + elif p.haslayer(BFD): + # ignore BFD + pass + else: + raise Exception(ppp("Received unknown packet:", p)) + self.test_session.update(required_min_echo_rx=0) + self.test_session.send_packet() + # echo packets shouldn't arrive anymore + for dummy in range(5): + wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.test_session.send_packet() + events = self.vapi.collect_events() + self.assert_equal(len(events), 0, "number of bfd events") + + def test_stale_echo(self): + """ stale echo packets don't keep a session up """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + self.test_session.send_packet() + # should be turned on - loopback echo packets + echo_packet = None + timeout_at = None + timeout_ok = False + for dummy in range(10 * self.vpp_session.detect_mult): + p = self.pg0.wait_for_packet(1) + if p[UDP].dport == BFD.udp_dport_echo: + if echo_packet is None: + self.logger.debug(ppp("Got first echo packet:", p)) + echo_packet = p + timeout_at = time.time() + self.vpp_session.detect_mult * \ + self.test_session.required_min_echo_rx / USEC_IN_SEC + else: + self.logger.debug(ppp("Got followup echo packet:", p)) + self.logger.debug(ppp("Looping back first echo packet:", p)) + self.pg0.add_stream(echo_packet) + self.pg_start() + elif p.haslayer(BFD): + self.logger.debug(ppp("Got packet:", p)) + if "P" in p.sprintf("%BFD.flags%"): + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + if p[BFD].state == BFDState.down: + self.assertIsNotNone( + timeout_at, + "Session went down before first echo packet received") + now = time.time() + self.assertGreaterEqual( + now, timeout_at, + "Session timeout at %s, but is expected at %s" % + (now, timeout_at)) + self.assert_equal(p[BFD].diag, + BFDDiagCode.echo_function_failed, + BFDDiagCode) + events = self.vapi.collect_events() + self.assert_equal(len(events), 1, "number of bfd events") + self.assert_equal(events[0].state, BFDState.down, BFDState) + timeout_ok = True + break + else: + raise Exception(ppp("Received unknown packet:", p)) + self.test_session.send_packet() + self.assertTrue(timeout_ok, "Expected timeout event didn't occur") + + def test_invalid_echo_checksum(self): + """ echo packets with invalid checksum don't keep a session up """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + self.test_session.send_packet() + # should be turned on - loopback echo packets + timeout_at = None + timeout_ok = False + for dummy in range(10 * self.vpp_session.detect_mult): + p = self.pg0.wait_for_packet(1) + if p[UDP].dport == BFD.udp_dport_echo: + self.logger.debug(ppp("Got echo packet:", p)) + if timeout_at is None: + timeout_at = time.time() + self.vpp_session.detect_mult * \ + self.test_session.required_min_echo_rx / USEC_IN_SEC + p[BFD_vpp_echo].checksum = getrandbits(64) + self.logger.debug(ppp("Looping back modified echo packet:", p)) + self.pg0.add_stream(p) + self.pg_start() + elif p.haslayer(BFD): + self.logger.debug(ppp("Got packet:", p)) + if "P" in p.sprintf("%BFD.flags%"): + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + if p[BFD].state == BFDState.down: + self.assertIsNotNone( + timeout_at, + "Session went down before first echo packet received") + now = time.time() + self.assertGreaterEqual( + now, timeout_at, + "Session timeout at %s, but is expected at %s" % + (now, timeout_at)) + self.assert_equal(p[BFD].diag, + BFDDiagCode.echo_function_failed, + BFDDiagCode) + events = self.vapi.collect_events() + self.assert_equal(len(events), 1, "number of bfd events") + self.assert_equal(events[0].state, BFDState.down, BFDState) + timeout_ok = True + break + else: + raise Exception(ppp("Received unknown packet:", p)) + self.test_session.send_packet() + self.assertTrue(timeout_ok, "Expected timeout event didn't occur") + def test_admin_up_down(self): + """ put session admin-up and admin-down """ bfd_session_up(self) self.vpp_session.admin_down() self.pg0.enable_capture() @@ -931,6 +1224,42 @@ class BFD4TestCase(VppTestCase): e = self.vapi.wait_for_event(1, "bfd_udp_session_details") verify_event(self, e, expected_state=BFDState.up) + def test_config_change_remote_demand(self): + """ configuration change while peer in demand mode """ + bfd_session_up(self) + demand = self.test_session.create_packet() + demand[BFD].flags = "D" + self.test_session.send_packet(demand) + self.vpp_session.modify_parameters( + required_min_rx=2 * self.vpp_session.required_min_rx) + p = wait_for_bfd_packet(self) + # poll bit must be set + self.assertIn("P", p.sprintf("%BFD.flags%"), "Poll bit not set") + # terminate poll sequence + final = self.test_session.create_packet() + final[BFD].flags = "D+F" + self.test_session.send_packet(final) + # vpp should be quiet now again + transmit_time = 0.9 \ + * max(self.vpp_session.required_min_rx, + self.test_session.desired_min_tx) \ + / USEC_IN_SEC + count = 0 + for dummy in range(self.test_session.detect_mult * 2): + time.sleep(transmit_time) + self.test_session.send_packet(demand) + try: + p = wait_for_bfd_packet(self, timeout=0) + self.logger.error(ppp("Received unexpected packet:", p)) + count += 1 + except CaptureTimeoutError: + pass + events = self.vapi.collect_events() + for e in events: + self.logger.error("Received unexpected event: %s", e) + self.assert_equal(count, 0, "number of packets received") + self.assert_equal(len(events), 0, "number of events received") + class BFD6TestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) (IPv6) """ @@ -949,6 +1278,10 @@ class BFD6TestCase(VppTestCase): cls.pg0.configure_ipv6_neighbors() cls.pg0.admin_up() cls.pg0.resolve_ndp() + cls.create_loopback_interfaces([0]) + cls.loopback0 = cls.lo_interfaces[0] + cls.loopback0.config_ip6() + cls.loopback0.admin_up() except Exception: super(BFD6TestCase, cls).tearDownClass() @@ -1003,7 +1336,7 @@ class BFD6TestCase(VppTestCase): echo_packet = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / IPv6(src=self.pg0.remote_ip6, - dst=self.pg0.local_ip6) / + dst=self.pg0.remote_ip6) / UDP(dport=BFD.udp_dport_echo) / Raw("this should be looped back")) for dummy in range(echo_packet_count): @@ -1022,17 +1355,68 @@ class BFD6TestCase(VppTestCase): self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC") ip = p[IPv6] self.assert_equal(self.pg0.remote_ip6, ip.dst, "Destination IP") - self.assert_equal(self.pg0.local_ip6, ip.src, "Destination IP") + self.assert_equal(self.pg0.remote_ip6, ip.src, "Destination IP") udp = p[UDP] self.assert_equal(udp.dport, BFD.udp_dport_echo, "UDP destination port") self.assert_equal(udp.sport, udp_sport_rx, "UDP source port") udp_sport_rx += 1 - self.assertTrue(p.haslayer(Raw) and p[Raw] == echo_packet[Raw], - "Received packet is not the echo packet sent") + # need to compare the hex payload here, otherwise BFD_vpp_echo + # gets in way + self.assertEqual(str(p[UDP].payload), + str(echo_packet[UDP].payload), + "Received packet is not the echo packet sent") + self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== " + "ECHO packet identifier for test purposes)") self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== " "ECHO packet identifier for test purposes)") + def test_echo(self): + """ echo function used """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.test_session.send_packet() + detection_time = self.test_session.detect_mult *\ + self.vpp_session.required_min_rx / USEC_IN_SEC + # echo shouldn't work without echo source set + for dummy in range(3): + sleep = 0.75 * detection_time + self.sleep(sleep, "delay before sending bfd packet") + self.test_session.send_packet() + p = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.assert_equal(p[BFD].required_min_rx_interval, + self.vpp_session.required_min_rx, + "BFD required min rx interval") + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + # should be turned on - loopback echo packets + for dummy in range(3): + loop_until = time.time() + 0.75 * detection_time + while time.time() < loop_until: + p = self.pg0.wait_for_packet(1) + self.logger.debug(ppp("Got packet:", p)) + if p[UDP].dport == BFD.udp_dport_echo: + self.assert_equal( + p[IPv6].dst, self.pg0.local_ip6, "BFD ECHO dst IP") + self.assertNotEqual(p[IPv6].src, self.loopback0.local_ip6, + "BFD ECHO src IP equal to loopback IP") + self.logger.debug(ppp("Looping back packet:", p)) + self.pg0.add_stream(p) + self.pg_start() + elif p.haslayer(BFD): + self.assertGreaterEqual(p[BFD].required_min_rx_interval, + 1000000) + if "P" in p.sprintf("%BFD.flags%"): + final = self.test_session.create_packet() + final[BFD].flags = "F" + self.test_session.send_packet(final) + else: + raise Exception(ppp("Received unknown packet:", p)) + + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.test_session.send_packet() + class BFDSHA1TestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """ @@ -1121,7 +1505,7 @@ class BFDSHA1TestCase(VppTestCase): self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) def test_send_bad_seq_number(self): - """ session is not kept alive by msgs with bad seq numbers""" + """ session is not kept alive by msgs with bad sequence numbers""" key = self.factory.create_random_key( self, BFDAuthType.meticulous_keyed_sha1) key.add_vpp_config() @@ -1133,16 +1517,13 @@ class BFDSHA1TestCase(VppTestCase): self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=self.vpp_session.bfd_key_id) bfd_session_up(self) - detection_time = self.vpp_session.detect_mult *\ + detection_time = self.test_session.detect_mult *\ self.vpp_session.required_min_rx / USEC_IN_SEC - session_timeout = time.time() + detection_time - while time.time() < session_timeout: - self.assert_equal(len(self.vapi.collect_events()), 0, - "number of bfd events") - wait_for_bfd_packet(self) + send_until = time.time() + 2 * detection_time + while time.time() < send_until: self.test_session.send_packet() - wait_for_bfd_packet(self) - self.test_session.send_packet() + self.sleep(0.7 * self.vpp_session.required_min_rx / USEC_IN_SEC, + "time between bfd packets") e = self.vapi.collect_events() # session should be down now, because the sequence numbers weren't # updated @@ -1250,7 +1631,7 @@ class BFDSHA1TestCase(VppTestCase): bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0) bfd_session_up(self) # don't send any packets for 2*detection_time - detection_time = self.vpp_session.detect_mult *\ + detection_time = self.test_session.detect_mult *\ self.vpp_session.required_min_rx / USEC_IN_SEC self.sleep(detection_time, "simulating peer restart") events = self.vapi.collect_events() diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 59e58ad0..dd9baff1 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1117,6 +1117,10 @@ class VppPapiProvider(object): def bfd_auth_keys_dump(self): return self.api(self.papi.bfd_auth_keys_dump, {}) + def bfd_udp_set_echo_source(self, sw_if_index): + return self.api(self.papi.bfd_udp_set_echo_source, + {'sw_if_index': sw_if_index}) + def classify_add_del_table( self, is_add, -- cgit 1.2.3-korg From 738844871220f853629504f61c248f0c9402dc77 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 23 Feb 2017 09:26:30 +0100 Subject: BFD: command line interface Implement command line interface to the BFD binary APIs. Add corresponding unit tests. Change-Id: Ia0542d0bc4c8d78e6f7b777a08fd94ebfe4d524f Signed-off-by: Klement Sekera --- src/vnet.am | 1 + src/vnet/api_errno.h | 1 - src/vnet/bfd/bfd.api | 28 +- src/vnet/bfd/bfd_api.c | 22 +- src/vnet/bfd/bfd_api.h | 1 - src/vnet/bfd/bfd_cli.c | 900 ++++++++++++++++++++++++++++++++++++++++++++ src/vnet/bfd/bfd_main.c | 154 ++++++-- src/vnet/bfd/bfd_main.h | 11 +- src/vnet/bfd/bfd_protocol.c | 6 + src/vnet/bfd/bfd_udp.c | 120 ++---- src/vnet/bfd/bfd_udp.h | 5 + test/bfd.py | 9 +- test/test_bfd.py | 579 +++++++++++++++++++++++++++- test/vpp_object.py | 1 + test/vpp_papi_provider.py | 12 +- 15 files changed, 1659 insertions(+), 191 deletions(-) create mode 100644 src/vnet/bfd/bfd_cli.c (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet.am b/src/vnet.am index 84930f05..d89d516e 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -369,6 +369,7 @@ libvnet_la_SOURCES += \ vnet/bfd/bfd_udp.c \ vnet/bfd/bfd_main.c \ vnet/bfd/bfd_protocol.c \ + vnet/bfd/bfd_cli.c \ vnet/bfd/bfd_api.c API_FILES += vnet/bfd/bfd.api diff --git a/src/vnet/api_errno.h b/src/vnet/api_errno.h index 861a5767..a5bcb377 100644 --- a/src/vnet/api_errno.h +++ b/src/vnet/api_errno.h @@ -51,7 +51,6 @@ _(ADDRESS_LENGTH_MISMATCH, -59, "Address length mismatch") \ _(ADDRESS_NOT_FOUND_FOR_INTERFACE, -60, "Address not found for interface") \ _(ADDRESS_NOT_LINK_LOCAL, -61, "Address not link-local") \ _(IP6_NOT_ENABLED, -62, "ip6 not enabled") \ -_(ADDRESS_MATCHES_INTERFACE_ADDRESS, -63, "Address matches interface address") \ _(IN_PROGRESS, 10, "Operation in progress") \ _(NO_SUCH_NODE, -63, "No such graph node") \ _(NO_SUCH_NODE2, -64, "No such graph node #2") \ diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api index 93bf0fb9..2cdcfad3 100644 --- a/src/vnet/bfd/bfd.api +++ b/src/vnet/bfd/bfd.api @@ -45,7 +45,7 @@ define bfd_udp_del_echo_source u32 context; }; -/** \brief Delete BFD feature response +/** \brief Delete BFD echo source response @param context - sender context, to match reply w/ request @param retval - return code for the request */ @@ -55,32 +55,6 @@ define bfd_udp_del_echo_source_reply i32 retval; }; -/** \brief Get BFD configuration -*/ -define bfd_get_config -{ - u32 client_index; - u32 context; -}; - -/** \brief Get BFD configuration response - @param context - sender context, to match reply w/ request - @param retval - return code for the request - @param slow_timer - slow timer (seconds) - @param min_tx - desired min tx interval - @param min_rx - desired min rx interval - @param detect_mult - desired detection multiplier -*/ -define bfd_get_config_reply -{ - u32 client_index; - u32 context; - u32 slow_timer; - u32 min_tx; - u32 min_rx; - u8 detect_mult; -}; - /** \brief Add UDP BFD session on interface @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c index 6632eae4..e64df48e 100644 --- a/src/vnet/bfd/bfd_api.c +++ b/src/vnet/bfd/bfd_api.c @@ -154,6 +154,21 @@ send_bfd_udp_session_details (unix_shared_memory_queue_t * q, u32 context, bfd_udp_key_t *key = &bus->key; mp->sw_if_index = clib_host_to_net_u32 (key->sw_if_index); mp->is_ipv6 = !(ip46_address_is_ip4 (&key->local_addr)); + if ((!bs->auth.is_delayed && bs->auth.curr_key) || + (bs->auth.is_delayed && bs->auth.next_key)) + { + mp->is_authenticated = 1; + } + if (bs->auth.is_delayed && bs->auth.next_key) + { + mp->bfd_key_id = bs->auth.next_bfd_key_id; + mp->conf_key_id = clib_host_to_net_u32 (bs->auth.next_key->conf_key_id); + } + else if (!bs->auth.is_delayed && bs->auth.curr_key) + { + mp->bfd_key_id = bs->auth.curr_bfd_key_id; + mp->conf_key_id = clib_host_to_net_u32 (bs->auth.curr_key->conf_key_id); + } if (mp->is_ipv6) { clib_memcpy (mp->local_addr, &key->local_addr, @@ -289,10 +304,9 @@ vl_api_bfd_udp_auth_activate_t_handler (vl_api_bfd_udp_auth_activate_t * mp) BFD_UDP_API_PARAM_COMMON_CODE; - rv = - bfd_udp_auth_activate (BFD_UDP_API_PARAM_FROM_MP (mp), - clib_net_to_host_u32 (mp->conf_key_id), - mp->bfd_key_id, mp->is_delayed); + rv = bfd_udp_auth_activate (BFD_UDP_API_PARAM_FROM_MP (mp), + clib_net_to_host_u32 (mp->conf_key_id), + mp->bfd_key_id, mp->is_delayed); BAD_SW_IF_INDEX_LABEL; REPLY_MACRO (VL_API_BFD_UDP_AUTH_ACTIVATE_REPLY); diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h index 63d4a62e..35ad3cda 100644 --- a/src/vnet/bfd/bfd_api.h +++ b/src/vnet/bfd/bfd_api.h @@ -22,7 +22,6 @@ #include #include #include -#include #define foreach_bfd_transport(F) \ F (UDP4, "ip4-rewrite") \ diff --git a/src/vnet/bfd/bfd_cli.c b/src/vnet/bfd/bfd_cli.c new file mode 100644 index 00000000..a3736d98 --- /dev/null +++ b/src/vnet/bfd/bfd_cli.c @@ -0,0 +1,900 @@ +/* + * Copyright (c) 2011-2016 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. + */ +/** + * @file + * @brief BFD CLI implementation + */ + +#include +#include +#include +#include +#include +#include +#include + +static u8 * +format_bfd_session_cli (u8 * s, va_list * args) +{ + bfd_main_t *bm = va_arg (*args, bfd_main_t *); + bfd_session_t *bs = va_arg (*args, bfd_session_t *); + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + s = format (s, "%=10u %-32s %20U %20U\n", bs->bs_idx, "IPv4 address", + format_ip4_address, bs->udp.key.local_addr.ip4.as_u8, + format_ip4_address, bs->udp.key.peer_addr.ip4.as_u8); + break; + case BFD_TRANSPORT_UDP6: + s = format (s, "%=10u %-32s %20U %20U\n", bs->bs_idx, "IPv6 address", + format_ip6_address, &bs->udp.key.local_addr.ip6, + format_ip6_address, &bs->udp.key.peer_addr.ip6); + break; + } + s = format (s, "%10s %-32s %20s %20s\n", "", "Session state", + bfd_state_string (bs->local_state), + bfd_state_string (bs->remote_state)); + s = format (s, "%10s %-32s %20s %20s\n", "", "Diagnostic code", + bfd_diag_code_string (bs->local_diag), + bfd_diag_code_string (bs->remote_diag)); + s = format (s, "%10s %-32s %20u %20u\n", "", "Detect multiplier", + bs->local_detect_mult, bs->remote_detect_mult); + s = format (s, "%10s %-32s %20u %20u\n", "", + "Required Min Rx Interval (usec)", + bs->config_required_min_rx_usec, bs->remote_min_rx_usec); + s = format (s, "%10s %-32s %20u %20u\n", "", + "Desired Min Tx Interval (usec)", + bs->config_desired_min_tx_usec, bfd_clocks_to_usec (bm, + bs->remote_desired_min_tx_clocks)); + s = + format (s, "%10s %-32s %20u\n", "", "Transmit interval", + bfd_clocks_to_usec (bm, bs->transmit_interval_clocks)); + s = + format (s, "%10s %-32s %20s %20s\n", "", "Demand mode", "no", + bs->remote_demand ? "yes" : "no"); + s = + format (s, "%10s %-32s %20s\n", "", "Poll state", + bfd_poll_state_string (bs->poll_state)); + if (bs->auth.curr_key) + { + s = format (s, "%10s %-32s %20u\n", "", "Authentication config key ID", + bs->auth.curr_key->conf_key_id); + s = format (s, "%10s %-32s %20u\n", "", "Authentication BFD key ID", + bs->auth.curr_bfd_key_id); + } + return s; +} + +static clib_error_t * +show_bfd (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + bfd_main_t *bm = &bfd_main; + bfd_session_t *bs = NULL; + + if (unformat (input, "keys")) + { + bfd_auth_key_t *key = NULL; + u8 *s = format (NULL, "%=10s %=25s %=10s\n", "Configuration Key ID", + "Type", "Use Count"); + /* *INDENT-OFF* */ + pool_foreach (key, bm->auth_keys, { + s = format (s, "%10u %-25s %10u\n", key->conf_key_id, + bfd_auth_type_str (key->auth_type), key->use_count); + }); + /* *INDENT-ON* */ + vlib_cli_output (vm, "%v\n", s); + vlib_cli_output (vm, "Number of configured BFD keys: %lu\n", + (u64) pool_elts (bm->auth_keys)); + } + else if (unformat (input, "sessions")) + { + u8 *s = format (NULL, "%=10s %=32s %=20s %=20s\n", "Index", "Property", + "Local value", "Remote value"); + /* *INDENT-OFF* */ + pool_foreach (bs, bm->sessions, + { s = format (s, "%U", format_bfd_session_cli, bm, bs); }); + /* *INDENT-ON* */ + vlib_cli_output (vm, "%v", s); + vec_free (s); + vlib_cli_output (vm, "Number of configured BFD sessions: %lu\n", + (u64) pool_elts (bm->sessions)); + } + else if (unformat (input, "echo-source")) + { + int is_set; + u32 sw_if_index; + int have_usable_ip4; + ip4_address_t ip4; + int have_usable_ip6; + ip6_address_t ip6; + bfd_udp_get_echo_source (&is_set, &sw_if_index, &have_usable_ip4, &ip4, + &have_usable_ip6, &ip6); + if (is_set) + { + vnet_sw_interface_t *sw_if = + vnet_get_sw_interface_safe (&vnet_main, sw_if_index); + vnet_hw_interface_t *hw_if = + vnet_get_hw_interface (&vnet_main, sw_if->hw_if_index); + u8 *s = format (NULL, "UDP echo source is: %v\n", hw_if->name); + s = format (s, "IPv4 address usable as echo source: "); + if (have_usable_ip4) + { + s = format (s, "%U\n", format_ip4_address, &ip4); + } + else + { + s = format (s, "none\n"); + } + s = format (s, "IPv6 address usable as echo source: "); + if (have_usable_ip6) + { + s = format (s, "%U\n", format_ip6_address, &ip6); + } + else + { + s = format (s, "none\n"); + } + vlib_cli_output (vm, "%v", s); + vec_free (s); + } + else + { + vlib_cli_output (vm, "UDP echo source is not set.\n"); + } + } + else + { + vlib_cli_output (vm, "Number of configured BFD sessions: %lu\n", + (u64) pool_elts (bm->sessions)); + vlib_cli_output (vm, "Number of configured BFD keys: %lu\n", + (u64) pool_elts (bm->auth_keys)); + } + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_bfd_command, static) = { + .path = "show bfd", + .short_help = "show bfd [keys|sessions|echo-source]", + .function = show_bfd, +}; +/* *INDENT-ON* */ + +static u8 * +format_vnet_api_errno (u8 * s, va_list * args) +{ + vnet_api_error_t api_error = va_arg (*args, vnet_api_error_t); +#define _(a, b, c) \ + case b: \ + s = format (s, "%s", c); \ + break; + switch (api_error) + { + foreach_vnet_api_error default:s = format (s, "UNKNOWN"); + break; + } + return s; +} + +static clib_error_t * +bfd_cli_key_add (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; + int have_key_id = 0; + u32 key_id = 0; + u8 *vec_auth_type = NULL; + bfd_auth_type_e auth_type = BFD_AUTH_TYPE_reserved; + u8 *secret = NULL; + static const u8 keyed_sha1[] = "keyed-sha1"; + static const u8 meticulous_keyed_sha1[] = "meticulous-keyed-sha1"; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "conf-key-id %u", &key_id)) + { + have_key_id = 1; + } + else if (unformat (input, "type %U", unformat_token, "a-zA-Z0-9-", + &vec_auth_type)) + { + if (vec_len (vec_auth_type) == sizeof (keyed_sha1) - 1 && + 0 == memcmp (vec_auth_type, keyed_sha1, + sizeof (keyed_sha1) - 1)) + { + auth_type = BFD_AUTH_TYPE_keyed_sha1; + } + else if (vec_len (vec_auth_type) == + sizeof (meticulous_keyed_sha1) - 1 && + 0 == memcmp (vec_auth_type, meticulous_keyed_sha1, + sizeof (meticulous_keyed_sha1) - 1)) + { + auth_type = BFD_AUTH_TYPE_meticulous_keyed_sha1; + } + else + { + ret = clib_error_return (0, "invalid type `%v'", vec_auth_type); + goto out; + } + } + else if (unformat (input, "secret %U", unformat_hex_string, &secret)) + { + /* nothing to do here */ + } + else + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + if (!have_key_id) + { + ret = + clib_error_return (0, "required parameter missing: `conf-key-id'"); + goto out; + } + if (!vec_auth_type) + { + ret = clib_error_return (0, "required parameter missing: `type'"); + goto out; + } + if (!secret) + { + ret = clib_error_return (0, "required parameter missing: `secret'"); + goto out; + } + + vnet_api_error_t rv = + bfd_auth_set_key (key_id, auth_type, vec_len (secret), secret); + if (rv) + { + ret = + clib_error_return (0, "`bfd_auth_set_key' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + } + +out: + vec_free (vec_auth_type); + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_key_add_command, static) = { + .path = "bfd key set", + .short_help = "bfd key set" + " conf-key-id " + " type " + " secret ", + .function = bfd_cli_key_add, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_key_del (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; + u32 key_id = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (!unformat (input, "conf-key-id %u", &key_id)) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + vnet_api_error_t rv = bfd_auth_del_key (key_id); + if (rv) + { + ret = + clib_error_return (0, "`bfd_auth_del_key' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_key_del_command, static) = { + .path = "bfd key del", + .short_help = "bfd key del conf-key-id ", + .function = bfd_cli_key_del, +}; +/* *INDENT-ON* */ + +#define INTERFACE_STR "interface" +#define LOCAL_ADDR_STR "local-addr" +#define PEER_ADDR_STR "peer-addr" +#define CONF_KEY_ID_STR "conf-key-id" +#define BFD_KEY_ID_STR "bfd-key-id" +#define DESIRED_MIN_TX_STR "desired-min-tx" +#define REQUIRED_MIN_RX_STR "required-min-rx" +#define DETECT_MULT_STR "detect-mult" +#define ADMIN_STR "admin" +#define DELAYED_STR "delayed" + +static const unsigned mandatory = 1; +static const unsigned optional = 0; + +#define DECLARE(t, n, s, r, ...) \ + int have_##n = 0; \ + t n; + +#define UNFORMAT(t, n, s, r, ...) \ + if (unformat (input, s " " __VA_ARGS__, &n)) \ + { \ + something_parsed = 1; \ + have_##n = 1; \ + } + +#define CHECK_MANDATORY(t, n, s, r, ...) \ + if (mandatory == r && !have_##n) \ + { \ + ret = clib_error_return (0, "Required parameter `%s' missing.", n); \ + goto out; \ + } + +static clib_error_t * +bfd_cli_udp_session_add (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_session_add_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) \ + F (ip46_address_t, local_addr, LOCAL_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (ip46_address_t, peer_addr, PEER_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (u32, desired_min_tx, DESIRED_MIN_TX_STR, mandatory, "%u") \ + F (u32, required_min_rx, REQUIRED_MIN_RX_STR, mandatory, "%u") \ + F (u32, detect_mult, DETECT_MULT_STR, mandatory, "%u") \ + F (u32, conf_key_id, CONF_KEY_ID_STR, optional, "%u") \ + F (u32, bfd_key_id, BFD_KEY_ID_STR, optional, "%u") + + foreach_bfd_cli_udp_session_add_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_session_add_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_session_add_cli_param (CHECK_MANDATORY); + + if (1 == have_conf_key_id + have_bfd_key_id) + { + ret = clib_error_return (0, "Incompatible parameter combination, `%s' " + "and `%s' must be either both specified or none", + CONF_KEY_ID_STR, BFD_KEY_ID_STR); + goto out; + } + + if (detect_mult > 255) + { + ret = clib_error_return (0, "%s value `%u' out of range <1,255>", + DETECT_MULT_STR, detect_mult); + goto out; + } + + if (have_bfd_key_id && bfd_key_id > 255) + { + ret = clib_error_return (0, "%s value `%u' out of range <1,255>", + BFD_KEY_ID_STR, bfd_key_id); + goto out; + } + + vnet_api_error_t rv = + bfd_udp_add_session (sw_if_index, &local_addr, &peer_addr, desired_min_tx, + required_min_rx, + detect_mult, have_conf_key_id, conf_key_id, + bfd_key_id); + if (rv) + { + ret = + clib_error_return (0, + "`bfd_add_add_session' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_session_add_command, static) = { + .path = "bfd udp session add", + .short_help = "bfd udp session add" + " interface " + " local-addr " + " peer-addr " + " desired-min-tx " + " required-min-rx " + " detect-mult " + "[" + " conf-key-id " + " bfd-key-id " + "]", + .function = bfd_cli_udp_session_add, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_udp_session_mod (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_session_mod_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) \ + F (ip46_address_t, local_addr, LOCAL_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (ip46_address_t, peer_addr, PEER_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (u32, desired_min_tx, DESIRED_MIN_TX_STR, mandatory, "%u") \ + F (u32, required_min_rx, REQUIRED_MIN_RX_STR, mandatory, "%u") \ + F (u32, detect_mult, DETECT_MULT_STR, mandatory, "%u") + + foreach_bfd_cli_udp_session_mod_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_session_mod_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_session_mod_cli_param (CHECK_MANDATORY); + + if (detect_mult > 255) + { + ret = clib_error_return (0, "%s value `%u' out of range <1,255>", + DETECT_MULT_STR, detect_mult); + goto out; + } + + vnet_api_error_t rv = + bfd_udp_mod_session (sw_if_index, &local_addr, &peer_addr, + desired_min_tx, required_min_rx, detect_mult); + if (rv) + { + ret = + clib_error_return (0, + "`bfd_udp_mod_session' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_session_mod_command, static) = { + .path = "bfd udp session mod", + .short_help = "bfd udp session mod interface" + " local-addr" + " peer-addr" + " desired-min-tx" + " required-min-rx" + " detect-mult" + " ", + .function = bfd_cli_udp_session_mod, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_udp_session_del (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_session_del_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) \ + F (ip46_address_t, local_addr, LOCAL_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (ip46_address_t, peer_addr, PEER_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) + + foreach_bfd_cli_udp_session_del_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_session_del_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_session_del_cli_param (CHECK_MANDATORY); + + vnet_api_error_t rv = + bfd_udp_del_session (sw_if_index, &local_addr, &peer_addr); + if (rv) + { + ret = + clib_error_return (0, + "`bfd_udp_del_session' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_session_del_command, static) = { + .path = "bfd udp session del", + .short_help = "bfd udp session del interface" + " local-addr" + " peer-addr" + " ", + .function = bfd_cli_udp_session_del, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_udp_session_set_flags (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_session_set_flags_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) \ + F (ip46_address_t, local_addr, LOCAL_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (ip46_address_t, peer_addr, PEER_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (u8 *, admin_up_down_token, ADMIN_STR, mandatory, "%v", \ + &admin_up_down_token) + + foreach_bfd_cli_udp_session_set_flags_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_session_set_flags_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_session_set_flags_cli_param (CHECK_MANDATORY); + + u8 admin_up_down; + static const char up[] = "up"; + static const char down[] = "down"; + if (!memcmp (admin_up_down_token, up, sizeof (up) - 1)) + { + admin_up_down = 1; + } + else if (!memcmp (admin_up_down_token, down, sizeof (down) - 1)) + { + admin_up_down = 0; + } + else + { + ret = + clib_error_return (0, "Unrecognized value for `%s' parameter: `%v'", + ADMIN_STR, admin_up_down_token); + goto out; + } + vnet_api_error_t rv = bfd_udp_session_set_flags (sw_if_index, &local_addr, + &peer_addr, admin_up_down); + if (rv) + { + ret = + clib_error_return (0, + "`bfd_udp_session_set_flags' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_session_set_flags_command, static) = { + .path = "bfd udp session set-flags", + .short_help = "bfd udp session set-flags" + " interface " + " local-addr " + " peer-addr " + " admin ", + .function = bfd_cli_udp_session_set_flags, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_udp_session_auth_activate (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_session_auth_activate_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) \ + F (ip46_address_t, local_addr, LOCAL_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (ip46_address_t, peer_addr, PEER_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (u8 *, delayed_token, DELAYED_STR, optional, "%v") \ + F (u32, conf_key_id, CONF_KEY_ID_STR, mandatory, "%u") \ + F (u32, bfd_key_id, BFD_KEY_ID_STR, mandatory, "%u") + + foreach_bfd_cli_udp_session_auth_activate_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_session_auth_activate_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_session_auth_activate_cli_param (CHECK_MANDATORY); + + u8 is_delayed = 0; + if (have_delayed_token) + { + static const char yes[] = "yes"; + static const char no[] = "no"; + if (!memcmp (delayed_token, yes, sizeof (yes) - 1)) + { + is_delayed = 1; + } + else if (!memcmp (delayed_token, no, sizeof (no) - 1)) + { + is_delayed = 0; + } + else + { + ret = + clib_error_return (0, + "Unrecognized value for `%s' parameter: `%v'", + DELAYED_STR, delayed_token); + goto out; + } + } + + if (have_bfd_key_id && bfd_key_id > 255) + { + ret = clib_error_return (0, "%s value `%u' out of range <1,255>", + BFD_KEY_ID_STR, bfd_key_id); + goto out; + } + + vnet_api_error_t rv = + bfd_udp_auth_activate (sw_if_index, &local_addr, &peer_addr, conf_key_id, + bfd_key_id, is_delayed); + if (rv) + { + ret = + clib_error_return (0, + "`bfd_udp_auth_activate' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_session_auth_activate_command, static) = { + .path = "bfd udp session auth activate", + .short_help = "bfd udp session auth activate" + " interface " + " local-addr " + " peer-addr " + " conf-key-id " + " bfd-key-id " + " [ delayed ]", + .function = bfd_cli_udp_session_auth_activate, +}; + +static clib_error_t * +bfd_cli_udp_session_auth_deactivate (vlib_main_t *vm, unformat_input_t *input, + CLIB_UNUSED (vlib_cli_command_t *lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_session_auth_deactivate_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) \ + F (ip46_address_t, local_addr, LOCAL_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (ip46_address_t, peer_addr, PEER_ADDR_STR, mandatory, "%U", \ + unformat_ip46_address) \ + F (u8 *, delayed_token, DELAYED_STR, optional, "%v") + + foreach_bfd_cli_udp_session_auth_deactivate_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_session_auth_deactivate_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_session_auth_deactivate_cli_param (CHECK_MANDATORY); + + u8 is_delayed = 0; + if (have_delayed_token) + { + static const char yes[] = "yes"; + static const char no[] = "no"; + if (!memcmp (delayed_token, yes, sizeof (yes) - 1)) + { + is_delayed = 1; + } + else if (!memcmp (delayed_token, no, sizeof (no) - 1)) + { + is_delayed = 0; + } + else + { + ret = clib_error_return ( + 0, "Unrecognized value for `%s' parameter: `%v'", DELAYED_STR, + delayed_token); + goto out; + } + } + + vnet_api_error_t rv = bfd_udp_auth_deactivate (sw_if_index, &local_addr, + &peer_addr, is_delayed); + if (rv) + { + ret = clib_error_return ( + 0, "`bfd_udp_auth_deactivate' API call failed, rv=%d:%U", (int)rv, + format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_session_auth_deactivate_command, static) = { + .path = "bfd udp session auth deactivate", + .short_help = "bfd udp session auth deactivate" + " interface " + " local-addr " + " peer-addr " + "[ delayed ]", + .function = bfd_cli_udp_session_auth_deactivate, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_udp_set_echo_source (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + clib_error_t *ret = NULL; +#define foreach_bfd_cli_udp_set_echo_source_cli_param(F) \ + F (u32, sw_if_index, INTERFACE_STR, mandatory, "%U", \ + unformat_vnet_sw_interface, &vnet_main) + + foreach_bfd_cli_udp_set_echo_source_cli_param (DECLARE); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + int something_parsed = 0; + foreach_bfd_cli_udp_set_echo_source_cli_param (UNFORMAT); + + if (!something_parsed) + { + ret = clib_error_return (0, "Unknown input `%U'", + format_unformat_error, input); + goto out; + } + } + + foreach_bfd_cli_udp_set_echo_source_cli_param (CHECK_MANDATORY); + + vnet_api_error_t rv = bfd_udp_set_echo_source (sw_if_index); + if (rv) + { + ret = + clib_error_return (0, + "`bfd_udp_set_echo_source' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + goto out; + } + +out: + return ret; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_set_echo_source_cmd, static) = { + .path = "bfd udp echo-source set", + .short_help = "bfd udp echo-source set interface ", + .function = bfd_cli_udp_set_echo_source, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_cli_udp_del_echo_source (vlib_main_t * vm, unformat_input_t * input, + CLIB_UNUSED (vlib_cli_command_t * lmd)) +{ + vnet_api_error_t rv = bfd_udp_del_echo_source (); + if (rv) + { + return clib_error_return (0, + "`bfd_udp_del_echo_source' API call failed, rv=%d:%U", + (int) rv, format_vnet_api_errno, rv); + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (bfd_cli_udp_del_echo_source_cmd, static) = { + .path = "bfd udp echo-source del", + .short_help = "bfd udp echo-source del", + .function = bfd_cli_udp_del_echo_source, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 29c40458..01de6375 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -55,7 +55,7 @@ bfd_usec_to_clocks (const bfd_main_t * bm, u64 us) return bm->cpu_cps * ((f64) us / USEC_PER_SECOND); } -static u32 +u32 bfd_clocks_to_usec (const bfd_main_t * bm, u64 clocks) { return (clocks / bm->cpu_cps) * USEC_PER_SECOND; @@ -70,7 +70,7 @@ static u32 bfd_node_index_by_transport[] = { #undef F }; -static u8 * +u8 * format_bfd_auth_key (u8 * s, va_list * args) { const bfd_auth_key_t *key = va_arg (*args, bfd_auth_key_t *); @@ -147,7 +147,7 @@ bfd_set_state (bfd_main_t * bm, bfd_session_t * bs, } } -static const char * +const char * bfd_poll_state_string (bfd_poll_state_e state) { switch (state) @@ -265,12 +265,16 @@ bfd_calc_next_echo_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now) static void bfd_recalc_detection_time (bfd_main_t * bm, bfd_session_t * bs) { - bs->detection_time_clocks = - bs->remote_detect_mult * clib_max (bs->effective_required_min_rx_clocks, - bs->remote_desired_min_tx_clocks); - BFD_DBG ("Recalculated detection time %lu clocks/%.2fs", - bs->detection_time_clocks, - bs->detection_time_clocks / bm->cpu_cps); + if (bs->local_state == BFD_STATE_init || bs->local_state == BFD_STATE_up) + { + bs->detection_time_clocks = + bs->remote_detect_mult * + clib_max (bs->effective_required_min_rx_clocks, + bs->remote_desired_min_tx_clocks); + BFD_DBG ("Recalculated detection time %lu clocks/%.2fs", + bs->detection_time_clocks, + bs->detection_time_clocks / bm->cpu_cps); + } } static void @@ -284,7 +288,8 @@ bfd_set_timer (bfd_main_t * bm, bfd_session_t * bs, u64 now, { rx_timeout = bs->last_rx_clocks + bs->detection_time_clocks; } - if (BFD_STATE_up != bs->local_state || !bs->remote_demand || + if (BFD_STATE_up != bs->local_state || + (!bs->remote_demand && bs->remote_min_rx_usec) || BFD_POLL_NOT_NEEDED != bs->poll_state) { tx_timeout = bs->tx_timeout_clocks; @@ -348,7 +353,7 @@ bfd_set_effective_desired_min_tx (bfd_main_t * bm, static void bfd_set_effective_required_min_rx (bfd_main_t * bm, - bfd_session_t * bs, u64 now, + bfd_session_t * bs, u64 required_min_rx_clocks) { bs->effective_required_min_rx_clocks = required_min_rx_clocks; @@ -393,44 +398,33 @@ void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) { BFD_DBG ("\nStarting session: %U", format_bfd_session, bs); + bfd_set_effective_required_min_rx (bm, bs, + bs->config_required_min_rx_clocks); bfd_recalc_tx_interval (bm, bs); vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index, BFD_EVENT_NEW_SESSION, bs->bs_idx); } -vnet_api_error_t -bfd_del_session (uword bs_idx) -{ - const bfd_main_t *bm = &bfd_main; - if (!pool_is_free_index (bm->sessions, bs_idx)) - { - bfd_session_t *bs = pool_elt_at_index (bm->sessions, bs_idx); - pool_put (bm->sessions, bs); - return 0; - } - else - { - BFD_ERR ("no such session"); - return VNET_API_ERROR_BFD_ENOENT; - } - return 0; -} - void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down) { bfd_main_t *bm = &bfd_main; + u64 now = clib_cpu_time_now (); if (admin_up_down) { BFD_DBG ("Session set admin-up, bs-idx=%u", bs->bs_idx); bfd_set_state (bm, bs, BFD_STATE_down, 0); bfd_set_diag (bs, BFD_DIAG_CODE_no_diag); + bfd_calc_next_tx (bm, bs, now); + bfd_set_timer (bm, bs, now, 0); } else { BFD_DBG ("Session set admin-down, bs-idx=%u", bs->bs_idx); bfd_set_diag (bs, BFD_DIAG_CODE_admin_down); bfd_set_state (bm, bs, BFD_STATE_admin_down, 0); + bfd_calc_next_tx (bm, bs, now); + bfd_set_timer (bm, bs, now, 0); } } @@ -515,7 +509,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, clib_max (bs->config_desired_min_tx_clocks, bm->default_desired_min_tx_clocks)); - bfd_set_effective_required_min_rx (bm, bs, now, + bfd_set_effective_required_min_rx (bm, bs, bs->config_required_min_rx_clocks); bfd_set_timer (bm, bs, now, handling_wakeup); break; @@ -525,7 +519,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, clib_max (bs->config_desired_min_tx_clocks, bm->default_desired_min_tx_clocks)); - bfd_set_effective_required_min_rx (bm, bs, now, + bfd_set_effective_required_min_rx (bm, bs, bs->config_required_min_rx_clocks); bfd_set_timer (bm, bs, now, handling_wakeup); break; @@ -540,7 +534,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, bs->config_desired_min_tx_clocks); if (BFD_POLL_NOT_NEEDED == bs->poll_state) { - bfd_set_effective_required_min_rx (bm, bs, now, + bfd_set_effective_required_min_rx (bm, bs, bs->config_required_min_rx_clocks); } bfd_set_timer (bm, bs, now, handling_wakeup); @@ -932,7 +926,7 @@ bfd_on_timeout (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, bs->echo = 1; bs->echo_last_rx_clocks = now; bs->echo_tx_timeout_clocks = now; - bfd_set_effective_required_min_rx (bm, bs, now, + bfd_set_effective_required_min_rx (bm, bs, clib_max (bm->min_required_min_rx_while_echo_clocks, bs->config_required_min_rx_clocks)); @@ -1569,6 +1563,7 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) bs->remote_discr = pkt->my_disc; bs->remote_state = bfd_pkt_get_state (pkt); bs->remote_demand = bfd_pkt_get_demand (pkt); + bs->remote_diag = bfd_pkt_get_diag_code (pkt); u64 now = clib_cpu_time_now (); bs->last_rx_clocks = now; if (bfd_pkt_get_auth_present (pkt)) @@ -1621,7 +1616,7 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) bfd_set_poll_state (bs, BFD_POLL_NOT_NEEDED); if (BFD_STATE_up == bs->local_state) { - bfd_set_effective_required_min_rx (bm, bs, now, + bfd_set_effective_required_min_rx (bm, bs, clib_max (bs->echo * bm->min_required_min_rx_while_echo_clocks, bs->config_required_min_rx_clocks)); @@ -1914,6 +1909,97 @@ bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, return 0; } +vnet_api_error_t +bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, + const u8 * key_data) +{ +#if WITH_LIBSSL > 0 + bfd_auth_key_t *auth_key = NULL; + if (!key_len || key_len > bfd_max_len_for_auth_type (auth_type)) + { + clib_warning ("Invalid authentication key length for auth_type=%d:%s " + "(key_len=%u, must be " + "non-zero, expected max=%u)", + auth_type, bfd_auth_type_str (auth_type), key_len, + (u32) bfd_max_len_for_auth_type (auth_type)); + return VNET_API_ERROR_INVALID_VALUE; + } + if (!bfd_auth_type_supported (auth_type)) + { + clib_warning ("Unsupported auth type=%d:%s", auth_type, + bfd_auth_type_str (auth_type)); + return VNET_API_ERROR_BFD_NOTSUPP; + } + bfd_main_t *bm = &bfd_main; + uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (key_idx_p) + { + /* modifying existing key - must not be used */ + const uword key_idx = *key_idx_p; + auth_key = pool_elt_at_index (bm->auth_keys, key_idx); + if (auth_key->use_count > 0) + { + clib_warning ("Authentication key with conf ID %u in use by %u BFD " + "session(s) - cannot modify", + conf_key_id, auth_key->use_count); + return VNET_API_ERROR_BFD_EINUSE; + } + } + else + { + /* adding new key */ + pool_get (bm->auth_keys, auth_key); + auth_key->conf_key_id = conf_key_id; + hash_set (bm->auth_key_by_conf_key_id, conf_key_id, + auth_key - bm->auth_keys); + } + auth_key->auth_type = auth_type; + memset (auth_key->key, 0, sizeof (auth_key->key)); + clib_memcpy (auth_key->key, key_data, key_len); + return 0; +#else + clib_warning ("SSL missing, cannot manipulate authentication keys"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_auth_del_key (u32 conf_key_id) +{ +#if WITH_LIBSSL > 0 + bfd_auth_key_t *auth_key = NULL; + bfd_main_t *bm = &bfd_main; + uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (key_idx_p) + { + /* deleting existing key - must not be used */ + const uword key_idx = *key_idx_p; + auth_key = pool_elt_at_index (bm->auth_keys, key_idx); + if (auth_key->use_count > 0) + { + clib_warning ("Authentication key with conf ID %u in use by %u BFD " + "session(s) - cannot delete", + conf_key_id, auth_key->use_count); + return VNET_API_ERROR_BFD_EINUSE; + } + hash_unset (bm->auth_key_by_conf_key_id, conf_key_id); + memset (auth_key, 0, sizeof (*auth_key)); + pool_put (bm->auth_keys, auth_key); + } + else + { + /* no such key */ + clib_warning ("Authentication key with conf ID %u does not exist", + conf_key_id); + return VNET_API_ERROR_BFD_ENOENT; + } + return 0; +#else + clib_warning ("SSL missing, cannot manipulate authentication keys"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + bfd_main_t bfd_main; /* diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index d8063f9d..3be3694c 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -74,11 +74,14 @@ typedef struct bfd_session_s /* session state */ bfd_state_e local_state; + /* remote session state */ + bfd_state_e remote_state; + /* local diagnostics */ bfd_diag_code_e local_diag; - /* remote session state */ - bfd_state_e remote_state; + /* remote diagnostics */ + bfd_diag_code_e remote_diag; /* local discriminator */ u32 local_discr; @@ -315,6 +318,7 @@ void bfd_event (bfd_main_t * bm, bfd_session_t * bs); void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, bfd_main_t * bm, bfd_session_t * bs); u8 *format_bfd_session (u8 * s, va_list * args); +u8 *format_bfd_auth_key (u8 * s, va_list * args); void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down); unsigned bfd_auth_type_supported (bfd_auth_type_e auth_type); vnet_api_error_t bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, @@ -325,6 +329,9 @@ vnet_api_error_t bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs, u32 required_min_rx_usec, u8 detect_mult); +u32 bfd_clocks_to_usec (const bfd_main_t * bm, u64 clocks); +const char *bfd_poll_state_string (bfd_poll_state_e state); + #define USEC_PER_MS 1000LL #define USEC_PER_SECOND (1000 * USEC_PER_MS) diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c index 92b226bd..5deb9702 100644 --- a/src/vnet/bfd/bfd_protocol.c +++ b/src/vnet/bfd/bfd_protocol.c @@ -82,11 +82,13 @@ bfd_pkt_get_control_plane_independent (const bfd_pkt_t * pkt) return (pkt->head.sta_flags >> 3) & 1; } +#if 0 void bfd_pkt_set_control_plane_independent (bfd_pkt_t * pkt) { pkt->head.sta_flags |= 1 << 3; } +#endif u8 bfd_pkt_get_auth_present (const bfd_pkt_t * pkt) @@ -106,11 +108,13 @@ bfd_pkt_get_demand (const bfd_pkt_t * pkt) return (pkt->head.sta_flags >> 1) & 1; } +#if 0 void bfd_pkt_set_demand (bfd_pkt_t * pkt) { pkt->head.sta_flags |= 1 << 1; } +#endif u8 bfd_pkt_get_multipoint (const bfd_pkt_t * pkt) @@ -118,11 +122,13 @@ bfd_pkt_get_multipoint (const bfd_pkt_t * pkt) return (pkt->head.sta_flags >> 0) & 1; } +#if 0 void bfd_pkt_set_multipoint (bfd_pkt_t * pkt) { pkt->head.sta_flags |= 1 << 0; } +#endif u32 bfd_max_len_for_auth_type (bfd_auth_type_e auth_type) diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index cf05089b..207f3b8c 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -60,8 +60,7 @@ vnet_api_error_t bfd_udp_set_echo_source (u32 sw_if_index) { vnet_sw_interface_t *sw_if = - vnet_get_sw_interface_safe (bfd_udp_main.vnet_main, - bfd_udp_main.echo_source_sw_if_index); + vnet_get_sw_interface_safe (bfd_udp_main.vnet_main, sw_if_index); if (sw_if) { bfd_udp_main.echo_source_sw_if_index = sw_if_index; @@ -84,6 +83,7 @@ bfd_udp_is_echo_available (bfd_transport_e transport) { if (!bfd_udp_main.echo_source_is_set) { + BFD_DBG ("UDP echo source not set - echo not available"); return 0; } /* @@ -127,6 +127,7 @@ bfd_udp_is_echo_available (bfd_transport_e transport) /* *INDENT-ON* */ } } + BFD_DBG ("No usable IP address for UDP echo - echo not available"); return 0; } @@ -144,11 +145,6 @@ bfd_udp_bs_idx_to_sport (u32 bs_idx) return 49152 + bs_idx % (65535 - 49152 + 1); } -static void -lol () -{ -} - int bfd_udp_get_echo_src_ip4 (ip4_address_t * addr) { @@ -204,7 +200,6 @@ bfd_udp_get_echo_src_ip6 (ip6_address_t * addr) { *addr = *x; addr->as_u8[15] ^= 1; /* flip the last bit of the address */ - lol (); return 1; } })); @@ -213,6 +208,24 @@ bfd_udp_get_echo_src_ip6 (ip6_address_t * addr) return 0; } +void +bfd_udp_get_echo_source (int *is_set, u32 * sw_if_index, int *have_usable_ip4, + ip4_address_t * ip4, int *have_usable_ip6, + ip6_address_t * ip6) +{ + if (bfd_udp_main.echo_source_is_set) + { + *is_set = 1; + *sw_if_index = bfd_udp_main.echo_source_sw_if_index; + *have_usable_ip4 = bfd_udp_get_echo_src_ip4 (ip4); + *have_usable_ip6 = bfd_udp_get_echo_src_ip6 (ip6); + } + else + { + *is_set = 0; + } +} + int bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, const bfd_session_t * bs, int is_echo) @@ -660,97 +673,6 @@ bfd_udp_session_set_flags (u32 sw_if_index, return 0; } -vnet_api_error_t -bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, - const u8 * key_data) -{ -#if WITH_LIBSSL > 0 - bfd_auth_key_t *auth_key = NULL; - if (!key_len || key_len > bfd_max_len_for_auth_type (auth_type)) - { - clib_warning ("Invalid authentication key length for auth_type=%d:%s " - "(key_len=%u, must be " - "non-zero, expected max=%u)", - auth_type, bfd_auth_type_str (auth_type), key_len, - (u32) bfd_max_len_for_auth_type (auth_type)); - return VNET_API_ERROR_INVALID_VALUE; - } - if (!bfd_auth_type_supported (auth_type)) - { - clib_warning ("Unsupported auth type=%d:%s", auth_type, - bfd_auth_type_str (auth_type)); - return VNET_API_ERROR_BFD_NOTSUPP; - } - bfd_main_t *bm = bfd_udp_main.bfd_main; - uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); - if (key_idx_p) - { - /* modifying existing key - must not be used */ - const uword key_idx = *key_idx_p; - auth_key = pool_elt_at_index (bm->auth_keys, key_idx); - if (auth_key->use_count > 0) - { - clib_warning ("Authentication key with conf ID %u in use by %u BFD " - "sessions - cannot modify", - conf_key_id, auth_key->use_count); - return VNET_API_ERROR_BFD_EINUSE; - } - } - else - { - /* adding new key */ - pool_get (bm->auth_keys, auth_key); - auth_key->conf_key_id = conf_key_id; - hash_set (bm->auth_key_by_conf_key_id, conf_key_id, - auth_key - bm->auth_keys); - } - auth_key->auth_type = auth_type; - memset (auth_key->key, 0, sizeof (auth_key->key)); - clib_memcpy (auth_key->key, key_data, key_len); - return 0; -#else - clib_warning ("SSL missing, cannot manipulate authentication keys"); - return VNET_API_ERROR_BFD_NOTSUPP; -#endif -} - -vnet_api_error_t -bfd_auth_del_key (u32 conf_key_id) -{ -#if WITH_LIBSSL > 0 - bfd_auth_key_t *auth_key = NULL; - bfd_main_t *bm = bfd_udp_main.bfd_main; - uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); - if (key_idx_p) - { - /* deleting existing key - must not be used */ - const uword key_idx = *key_idx_p; - auth_key = pool_elt_at_index (bm->auth_keys, key_idx); - if (auth_key->use_count > 0) - { - clib_warning ("Authentication key with conf ID %u in use by %u BFD " - "sessions - cannot delete", - conf_key_id, auth_key->use_count); - return VNET_API_ERROR_BFD_EINUSE; - } - hash_unset (bm->auth_key_by_conf_key_id, conf_key_id); - memset (auth_key, 0, sizeof (*auth_key)); - pool_put (bm->auth_keys, auth_key); - } - else - { - /* no such key */ - clib_warning ("Authentication key with conf ID %u does not exist", - conf_key_id); - return VNET_API_ERROR_BFD_ENOENT; - } - return 0; -#else - clib_warning ("SSL missing, cannot manipulate authentication keys"); - return VNET_API_ERROR_BFD_NOTSUPP; -#endif -} - vnet_api_error_t bfd_udp_auth_activate (u32 sw_if_index, const ip46_address_t * local_addr, diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index ce2ee3cb..e33b7407 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -62,6 +62,11 @@ int bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, */ int bfd_udp_is_echo_available (bfd_transport_e transport); +void +bfd_udp_get_echo_source (int *is_set, u32 * sw_if_index, int *have_usable_ip4, + ip4_address_t * ip4, int *have_usable_ip6, + ip6_address_t * ip6); + #endif /* __included_bfd_udp_h__ */ /* diff --git a/test/bfd.py b/test/bfd.py index b467cc79..d1d948b3 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -198,6 +198,10 @@ class VppBFDAuthKey(VppObject): """ key data """ return self._key + @key.setter + def key(self, value): + self._key = value + @property def conf_key_id(self): """ configuration key ID """ @@ -249,7 +253,10 @@ class VppBFDUDPSession(VppObject): self._required_min_rx = required_min_rx self._detect_mult = detect_mult self._sha1_key = sha1_key - self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255) + if bfd_key_id is not None: + self._bfd_key_id = bfd_key_id + else: + self._bfd_key_id = randint(0, 255) @property def test(self): diff --git a/test/test_bfd.py b/test/test_bfd.py index ce0cca55..e7ebb214 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -6,8 +6,9 @@ import unittest import hashlib import binascii import time +from struct import pack, unpack from random import randint, shuffle, getrandbits -from socket import AF_INET, AF_INET6 +from socket import AF_INET, AF_INET6, inet_ntop from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import UDP, IP @@ -17,6 +18,7 @@ from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \ from framework import VppTestCase, VppTestRunner from vpp_pg_interface import CaptureTimeoutError from util import ppp +from vpp_papi_provider import UnexpectedApiReturnValueError USEC_IN_SEC = 1000000 @@ -461,19 +463,25 @@ def bfd_session_up(test): test.vpp_clock_offset) if old_offset: test.assertAlmostEqual( - old_offset, test.vpp_clock_offset, delta=0.1, + old_offset, test.vpp_clock_offset, delta=0.5, msg="vpp clock offset not stable (new: %s, old: %s)" % (test.vpp_clock_offset, old_offset)) test.logger.info("BFD: Sending Init") test.test_session.update(my_discriminator=randint(0, 40000000), your_discriminator=p[BFD].my_discriminator, state=BFDState.init) + if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + test.test_session.inc_seq_num() test.test_session.send_packet() test.logger.info("BFD: Waiting for event") e = test.vapi.wait_for_event(1, "bfd_udp_session_details") verify_event(test, e, expected_state=BFDState.up) test.logger.info("BFD: Session is Up") test.test_session.update(state=BFDState.up) + if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + test.test_session.inc_seq_num() test.test_session.send_packet() test.assert_equal(test.vpp_session.state, BFDState.up, BFDState) @@ -482,6 +490,9 @@ def bfd_session_down(test): """ Bring BFD session down """ test.assert_equal(test.vpp_session.state, BFDState.up, BFDState) test.test_session.update(state=BFDState.down) + if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + test.test_session.inc_seq_num() test.test_session.send_packet() test.logger.info("BFD: Waiting for event") e = test.vapi.wait_for_event(1, "bfd_udp_session_details") @@ -490,6 +501,30 @@ def bfd_session_down(test): test.assert_equal(test.vpp_session.state, BFDState.down, BFDState) +def verify_bfd_session_config(test, session, state=None): + dump = session.get_bfd_udp_session_dump_entry() + test.assertIsNotNone(dump) + # since dump is not none, we have verified that sw_if_index and addresses + # are valid (in get_bfd_udp_session_dump_entry) + if state: + test.assert_equal(dump.state, state, "session state") + test.assert_equal(dump.required_min_rx, session.required_min_rx, + "required min rx interval") + test.assert_equal(dump.desired_min_tx, session.desired_min_tx, + "desired min tx interval") + test.assert_equal(dump.detect_mult, session.detect_mult, + "detect multiplier") + if session.sha1_key is None: + test.assert_equal(dump.is_authenticated, 0, "is_authenticated flag") + else: + test.assert_equal(dump.is_authenticated, 1, "is_authenticated flag") + test.assert_equal(dump.bfd_key_id, session.bfd_key_id, + "bfd key id") + test.assert_equal(dump.conf_key_id, + session.sha1_key.conf_key_id, + "config key id") + + def verify_ip(test, packet): """ Verify correctness of IP layer. """ if test.vpp_session.af == AF_INET6: @@ -626,6 +661,32 @@ class BFD4TestCase(VppTestCase): """ bring BFD session up """ bfd_session_up(self) + def test_session_up_by_ip(self): + """ bring BFD session up - first frame looked up by address pair """ + self.logger.info("BFD: Sending Slow control frame") + self.test_session.update(my_discriminator=randint(0, 40000000)) + self.test_session.send_packet() + self.pg0.enable_capture() + p = self.pg0.wait_for_packet(1) + self.assert_equal(p[BFD].your_discriminator, + self.test_session.my_discriminator, + "BFD - your discriminator") + self.assert_equal(p[BFD].state, BFDState.init, BFDState) + self.test_session.update(your_discriminator=p[BFD].my_discriminator, + state=BFDState.up) + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + verify_event(self, e, expected_state=BFDState.init) + self.logger.info("BFD: Sending Up") + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + verify_event(self, e, expected_state=BFDState.up) + self.logger.info("BFD: Session is Up") + self.test_session.update(state=BFDState.up) + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + def test_session_down(self): """ bring BFD session down """ bfd_session_up(self) @@ -905,6 +966,16 @@ class BFD4TestCase(VppTestCase): self.assertNotIn("P", p.sprintf("%BFD.flags%"), "Poll bit set in BFD packet") + def test_poll_response(self): + """ test correct response to control frame with poll bit set """ + bfd_session_up(self) + poll = self.test_session.create_packet() + poll[BFD].flags = "P" + self.test_session.send_packet(poll) + final = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.assertIn("F", final.sprintf("%BFD.flags%")) + def test_no_periodic_if_remote_demand(self): """ no periodic frames outside poll sequence if remote demand set """ bfd_session_up(self) @@ -1091,6 +1162,36 @@ class BFD4TestCase(VppTestCase): events = self.vapi.collect_events() self.assert_equal(len(events), 0, "number of bfd events") + def test_echo_source_removed(self): + """ echo function stops if echo source is removed """ + bfd_session_up(self) + self.test_session.update(required_min_echo_rx=50000) + self.test_session.send_packet() + self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index) + # wait for first echo packet + while True: + p = self.pg0.wait_for_packet(1) + self.logger.debug(ppp("Got packet:", p)) + if p[UDP].dport == BFD.udp_dport_echo: + self.logger.debug(ppp("Looping back packet:", p)) + self.pg0.add_stream(p) + self.pg_start() + break + elif p.haslayer(BFD): + # ignore BFD + pass + else: + raise Exception(ppp("Received unknown packet:", p)) + self.vapi.bfd_udp_del_echo_source() + self.test_session.send_packet() + # echo packets shouldn't arrive anymore + for dummy in range(5): + wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.test_session.send_packet() + events = self.vapi.collect_events() + self.assert_equal(len(events), 0, "number of bfd events") + def test_stale_echo(self): """ stale echo packets don't keep a session up """ bfd_session_up(self) @@ -1199,28 +1300,31 @@ class BFD4TestCase(VppTestCase): verify_event(self, e, expected_state=BFDState.admin_down) for dummy in range(2): p = wait_for_bfd_packet(self) - self.assert_equal(BFDState.admin_down, p[BFD].state, BFDState) + self.assert_equal(p[BFD].state, BFDState.admin_down, BFDState) # try to bring session up - shouldn't be possible self.test_session.update(state=BFDState.init) self.test_session.send_packet() for dummy in range(2): p = wait_for_bfd_packet(self) - self.assert_equal(BFDState.admin_down, p[BFD].state, BFDState) + self.assert_equal(p[BFD].state, BFDState.admin_down, BFDState) self.vpp_session.admin_up() self.test_session.update(state=BFDState.down) e = self.vapi.wait_for_event(1, "bfd_udp_session_details") verify_event(self, e, expected_state=BFDState.down) - p = wait_for_bfd_packet(self) - self.assert_equal(BFDState.down, p[BFD].state, BFDState) + p = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.assert_equal(p[BFD].state, BFDState.down, BFDState) self.test_session.send_packet() - p = wait_for_bfd_packet(self) - self.assert_equal(BFDState.init, p[BFD].state, BFDState) + p = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.assert_equal(p[BFD].state, BFDState.init, BFDState) e = self.vapi.wait_for_event(1, "bfd_udp_session_details") verify_event(self, e, expected_state=BFDState.init) self.test_session.update(state=BFDState.up) self.test_session.send_packet() - p = wait_for_bfd_packet(self) - self.assert_equal(BFDState.up, p[BFD].state, BFDState) + p = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) + self.assert_equal(p[BFD].state, BFDState.up, BFDState) e = self.vapi.wait_for_event(1, "bfd_udp_session_details") verify_event(self, e, expected_state=BFDState.up) @@ -1232,7 +1336,8 @@ class BFD4TestCase(VppTestCase): self.test_session.send_packet(demand) self.vpp_session.modify_parameters( required_min_rx=2 * self.vpp_session.required_min_rx) - p = wait_for_bfd_packet(self) + p = wait_for_bfd_packet( + self, pcap_time_min=time.time() - self.vpp_clock_offset) # poll bit must be set self.assertIn("P", p.sprintf("%BFD.flags%"), "Poll bit not set") # terminate poll sequence @@ -1314,6 +1419,32 @@ class BFD6TestCase(VppTestCase): """ bring BFD session up """ bfd_session_up(self) + def test_session_up_by_ip(self): + """ bring BFD session up - first frame looked up by address pair """ + self.logger.info("BFD: Sending Slow control frame") + self.test_session.update(my_discriminator=randint(0, 40000000)) + self.test_session.send_packet() + self.pg0.enable_capture() + p = self.pg0.wait_for_packet(1) + self.assert_equal(p[BFD].your_discriminator, + self.test_session.my_discriminator, + "BFD - your discriminator") + self.assert_equal(p[BFD].state, BFDState.init, BFDState) + self.test_session.update(your_discriminator=p[BFD].my_discriminator, + state=BFDState.up) + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + verify_event(self, e, expected_state=BFDState.init) + self.logger.info("BFD: Sending Up") + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + verify_event(self, e, expected_state=BFDState.up) + self.logger.info("BFD: Session is Up") + self.test_session.update(state=BFDState.up) + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + def test_hold_up(self): """ hold BFD session up """ bfd_session_up(self) @@ -1512,7 +1643,6 @@ class BFDSHA1TestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession( self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=self.vpp_session.bfd_key_id) @@ -1547,7 +1677,6 @@ class BFDSHA1TestCase(VppTestCase): self.vpp_session = vpp_bfd_udp_session self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = legitimate_test_session # bring vpp session up bfd_session_up(self) @@ -1625,7 +1754,6 @@ class BFDSHA1TestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession( self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0) @@ -1633,7 +1761,7 @@ class BFDSHA1TestCase(VppTestCase): # don't send any packets for 2*detection_time detection_time = self.test_session.detect_mult *\ self.vpp_session.required_min_rx / USEC_IN_SEC - self.sleep(detection_time, "simulating peer restart") + self.sleep(2*detection_time, "simulating peer restart") events = self.vapi.collect_events() self.assert_equal(len(events), 1, "number of bfd events") verify_event(self, events[0], expected_state=BFDState.down) @@ -1685,7 +1813,6 @@ class BFDAuthOnOffTestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession(self, self.pg0, AF_INET) bfd_session_up(self) for dummy in range(self.test_session.detect_mult * 2): @@ -1710,7 +1837,6 @@ class BFDAuthOnOffTestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession( self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=self.vpp_session.bfd_key_id) @@ -1742,7 +1868,6 @@ class BFDAuthOnOffTestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key1) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession( self, self.pg0, AF_INET, sha1_key=key1, bfd_key_id=self.vpp_session.bfd_key_id) @@ -1769,7 +1894,6 @@ class BFDAuthOnOffTestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession(self, self.pg0, AF_INET) bfd_session_up(self) for dummy in range(self.test_session.detect_mult * 2): @@ -1798,7 +1922,6 @@ class BFDAuthOnOffTestCase(VppTestCase): self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key) self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() self.test_session = BFDTestSession( self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=self.vpp_session.bfd_key_id) @@ -1857,5 +1980,421 @@ class BFDAuthOnOffTestCase(VppTestCase): self.assert_equal(len(self.vapi.collect_events()), 0, "number of bfd events") + +class BFDCLITestCase(VppTestCase): + """Bidirectional Forwarding Detection (BFD) (CLI) """ + pg0 = None + + @classmethod + def setUpClass(cls): + super(BFDCLITestCase, cls).setUpClass() + + try: + cls.create_pg_interfaces((0,)) + cls.pg0.config_ip4() + cls.pg0.config_ip6() + cls.pg0.resolve_arp() + cls.pg0.resolve_ndp() + + except Exception: + super(BFDCLITestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFDCLITestCase, self).setUp() + self.factory = AuthKeyFactory() + self.pg0.enable_capture() + + def tearDown(self): + try: + self.vapi.want_bfd_events(enable_disable=0) + except UnexpectedApiReturnValueError: + # some tests aren't subscribed, so this is not an issue + pass + self.vapi.collect_events() # clear the event queue + super(BFDCLITestCase, self).tearDown() + + def cli_verify_no_response(self, cli): + """ execute a CLI, asserting that the response is empty """ + self.assert_equal(self.vapi.cli(cli), + "", + "CLI command response") + + def cli_verify_response(self, cli, expected): + """ execute a CLI, asserting that the response matches expectation """ + self.assert_equal(self.vapi.cli(cli).strip(), + expected, + "CLI command response") + + def test_show(self): + """ show commands """ + k1 = self.factory.create_random_key(self) + k1.add_vpp_config() + k2 = self.factory.create_random_key( + self, auth_type=BFDAuthType.meticulous_keyed_sha1) + k2.add_vpp_config() + s1 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + s1.add_vpp_config() + s2 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, + sha1_key=k2) + s2.add_vpp_config() + self.logger.info(self.vapi.ppcli("show bfd keys")) + self.logger.info(self.vapi.ppcli("show bfd sessions")) + self.logger.info(self.vapi.ppcli("show bfd")) + + def test_set_del_sha1_key(self): + """ set/delete SHA1 auth key """ + k = self.factory.create_random_key(self) + self.registry.register(k, self.logger) + self.cli_verify_no_response( + "bfd key set conf-key-id %s type keyed-sha1 secret %s" % + (k.conf_key_id, + "".join("{:02x}".format(ord(c)) for c in k.key))) + self.assertTrue(k.query_vpp_config()) + self.vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=k) + self.vpp_session.add_vpp_config() + self.test_session = \ + BFDTestSession(self, self.pg0, AF_INET, sha1_key=k, + bfd_key_id=self.vpp_session.bfd_key_id) + self.vapi.want_bfd_events() + bfd_session_up(self) + bfd_session_down(self) + # try to replace the secret for the key - should fail because the key + # is in-use + k2 = self.factory.create_random_key(self) + self.cli_verify_response( + "bfd key set conf-key-id %s type keyed-sha1 secret %s" % + (k.conf_key_id, + "".join("{:02x}".format(ord(c)) for c in k2.key)), + "bfd key set: `bfd_auth_set_key' API call failed, " + "rv=-103:BFD object in use") + # manipulating the session using old secret should still work + bfd_session_up(self) + bfd_session_down(self) + self.vpp_session.remove_vpp_config() + self.cli_verify_no_response( + "bfd key del conf-key-id %s" % k.conf_key_id) + self.assertFalse(k.query_vpp_config()) + + def test_set_del_meticulous_sha1_key(self): + """ set/delete meticulous SHA1 auth key """ + k = self.factory.create_random_key( + self, auth_type=BFDAuthType.meticulous_keyed_sha1) + self.registry.register(k, self.logger) + self.cli_verify_no_response( + "bfd key set conf-key-id %s type meticulous-keyed-sha1 secret %s" % + (k.conf_key_id, + "".join("{:02x}".format(ord(c)) for c in k.key))) + self.assertTrue(k.query_vpp_config()) + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip6, af=AF_INET6, + sha1_key=k) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = \ + BFDTestSession(self, self.pg0, AF_INET6, sha1_key=k, + bfd_key_id=self.vpp_session.bfd_key_id) + self.vapi.want_bfd_events() + bfd_session_up(self) + bfd_session_down(self) + # try to replace the secret for the key - should fail because the key + # is in-use + k2 = self.factory.create_random_key(self) + self.cli_verify_response( + "bfd key set conf-key-id %s type keyed-sha1 secret %s" % + (k.conf_key_id, + "".join("{:02x}".format(ord(c)) for c in k2.key)), + "bfd key set: `bfd_auth_set_key' API call failed, " + "rv=-103:BFD object in use") + # manipulating the session using old secret should still work + bfd_session_up(self) + bfd_session_down(self) + self.vpp_session.remove_vpp_config() + self.cli_verify_no_response( + "bfd key del conf-key-id %s" % k.conf_key_id) + self.assertFalse(k.query_vpp_config()) + + def test_add_mod_del_bfd_udp(self): + """ create/modify/delete IPv4 BFD UDP session """ + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4) + self.registry.register(vpp_session, self.logger) + cli_add_cmd = "bfd udp session add interface %s local-addr %s " \ + "peer-addr %s desired-min-tx %s required-min-rx %s "\ + "detect-mult %s" % (self.pg0.name, self.pg0.local_ip4, + self.pg0.remote_ip4, + vpp_session.desired_min_tx, + vpp_session.required_min_rx, + vpp_session.detect_mult) + self.cli_verify_no_response(cli_add_cmd) + # 2nd add should fail + self.cli_verify_response( + cli_add_cmd, + "bfd udp session add: `bfd_add_add_session' API call" + " failed, rv=-101:Duplicate BFD object") + verify_bfd_session_config(self, vpp_session) + mod_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, + required_min_rx=2 * vpp_session.required_min_rx, + desired_min_tx=3 * vpp_session.desired_min_tx, + detect_mult=4 * vpp_session.detect_mult) + self.cli_verify_no_response( + "bfd udp session mod interface %s local-addr %s peer-addr %s " + "desired-min-tx %s required-min-rx %s detect-mult %s" % + (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4, + mod_session.desired_min_tx, mod_session.required_min_rx, + mod_session.detect_mult)) + verify_bfd_session_config(self, mod_session) + cli_del_cmd = "bfd udp session del interface %s local-addr %s "\ + "peer-addr %s" % (self.pg0.name, + self.pg0.local_ip4, self.pg0.remote_ip4) + self.cli_verify_no_response(cli_del_cmd) + # 2nd del is expected to fail + self.cli_verify_response( + cli_del_cmd, "bfd udp session del: `bfd_udp_del_session' API call" + " failed, rv=-102:No such BFD object") + self.assertFalse(vpp_session.query_vpp_config()) + + def test_add_mod_del_bfd_udp6(self): + """ create/modify/delete IPv6 BFD UDP session """ + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip6, af=AF_INET6) + self.registry.register(vpp_session, self.logger) + cli_add_cmd = "bfd udp session add interface %s local-addr %s " \ + "peer-addr %s desired-min-tx %s required-min-rx %s "\ + "detect-mult %s" % (self.pg0.name, self.pg0.local_ip6, + self.pg0.remote_ip6, + vpp_session.desired_min_tx, + vpp_session.required_min_rx, + vpp_session.detect_mult) + self.cli_verify_no_response(cli_add_cmd) + # 2nd add should fail + self.cli_verify_response( + cli_add_cmd, + "bfd udp session add: `bfd_add_add_session' API call" + " failed, rv=-101:Duplicate BFD object") + verify_bfd_session_config(self, vpp_session) + mod_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, + required_min_rx=2 * vpp_session.required_min_rx, + desired_min_tx=3 * vpp_session.desired_min_tx, + detect_mult=4 * vpp_session.detect_mult) + self.cli_verify_no_response( + "bfd udp session mod interface %s local-addr %s peer-addr %s " + "desired-min-tx %s required-min-rx %s detect-mult %s" % + (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6, + mod_session.desired_min_tx, + mod_session.required_min_rx, mod_session.detect_mult)) + verify_bfd_session_config(self, mod_session) + cli_del_cmd = "bfd udp session del interface %s local-addr %s "\ + "peer-addr %s" % (self.pg0.name, + self.pg0.local_ip6, self.pg0.remote_ip6) + self.cli_verify_no_response(cli_del_cmd) + # 2nd del is expected to fail + self.cli_verify_response( + cli_del_cmd, + "bfd udp session del: `bfd_udp_del_session' API call" + " failed, rv=-102:No such BFD object") + self.assertFalse(vpp_session.query_vpp_config()) + + def test_add_mod_del_bfd_udp_auth(self): + """ create/modify/delete IPv4 BFD UDP session (authenticated) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + self.registry.register(vpp_session, self.logger) + cli_add_cmd = "bfd udp session add interface %s local-addr %s " \ + "peer-addr %s desired-min-tx %s required-min-rx %s "\ + "detect-mult %s conf-key-id %s bfd-key-id %s"\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4, + vpp_session.desired_min_tx, vpp_session.required_min_rx, + vpp_session.detect_mult, key.conf_key_id, + vpp_session.bfd_key_id) + self.cli_verify_no_response(cli_add_cmd) + # 2nd add should fail + self.cli_verify_response( + cli_add_cmd, + "bfd udp session add: `bfd_add_add_session' API call" + " failed, rv=-101:Duplicate BFD object") + verify_bfd_session_config(self, vpp_session) + mod_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id, + required_min_rx=2 * vpp_session.required_min_rx, + desired_min_tx=3 * vpp_session.desired_min_tx, + detect_mult=4 * vpp_session.detect_mult) + self.cli_verify_no_response( + "bfd udp session mod interface %s local-addr %s peer-addr %s " + "desired-min-tx %s required-min-rx %s detect-mult %s" % + (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4, + mod_session.desired_min_tx, + mod_session.required_min_rx, mod_session.detect_mult)) + verify_bfd_session_config(self, mod_session) + cli_del_cmd = "bfd udp session del interface %s local-addr %s "\ + "peer-addr %s" % (self.pg0.name, + self.pg0.local_ip4, self.pg0.remote_ip4) + self.cli_verify_no_response(cli_del_cmd) + # 2nd del is expected to fail + self.cli_verify_response( + cli_del_cmd, + "bfd udp session del: `bfd_udp_del_session' API call" + " failed, rv=-102:No such BFD object") + self.assertFalse(vpp_session.query_vpp_config()) + + def test_add_mod_del_bfd_udp6_auth(self): + """ create/modify/delete IPv6 BFD UDP session (authenticated) """ + key = self.factory.create_random_key( + self, auth_type=BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, sha1_key=key) + self.registry.register(vpp_session, self.logger) + cli_add_cmd = "bfd udp session add interface %s local-addr %s " \ + "peer-addr %s desired-min-tx %s required-min-rx %s "\ + "detect-mult %s conf-key-id %s bfd-key-id %s" \ + % (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6, + vpp_session.desired_min_tx, vpp_session.required_min_rx, + vpp_session.detect_mult, key.conf_key_id, + vpp_session.bfd_key_id) + self.cli_verify_no_response(cli_add_cmd) + # 2nd add should fail + self.cli_verify_response( + cli_add_cmd, + "bfd udp session add: `bfd_add_add_session' API call" + " failed, rv=-101:Duplicate BFD object") + verify_bfd_session_config(self, vpp_session) + mod_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id, + required_min_rx=2 * vpp_session.required_min_rx, + desired_min_tx=3 * vpp_session.desired_min_tx, + detect_mult=4 * vpp_session.detect_mult) + self.cli_verify_no_response( + "bfd udp session mod interface %s local-addr %s peer-addr %s " + "desired-min-tx %s required-min-rx %s detect-mult %s" % + (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6, + mod_session.desired_min_tx, + mod_session.required_min_rx, mod_session.detect_mult)) + verify_bfd_session_config(self, mod_session) + cli_del_cmd = "bfd udp session del interface %s local-addr %s "\ + "peer-addr %s" % (self.pg0.name, + self.pg0.local_ip6, self.pg0.remote_ip6) + self.cli_verify_no_response(cli_del_cmd) + # 2nd del is expected to fail + self.cli_verify_response( + cli_del_cmd, + "bfd udp session del: `bfd_udp_del_session' API call" + " failed, rv=-102:No such BFD object") + self.assertFalse(vpp_session.query_vpp_config()) + + def test_auth_on_off(self): + """ turn authentication on and off """ + key = self.factory.create_random_key( + self, auth_type=BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + auth_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key) + session.add_vpp_config() + cli_activate = \ + "bfd udp session auth activate interface %s local-addr %s "\ + "peer-addr %s conf-key-id %s bfd-key-id %s"\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4, + key.conf_key_id, auth_session.bfd_key_id) + self.cli_verify_no_response(cli_activate) + verify_bfd_session_config(self, auth_session) + self.cli_verify_no_response(cli_activate) + verify_bfd_session_config(self, auth_session) + cli_deactivate = \ + "bfd udp session auth deactivate interface %s local-addr %s "\ + "peer-addr %s "\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4) + self.cli_verify_no_response(cli_deactivate) + verify_bfd_session_config(self, session) + self.cli_verify_no_response(cli_deactivate) + verify_bfd_session_config(self, session) + + def test_auth_on_off_delayed(self): + """ turn authentication on and off (delayed) """ + key = self.factory.create_random_key( + self, auth_type=BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + auth_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key) + session.add_vpp_config() + cli_activate = \ + "bfd udp session auth activate interface %s local-addr %s "\ + "peer-addr %s conf-key-id %s bfd-key-id %s delayed yes"\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4, + key.conf_key_id, auth_session.bfd_key_id) + self.cli_verify_no_response(cli_activate) + verify_bfd_session_config(self, auth_session) + self.cli_verify_no_response(cli_activate) + verify_bfd_session_config(self, auth_session) + cli_deactivate = \ + "bfd udp session auth deactivate interface %s local-addr %s "\ + "peer-addr %s delayed yes"\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4) + self.cli_verify_no_response(cli_deactivate) + verify_bfd_session_config(self, session) + self.cli_verify_no_response(cli_deactivate) + verify_bfd_session_config(self, session) + + def test_admin_up_down(self): + """ put session admin-up and admin-down """ + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + cli_down = \ + "bfd udp session set-flags admin down interface %s local-addr %s "\ + "peer-addr %s "\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4) + cli_up = \ + "bfd udp session set-flags admin up interface %s local-addr %s "\ + "peer-addr %s "\ + % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4) + self.cli_verify_no_response(cli_down) + verify_bfd_session_config(self, session, state=BFDState.admin_down) + self.cli_verify_no_response(cli_up) + verify_bfd_session_config(self, session, state=BFDState.down) + + def test_set_del_udp_echo_source(self): + """ set/del udp echo source """ + self.create_loopback_interfaces([0]) + self.loopback0 = self.lo_interfaces[0] + self.loopback0.admin_up() + self.cli_verify_response("show bfd echo-source", + "UDP echo source is not set.") + cli_set = "bfd udp echo-source set interface %s" % self.loopback0.name + self.cli_verify_no_response(cli_set) + self.cli_verify_response("show bfd echo-source", + "UDP echo source is: %s\n" + "IPv4 address usable as echo source: none\n" + "IPv6 address usable as echo source: none" % + self.loopback0.name) + self.loopback0.config_ip4() + unpacked = unpack("!L", self.loopback0.local_ip4n) + echo_ip4 = inet_ntop(AF_INET, pack("!L", unpacked[0] ^ 1)) + self.cli_verify_response("show bfd echo-source", + "UDP echo source is: %s\n" + "IPv4 address usable as echo source: %s\n" + "IPv6 address usable as echo source: none" % + (self.loopback0.name, echo_ip4)) + unpacked = unpack("!LLLL", self.loopback0.local_ip6n) + echo_ip6 = inet_ntop(AF_INET6, pack("!LLLL", unpacked[0], unpacked[1], + unpacked[2], unpacked[3] ^ 1)) + self.loopback0.config_ip6() + self.cli_verify_response("show bfd echo-source", + "UDP echo source is: %s\n" + "IPv4 address usable as echo source: %s\n" + "IPv6 address usable as echo source: %s" % + (self.loopback0.name, echo_ip4, echo_ip6)) + cli_del = "bfd udp echo-source del" + self.cli_verify_no_response(cli_del) + self.cli_verify_response("show bfd echo-source", + "UDP echo source is not set.") + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_object.py b/test/vpp_object.py index 0d74baa5..61a96ec2 100644 --- a/test/vpp_object.py +++ b/test/vpp_object.py @@ -46,6 +46,7 @@ class VppObjectRegistry(object): if obj.object_id() not in self._object_dict: self._object_registry.append(obj) self._object_dict[obj.object_id()] = obj + logger.debug("REG: registering %s" % obj) else: logger.debug("REG: duplicate add, ignoring (%s)" % obj) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 67b3e141..bebbe76d 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -29,6 +29,11 @@ class L2_VTR_OP: L2_POP_1 = 3 +class UnexpectedApiReturnValueError(Exception): + """ exception raised when the API return value is unexpected """ + pass + + class VppPapiProvider(object): """VPP-api provider using vpp-papi @@ -144,13 +149,13 @@ class VppPapiProvider(object): "return value instead of %d in %s" % \ (reply.retval, repr(reply)) self.test_class.logger.info(msg) - raise Exception(msg) + raise UnexpectedApiReturnValueError(msg) elif self._expect_api_retval == self._zero: if hasattr(reply, 'retval') and reply.retval != expected_retval: msg = "API call failed, expected zero return value instead "\ "of %d in %s" % (expected_retval, repr(reply)) self.test_class.logger.info(msg) - raise Exception(msg) + raise UnexpectedApiReturnValueError(msg) else: raise Exception("Internal error, unexpected value for " "self._expect_api_retval %s" % @@ -1188,6 +1193,9 @@ class VppPapiProvider(object): return self.api(self.papi.bfd_udp_set_echo_source, {'sw_if_index': sw_if_index}) + def bfd_udp_del_echo_source(self): + return self.api(self.papi.bfd_udp_del_echo_source, {}) + def classify_add_del_table( self, is_add, -- cgit 1.2.3-korg From b16bfe3f94739821c7382bd0849630b21e03a8b7 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Tue, 28 Feb 2017 11:56:48 +0100 Subject: BFD: documentation Change-Id: I06a23d24340c5527f3848177d2178bf3e55f7614 Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd_api.c | 4 + src/vnet/bfd/bfd_api.h | 32 +++- src/vnet/bfd/bfd_cli.c | 4 +- src/vnet/bfd/bfd_doc.md | 375 +++++++++++++++++++++++++++++++++++++++++++- src/vnet/bfd/bfd_main.c | 6 +- src/vnet/bfd/bfd_main.h | 140 ++++++++--------- src/vnet/bfd/bfd_protocol.c | 6 +- src/vnet/bfd/bfd_protocol.h | 5 +- src/vnet/bfd/bfd_udp.c | 4 + src/vnet/bfd/bfd_udp.h | 39 +++-- src/vnet/bfd/dir.dox | 2 +- 11 files changed, 526 insertions(+), 91 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c index e64df48e..185c03cf 100644 --- a/src/vnet/bfd/bfd_api.c +++ b/src/vnet/bfd/bfd_api.c @@ -16,6 +16,10 @@ * limitations under the License. *------------------------------------------------------------------ */ +/** + * @file + * @brief BFD binary API implementation + */ #include #include diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h index 35ad3cda..9f0509d5 100644 --- a/src/vnet/bfd/bfd_api.h +++ b/src/vnet/bfd/bfd_api.h @@ -14,7 +14,7 @@ */ /** * @file - * @brief BFD global declarations + * @brief BFD API declarations */ #ifndef __included_bfd_api_h__ #define __included_bfd_api_h__ @@ -34,6 +34,9 @@ typedef enum #undef F } bfd_transport_e; +/** + * @brief create a new bfd session + */ vnet_api_error_t bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, @@ -41,39 +44,66 @@ bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, u8 detect_mult, u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id); +/** + * @brief modify existing session + */ vnet_api_error_t bfd_udp_mod_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, u32 desired_min_tx_usec, u32 required_min_rx_usec, u8 detect_mult); +/** + * @brief delete existing session + */ vnet_api_error_t bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr); +/** + * @brief set session admin down/up + */ vnet_api_error_t bfd_udp_session_set_flags (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, u8 admin_up_down); +/** + * @brief create or modify bfd authentication key + */ vnet_api_error_t bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, const u8 * key); +/** + * @brief delete existing authentication key + */ vnet_api_error_t bfd_auth_del_key (u32 conf_key_id); +/** + * @brief activate authentication for existing session + */ vnet_api_error_t bfd_udp_auth_activate (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, u32 conf_key_id, u8 bfd_key_id, u8 is_delayed); +/** + * @brief deactivate authentication for existing session + */ vnet_api_error_t bfd_udp_auth_deactivate (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, u8 is_delayed); +/** + * @brief set echo-source interface + */ vnet_api_error_t bfd_udp_set_echo_source (u32 loopback_sw_if_index); +/** + * @brief unset echo-source interface + */ vnet_api_error_t bfd_udp_del_echo_source (); #endif /* __included_bfd_api_h__ */ diff --git a/src/vnet/bfd/bfd_cli.c b/src/vnet/bfd/bfd_cli.c index a3736d98..f15acb4b 100644 --- a/src/vnet/bfd/bfd_cli.c +++ b/src/vnet/bfd/bfd_cli.c @@ -737,7 +737,7 @@ VLIB_CLI_COMMAND (bfd_cli_udp_session_auth_activate_command, static) = { " peer-addr " " conf-key-id " " bfd-key-id " - " [ delayed ]", + " [ delayed ]", .function = bfd_cli_udp_session_auth_activate, }; @@ -815,7 +815,7 @@ VLIB_CLI_COMMAND (bfd_cli_udp_session_auth_deactivate_command, static) = { " interface " " local-addr " " peer-addr " - "[ delayed ]", + "[ delayed ]", .function = bfd_cli_udp_session_auth_deactivate, }; /* *INDENT-ON* */ diff --git a/src/vnet/bfd/bfd_doc.md b/src/vnet/bfd/bfd_doc.md index 3e86b178..7d7606e4 100644 --- a/src/vnet/bfd/bfd_doc.md +++ b/src/vnet/bfd/bfd_doc.md @@ -1,3 +1,374 @@ -# BFD Notes {#bfd_doc} +# BFD module {#bfd_doc} -@todo Someone needs to produce this or remove the stub file. +## Overview + +Bidirectional Forwarding Detection in VPP currently supports single-hop UDP +transport based on RFC 5880 and RFC 5881. + +## Usage + +### General usage + +BFD sessions are created using APIs only. The following CLIs are implemented, +which call the APIs to manipulate the BFD: + +#### Show commands: + +> show bfd [keys|sessions|echo-source] + +Show the existing keys, sessions or echo-source. + +#### Key manipulation + +##### Create a new key or modify an existing key + +> bfd key set conf-key-id type secret + +Parameters: + +* conf-key-id - local configuration key ID, used to uniquely identify this key +* type - type of the key +* secret - shared secret (hex data) + +Example: + +> bfd key set conf-key-id 2368880803 type meticulous-keyed-sha1 secret 69d685b0d990cdba46872706dc + +Notes: + +* in-use key cannot be modified + +##### Delete an existing key + +> bfd key del conf-key-id + +Parameters: + +* conf-key-id - local configuration key ID, used to uniquely identify this key + +Example: + +> bfd key del conf-key-id 2368880803 + +Notes: + +* in-use key cannot be deleted + +##### Create a new (plain or authenticated) BFD session + +> bfd udp session add interface local-addr
peer-addr
desired-min-tx required-min-rx detect-mult [ conf-key-id bfd-key-id ] + +Parameters: + +* interface - interface to which this session is tied to +* local-addr - local address (ipv4 or ipv6) +* peer-addr - peer address (ipv4 or ipv6, must match local-addr family) +* desired-min-tx - desired minimum tx interval (microseconds) +* required-min-rx - required minimum rx interval (microseconds) +* detect-mult - detect multiplier (must be non-zero) +* conf-key-id - local configuration key ID +* bfd-key-id - BFD key ID, as carried in BFD control frames + +Example: + +> bfd udp session add interface pg0 local-addr fd01:1::1 peer-addr fd01:1::2 desired-min-tx 100000 required-min-rx 100000 detect-mult 3 conf-key-id 1029559112 bfd-key-id 13 + +Notes: + +* if conf-key-id and bfd-key-id are not specified, session is non-authenticated +* desired-min-tx controls desired transmission rate of both control frames and echo packets + +##### Modify BFD session + +> bfd udp session mod interface local-addr
peer-addr
desired-min-tx required-min-rx detect-mult + +Parameters: + +* interface - interface to which this session is tied to +* local-addr - local address (ipv4 or ipv6) +* peer-addr - peer address (ipv4 or ipv6, must match local-addr family) +* desired-min-tx - desired minimum tx interval (microseconds) +* required-min-rx - required minimum rx interval (microseconds) +* detect-mult - detect multiplier (must be non-zero) + +Example: + +> bfd udp session mod interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 desired-min-tx 300000 required-min-rx 200000 detect-mult 12 + +Notes: + +* desired-min-tx controls desired transmission rate of both control frames and echo packets + +##### Delete an existing BFD session + +> bfd udp session del interface local-addr
peer-addr
+ +Parameters: + +* interface - interface to which this session is tied to +* local-addr - local address (ipv4 or ipv6) +* peer-addr - peer address (ipv4 or ipv6, must match local-addr family) + +Example: + +> bfd udp session del interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 + +##### Set session admin-up or admin-down + +> bfd udp session set-flags interface local-addr
peer-addr
admin + +Parameters: + +* interface - interface to which this session is tied to +* local-addr - local address (ipv4 or ipv6) +* peer-addr - peer address (ipv4 or ipv6, must match local-addr family) +* admin - up/down based on desired action + +Example: + +> bfd udp session set-flags admin down interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 + +##### Activate/change authentication for existing session + +> bfd udp session auth activate interface local-addr
peer-addr
conf-key-id bfd-key-id [ delayed ] + +Parameters: + +* interface - interface to which this session is tied to +* local-addr - local address (ipv4 or ipv6) +* peer-addr - peer address (ipv4 or ipv6, must match local-addr family) +* conf-key-id - local configuration key ID +* bfd-key-id - BFD key ID, as carried in BFD control frames +* delayed - is yes then this action is delayed until the peer performs the same action + +Example: + +> bfd udp session auth activate interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 conf-key-id 540928695 bfd-key-id 239 delayed yes + +Notes: + +* see [Delayed option] for more information + +##### Deactivate authentication for existing session + +> bfd udp session auth deactivate interface local-addr
peer-addr
[ delayed ] + +Parameters: + +* interface - interface to which this session is tied to +* local-addr - local address (ipv4 or ipv6) +* peer-addr - peer address (ipv4 or ipv6, must match local-addr family) +* delayed - is yes then this action is delayed until the peer performs the same action + +Example: + +> bfd udp session auth deactivate interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 + +Notes: + +* see [Delayed option] for more information + +##### Set echo-source interface + +> bfd udp echo-source set interface + +Parameters: + +* interface - interface used for getting source address for echo packets + +Example: + +> bfd udp echo-source set interface loop0 + +##### Delete echo-source interface + +> bfd udp echo-source del + +Example: + +> bfd udp echo-source del + +### Authentication + +BFD sessions should be authenticated for security purposes. SHA1 and meticulous +SHA1 authentication is supported by VPP. First, authentication keys are +configured in VPP and afterwards they can be used by sessions. + +There are two key IDs in the scope of BFD session: + +* configuration key ID is the internal unique key ID inside VPP and is never + communicated to any peer, it serves only the purpose of identifying the key +* BFD key ID is the key ID carried in BFD control frames and is used for + verifying authentication + +#### Turning auth on/off + +Authentication can be turned on or off at any time. Care must be taken however, +to either synchronize the authentication manipulation with peer's actions +to avoid the session going down. + +##### Delayed option + +Delayed option is useful for synchronizing authentication changes with a peer. +If it's specified, then authentication change is not performed immediately. +In this case, VPP continues to transmit packets using the old authentication +method (unauthenticated or using old sha1 key). If a packet is received, which +does not pass the current authentication, then VPP tries to authenticate it +using the new method (which might be none, if deactivating authentication) +and if it passes, then the new authentication method is put in use. + +The recommended procedure for enabling/changing/disabling session +authentication is: + +1. perform authentication change on vpp's side with delayed option set to yes +2. perform authentication change on peer's side (without delayed option) + +Notes: + +* if both peers use delayed option at the same time, the change will never + be carried out, since none of the peers will see any packet with the new + authentication which could trigger the change +* remote peer does not need to support or even be aware of this mechanism + for it to work properly + + +### Echo function + +Echo function is used by VPP whenever a peer declares the willingness +to support it, echo-source is set and it contains a usable subnet (see below). +When echo function is switched on, the required min rx interval advertised +to peer is set to 1 second (or the configured value, if its higher). + +#### Echo source address + +Because echo packets are only looped back (and not processed in any way) +by a peer, it's necessary to set the source address in a way which avoids +packet drop due to spoofing protection by VPP. Per RFC, the source address +should not be in the subnet set on the interface over which the echo packets +are sent. Also, it must not be any VPP-local address, otherwise the packet +gets dropped on receipt by VPP. The solution is to create a loopback interface +with a (private) IPv4/IPv6 subnet assigned as echo-source. The BFD then picks +an unused address from the subnet by flipping the last bit and uses that as +source address in the echo packets, thus meeting RFC recommendation while +avoiding spoofing protection. + +Example: if 10.10.10.3/31 is the subnet, then 10.10.10.2 will be used as + source address in (IPv4) echo packets + +### Demand mode + +Demand mode is respected by VPP, but not used locally. The only scenario when +demand mode could make sense currently is when echo is active. Because echo +packets are inherently insecure against an adversary looping them back a poll +sequence would be required for slow periodic connectivity verification anyway. +It's more efficient to just ask the remote peer to send slow periodic control +frames without VPP initiating periodic poll sequences. + +### Admin-down + +Session may be put admin-down at any time. This immediately causes the state +to be changed to AdminDown and remain so unless the session is put admin-up. + +## BFD implementation notes + +Because BFD can work over different transport layers, the BFD code is separated +into core BFD functionality - main module implemented in bfd_main.c +and transport-specific code implemented in bfd_udp.c. + +### Main module + +Main module is responsible for handling all the BFD functionality defined +in RFC 5880. + +#### Internal API + +Internal APIs defined in bfd_main.h are called from transport-specific code +to create/modify/delete + +#### Packet receipt + +When a packet is received by the transport layer, it is forwarded to main +module (to main thread) via an RPC call. At this point, the authentication has +been verified, so the packet is consumed, session parameters are updated +accordingly and state change (if applicable). Based on these, the timeouts +are adjusted if required and an event is sent to the process node to wake up +and recalculate sleep time. + +#### Packet transmit + +Main module allocates a vlib_buffer_t, creates the required BFD frame (control +or echo in it), then calls the transport layer to add the transport layer. +Then a frame containing the buffer to the aprropriate node is created +and enqueued. + +#### Process node + +Main module implements one process node which is a simple loop. The process +node gets next timeout from the timer wheel, sleeps until the timeout expires +and then calls a timeout routine which drives the state machine for each +session which timed out. The sleep is interrupted externally via vlib event, +when a session is added or modified in a way which might require timer wheel +manipulation. In this case the caller inserts the necessary timeout to timer +wheel and then signals the process node to wake up early, handle possible +timeouts and recalculate the sleep time again. + +#### State machine + +Default state of BFD session when created is Down, per RFC 5880. State changes +to Init, Up or Down based on events like received state from peer and timeouts. +The session state can be set AdminDown using a binary API, which prevents it +from going to any other state, until this limitation is removed. This state +is advertised to peers in slow periodic control frames. + +For each session, the following timeouts are maintained: + +1. tx timeout - used for sending out control frames +2. rx timeout - used for detecting session timeout +3. echo tx timeout - used for sending out echo frames +3. echo rx timeout - used for detecting session timeout based on echo + +These timeouts are maintained in cpu clocks and recalculated when appropriate +(e.g. rx timeout is bumped when a packet is received, keeping the session +alive). Only the earliest timeout is inserted into the timer wheel at a time +and timer wheel events are never deleted, rather spurious events are ignored. +This allows efficient operation, like not inserting events into timing wheel +for each packet received or ignoring left-over events in case a bfd session +gets removed and a new one is recreated with the same session index. + +#### Authentication keys management + +Authentication keys are managed internally in a pool, with each key tracking +it's use count. The removal/modification is only allowed if the key is not in +use. + +### UDP module + +UDP module is responsible for: + +1. public APIs/CLIs to configure BFD over UDP. +2. support code called by main module to encapsulate/decapsulate BFD packets + +This module implements two graph nodes - for consuming ipv4 and ipv6 packets +target at BFD ports 3874 and 3875. + +#### Packet receipt + +BFD packet receipt receipt starts in the bfd udp graph nodes. Since the code +needs to verify IP/UDP header data, it relies on ip4-local (and ip6-local) +nodes to store pointers to the appropriate headers. First, your discriminator +is extracted from BFD packet and used to lookup the existing session. In case +it's zero, the pair of IP addresses and sw_if_index is used to lookup session. +Then, main module is called to verify the authentication, if present. +Afterwards a check is made if the IP/UDP headers are correct. If yes, then +an RPC call is made to the main thread to consume the packet and take action +upon it. + +#### Packet transmission + +When process node decides that there is a need to transmit the packet, it +creates a buffer, fills the BFD frame data in and calls the UDP module to +add the transport layer. This is a simple operation for the control frames +consisting of just adding UDP/IP headers based on session data. For echo +frames, an additional step, looking at the echo-source interface and picking +and address is performed and if this fails, then the packet cannot be +transmitted and an error is returned to main thread. diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index d38623c1..ea6db1f9 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -1606,8 +1606,6 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) bfd_set_remote_required_min_echo_rx (bm, bs, now, clib_net_to_host_u32 (pkt->req_min_echo_rx)); - /* FIXME 6.8.2 */ - /* FIXME 6.8.4 */ if (bfd_pkt_get_final (pkt)) { if (BFD_POLL_IN_PROGRESS == bs->poll_state) @@ -1915,13 +1913,13 @@ bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, { #if WITH_LIBSSL > 0 bfd_auth_key_t *auth_key = NULL; - if (!key_len || key_len > bfd_max_len_for_auth_type (auth_type)) + if (!key_len || key_len > bfd_max_key_len_for_auth_type (auth_type)) { clib_warning ("Invalid authentication key length for auth_type=%d:%s " "(key_len=%u, must be " "non-zero, expected max=%u)", auth_type, bfd_auth_type_str (auth_type), key_len, - (u32) bfd_max_len_for_auth_type (auth_type)); + (u32) bfd_max_key_len_for_auth_type (auth_type)); return VNET_API_ERROR_INVALID_VALUE; } if (!bfd_auth_type_supported (auth_type)) diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index 3be3694c..4d460f41 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -37,19 +37,19 @@ typedef enum typedef struct { - /* global configuration key ID */ + /** global configuration key ID */ u32 conf_key_id; - /* keeps track of how many sessions reference this key */ + /** keeps track of how many sessions reference this key */ u32 use_count; - /* + /** * key data directly usable for bfd purposes - already padded with zeroes * (so we don't need the actual length) */ u8 key[20]; - /* authentication type for this key */ + /** authentication type for this key */ bfd_auth_type_e auth_type; } bfd_auth_key_t; @@ -68,152 +68,152 @@ typedef enum typedef struct bfd_session_s { - /* index in bfd_main.sessions pool */ + /** index in bfd_main.sessions pool */ u32 bs_idx; - /* session state */ + /** session state */ bfd_state_e local_state; - /* remote session state */ + /** remote session state */ bfd_state_e remote_state; - /* local diagnostics */ + /** local diagnostics */ bfd_diag_code_e local_diag; - /* remote diagnostics */ + /** remote diagnostics */ bfd_diag_code_e remote_diag; - /* local discriminator */ + /** local discriminator */ u32 local_discr; - /* remote discriminator */ + /** remote discriminator */ u32 remote_discr; - /* configured desired min tx interval (microseconds) */ + /** configured desired min tx interval (microseconds) */ u32 config_desired_min_tx_usec; - /* configured desired min tx interval (clocks) */ + /** configured desired min tx interval (clocks) */ u64 config_desired_min_tx_clocks; - /* effective desired min tx interval (clocks) */ + /** effective desired min tx interval (clocks) */ u64 effective_desired_min_tx_clocks; - /* configured required min rx interval (microseconds) */ + /** configured required min rx interval (microseconds) */ u32 config_required_min_rx_usec; - /* configured required min rx interval (clocks) */ + /** configured required min rx interval (clocks) */ u64 config_required_min_rx_clocks; - /* effective required min rx interval (clocks) */ + /** effective required min rx interval (clocks) */ u64 effective_required_min_rx_clocks; - /* remote min rx interval (microseconds) */ + /** remote min rx interval (microseconds) */ u64 remote_min_rx_usec; - /* remote min rx interval (clocks) */ + /** remote min rx interval (clocks) */ u64 remote_min_rx_clocks; - /* remote min echo rx interval (microseconds) */ + /** remote min echo rx interval (microseconds) */ u64 remote_min_echo_rx_usec; - /* remote min echo rx interval (clocks) */ + /** remote min echo rx interval (clocks) */ u64 remote_min_echo_rx_clocks; - /* remote desired min tx interval (clocks) */ + /** remote desired min tx interval (clocks) */ u64 remote_desired_min_tx_clocks; - /* configured detect multiplier */ + /** configured detect multiplier */ u8 local_detect_mult; - /* 1 if remote system sets demand mode, 0 otherwise */ + /** 1 if remote system sets demand mode, 0 otherwise */ u8 remote_demand; - /* remote detect multiplier */ + /** remote detect multiplier */ u8 remote_detect_mult; - /* 1 is echo function is active, 0 otherwise */ + /** 1 is echo function is active, 0 otherwise */ u8 echo; - /* set to value of timer in timing wheel, 0 if never set */ + /** set to value of timer in timing wheel, 0 if never set */ u64 wheel_time_clocks; - /* transmit interval */ + /** transmit interval */ u64 transmit_interval_clocks; - /* next time at which to transmit a packet */ + /** next time at which to transmit a packet */ u64 tx_timeout_clocks; - /* timestamp of last packet transmitted */ + /** timestamp of last packet transmitted */ u64 last_tx_clocks; - /* timestamp of last packet received */ + /** timestamp of last packet received */ u64 last_rx_clocks; - /* transmit interval for echo packets */ + /** transmit interval for echo packets */ u64 echo_transmit_interval_clocks; - /* next time at which to transmit echo packet */ + /** next time at which to transmit echo packet */ u64 echo_tx_timeout_clocks; - /* timestamp of last echo packet transmitted */ + /** timestamp of last echo packet transmitted */ u64 echo_last_tx_clocks; - /* timestamp of last echo packet received */ + /** timestamp of last echo packet received */ u64 echo_last_rx_clocks; - /* secret used for calculating/checking checksum of echo packets */ + /** secret used for calculating/checking checksum of echo packets */ u32 echo_secret; - /* detection time */ + /** detection time */ u64 detection_time_clocks; - /* state info regarding poll sequence */ + /** state info regarding poll sequence */ bfd_poll_state_e poll_state; - /* + /** * helper for delayed poll sequence - marks either start of running poll * sequence or timeout, after which we can start the next poll sequnce */ u64 poll_state_start_or_timeout_clocks; - /* authentication information */ + /** authentication information */ struct { - /* current key in use */ + /** current key in use */ bfd_auth_key_t *curr_key; - /* + /** * set to next key to use if delayed switch is enabled - in that case * the key is switched when first incoming packet is signed with next_key */ bfd_auth_key_t *next_key; - /* sequence number incremented occasionally or always (if meticulous) */ + /** sequence number incremented occasionally or always (if meticulous) */ u32 local_seq_number; - /* remote sequence number */ + /** remote sequence number */ u32 remote_seq_number; - /* set to 1 if remote sequence number is known */ + /** set to 1 if remote sequence number is known */ u8 remote_seq_number_known; - /* current key ID sent out in bfd packet */ + /** current key ID sent out in bfd packet */ u8 curr_bfd_key_id; - /* key ID to use when switched to next_key */ + /** key ID to use when switched to next_key */ u8 next_bfd_key_id; - /* + /** * set to 1 if delayed action is pending, which might be activation * of authentication, change of key or deactivation */ u8 is_delayed; } auth; - /* transport type for this session */ + /** transport type for this session */ bfd_transport_e transport; - /* union of transport-specific data */ + /** union of transport-specific data */ union { bfd_udp_session_t udp; @@ -222,48 +222,48 @@ typedef struct bfd_session_s typedef struct { - /* pool of bfd sessions context data */ + /** pool of bfd sessions context data */ bfd_session_t *sessions; - /* timing wheel for scheduling timeouts */ + /** timing wheel for scheduling timeouts */ timing_wheel_t wheel; - /* timing wheel inaccuracy, in clocks */ + /** timing wheel inaccuracy, in clocks */ u64 wheel_inaccuracy; - /* hashmap - bfd session by discriminator */ + /** hashmap - bfd session by discriminator */ u32 *session_by_disc; - /* background process node index */ + /** background process node index */ u32 bfd_process_node_index; - /* convenience variables */ + /** convenience variables */ vlib_main_t *vlib_main; vnet_main_t *vnet_main; - /* cpu clocks per second */ + /** cpu clocks per second */ f64 cpu_cps; - /* default desired min tx in clocks */ + /** default desired min tx in clocks */ u64 default_desired_min_tx_clocks; - /* minimum required min rx while echo function is active - clocks */ + /** minimum required min rx while echo function is active - clocks */ u64 min_required_min_rx_while_echo_clocks; - /* for generating random numbers */ + /** for generating random numbers */ u32 random_seed; - /* pool of authentication keys */ + /** pool of authentication keys */ bfd_auth_key_t *auth_keys; - /* hashmap - index in pool auth_keys by conf_key_id */ + /** hashmap - index in pool auth_keys by conf_key_id */ u32 *auth_key_by_conf_key_id; } bfd_main_t; extern bfd_main_t bfd_main; -/* Packet counters */ +/** Packet counters */ #define foreach_bfd_error(F) \ F (NONE, "good bfd packets (processed)") \ F (BAD, "invalid bfd packets") \ @@ -277,7 +277,7 @@ typedef enum BFD_N_ERROR, } bfd_error_t; -/* bfd packet trace capture */ +/** bfd packet trace capture */ typedef struct { u32 len; @@ -291,14 +291,14 @@ enum BFD_EVENT_CONFIG_CHANGED, } bfd_process_event_e; -/* echo packet structure */ /* *INDENT-OFF* */ +/** echo packet structure */ typedef CLIB_PACKED (struct { - /* local discriminator */ + /** local discriminator */ u32 discriminator; - /* expire time of this packet - clocks */ + /** expire time of this packet - clocks */ u64 expire_time_clocks; - /* checksum - based on discriminator, local secret and expire time */ + /** checksum - based on discriminator, local secret and expire time */ u64 checksum; }) bfd_echo_pkt_t; /* *INDENT-ON* */ @@ -335,10 +335,10 @@ const char *bfd_poll_state_string (bfd_poll_state_e state); #define USEC_PER_MS 1000LL #define USEC_PER_SECOND (1000 * USEC_PER_MS) -/* default, slow transmission interval for BFD packets, per spec at least 1s */ +/** default, slow transmission interval for BFD packets, per spec at least 1s */ #define BFD_DEFAULT_DESIRED_MIN_TX_USEC USEC_PER_SECOND -/* +/** * minimum required min rx set locally when echo function is used, per spec * should be set to at least 1s */ diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c index 5deb9702..cd51e91a 100644 --- a/src/vnet/bfd/bfd_protocol.c +++ b/src/vnet/bfd/bfd_protocol.c @@ -12,6 +12,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @file + * @brief BFD protocol implementation + */ #include u8 @@ -131,7 +135,7 @@ bfd_pkt_set_multipoint (bfd_pkt_t * pkt) #endif u32 -bfd_max_len_for_auth_type (bfd_auth_type_e auth_type) +bfd_max_key_len_for_auth_type (bfd_auth_type_e auth_type) { #define F(t, l, n, s) \ if (auth_type == t) \ diff --git a/src/vnet/bfd/bfd_protocol.h b/src/vnet/bfd/bfd_protocol.h index cdbb8fa7..210c561b 100644 --- a/src/vnet/bfd/bfd_protocol.h +++ b/src/vnet/bfd/bfd_protocol.h @@ -40,7 +40,10 @@ typedef enum #undef F } bfd_auth_type_e; -u32 bfd_max_len_for_auth_type (bfd_auth_type_e auth_type); +/** + * @brief get the maximum length of key data for given auth type + */ +u32 bfd_max_key_len_for_auth_type (bfd_auth_type_e auth_type); const char *bfd_auth_type_str (bfd_auth_type_e auth_type); /* *INDENT-OFF* */ diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index e6dbaabb..b3eabc9c 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -12,6 +12,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @file + * @brief BFD UDP transport layer implementation + */ #include #include #include diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index e33b7407..5080ec98 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -13,7 +13,7 @@ */ /** * @file - * @brief BFD global declarations + * @brief BFD UDP transport layer declarations */ #ifndef __included_bfd_udp_h__ @@ -25,23 +25,27 @@ #include /* *INDENT-OFF* */ +/** identifier of BFD session based on UDP transport only */ typedef CLIB_PACKED (struct { - + /** interface to which the session is tied */ u32 sw_if_index; + /** local address */ ip46_address_t local_addr; + /** peer address */ ip46_address_t peer_addr; - }) bfd_udp_key_t; /* *INDENT-ON* */ +/** UDP transport specific data embedded in bfd_session's union */ typedef struct { + /** key identifying this session */ bfd_udp_key_t key; - + /** adjacency index returned from adj lock call */ adj_index_t adj_index; } bfd_udp_session_t; -/* bfd udp echo packet trace capture */ +/** bfd udp echo packet trace capture */ typedef struct { u32 len; @@ -50,8 +54,23 @@ typedef struct struct bfd_session_s; +/** + * @brief add the necessary transport layer by prepending it to existing data + * + * @param is_echo 1 if this is echo packet, 0 if control frame + * + * @return 1 on success, 0 on failure + */ int bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, const struct bfd_session_s *bs, int is_echo); + +/** + * @brief add the necessary transport layer by prepending it to existing data + * + * @param is_echo 1 if this is echo packet, 0 if control frame + * + * @return 1 on success, 0 on failure + */ int bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, const struct bfd_session_s *bs, int is_echo); @@ -62,10 +81,12 @@ int bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, */ int bfd_udp_is_echo_available (bfd_transport_e transport); -void -bfd_udp_get_echo_source (int *is_set, u32 * sw_if_index, int *have_usable_ip4, - ip4_address_t * ip4, int *have_usable_ip6, - ip6_address_t * ip6); +/** + * @brief get echo source information - used by CLI + */ +void bfd_udp_get_echo_source (int *is_set, u32 * sw_if_index, + int *have_usable_ip4, ip4_address_t * ip4, + int *have_usable_ip6, ip6_address_t * ip6); #endif /* __included_bfd_udp_h__ */ diff --git a/src/vnet/bfd/dir.dox b/src/vnet/bfd/dir.dox index ed656b52..b9a5978f 100644 --- a/src/vnet/bfd/dir.dox +++ b/src/vnet/bfd/dir.dox @@ -13,6 +13,6 @@ * limitations under the License. */ /** - @dir vnet/vnet/bfd + @dir @brief Bidirectional Forwarding Detection (BFD) implementation */ -- cgit 1.2.3-korg From e50e8568c160f51cc2a268b59e209d13cb7344be Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Tue, 4 Apr 2017 16:19:48 +0200 Subject: BFD: add ARP-awareness, fix bugs Make BFD ARP-aware when sending out packets. Fix a few one-liner bugs discovered while integrating with cisco nexus. Enhance CLI view to better observe session state. Change-Id: I266c29492f351207b84328ab665d9d697969da9c Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd_cli.c | 57 ++++++++++++++++---- src/vnet/bfd/bfd_main.c | 136 ++++++++++++++++++++++++++++------------------- src/vnet/bfd/bfd_main.h | 3 +- src/vnet/bfd/bfd_udp.c | 138 ++++++++++++++++++++++++++++++++++++++++++++---- src/vnet/bfd/bfd_udp.h | 25 ++++++++- 5 files changed, 282 insertions(+), 77 deletions(-) (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet/bfd/bfd_cli.c b/src/vnet/bfd/bfd_cli.c index f15acb4b..44e671c5 100644 --- a/src/vnet/bfd/bfd_cli.c +++ b/src/vnet/bfd/bfd_cli.c @@ -28,6 +28,7 @@ static u8 * format_bfd_session_cli (u8 * s, va_list * args) { + vlib_main_t *vm = va_arg (*args, vlib_main_t *); bfd_main_t *bm = va_arg (*args, bfd_main_t *); bfd_session_t *bs = va_arg (*args, bfd_session_t *); switch (bs->transport) @@ -51,7 +52,7 @@ format_bfd_session_cli (u8 * s, va_list * args) bfd_diag_code_string (bs->remote_diag)); s = format (s, "%10s %-32s %20u %20u\n", "", "Detect multiplier", bs->local_detect_mult, bs->remote_detect_mult); - s = format (s, "%10s %-32s %20u %20u\n", "", + s = format (s, "%10s %-32s %20u %20llu\n", "", "Required Min Rx Interval (usec)", bs->config_required_min_rx_usec, bs->remote_min_rx_usec); s = format (s, "%10s %-32s %20u %20u\n", "", @@ -61,18 +62,54 @@ format_bfd_session_cli (u8 * s, va_list * args) s = format (s, "%10s %-32s %20u\n", "", "Transmit interval", bfd_clocks_to_usec (bm, bs->transmit_interval_clocks)); + u64 now = clib_cpu_time_now (); + u8 *tmp = NULL; + if (bs->last_tx_clocks > 0) + { + tmp = format (tmp, "%.2fs ago", (now - bs->last_tx_clocks) * + vm->clib_time.seconds_per_clock); + s = format (s, "%10s %-32s %20v\n", "", "Last control frame tx", tmp); + vec_reset_length (tmp); + } + if (bs->last_rx_clocks) + { + tmp = format (tmp, "%.2fs ago", (now - bs->last_rx_clocks) * + vm->clib_time.seconds_per_clock); + s = format (s, "%10s %-32s %20v\n", "", "Last control frame rx", tmp); + vec_reset_length (tmp); + } s = - format (s, "%10s %-32s %20s %20s\n", "", "Demand mode", "no", - bs->remote_demand ? "yes" : "no"); - s = - format (s, "%10s %-32s %20s\n", "", "Poll state", - bfd_poll_state_string (bs->poll_state)); + format (s, "%10s %-32s %20u %20llu\n", "", "Min Echo Rx Interval (usec)", + 1, bs->remote_min_echo_rx_usec); + if (bs->echo) + { + s = format (s, "%10s %-32s %20u\n", "", "Echo transmit interval", + bfd_clocks_to_usec (bm, bs->echo_transmit_interval_clocks)); + tmp = format (tmp, "%.2fs ago", (now - bs->echo_last_tx_clocks) * + vm->clib_time.seconds_per_clock); + s = format (s, "%10s %-32s %20v\n", "", "Last echo frame tx", tmp); + vec_reset_length (tmp); + tmp = format (tmp, "%.6fs", + (bs->echo_last_rx_clocks - bs->echo_last_tx_clocks) * + vm->clib_time.seconds_per_clock); + s = + format (s, "%10s %-32s %20v\n", "", "Last echo frame roundtrip time", + tmp); + } + vec_free (tmp); + tmp = NULL; + s = format (s, "%10s %-32s %20s %20s\n", "", "Demand mode", "no", + bs->remote_demand ? "yes" : "no"); + s = format (s, "%10s %-32s %20s\n", "", "Poll state", + bfd_poll_state_string (bs->poll_state)); if (bs->auth.curr_key) { s = format (s, "%10s %-32s %20u\n", "", "Authentication config key ID", bs->auth.curr_key->conf_key_id); s = format (s, "%10s %-32s %20u\n", "", "Authentication BFD key ID", bs->auth.curr_bfd_key_id); + s = format (s, "%10s %-32s %20u %20u\n", "", "Sequence number", + bs->auth.local_seq_number, bs->auth.remote_seq_number); } return s; } @@ -96,6 +133,7 @@ show_bfd (vlib_main_t * vm, unformat_input_t * input, }); /* *INDENT-ON* */ vlib_cli_output (vm, "%v\n", s); + vec_free (s); vlib_cli_output (vm, "Number of configured BFD keys: %lu\n", (u64) pool_elts (bm->auth_keys)); } @@ -104,8 +142,9 @@ show_bfd (vlib_main_t * vm, unformat_input_t * input, u8 *s = format (NULL, "%=10s %=32s %=20s %=20s\n", "Index", "Property", "Local value", "Remote value"); /* *INDENT-OFF* */ - pool_foreach (bs, bm->sessions, - { s = format (s, "%U", format_bfd_session_cli, bm, bs); }); + pool_foreach (bs, bm->sessions, { + s = format (s, "%U", format_bfd_session_cli, vm, bm, bs); + }); /* *INDENT-ON* */ vlib_cli_output (vm, "%v", s); vec_free (s); @@ -349,7 +388,7 @@ static const unsigned optional = 0; #define CHECK_MANDATORY(t, n, s, r, ...) \ if (mandatory == r && !have_##n) \ { \ - ret = clib_error_return (0, "Required parameter `%s' missing.", n); \ + ret = clib_error_return (0, "Required parameter `%s' missing.", s); \ goto out; \ } diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index ea6db1f9..2b70a20c 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -63,13 +63,6 @@ bfd_clocks_to_usec (const bfd_main_t * bm, u64 clocks) static vlib_node_registration_t bfd_process_node; -/* set to 0 here, real values filled at startup */ -static u32 bfd_node_index_by_transport[] = { -#define F(t, n) [BFD_TRANSPORT_##t] = 0, - foreach_bfd_transport (F) -#undef F -}; - u8 * format_bfd_auth_key (u8 * s, va_list * args) { @@ -560,51 +553,70 @@ bfd_on_config_change (vlib_main_t * vm, vlib_node_runtime_t * rt, } static void -bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, - bfd_session_t * bs) +bfd_add_transport_layer (vlib_main_t * vm, u32 bi, bfd_session_t * bs) +{ + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + BFD_DBG ("Transport bfd via udp4, bs_idx=%u", bs->bs_idx); + bfd_add_udp4_transport (vm, bi, bs, 0 /* is_echo */ ); + break; + case BFD_TRANSPORT_UDP6: + BFD_DBG ("Transport bfd via udp6, bs_idx=%u", bs->bs_idx); + bfd_add_udp6_transport (vm, bi, bs, 0 /* is_echo */ ); + break; + } +} + +static int +bfd_transport_control_frame (vlib_main_t * vm, u32 bi, bfd_session_t * bs) { switch (bs->transport) { case BFD_TRANSPORT_UDP4: BFD_DBG ("Transport bfd via udp4, bs_idx=%u", bs->bs_idx); - bfd_add_udp4_transport (vm, b, bs, 0 /* is_echo */ ); + return bfd_transport_udp4 (vm, bi, bs); break; case BFD_TRANSPORT_UDP6: BFD_DBG ("Transport bfd via udp6, bs_idx=%u", bs->bs_idx); - bfd_add_udp6_transport (vm, b, bs, 0 /* is_echo */ ); + return bfd_transport_udp6 (vm, bi, bs); break; } + return 0; } static int -bfd_echo_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, - bfd_session_t * bs) +bfd_echo_add_transport_layer (vlib_main_t * vm, u32 bi, bfd_session_t * bs) { switch (bs->transport) { case BFD_TRANSPORT_UDP4: BFD_DBG ("Transport bfd echo via udp4, bs_idx=%u", bs->bs_idx); - return bfd_add_udp4_transport (vm, b, bs, 1 /* is_echo */ ); + return bfd_add_udp4_transport (vm, bi, bs, 1 /* is_echo */ ); break; case BFD_TRANSPORT_UDP6: BFD_DBG ("Transport bfd echo via udp6, bs_idx=%u", bs->bs_idx); - return bfd_add_udp6_transport (vm, b, bs, 1 /* is_echo */ ); + return bfd_add_udp6_transport (vm, bi, bs, 1 /* is_echo */ ); break; } return 0; } -static void -bfd_create_frame_to_next_node (vlib_main_t * vm, bfd_session_t * bs, u32 bi) +static int +bfd_transport_echo (vlib_main_t * vm, u32 bi, bfd_session_t * bs) { - - vlib_frame_t *f = - vlib_get_frame_to_node (vm, bfd_node_index_by_transport[bs->transport]); - - u32 *to_next = vlib_frame_vector_args (f); - to_next[0] = bi; - f->n_vectors = 1; - vlib_put_frame_to_node (vm, bfd_node_index_by_transport[bs->transport], f); + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + BFD_DBG ("Transport bfd echo via udp4, bs_idx=%u", bs->bs_idx); + return bfd_transport_udp4 (vm, bi, bs); + break; + case BFD_TRANSPORT_UDP6: + BFD_DBG ("Transport bfd echo via udp6, bs_idx=%u", bs->bs_idx); + return bfd_transport_udp6 (vm, bi, bs); + break; + } + return 0; } #if WITH_LIBSSL > 0 @@ -704,7 +716,7 @@ bfd_init_control_frame (bfd_main_t * bm, bfd_session_t * bs, bfd_pkt_set_diag_code (pkt, bs->local_diag); bfd_pkt_set_state (pkt, bs->local_state); pkt->head.detect_mult = bs->local_detect_mult; - pkt->head.length = clib_host_to_net_u32 (bfd_length); + pkt->head.length = bfd_length; pkt->my_disc = bs->local_discr; pkt->your_disc = bs->remote_discr; pkt->des_min_tx = clib_host_to_net_u32 (bs->config_desired_min_tx_usec); @@ -725,8 +737,7 @@ bfd_init_control_frame (bfd_main_t * bm, bfd_session_t * bs, static void bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, - bfd_main_t * bm, bfd_session_t * bs, u64 now, - int handling_wakeup) + bfd_main_t * bm, bfd_session_t * bs, u64 now) { if (!bfd_is_echo_possible (bs)) { @@ -734,7 +745,8 @@ bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, bs->echo = 0; return; } - /* sometimes the wheel expires an event a bit sooner than requested, account + /* sometimes the wheel expires an event a bit sooner than requested, + account for that here */ if (now + bm->wheel_inaccuracy >= bs->echo_tx_timeout_clocks) { @@ -747,6 +759,8 @@ bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, } vlib_buffer_t *b = vlib_get_buffer (vm, bi); ASSERT (b->current_data == 0); + memset (vnet_buffer (b), 0, sizeof (*vnet_buffer (b))); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b); bfd_echo_pkt_t *pkt = vlib_buffer_get_current (b); memset (pkt, 0, sizeof (*pkt)); pkt->discriminator = bs->local_discr; @@ -756,7 +770,14 @@ bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_calc_echo_checksum (bs->local_discr, pkt->expire_time_clocks, bs->echo_secret); b->current_length = sizeof (*pkt); - if (!bfd_echo_add_transport_layer (vm, b, bs)) + if (!bfd_echo_add_transport_layer (vm, bi, bs)) + { + BFD_ERR ("cannot send echo packet out, turning echo off"); + bs->echo = 0; + vlib_buffer_free_one (vm, bi); + return; + } + if (!bfd_transport_echo (vm, bi, bs)) { BFD_ERR ("cannot send echo packet out, turning echo off"); bs->echo = 0; @@ -765,7 +786,6 @@ bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, } bs->echo_last_tx_clocks = now; bfd_calc_next_echo_tx (bm, bs, now); - bfd_create_frame_to_next_node (vm, bs, bi); } else { @@ -777,8 +797,7 @@ bfd_send_echo (vlib_main_t * vm, vlib_node_runtime_t * rt, static void bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, - bfd_main_t * bm, bfd_session_t * bs, u64 now, - int handling_wakeup) + bfd_main_t * bm, bfd_session_t * bs, u64 now) { if (!bs->remote_min_rx_usec && BFD_POLL_NOT_NEEDED == bs->poll_state) { @@ -798,8 +817,10 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, BFD_DBG ("Remote demand is set, not sending periodic control frame"); return; } - /* sometimes the wheel expires an event a bit sooner than requested, account - for that here */ + /* + * sometimes the wheel expires an event a bit sooner than requested, account + * for that here + */ if (now + bm->wheel_inaccuracy >= bs->tx_timeout_clocks) { BFD_DBG ("\nSending periodic control frame: %U", format_bfd_session, @@ -812,6 +833,8 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, } vlib_buffer_t *b = vlib_get_buffer (vm, bi); ASSERT (b->current_data == 0); + memset (vnet_buffer (b), 0, sizeof (*vnet_buffer (b))); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b); bfd_init_control_frame (bm, bs, b); switch (bs->poll_state) { @@ -837,10 +860,13 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, break; } bfd_add_auth_section (b, bs); - bfd_add_transport_layer (vm, b, bs); + bfd_add_transport_layer (vm, bi, bs); + if (!bfd_transport_control_frame (vm, bi, bs)) + { + vlib_buffer_free_one (vm, bi); + } bs->last_tx_clocks = now; bfd_calc_next_tx (bm, bs, now); - bfd_create_frame_to_next_node (vm, bs, bi); } else { @@ -852,13 +878,15 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, - bfd_main_t * bm, bfd_session_t * bs) + bfd_main_t * bm, bfd_session_t * bs, + int is_local) { BFD_DBG ("Send final control frame for bs_idx=%lu", bs->bs_idx); bfd_init_control_frame (bm, bs, b); bfd_pkt_set_final (vlib_buffer_get_current (b)); bfd_add_auth_section (b, bs); - bfd_add_transport_layer (vm, b, bs); + u32 bi = vlib_get_buffer_index (vm, b); + bfd_add_transport_layer (vm, bi, bs); bs->last_tx_clocks = clib_cpu_time_now (); /* * RFC allows to include changes in final frame, so if there were any @@ -871,8 +899,10 @@ static void bfd_check_rx_timeout (bfd_main_t * bm, bfd_session_t * bs, u64 now, int handling_wakeup) { - /* sometimes the wheel expires an event a bit sooner than requested, account - for that here */ + /* + * sometimes the wheel expires an event a bit sooner than requested, account + * for that here + */ if (bs->last_rx_clocks + bs->detection_time_clocks <= now + bm->wheel_inaccuracy) { @@ -907,14 +937,14 @@ bfd_on_timeout (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, switch (bs->local_state) { case BFD_STATE_admin_down: - bfd_send_periodic (vm, rt, bm, bs, now, 1); + bfd_send_periodic (vm, rt, bm, bs, now); break; case BFD_STATE_down: - bfd_send_periodic (vm, rt, bm, bs, now, 1); + bfd_send_periodic (vm, rt, bm, bs, now); break; case BFD_STATE_init: bfd_check_rx_timeout (bm, bs, now, 1); - bfd_send_periodic (vm, rt, bm, bs, now, 1); + bfd_send_periodic (vm, rt, bm, bs, now); break; case BFD_STATE_up: bfd_check_rx_timeout (bm, bs, now, 1); @@ -932,10 +962,10 @@ bfd_on_timeout (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, bs->config_required_min_rx_clocks)); bfd_set_poll_state (bs, BFD_POLL_NEEDED); } - bfd_send_periodic (vm, rt, bm, bs, now, 1); + bfd_send_periodic (vm, rt, bm, bs, now); if (bs->echo) { - bfd_send_echo (vm, rt, bm, bs, now, 1); + bfd_send_echo (vm, rt, bm, bs, now); } break; } @@ -996,7 +1026,8 @@ bfd_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) { bfd_session_t *bs = pool_elt_at_index (bm->sessions, *event_data); - bfd_send_periodic (vm, rt, bm, bs, now, 1); + bfd_send_periodic (vm, rt, bm, bs, now); + bfd_set_timer (bm, bs, now, 1); } else { @@ -1113,14 +1144,6 @@ bfd_main_init (vlib_main_t * vm) const u64 now = clib_cpu_time_now (); timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; - - vlib_node_t *node = NULL; -#define F(t, n) \ - node = vlib_get_node_by_name (vm, (u8 *)n); \ - bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index; \ - BFD_DBG ("node '%s' has index %u", n, node->index); - foreach_bfd_transport (F); -#undef F return 0; } @@ -1654,10 +1677,12 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) { if (BFD_STATE_down == bs->remote_state) { + bfd_set_diag (bs, BFD_DIAG_CODE_no_diag); bfd_set_state (bm, bs, BFD_STATE_init, 0); } else if (BFD_STATE_init == bs->remote_state) { + bfd_set_diag (bs, BFD_DIAG_CODE_no_diag); bfd_set_state (bm, bs, BFD_STATE_up, 0); } } @@ -1666,6 +1691,7 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) if (BFD_STATE_up == bs->remote_state || BFD_STATE_init == bs->remote_state) { + bfd_set_diag (bs, BFD_DIAG_CODE_no_diag); bfd_set_state (bm, bs, BFD_STATE_up, 0); } } diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index 4d460f41..d722a552 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -316,7 +316,8 @@ int bfd_verify_pkt_auth (const bfd_pkt_t * pkt, u16 pkt_size, bfd_session_t * bs); void bfd_event (bfd_main_t * bm, bfd_session_t * bs); void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, - bfd_main_t * bm, bfd_session_t * bs); + bfd_main_t * bm, bfd_session_t * bs, + int is_local); u8 *format_bfd_session (u8 * s, va_list * args); u8 *format_bfd_auth_key (u8 * s, va_list * args); void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down); diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index b3eabc9c..ebee590b 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -51,6 +51,14 @@ typedef struct int echo_source_is_set; /* loopback interface used to get echo source ip */ u32 echo_source_sw_if_index; + /* node index of "ip4-arp" node */ + u32 ip4_arp_idx; + /* node index of "ip6-discover-neighbor" node */ + u32 ip6_ndp_idx; + /* node index of "ip4-rewrite" node */ + u32 ip4_rewrite_idx; + /* node index of "ip6-rewrite" node */ + u32 ip6_rewrite_idx; } bfd_udp_main_t; static vlib_node_registration_t bfd_udp4_input_node; @@ -231,15 +239,18 @@ bfd_udp_get_echo_source (int *is_set, u32 * sw_if_index, int *have_usable_ip4, } int -bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, +bfd_add_udp4_transport (vlib_main_t * vm, u32 bi, const bfd_session_t * bs, int is_echo) { const bfd_udp_session_t *bus = &bs->udp; const bfd_udp_key_t *key = &bus->key; + vlib_buffer_t *b = vlib_get_buffer (vm, bi); b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; vnet_buffer (b)->ip.adj_index[VLIB_RX] = bus->adj_index; vnet_buffer (b)->ip.adj_index[VLIB_TX] = bus->adj_index; + vnet_buffer (b)->sw_if_index[VLIB_RX] = 0; + vnet_buffer (b)->sw_if_index[VLIB_TX] = ~0; typedef struct { ip4_header_t ip4; @@ -283,15 +294,18 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, } int -bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, +bfd_add_udp6_transport (vlib_main_t * vm, u32 bi, const bfd_session_t * bs, int is_echo) { const bfd_udp_session_t *bus = &bs->udp; const bfd_udp_key_t *key = &bus->key; + vlib_buffer_t *b = vlib_get_buffer (vm, bi); b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; vnet_buffer (b)->ip.adj_index[VLIB_RX] = bus->adj_index; vnet_buffer (b)->ip.adj_index[VLIB_TX] = bus->adj_index; + vnet_buffer (b)->sw_if_index[VLIB_RX] = 0; + vnet_buffer (b)->sw_if_index[VLIB_TX] = 0; typedef struct { ip6_header_t ip6; @@ -346,6 +360,76 @@ bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, return 1; } +static void +bfd_create_frame_to_next_node (vlib_main_t * vm, u32 bi, u32 next_node) +{ + vlib_frame_t *f = vlib_get_frame_to_node (vm, next_node); + u32 *to_next = vlib_frame_vector_args (f); + to_next[0] = bi; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, next_node, f); +} + +int +bfd_udp_calc_next_node (const struct bfd_session_s *bs, u32 * next_node) +{ + const bfd_udp_session_t *bus = &bs->udp; + ip_adjacency_t *adj = adj_get (bus->adj_index); + switch (adj->lookup_next_index) + { + case IP_LOOKUP_NEXT_ARP: + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + *next_node = bfd_udp_main.ip4_arp_idx; + return 1; + case BFD_TRANSPORT_UDP6: + *next_node = bfd_udp_main.ip6_ndp_idx; + return 1; + } + break; + case IP_LOOKUP_NEXT_REWRITE: + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + *next_node = bfd_udp_main.ip4_rewrite_idx; + return 1; + case BFD_TRANSPORT_UDP6: + *next_node = bfd_udp_main.ip6_rewrite_idx; + return 1; + } + break; + default: + /* drop */ + break; + } + return 0; +} + +int +bfd_transport_udp4 (vlib_main_t * vm, u32 bi, const struct bfd_session_s *bs) +{ + u32 next_node; + int rv = bfd_udp_calc_next_node (bs, &next_node); + if (rv) + { + bfd_create_frame_to_next_node (vm, bi, next_node); + } + return rv; +} + +int +bfd_transport_udp6 (vlib_main_t * vm, u32 bi, const struct bfd_session_s *bs) +{ + u32 next_node; + int rv = bfd_udp_calc_next_node (bs, &next_node); + if (rv) + { + bfd_create_frame_to_next_node (vm, bi, next_node); + } + return 1; +} + static bfd_session_t * bfd_lookup_session (bfd_udp_main_t * bum, const bfd_udp_key_t * key) { @@ -703,7 +787,8 @@ bfd_udp_auth_deactivate (u32 sw_if_index, typedef enum { BFD_UDP_INPUT_NEXT_NORMAL, - BFD_UDP_INPUT_NEXT_REPLY, + BFD_UDP_INPUT_NEXT_REPLY_ARP, + BFD_UDP_INPUT_NEXT_REPLY_REWRITE, BFD_UDP_INPUT_N_NEXT, } bfd_udp_input_next_t; @@ -1112,8 +1197,11 @@ bfd_udp_input (vlib_main_t * vm, vlib_node_runtime_t * rt, const bfd_pkt_t *pkt = vlib_buffer_get_current (b0); if (bfd_pkt_get_poll (pkt)) { + b0->current_data = 0; + b0->current_length = 0; + memset (vnet_buffer (b0), 0, sizeof (*vnet_buffer (b0))); bfd_init_final_control_frame (vm, b0, bfd_udp_main.bfd_main, - bs); + bs, 0); if (is_ipv6) { vlib_node_increment_counter (vm, bfd_udp6_input_node.index, @@ -1124,7 +1212,20 @@ bfd_udp_input (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_node_increment_counter (vm, bfd_udp4_input_node.index, b0->error, 1); } - next0 = BFD_UDP_INPUT_NEXT_REPLY; + const bfd_udp_session_t *bus = &bs->udp; + ip_adjacency_t *adj = adj_get (bus->adj_index); + switch (adj->lookup_next_index) + { + case IP_LOOKUP_NEXT_ARP: + next0 = BFD_UDP_INPUT_NEXT_REPLY_ARP; + break; + case IP_LOOKUP_NEXT_REWRITE: + next0 = BFD_UDP_INPUT_NEXT_REPLY_REWRITE; + break; + default: + /* drop */ + break; + } } } vlib_set_next_frame_buffer (vm, rt, next0, bi0); @@ -1161,7 +1262,8 @@ VLIB_REGISTER_NODE (bfd_udp4_input_node, static) = { .next_nodes = { [BFD_UDP_INPUT_NEXT_NORMAL] = "error-drop", - [BFD_UDP_INPUT_NEXT_REPLY] = "ip4-lookup", + [BFD_UDP_INPUT_NEXT_REPLY_ARP] = "ip4-arp", + [BFD_UDP_INPUT_NEXT_REPLY_REWRITE] = "ip4-lookup", }, }; /* *INDENT-ON* */ @@ -1188,7 +1290,8 @@ VLIB_REGISTER_NODE (bfd_udp6_input_node, static) = { .next_nodes = { [BFD_UDP_INPUT_NEXT_NORMAL] = "error-drop", - [BFD_UDP_INPUT_NEXT_REPLY] = "ip6-lookup", + [BFD_UDP_INPUT_NEXT_REPLY_ARP] = "ip6-discover-neighbor", + [BFD_UDP_INPUT_NEXT_REPLY_REWRITE] = "ip6-lookup", }, }; /* *INDENT-ON* */ @@ -1246,7 +1349,7 @@ bfd_udp_echo_input (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_node_increment_counter (vm, bfd_udp_echo4_input_node.index, b0->error, 1); } - next0 = BFD_UDP_INPUT_NEXT_REPLY; + next0 = BFD_UDP_INPUT_NEXT_REPLY_REWRITE; } vlib_set_next_frame_buffer (vm, rt, next0, bi0); @@ -1300,7 +1403,8 @@ VLIB_REGISTER_NODE (bfd_udp_echo4_input_node, static) = { .next_nodes = { [BFD_UDP_INPUT_NEXT_NORMAL] = "error-drop", - [BFD_UDP_INPUT_NEXT_REPLY] = "ip4-lookup", + [BFD_UDP_INPUT_NEXT_REPLY_ARP] = "ip4-arp", + [BFD_UDP_INPUT_NEXT_REPLY_REWRITE] = "ip4-lookup", }, }; /* *INDENT-ON* */ @@ -1328,7 +1432,8 @@ VLIB_REGISTER_NODE (bfd_udp_echo6_input_node, static) = { .next_nodes = { [BFD_UDP_INPUT_NEXT_NORMAL] = "error-drop", - [BFD_UDP_INPUT_NEXT_REPLY] = "ip6-lookup", + [BFD_UDP_INPUT_NEXT_REPLY_ARP] = "ip6-discover-neighbor", + [BFD_UDP_INPUT_NEXT_REPLY_REWRITE] = "ip6-lookup", }, }; @@ -1375,6 +1480,19 @@ bfd_udp_init (vlib_main_t * vm) bfd_udp_echo4_input_node.index, 1); udp_register_dst_port (vm, UDP_DST_PORT_bfd_echo6, bfd_udp_echo6_input_node.index, 0); + vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "ip4-arp"); + ASSERT (node); + bfd_udp_main.ip4_arp_idx = node->index; + node = vlib_get_node_by_name (vm, (u8 *) "ip6-discover-neighbor"); + ASSERT (node); + bfd_udp_main.ip6_ndp_idx = node->index; + node = vlib_get_node_by_name (vm, (u8 *) "ip4-rewrite"); + ASSERT (node); + bfd_udp_main.ip4_rewrite_idx = node->index; + node = vlib_get_node_by_name (vm, (u8 *) "ip6-rewrite"); + ASSERT (node); + bfd_udp_main.ip6_rewrite_idx = node->index; + return 0; } diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index 5080ec98..a4adbadf 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -57,11 +57,12 @@ struct bfd_session_s; /** * @brief add the necessary transport layer by prepending it to existing data * + * * @param is_echo 1 if this is echo packet, 0 if control frame * * @return 1 on success, 0 on failure */ -int bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, +int bfd_add_udp4_transport (vlib_main_t * vm, u32 bi, const struct bfd_session_s *bs, int is_echo); /** @@ -71,9 +72,29 @@ int bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, * * @return 1 on success, 0 on failure */ -int bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, +int bfd_add_udp6_transport (vlib_main_t * vm, u32 bi, const struct bfd_session_s *bs, int is_echo); +/** + * @brief transport packet over udpv4 + * + * @param is_echo 1 if this is echo packet, 0 if control frame + * + * @return 1 on success, 0 on failure + */ +int bfd_transport_udp4 (vlib_main_t * vm, u32 bi, + const struct bfd_session_s *bs); + +/** + * @brief transport packet over udpv6 + * + * @param is_echo 1 if this is echo packet, 0 if control frame + * + * @return 1 on success, 0 on failure + */ +int bfd_transport_udp6 (vlib_main_t * vm, u32 bi, + const struct bfd_session_s *bs); + /** * @brief check if the bfd udp layer is echo-capable at this time * -- cgit 1.2.3-korg From 88fc83eb716bf07f4634de6de5b569f795a56418 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Wed, 5 Apr 2017 08:11:14 -0700 Subject: BFD-FIB interactions - single-hop BFD: attach a delegate to the appropriate adjacency - multi-hop BFD [not supported yet]: attach a delegate to the FIB entry. adjacency/fib_entry state tracks the BFD session state. when the state is down the object does not contribute forwarding hence and hence dependent objects will not use it. For example, if a route is ECMP via two adjacencies and one of them is BFD down, then only the other is used to forward (i.e. we don't drop half the traffic). Change-Id: I0ef53e20e73b067001a132cd0a3045408811a822 Signed-off-by: Neale Ranns --- src/vnet.am | 7 +- src/vnet/adj/adj.c | 51 +++- src/vnet/adj/adj.h | 15 ++ src/vnet/adj/adj_bfd.c | 184 +++++++++++++ src/vnet/adj/adj_delegate.c | 144 ++++++++++ src/vnet/adj/adj_delegate.h | 104 ++++++++ src/vnet/bfd/bfd_main.c | 23 ++ src/vnet/bfd/bfd_main.h | 44 ++++ src/vnet/bfd/bfd_udp.h | 8 +- src/vnet/fib/fib_attached_export.c | 75 +++--- src/vnet/fib/fib_attached_export.h | 4 +- src/vnet/fib/fib_bfd.c | 197 ++++++++++++++ src/vnet/fib/fib_entry.c | 50 ++-- src/vnet/fib/fib_entry.h | 1 + src/vnet/fib/fib_entry_cover.c | 45 ---- src/vnet/fib/fib_entry_cover.h | 5 - src/vnet/fib/fib_entry_delegate.c | 100 +++++++ src/vnet/fib/fib_entry_delegate.h | 33 +++ src/vnet/fib/fib_path.c | 52 ++-- src/vnet/fib/fib_test.c | 522 +++++++++++++++++++++++++++++++++++++ src/vnet/fib/ip4_fib.c | 16 +- src/vnet/fib/ip6_fib.c | 21 +- test/framework.py | 2 +- test/test_bfd.py | 104 +++++++- test/vpp_papi_provider.py | 5 +- 25 files changed, 1662 insertions(+), 150 deletions(-) create mode 100644 src/vnet/adj/adj_bfd.c create mode 100644 src/vnet/adj/adj_delegate.c create mode 100644 src/vnet/adj/adj_delegate.h create mode 100644 src/vnet/fib/fib_bfd.c (limited to 'src/vnet/bfd/bfd_main.h') diff --git a/src/vnet.am b/src/vnet.am index 1d52ae10..643ae92e 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -936,7 +936,8 @@ libvnet_la_SOURCES += \ vnet/fib/fib_path.c \ vnet/fib/fib_path_ext.c \ vnet/fib/fib_urpf_list.c \ - vnet/fib/fib_attached_export.c + vnet/fib/fib_attached_export.c \ + vnet/fib/fib_bfd.c nobase_include_HEADERS += \ vnet/fib/fib.h \ @@ -962,7 +963,9 @@ libvnet_la_SOURCES += \ vnet/adj/adj_l2.c \ vnet/adj/adj_nsh.c \ vnet/adj/adj.c \ - vnet/adj/rewrite.c + vnet/adj/rewrite.c \ + vnet/adj/adj_bfd.c \ + vnet/adj/adj_delegate.c nobase_include_HEADERS += \ vnet/adj/adj.h \ diff --git a/src/vnet/adj/adj.c b/src/vnet/adj/adj.c index 7cf9e9d0..90182006 100644 --- a/src/vnet/adj/adj.c +++ b/src/vnet/adj/adj.c @@ -18,6 +18,7 @@ #include #include #include +#include #include /* Adjacency packet/byte counters indexed by adjacency index. */ @@ -57,13 +58,14 @@ adj_alloc (fib_protocol_t proto) vlib_validate_combined_counter(&adjacency_counters, adj_get_index(adj)); - adj->rewrite_header.sw_if_index = ~0; - adj->lookup_next_index = 0; - fib_node_init(&adj->ia_node, FIB_NODE_TYPE_ADJ); + adj->ia_nh_proto = proto; adj->ia_flags = 0; + adj->rewrite_header.sw_if_index = ~0; + adj->lookup_next_index = 0; + adj->ia_delegates = NULL; ip4_main.lookup_main.adjacency_heap = adj_pool; ip6_main.lookup_main.adjacency_heap = adj_pool; @@ -122,11 +124,19 @@ format_ip_adjacency (u8 * s, va_list * args) if (fiaf & FORMAT_IP_ADJACENCY_DETAIL) { + adj_delegate_type_t adt; + adj_delegate_t *aed; vlib_counter_t counts; vlib_get_combined_counter(&adjacency_counters, adj_index, &counts); s = format (s, "\n counts:[%Ld:%Ld]", counts.packets, counts.bytes); s = format (s, "\n locks:%d", adj->ia_node.fn_locks); + s = format(s, "\n delegates:\n "); + FOR_EACH_ADJ_DELEGATE(adj, adt, aed, + { + s = format(s, " %U\n", format_adj_deletegate, aed); + }); + s = format(s, "\n children:\n "); s = fib_node_children_format(adj->ia_node.fn_children, s); } @@ -173,7 +183,11 @@ adj_last_lock_gone (ip_adjacency_t *adj) adj_mcast_remove(adj->ia_nh_proto, adj->rewrite_header.sw_if_index); break; - default: + case IP_LOOKUP_NEXT_DROP: + case IP_LOOKUP_NEXT_PUNT: + case IP_LOOKUP_NEXT_LOCAL: + case IP_LOOKUP_NEXT_ICMP_ERROR: + case IP_LOOKUP_N_NEXT: /* * type not stored in any DB from which we need to remove it */ @@ -183,6 +197,8 @@ adj_last_lock_gone (ip_adjacency_t *adj) vlib_worker_thread_barrier_release(vm); fib_node_deinit(&adj->ia_node); + ASSERT(0 == vec_len(adj->ia_delegates)); + vec_free(adj->ia_delegates); pool_put(adj_pool, adj); } @@ -351,6 +367,33 @@ adj_get_sw_if_index (adj_index_t ai) return (adj->rewrite_header.sw_if_index); } +/** + * @brief Return true if the adjacency is 'UP', i.e. can be used for forwarding + * 0 is down, !0 is up. + */ +int +adj_is_up (adj_index_t ai) +{ + const adj_delegate_t *aed; + + aed = adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD); + + if (NULL == aed) + { + /* + * no BFD tracking - resolved + */ + return (!0); + } + else + { + /* + * defer to the state of the BFD tracking + */ + return (ADJ_BFD_STATE_UP == aed->ad_bfd_state); + } +} + /** * @brief Return the rewrite string of the adjacency */ diff --git a/src/vnet/adj/adj.h b/src/vnet/adj/adj.h index af7730f7..32997c91 100644 --- a/src/vnet/adj/adj.h +++ b/src/vnet/adj/adj.h @@ -255,6 +255,15 @@ typedef struct ip_adjacency_t_ /* Rewrite in second/third cache lines */ vnet_declare_rewrite (VLIB_BUFFER_PRE_DATA_SIZE); + + /** + * more control plane members that do not fit on the first cacheline + */ + /** + * A sorted vector of delegates + */ + struct adj_delegate_t_ *ia_delegates; + } ip_adjacency_t; STATIC_ASSERT ((STRUCT_OFFSET_OF (ip_adjacency_t, cacheline0) == 0), @@ -307,6 +316,12 @@ extern vnet_link_t adj_get_link_type (adj_index_t ai); */ extern u32 adj_get_sw_if_index (adj_index_t ai); +/** + * @brief Return true if the adjacency is 'UP', i.e. can be used for forwarding. + * 0 is down, !0 is up. + */ +extern int adj_is_up (adj_index_t ai); + /** * @brief Return the link type of the adjacency */ diff --git a/src/vnet/adj/adj_bfd.c b/src/vnet/adj/adj_bfd.c new file mode 100644 index 00000000..3d294c46 --- /dev/null +++ b/src/vnet/adj/adj_bfd.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016 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. + */ + +#include + +#include +#include +#include + +static adj_bfd_state_t +adj_bfd_bfd_state_to_fib (bfd_state_e bstate) +{ + switch (bstate) + { + case BFD_STATE_up: + return (ADJ_BFD_STATE_UP); + case BFD_STATE_down: + case BFD_STATE_admin_down: + case BFD_STATE_init: + return (ADJ_BFD_STATE_DOWN); + } + return (ADJ_BFD_STATE_DOWN); +} + +static void +adj_bfd_update_walk (adj_index_t ai) +{ + /* + * initiate a backwalk of dependent children + * to notify of the state change of this adj. + */ + fib_node_back_walk_ctx_t ctx = { + .fnbw_reason = FIB_NODE_BW_REASON_FLAG_ADJ_UPDATE, + }; + fib_walk_sync(FIB_NODE_TYPE_ADJ, ai, &ctx); +} + +/** + * @brief Callback function registered with BFD module to receive notifications + * of the CRUD of BFD sessions + * would be static but for the fact it's called from the unit-tests + */ +void +adj_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session) +{ + const bfd_udp_key_t *key; + fib_protocol_t fproto; + adj_delegate_t *aed; + adj_index_t ai; + + if (BFD_HOP_TYPE_SINGLE != session->hop_type) + { + /* + * multi-hop BFD sessions attach directly to the FIB entry + * single-hop adj to the associate adjacency. + */ + return; + } + + key = &session->udp.key; + + fproto = (ip46_address_is_ip4 (&key->peer_addr) ? + FIB_PROTOCOL_IP4: + FIB_PROTOCOL_IP6); + + /* + * find the adj that corresponds to the BFD session. + */ + ai = adj_nbr_add_or_lock(fproto, + fib_proto_to_link(fproto), + &key->peer_addr, + key->sw_if_index); + + switch (event) + { + case BFD_LISTEN_EVENT_CREATE: + /* + * The creation of a new session + */ + if ((ADJ_INDEX_INVALID != ai) && + (aed = adj_delegate_get(adj_get(ai), + ADJ_DELEGATE_BFD))) + { + /* + * already got state for this adj + */ + } + else + { + /* + * lock the adj. add the delegate. + * Lockinging the adj prevents it being removed and thus maintains + * the BFD derived states + */ + adj_lock(ai); + + aed = adj_delegate_find_or_add(adj_get(ai), ADJ_DELEGATE_BFD); + + /* + * pretend the session is up and skip the walk. + * If we set it down then we get traffic loss on new children. + * if we walk then we lose traffic for existing children. Wait + * for the first BFD UP/DOWN before we let the session's state + * influence forwarding. + */ + aed->ad_bfd_state = ADJ_BFD_STATE_UP; + aed->ad_bfd_index = session->bs_idx; + } + break; + + case BFD_LISTEN_EVENT_UPDATE: + /* + * state change up/dowm and + */ + aed = adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD); + + if (NULL != aed) + { + aed->ad_bfd_state = adj_bfd_bfd_state_to_fib(session->local_state); + adj_bfd_update_walk(ai); + } + /* + * else + * not an adj with BFD state + */ + break; + + case BFD_LISTEN_EVENT_DELETE: + /* + * session has been removed. + */ + + if (adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD)) + { + /* + * has an associated BFD tracking delegate + * remove the BFD tracking deletgate, update children, then + * unlock the adj + */ + adj_delegate_remove(adj_get(ai), ADJ_DELEGATE_BFD); + + adj_bfd_update_walk(ai); + adj_unlock(ai); + } + /* + * else + * no BFD associated state + */ + break; + } + + /* + * unlock match of the add-or-lock at the start + */ + adj_unlock(ai); +} + +static clib_error_t * +adj_bfd_main_init (vlib_main_t * vm) +{ + clib_error_t * error = NULL; + + if ((error = vlib_call_init_function (vm, bfd_main_init))) + return (error); + + bfd_register_listener(adj_bfd_notify); + + return (error); +} + +VLIB_INIT_FUNCTION (adj_bfd_main_init); diff --git a/src/vnet/adj/adj_delegate.c b/src/vnet/adj/adj_delegate.c new file mode 100644 index 00000000..701b36e2 --- /dev/null +++ b/src/vnet/adj/adj_delegate.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016 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. + */ + +#include +#include +#include + +static adj_delegate_t * +adj_delegate_find_i (const ip_adjacency_t *adj, + adj_delegate_type_t type, + u32 *index) +{ + adj_delegate_t *delegate; + int ii; + + ii = 0; + vec_foreach(delegate, adj->ia_delegates) + { + if (delegate->ad_type == type) + { + if (NULL != index) + *index = ii; + + return (delegate); + } + else + { + ii++; + } + } + + return (NULL); +} + +adj_delegate_t * +adj_delegate_get (const ip_adjacency_t *adj, + adj_delegate_type_t type) +{ + return (adj_delegate_find_i(adj, type, NULL)); +} + +void +adj_delegate_remove (ip_adjacency_t *adj, + adj_delegate_type_t type) +{ + adj_delegate_t *aed; + u32 index = ~0; + + aed = adj_delegate_find_i(adj, type, &index); + + ASSERT(NULL != aed); + + vec_del1(adj->ia_delegates, index); +} + +static int +adj_delegate_cmp_for_sort (void * v1, + void * v2) +{ + adj_delegate_t *delegate1 = v1, *delegate2 = v2; + + return (delegate1->ad_type - delegate2->ad_type); +} + +static void +adj_delegate_init (ip_adjacency_t *adj, + adj_delegate_type_t type) + +{ + adj_delegate_t delegate = { + .ad_adj_index = adj_get_index(adj), + .ad_type = type, + }; + + vec_add1(adj->ia_delegates, delegate); + vec_sort_with_function(adj->ia_delegates, + adj_delegate_cmp_for_sort); +} + +adj_delegate_t * +adj_delegate_find_or_add (ip_adjacency_t *adj, + adj_delegate_type_t adt) +{ + adj_delegate_t *delegate; + + delegate = adj_delegate_get(adj, adt); + + if (NULL == delegate) + { + adj_delegate_init(adj, adt); + } + + return (adj_delegate_get(adj, adt)); +} + +/** + * typedef for printing a delegate + */ +typedef u8 * (*adj_delegate_format_t)(const adj_delegate_t *aed, + u8 *s); + +/** + * Print a delegate that represents BFD tracking + */ +static u8 * +adj_delegate_fmt_bfd (const adj_delegate_t *aed, + u8 *s) +{ + s = format(s, "BFD:[state:%d index:%d]", + aed->ad_bfd_state, + aed->ad_bfd_index); + + return (s); +} + +/** + * A delegate type to formatter map + */ +static adj_delegate_format_t aed_formatters[] = +{ + [ADJ_DELEGATE_BFD] = adj_delegate_fmt_bfd, +}; + +u8 * +format_adj_deletegate (u8 * s, va_list * args) +{ + adj_delegate_t *aed; + + aed = va_arg (*args, adj_delegate_t *); + + return (aed_formatters[aed->ad_type](aed, s)); +} diff --git a/src/vnet/adj/adj_delegate.h b/src/vnet/adj/adj_delegate.h new file mode 100644 index 00000000..17651203 --- /dev/null +++ b/src/vnet/adj/adj_delegate.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 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. + */ + +#ifndef __ADJ_DELEGATE_T__ +#define __ADJ_DELEGATE_T__ + +#include + +/** + * Delegate types + */ +typedef enum adj_delegate_type_t_ { + /** + * BFD session state + */ + ADJ_DELEGATE_BFD, +} adj_delegate_type_t; + +#define FOR_EACH_ADJ_DELEGATE(_adj, _adt, _aed, _body) \ +{ \ + for (_adt = ADJ_DELEGATE_BFD; \ + _adt <= ADJ_DELEGATE_BFD; \ + _adt++) \ + { \ + _aed = adj_delegate_get(_adj, _adt); \ + if (NULL != _aed) { \ + _body; \ + } \ + } \ +} + +/** + * Distillation of the BFD session states into a go/no-go for using + * the associated tracked adjacency + */ +typedef enum adj_bfd_state_t_ +{ + ADJ_BFD_STATE_DOWN, + ADJ_BFD_STATE_UP, +} adj_bfd_state_t; + +/** + * A Delagate is a means to implement the Delagation design pattern; + * the extension of an object's functionality through the composition of, + * and delgation to, other objects. + * These 'other' objects are delegates. Delagates are thus attached to + * ADJ objects to extend their functionality. + */ +typedef struct adj_delegate_t_ +{ + /** + * The ADJ entry object to which the delagate is attached + */ + adj_index_t ad_adj_index; + + /** + * The delagate type + */ + adj_delegate_type_t ad_type; + + /** + * A union of data for the different delegate types + */ + union + { + /** + * BFD delegate daa + */ + struct { + /** + * BFD session state + */ + adj_bfd_state_t ad_bfd_state; + /** + * BFD session index + */ + u32 ad_bfd_index; + }; + }; +} adj_delegate_t; + +extern void adj_delegate_remove(ip_adjacency_t *adj, + adj_delegate_type_t type); + +extern adj_delegate_t *adj_delegate_find_or_add(ip_adjacency_t *adj, + adj_delegate_type_t fdt); +extern adj_delegate_t *adj_delegate_get(const ip_adjacency_t *adj, + adj_delegate_type_t type); + +extern u8 *format_adj_deletegate(u8 * s, va_list * args); + +#endif diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 2b70a20c..66b31ce5 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -101,6 +101,7 @@ bfd_set_defaults (bfd_main_t * bm, bfd_session_t * bs) bs->local_diag = BFD_DIAG_CODE_no_diag; bs->remote_state = BFD_STATE_down; bs->remote_discr = 0; + bs->hop_type = BFD_HOP_TYPE_SINGLE; bs->config_desired_min_tx_usec = BFD_DEFAULT_DESIRED_MIN_TX_USEC; bs->config_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; bs->effective_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; @@ -387,6 +388,17 @@ bfd_set_remote_required_min_echo_rx (bfd_main_t * bm, bfd_session_t * bs, } } +static void +bfd_notify_listeners (bfd_main_t * bm, + bfd_listen_event_e event, const bfd_session_t * bs) +{ + bfd_notify_fn_t *fn; + vec_foreach (fn, bm->listeners) + { + (*fn) (event, bs); + } +} + void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) { @@ -396,6 +408,7 @@ bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) bfd_recalc_tx_interval (bm, bs); vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index, BFD_EVENT_NEW_SESSION, bs->bs_idx); + bfd_notify_listeners (bm, BFD_LISTEN_EVENT_CREATE, bs); } void @@ -533,6 +546,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, bfd_set_timer (bm, bs, now, handling_wakeup); break; } + bfd_notify_listeners (bm, BFD_LISTEN_EVENT_UPDATE, bs); } static void @@ -1121,6 +1135,14 @@ bfd_hw_interface_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (bfd_hw_interface_up_down); +void +bfd_register_listener (bfd_notify_fn_t fn) +{ + bfd_main_t *bm = &bfd_main; + + vec_add1 (bm->listeners, fn); +} + /* * setup function */ @@ -1180,6 +1202,7 @@ bfd_get_session (bfd_main_t * bm, bfd_transport_e t) void bfd_put_session (bfd_main_t * bm, bfd_session_t * bs) { + bfd_notify_listeners (bm, BFD_LISTEN_EVENT_DELETE, bs); if (bs->auth.curr_key) { --bs->auth.curr_key->use_count; diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index d722a552..93adac3d 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -66,6 +66,20 @@ typedef enum #undef F } bfd_poll_state_e; +/** + * hop types + */ +#define foreach_bfd_hop(F) \ + F (SINGLE, "single") \ + F (MULTI, "multi") \ + +typedef enum +{ +#define F(sym, str) BFD_HOP_TYPE_##sym, + foreach_bfd_hop (F) +#undef F +} bfd_hop_type_e; + typedef struct bfd_session_s { /** index in bfd_main.sessions pool */ @@ -77,6 +91,9 @@ typedef struct bfd_session_s /** remote session state */ bfd_state_e remote_state; + /** BFD hop type */ + bfd_hop_type_e hop_type; + /** local diagnostics */ bfd_diag_code_e local_diag; @@ -220,6 +237,26 @@ typedef struct bfd_session_s }; } bfd_session_t; +/** + * listener events + */ +#define foreach_bfd_listen_event(F) \ + F (CREATE, "sesion-created") \ + F (UPDATE, "session-updated") \ + F (DELETE, "session-deleted") + +typedef enum +{ +#define F(sym, str) BFD_LISTEN_EVENT_##sym, + foreach_bfd_listen_event (F) +#undef F +} bfd_listen_event_e; + +/** + * session nitification call back function type + */ +typedef void (*bfd_notify_fn_t) (bfd_listen_event_e, const bfd_session_t *); + typedef struct { /** pool of bfd sessions context data */ @@ -259,6 +296,8 @@ typedef struct /** hashmap - index in pool auth_keys by conf_key_id */ u32 *auth_key_by_conf_key_id; + /** A vector of callback notification functions */ + bfd_notify_fn_t *listeners; } bfd_main_t; extern bfd_main_t bfd_main; @@ -345,6 +384,11 @@ const char *bfd_poll_state_string (bfd_poll_state_e state); */ #define BFD_REQUIRED_MIN_RX_USEC_WHILE_ECHO USEC_PER_SECOND +/** + * Register a callback function to receive session notifications. + */ +void bfd_register_listener (bfd_notify_fn_t fn); + #endif /* __included_bfd_main_h__ */ /* diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index a4adbadf..87868104 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -27,8 +27,12 @@ /* *INDENT-OFF* */ /** identifier of BFD session based on UDP transport only */ typedef CLIB_PACKED (struct { - /** interface to which the session is tied */ - u32 sw_if_index; + union { + /** interface to which the session is tied - single-hop */ + u32 sw_if_index; + /** the FIB index the peer is in - multi-hop*/ + u32 fib_index; + }; /** local address */ ip46_address_t local_addr; /** peer address */ diff --git a/src/vnet/fib/fib_attached_export.c b/src/vnet/fib/fib_attached_export.c index 715e63e7..cc8ebc86 100644 --- a/src/vnet/fib/fib_attached_export.c +++ b/src/vnet/fib/fib_attached_export.c @@ -514,67 +514,52 @@ fib_attached_export_cover_update (fib_entry_t *fib_entry) } u8* -fib_ae_import_format (fib_entry_t *fib_entry, +fib_ae_import_format (fib_node_index_t impi, u8* s) { - fib_entry_delegate_t *fed; - - fed = fib_entry_delegate_get(fib_entry, - FIB_ENTRY_DELEGATE_ATTACHED_IMPORT); - - if (NULL != fed) - { - fib_node_index_t *index; - fib_ae_import_t *import; - - import = pool_elt_at_index(fib_ae_import_pool, fed->fd_index); + fib_node_index_t *index; + fib_ae_import_t *import; - s = format(s, "\n Attached-Import:%d:[", (import - fib_ae_import_pool)); - s = format(s, "export-prefix:%U ", format_fib_prefix, &import->faei_prefix); - s = format(s, "export-entry:%d ", import->faei_export_entry); - s = format(s, "export-sibling:%d ", import->faei_export_sibling); - s = format(s, "exporter:%d ", import->faei_exporter); - s = format(s, "export-fib:%d ", import->faei_export_fib); + import = pool_elt_at_index(fib_ae_import_pool, impi); - s = format(s, "import-entry:%d ", import->faei_import_entry); - s = format(s, "import-fib:%d ", import->faei_import_fib); + s = format(s, "\n Attached-Import:%d:[", (import - fib_ae_import_pool)); + s = format(s, "export-prefix:%U ", format_fib_prefix, &import->faei_prefix); + s = format(s, "export-entry:%d ", import->faei_export_entry); + s = format(s, "export-sibling:%d ", import->faei_export_sibling); + s = format(s, "exporter:%d ", import->faei_exporter); + s = format(s, "export-fib:%d ", import->faei_export_fib); + + s = format(s, "import-entry:%d ", import->faei_import_entry); + s = format(s, "import-fib:%d ", import->faei_import_fib); - s = format(s, "importeds:["); - vec_foreach(index, import->faei_importeds) - { - s = format(s, "%d, ", *index); - } - s = format(s, "]]"); + s = format(s, "importeds:["); + vec_foreach(index, import->faei_importeds) + { + s = format(s, "%d, ", *index); } + s = format(s, "]]"); return (s); } u8* -fib_ae_export_format (fib_entry_t *fib_entry, +fib_ae_export_format (fib_node_index_t expi, u8* s) { - fib_entry_delegate_t *fed; - - fed = fib_entry_delegate_get(fib_entry, - FIB_ENTRY_DELEGATE_ATTACHED_EXPORT); - - if (NULL != fed) - { - fib_node_index_t *index; - fib_ae_export_t *export; + fib_node_index_t *index; + fib_ae_export_t *export; - export = pool_elt_at_index(fib_ae_export_pool, fed->fd_list); + export = pool_elt_at_index(fib_ae_export_pool, expi); - s = format(s, "\n Attached-Export:%d:[", (export - fib_ae_export_pool)); - s = format(s, "export-entry:%d ", export->faee_ei); + s = format(s, "\n Attached-Export:%d:[", (export - fib_ae_export_pool)); + s = format(s, "export-entry:%d ", export->faee_ei); - s = format(s, "importers:["); - vec_foreach(index, export->faee_importers) - { - s = format(s, "%d, ", *index); - } - s = format(s, "]]"); + s = format(s, "importers:["); + vec_foreach(index, export->faee_importers) + { + s = format(s, "%d, ", *index); } + s = format(s, "]]"); + return (s); } diff --git a/src/vnet/fib/fib_attached_export.h b/src/vnet/fib/fib_attached_export.h index fa28a6e1..d4c2b57c 100644 --- a/src/vnet/fib/fib_attached_export.h +++ b/src/vnet/fib/fib_attached_export.h @@ -51,7 +51,7 @@ extern void fib_attached_export_covered_removed(fib_entry_t *cover, extern void fib_attached_export_cover_change(fib_entry_t *fib_entry); extern void fib_attached_export_cover_update(fib_entry_t *fib_entry); -extern u8* fib_ae_import_format(fib_entry_t *fib_entry, u8*s); -extern u8* fib_ae_export_format(fib_entry_t *fib_entry, u8*s); +extern u8* fib_ae_import_format(fib_node_index_t impi, u8*s); +extern u8* fib_ae_export_format(fib_node_index_t expi, u8*s); #endif diff --git a/src/vnet/fib/fib_bfd.c b/src/vnet/fib/fib_bfd.c new file mode 100644 index 00000000..e5affb8d --- /dev/null +++ b/src/vnet/fib/fib_bfd.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016 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. + */ + +#include + +#include +#include +#include +#include + +static fib_bfd_state_t +fib_bfd_bfd_state_to_fib (bfd_state_e bstate) +{ + switch (bstate) + { + case BFD_STATE_up: + return (FIB_BFD_STATE_UP); + case BFD_STATE_down: + case BFD_STATE_admin_down: + case BFD_STATE_init: + return (FIB_BFD_STATE_DOWN); + } + return (FIB_BFD_STATE_DOWN); +} + +static void +fib_bfd_update_walk (fib_node_index_t fei) +{ + /* + * initiate a backwalk of dependent children + * to notify of the state change of this entry. + */ + fib_node_back_walk_ctx_t ctx = { + .fnbw_reason = FIB_NODE_BW_REASON_FLAG_EVALUATE, + }; + fib_walk_sync(FIB_NODE_TYPE_ENTRY, fei, &ctx); +} + +/** + * @brief Callback function registered with BFD module to receive notifications + * of the CRUD of BFD sessions + * would be static but for the fact it's called from the unit-tests + */ +void +fib_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session) +{ + fib_entry_delegate_t *fed; + const bfd_udp_key_t *key; + fib_node_index_t fei; + + if (BFD_HOP_TYPE_MULTI != session->hop_type) + { + /* + * multi-hop BFD sessions attach directly to the FIB entry + * single-hop adj to the associate adjacency. + */ + return; + } + + key = &session->udp.key; + + fib_prefix_t pfx = { + .fp_addr = key->peer_addr, + .fp_proto = (ip46_address_is_ip4 (&key->peer_addr) ? + FIB_PROTOCOL_IP4: + FIB_PROTOCOL_IP6), + .fp_len = (ip46_address_is_ip4 (&key->peer_addr) ? + 32: + 128), + }; + + /* + * get the FIB entry + */ + fei = fib_table_lookup_exact_match(key->fib_index, &pfx); + + switch (event) + { + case BFD_LISTEN_EVENT_CREATE: + /* + * The creation of a new session + */ + if ((FIB_NODE_INDEX_INVALID != fei) && + (fed = fib_entry_delegate_get(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD))) + { + /* + * already got state for this entry + */ + } + else + { + /* + * source and lock the entry. add the delegate + */ + fei = fib_table_entry_special_add(key->fib_index, + &pfx, + FIB_SOURCE_RR, + FIB_ENTRY_FLAG_NONE, + ADJ_INDEX_INVALID); + fib_entry_lock(fei); + + fed = fib_entry_delegate_find_or_add(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD); + + /* + * pretend the session is up and skip the walk. + * If we set it down then we get traffic loss on new children. + * if we walk then we lose traffic for existing children. Wait + * for the first BFD UP/DOWN before we let the session's state + * influence forwarding. + */ + fed->fd_bfd_state = FIB_BFD_STATE_UP; + } + break; + + case BFD_LISTEN_EVENT_UPDATE: + /* + * state change up/dowm and + */ + ASSERT(FIB_NODE_INDEX_INVALID != fei); + + fed = fib_entry_delegate_get(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD); + + if (NULL != fed) + { + fed->fd_bfd_state = fib_bfd_bfd_state_to_fib(session->local_state); + fib_bfd_update_walk(fei); + } + /* + * else + * no BFD state + */ + break; + + case BFD_LISTEN_EVENT_DELETE: + /* + * session has been removed. + */ + if (FIB_NODE_INDEX_INVALID == fei) + { + /* + * no FIB entry + */ + } + else if (fib_entry_delegate_get(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD)) + { + /* + * has an associated BFD tracking delegate + * usource the entry and remove the BFD tracking deletgate + */ + fib_entry_delegate_remove(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD); + fib_bfd_update_walk(fei); + + fib_table_entry_special_remove(key->fib_index, + &pfx, + FIB_SOURCE_RR); + fib_entry_unlock(fei); + } + /* + * else + * no BFD associated state + */ + break; + } +} + +static clib_error_t * +fib_bfd_main_init (vlib_main_t * vm) +{ + clib_error_t * error = NULL; + + if ((error = vlib_call_init_function (vm, bfd_main_init))) + return (error); + + bfd_register_listener(fib_bfd_notify); + + return (error); +} + +VLIB_INIT_FUNCTION (fib_bfd_main_init); diff --git a/src/vnet/fib/fib_entry.c b/src/vnet/fib/fib_entry.c index 6ac5461d..dac1fce9 100644 --- a/src/vnet/fib/fib_entry.c +++ b/src/vnet/fib/fib_entry.c @@ -99,7 +99,6 @@ format_fib_entry (u8 * s, va_list * args) fib_entry_src_t *src; fib_node_index_t fei; fib_source_t source; - u32 n_covered; int level; fei = va_arg (*args, fib_node_index_t); @@ -143,14 +142,6 @@ format_fib_entry (u8 * s, va_list * args) } })); - n_covered = fib_entry_cover_get_size(fib_entry); - if (n_covered > 0) { - s = format(s, "\n tracking %d covered: ", n_covered); - s = fib_entry_cover_list_format(fib_entry, s); - } - s = fib_ae_import_format(fib_entry, s); - s = fib_ae_export_format(fib_entry, s); - s = format (s, "\n forwarding: "); } else @@ -179,20 +170,17 @@ format_fib_entry (u8 * s, va_list * args) fib_entry_delegate_type_t fdt; fib_entry_delegate_t *fed; - FOR_EACH_DELEGATE_CHAIN(fib_entry, fdt, fed, + s = format (s, " Delegates:\n"); + FOR_EACH_DELEGATE(fib_entry, fdt, fed, { - s = format(s, " %U-chain\n %U", - format_fib_forw_chain_type, - fib_entry_delegate_type_to_chain_type(fdt), - format_dpo_id, &fed->fd_dpo, 2); - s = format(s, "\n"); + s = format(s, " %U\n", format_fib_entry_deletegate, fed); }); } } if (level >= FIB_ENTRY_FORMAT_DETAIL2) { - s = format(s, "\nchildren:"); + s = format(s, " Children:"); s = fib_node_children_format(fib_entry->fe_node.fn_children, s); } @@ -1339,6 +1327,36 @@ fib_entry_get_best_source (fib_node_index_t entry_index) return (fib_entry_src_get_source(bsrc)); } +/** + * Return !0 is the entry is reoslved, i.e. will return a valid forwarding + * chain + */ +int +fib_entry_is_resolved (fib_node_index_t fib_entry_index) +{ + fib_entry_delegate_t *fed; + fib_entry_t *fib_entry; + + fib_entry = fib_entry_get(fib_entry_index); + + fed = fib_entry_delegate_get(fib_entry, FIB_ENTRY_DELEGATE_BFD); + + if (NULL == fed) + { + /* + * no BFD tracking - resolved + */ + return (!0); + } + else + { + /* + * defer to the state of the BFD tracking + */ + return (FIB_BFD_STATE_UP == fed->fd_bfd_state); + } +} + static int fib_ip4_address_compare (const ip4_address_t * a1, const ip4_address_t * a2) diff --git a/src/vnet/fib/fib_entry.h b/src/vnet/fib/fib_entry.h index 12fa9eb4..a3f75e60 100644 --- a/src/vnet/fib/fib_entry.h +++ b/src/vnet/fib/fib_entry.h @@ -525,6 +525,7 @@ extern int fib_entry_is_sourced(fib_node_index_t fib_entry_index, fib_source_t source); extern fib_node_index_t fib_entry_get_path_list(fib_node_index_t fib_entry_index); +extern int fib_entry_is_resolved(fib_node_index_t fib_entry_index); extern void fib_entry_module_init(void); diff --git a/src/vnet/fib/fib_entry_cover.c b/src/vnet/fib/fib_entry_cover.c index 147c5daa..814df578 100644 --- a/src/vnet/fib/fib_entry_cover.c +++ b/src/vnet/fib/fib_entry_cover.c @@ -106,51 +106,6 @@ fib_entry_cover_walk (fib_entry_t *cover, &ctx); } -u32 -fib_entry_cover_get_size (fib_entry_t *cover) -{ - fib_entry_delegate_t *fed; - - fed = fib_entry_delegate_get(cover, FIB_ENTRY_DELEGATE_COVERED); - - if (NULL == fed) - return (0); - - return (fib_node_list_get_size(fed->fd_list)); -} - -typedef struct fib_entry_cover_list_format_ctx_t_ { - u8 *s; -} fib_entry_cover_list_format_ctx_t; - -static int -fib_entry_covered_list_format_one (fib_entry_t *cover, - fib_node_index_t covered, - void *args) -{ - fib_entry_cover_list_format_ctx_t * ctx = args; - - ctx->s = format(ctx->s, "%d, ", covered); - - /* continue */ - return (1); -} - -u8* -fib_entry_cover_list_format (fib_entry_t *fib_entry, - u8 *s) -{ - fib_entry_cover_list_format_ctx_t ctx = { - .s = s, - }; - - fib_entry_cover_walk(fib_entry, - fib_entry_covered_list_format_one, - &ctx); - - return (ctx.s); -} - static int fib_entry_cover_change_one (fib_entry_t *cover, fib_node_index_t covered, diff --git a/src/vnet/fib/fib_entry_cover.h b/src/vnet/fib/fib_entry_cover.h index fbbbc211..500d5b33 100644 --- a/src/vnet/fib/fib_entry_cover.h +++ b/src/vnet/fib/fib_entry_cover.h @@ -39,9 +39,4 @@ extern void fib_entry_cover_change_notify(fib_node_index_t cover_index, fib_node_index_t covered_index); extern void fib_entry_cover_update_notify(fib_entry_t *cover); -extern u32 fib_entry_cover_get_size(fib_entry_t *cover); - -extern u8* fib_entry_cover_list_format(fib_entry_t *fib_entry, - u8 *s); - #endif diff --git a/src/vnet/fib/fib_entry_delegate.c b/src/vnet/fib/fib_entry_delegate.c index 70840b16..41af14f2 100644 --- a/src/vnet/fib/fib_entry_delegate.c +++ b/src/vnet/fib/fib_entry_delegate.c @@ -15,6 +15,7 @@ #include #include +#include static fib_entry_delegate_t * fib_entry_delegate_find_i (const fib_entry_t *fib_entry, @@ -149,8 +150,107 @@ fib_entry_delegate_type_to_chain_type (fib_entry_delegate_type_t fdt) case FIB_ENTRY_DELEGATE_COVERED: case FIB_ENTRY_DELEGATE_ATTACHED_IMPORT: case FIB_ENTRY_DELEGATE_ATTACHED_EXPORT: + case FIB_ENTRY_DELEGATE_BFD: break; } ASSERT(0); return (FIB_FORW_CHAIN_TYPE_UNICAST_IP4); } + +/** + * typedef for printing a delegate + */ +typedef u8 * (*fib_entry_delegate_format_t)(const fib_entry_delegate_t *fed, + u8 *s); + +/** + * Print a delegate that represents a forwarding chain + */ +static u8 * +fib_entry_delegate_fmt_fwd_chain (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "%U-chain\n %U", + format_fib_forw_chain_type, + fib_entry_delegate_type_to_chain_type(fed->fd_type), + format_dpo_id, &fed->fd_dpo, 2); + + return (s); +} + +/** + * Print a delegate that represents cover tracking + */ +static u8 * +fib_entry_delegate_fmt_covered (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "covered:["); + s = fib_node_children_format(fed->fd_list, s); + s = format(s, "]"); + + return (s); +} + +/** + * Print a delegate that represents attached-import tracking + */ +static u8 * +fib_entry_delegate_fmt_import (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "import:%U", fib_ae_import_format, fed->fd_index); + + return (s); +} + +/** + * Print a delegate that represents attached-export tracking + */ +static u8 * +fib_entry_delegate_fmt_export (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "export:%U", fib_ae_export_format, fed->fd_index); + + return (s); +} + +/** + * Print a delegate that represents BFD tracking + */ +static u8 * +fib_entry_delegate_fmt_bfd (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "BFD:%d", fed->fd_bfd_state); + + return (s); +} + +/** + * A delegate type to formatter map + */ +static fib_entry_delegate_format_t fed_formatters[] = +{ + [FIB_ENTRY_DELEGATE_CHAIN_UNICAST_IP4] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_UNICAST_IP6] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_MPLS_EOS] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_MPLS_NON_EOS] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_ETHERNET] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_NSH] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_COVERED] = fib_entry_delegate_fmt_covered, + [FIB_ENTRY_DELEGATE_ATTACHED_IMPORT] = fib_entry_delegate_fmt_import, + [FIB_ENTRY_DELEGATE_ATTACHED_EXPORT] = fib_entry_delegate_fmt_export, + [FIB_ENTRY_DELEGATE_BFD] = fib_entry_delegate_fmt_bfd, +}; + +u8 * +format_fib_entry_deletegate (u8 * s, va_list * args) +{ + fib_entry_delegate_t *fed; + + fed = va_arg (*args, fib_entry_delegate_t *); + + return (fed_formatters[fed->fd_type](fed, s)); +} diff --git a/src/vnet/fib/fib_entry_delegate.h b/src/vnet/fib/fib_entry_delegate.h index d9183c5f..333d357c 100644 --- a/src/vnet/fib/fib_entry_delegate.h +++ b/src/vnet/fib/fib_entry_delegate.h @@ -42,6 +42,10 @@ typedef enum fib_entry_delegate_type_t_ { * to their respective cover */ FIB_ENTRY_DELEGATE_COVERED, + /** + * BFD session state + */ + FIB_ENTRY_DELEGATE_BFD, /** * Attached import/export functionality */ @@ -61,6 +65,28 @@ typedef enum fib_entry_delegate_type_t_ { } \ } \ } +#define FOR_EACH_DELEGATE(_entry, _fdt, _fed, _body) \ +{ \ + for (_fdt = FIB_ENTRY_DELEGATE_CHAIN_UNICAST_IP4; \ + _fdt <= FIB_ENTRY_DELEGATE_ATTACHED_EXPORT; \ + _fdt++) \ + { \ + _fed = fib_entry_delegate_get(_entry, _fdt); \ + if (NULL != _fed) { \ + _body; \ + } \ + } \ +} + +/** + * Distillation of the BFD session states into a go/no-go for using + * the associated tracked FIB entry + */ +typedef enum fib_bfd_state_t_ +{ + FIB_BFD_STATE_UP, + FIB_BFD_STATE_DOWN, +} fib_bfd_state_t; /** * A Delagate is a means to implmenet the Delagation design pattern; the extension of an @@ -103,6 +129,11 @@ typedef struct fib_entry_delegate_t_ * For the cover tracking. The node list; */ fib_node_list_t fd_list; + + /** + * BFD state + */ + fib_bfd_state_t fd_bfd_state; }; } fib_entry_delegate_t; @@ -122,4 +153,6 @@ extern fib_forward_chain_type_t fib_entry_delegate_type_to_chain_type( extern fib_entry_delegate_type_t fib_entry_chain_type_to_delegate_type( fib_forward_chain_type_t type); +extern u8 *format_fib_entry_deletegate(u8 * s, va_list * args); + #endif diff --git a/src/vnet/fib/fib_path.c b/src/vnet/fib/fib_path.c index 928a9d43..6b202a97 100644 --- a/src/vnet/fib/fib_path.c +++ b/src/vnet/fib/fib_path.c @@ -558,12 +558,6 @@ fib_path_attached_next_hop_set (fib_path_t *path) * resolve directly via the adjacnecy discribed by the * interface and next-hop */ - if (!vnet_sw_interface_is_admin_up(vnet_get_main(), - path->attached_next_hop.fp_interface)) - { - path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; - } - dpo_set(&path->fp_dpo, DPO_ADJACENCY, fib_proto_to_dpo(path->fp_nh_proto), @@ -578,6 +572,13 @@ fib_path_attached_next_hop_set (fib_path_t *path) path->fp_sibling = adj_child_add(path->fp_dpo.dpoi_index, FIB_NODE_TYPE_PATH, fib_path_get_index(path)); + + if (!vnet_sw_interface_is_admin_up(vnet_get_main(), + path->attached_next_hop.fp_interface) || + !adj_is_up(path->fp_dpo.dpoi_index)) + { + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; + } } /* @@ -653,6 +654,19 @@ fib_path_recursive_adj_update (fib_path_t *path, load_balance_map_path_state_change(fib_path_get_index(path)); } } + /* + * check for over-riding factors on the FIB entry itself + */ + if (!fib_entry_is_resolved(path->fp_via_fib)) + { + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; + dpo_copy(&via_dpo, drop_dpo_get(fib_proto_to_dpo(path->fp_nh_proto))); + + /* + * PIC edge trigger. let the load-balance maps know + */ + load_balance_map_path_state_change(fib_path_get_index(path)); + } /* * update the path's contributed DPO @@ -855,15 +869,16 @@ FIXME comment vnet_get_main(), path->attached_next_hop.fp_interface); - if (if_is_up) - { - path->fp_oper_flags |= FIB_PATH_OPER_FLAG_RESOLVED; - } - ai = fib_path_attached_next_hop_get_adj( path, fib_proto_to_link(path->fp_nh_proto)); + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; + if (if_is_up && adj_is_up(ai)) + { + path->fp_oper_flags |= FIB_PATH_OPER_FLAG_RESOLVED; + } + dpo_set(&path->fp_dpo, DPO_ADJACENCY, fib_proto_to_dpo(path->fp_nh_proto), ai); @@ -1684,11 +1699,11 @@ fib_path_contribute_urpf (fib_node_index_t path_index, { fib_path_t *path; - if (!fib_path_is_resolved(path_index)) - return; - path = fib_path_get(path_index); + /* + * resolved and unresolved paths contribute to the RPF list. + */ switch (path->fp_type) { case FIB_PATH_TYPE_ATTACHED_NEXT_HOP: @@ -1700,7 +1715,14 @@ fib_path_contribute_urpf (fib_node_index_t path_index, break; case FIB_PATH_TYPE_RECURSIVE: - fib_entry_contribute_urpf(path->fp_via_fib, urpf); + if (FIB_NODE_INDEX_INVALID != path->fp_via_fib) + { + /* + * there's unresolved due to constraints, and there's unresolved + * due to ain't go no via. can't do nowt w'out via. + */ + fib_entry_contribute_urpf(path->fp_via_fib, urpf); + } break; case FIB_PATH_TYPE_EXCLUSIVE: diff --git a/src/vnet/fib/fib_test.c b/src/vnet/fib/fib_test.c index 92141ddf..3c9b8a38 100644 --- a/src/vnet/fib/fib_test.c +++ b/src/vnet/fib/fib_test.c @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -33,6 +34,11 @@ #include #include +/* + * Add debugs for passing tests + */ +static int fib_test_do_debug; + #define FIB_TEST_I(_cond, _comment, _args...) \ ({ \ int _evald = (_cond); \ @@ -40,6 +46,9 @@ fformat(stderr, "FAIL:%d: " _comment "\n", \ __LINE__, ##_args); \ } else { \ + if (fib_test_do_debug) \ + fformat(stderr, "PASS:%d: " _comment "\n", \ + __LINE__, ##_args); \ } \ _evald; \ }) @@ -6735,6 +6744,509 @@ fib_test_walk (void) return (0); } +/* + * declaration of the otherwise static callback functions + */ +void fib_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session); +void adj_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session); + +/** + * Test BFD session interaction with FIB + */ +static int +fib_test_bfd (void) +{ + fib_node_index_t fei; + test_main_t *tm; + int n_feis; + + /* via 10.10.10.1 */ + ip46_address_t nh_10_10_10_1 = { + .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a01), + }; + /* via 10.10.10.2 */ + ip46_address_t nh_10_10_10_2 = { + .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a02), + }; + /* via 10.10.10.10 */ + ip46_address_t nh_10_10_10_10 = { + .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a0a), + }; + n_feis = fib_entry_pool_size(); + + tm = &test_main; + + /* + * add interface routes. we'll assume this works. it's tested elsewhere + */ + fib_prefix_t pfx_10_10_10_10_s_24 = { + .fp_len = 24, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = nh_10_10_10_10, + }; + + fib_table_entry_update_one_path(0, &pfx_10_10_10_10_s_24, + FIB_SOURCE_INTERFACE, + (FIB_ENTRY_FLAG_CONNECTED | + FIB_ENTRY_FLAG_ATTACHED), + FIB_PROTOCOL_IP4, + NULL, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, // weight + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + fib_prefix_t pfx_10_10_10_10_s_32 = { + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = nh_10_10_10_10, + }; + fib_table_entry_update_one_path(0, &pfx_10_10_10_10_s_32, + FIB_SOURCE_INTERFACE, + (FIB_ENTRY_FLAG_CONNECTED | + FIB_ENTRY_FLAG_LOCAL), + FIB_PROTOCOL_IP4, + NULL, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, // weight + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + /* + * A BFD session via a neighbour we do not yet know + */ + bfd_session_t bfd_10_10_10_1 = { + .udp = { + .key = { + .fib_index = 0, + .peer_addr = nh_10_10_10_1, + }, + }, + .hop_type = BFD_HOP_TYPE_MULTI, + .local_state = BFD_STATE_init, + }; + + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + /* + * A new entry will be created that forwards via the adj + */ + adj_index_t ai_10_10_10_1 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index); + fib_prefix_t pfx_10_10_10_1_s_32 = { + .fp_addr = nh_10_10_10_1, + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + }; + fib_test_lb_bucket_t adj_o_10_10_10_1 = { + .type = FT_LB_ADJ, + .adj = { + .adj = ai_10_10_10_1, + }, + }; + + fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32); + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &adj_o_10_10_10_1), + "BFD sourced %U via %U", + format_fib_prefix, &pfx_10_10_10_1_s_32, + format_ip_adjacency, ai_10_10_10_1, FORMAT_IP_ADJACENCY_NONE); + + /* + * Delete the BFD session. Expect the fib_entry to be removed + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32); + FIB_TEST(FIB_NODE_INDEX_INVALID == fei, + "BFD sourced %U removed", + format_fib_prefix, &pfx_10_10_10_1_s_32); + + /* + * Add the BFD source back + */ + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + /* + * source the entry via the ADJ fib + */ + fei = fib_table_entry_update_one_path(0, + &pfx_10_10_10_1_s_32, + FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + FIB_PROTOCOL_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + /* + * Delete the BFD session. Expect the fib_entry to remain + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32); + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &adj_o_10_10_10_1), + "BFD sourced %U remains via %U", + format_fib_prefix, &pfx_10_10_10_1_s_32, + format_ip_adjacency, ai_10_10_10_1, FORMAT_IP_ADJACENCY_NONE); + + /* + * Add the BFD source back + */ + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + /* + * Create another ADJ FIB + */ + fib_prefix_t pfx_10_10_10_2_s_32 = { + .fp_addr = nh_10_10_10_2, + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + }; + fib_table_entry_update_one_path(0, + &pfx_10_10_10_2_s_32, + FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + /* + * A BFD session for the new ADJ FIB + */ + bfd_session_t bfd_10_10_10_2 = { + .udp = { + .key = { + .fib_index = 0, + .peer_addr = nh_10_10_10_2, + }, + }, + .hop_type = BFD_HOP_TYPE_MULTI, + .local_state = BFD_STATE_init, + }; + + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_2); + + /* + * remove the adj-fib source whilst the session is present + * then add it back + */ + fib_table_entry_delete(0, &pfx_10_10_10_2_s_32, FIB_SOURCE_ADJ); + fib_table_entry_update_one_path(0, + &pfx_10_10_10_2_s_32, + FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + /* + * Before adding a recursive via the BFD tracked ADJ-FIBs, + * bring one of the sessions UP, leave the other down + */ + bfd_10_10_10_1.local_state = BFD_STATE_up; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + bfd_10_10_10_2.local_state = BFD_STATE_down; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2); + + /* + * A recursive prefix via both of the ADJ FIBs + */ + fib_prefix_t pfx_200_0_0_0_s_24 = { + .fp_proto = FIB_PROTOCOL_IP4, + .fp_len = 32, + .fp_addr = { + .ip4.as_u32 = clib_host_to_net_u32(0xc8000000), + }, + }; + const dpo_id_t *dpo_10_10_10_1, *dpo_10_10_10_2; + + dpo_10_10_10_1 = + fib_entry_contribute_ip_forwarding( + fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32)); + dpo_10_10_10_2 = + fib_entry_contribute_ip_forwarding( + fib_table_lookup_exact_match(0, &pfx_10_10_10_2_s_32)); + + fib_test_lb_bucket_t lb_o_10_10_10_1 = { + .type = FT_LB_O_LB, + .lb = { + .lb = dpo_10_10_10_1->dpoi_index, + }, + }; + fib_test_lb_bucket_t lb_o_10_10_10_2 = { + .type = FT_LB_O_LB, + .lb = { + .lb = dpo_10_10_10_2->dpoi_index, + }, + }; + + /* + * A prefix via the adj-fib that is BFD down => DROP + */ + fei = fib_table_entry_path_add(0, + &pfx_200_0_0_0_s_24, + FIB_SOURCE_API, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + ~0, // recursive + 0, // default fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)), + "%U resolves via drop", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * add a path via the UP BFD adj-fib. + * we expect that the DOWN BFD ADJ FIB is not used. + */ + fei = fib_table_entry_path_add(0, + &pfx_200_0_0_0_s_24, + FIB_SOURCE_API, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_1, + ~0, // recursive + 0, // default fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &lb_o_10_10_10_1), + "Recursive %U only UP BFD adj-fibs", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Send a BFD state change to UP - both sessions are now up + * the recursive prefix should LB over both + */ + bfd_10_10_10_2.local_state = BFD_STATE_up; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2); + + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &lb_o_10_10_10_1, + &lb_o_10_10_10_2), + "Recursive %U via both UP BFD adj-fibs", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Send a BFD state change to DOWN + * the recursive prefix should exclude the down + */ + bfd_10_10_10_2.local_state = BFD_STATE_down; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2); + + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &lb_o_10_10_10_1), + "Recursive %U via only UP", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Delete the BFD session while it is in the DOWN state. + * FIB should consider the entry's state as back up + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_2); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &lb_o_10_10_10_1, + &lb_o_10_10_10_2), + "Recursive %U via both UP BFD adj-fibs post down session delete", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Delete the BFD other session while it is in the UP state. + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &lb_o_10_10_10_1, + &lb_o_10_10_10_2), + "Recursive %U via both UP BFD adj-fibs post up session delete", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * cleaup + */ + fib_table_entry_delete(0, &pfx_200_0_0_0_s_24, FIB_SOURCE_API); + fib_table_entry_delete(0, &pfx_10_10_10_1_s_32, FIB_SOURCE_ADJ); + fib_table_entry_delete(0, &pfx_10_10_10_2_s_32, FIB_SOURCE_ADJ); + + fib_table_entry_delete(0, &pfx_10_10_10_10_s_32, FIB_SOURCE_INTERFACE); + fib_table_entry_delete(0, &pfx_10_10_10_10_s_24, FIB_SOURCE_INTERFACE); + + adj_unlock(ai_10_10_10_1); + /* + * test no-one left behind + */ + FIB_TEST((n_feis == fib_entry_pool_size()), "Entries gone"); + FIB_TEST(0 == adj_nbr_db_size(), "All adjacencies removed"); + + /* + * Single-hop BFD tests + */ + bfd_10_10_10_1.hop_type = BFD_HOP_TYPE_SINGLE; + bfd_10_10_10_1.udp.key.sw_if_index = tm->hw[0]->sw_if_index; + + adj_bfd_notify(BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + ai_10_10_10_1 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index); + /* + * whilst the BFD session is not signalled, the adj is up + */ + FIB_TEST(adj_is_up(ai_10_10_10_1), "Adj state up on uninit session"); + + /* + * bring the BFD session up + */ + bfd_10_10_10_1.local_state = BFD_STATE_up; + adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + FIB_TEST(adj_is_up(ai_10_10_10_1), "Adj state up on UP session"); + + /* + * bring the BFD session down + */ + bfd_10_10_10_1.local_state = BFD_STATE_down; + adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + FIB_TEST(!adj_is_up(ai_10_10_10_1), "Adj state down on DOWN session"); + + + /* + * add an attached next hop FIB entry via the down adj + */ + fib_prefix_t pfx_5_5_5_5_s_32 = { + .fp_addr = { + .ip4 = { + .as_u32 = clib_host_to_net_u32(0x05050505), + }, + }, + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + }; + + fei = fib_table_entry_path_add(0, + &pfx_5_5_5_5_s_32, + FIB_SOURCE_CLI, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)), + "%U resolves via drop", + format_fib_prefix, &pfx_5_5_5_5_s_32); + + /* + * Add a path via an ADJ that is up + */ + adj_index_t ai_10_10_10_2 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index); + + fib_test_lb_bucket_t adj_o_10_10_10_2 = { + .type = FT_LB_ADJ, + .adj = { + .adj = ai_10_10_10_2, + }, + }; + adj_o_10_10_10_1.adj.adj = ai_10_10_10_1; + + fei = fib_table_entry_path_add(0, + &pfx_5_5_5_5_s_32, + FIB_SOURCE_CLI, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &adj_o_10_10_10_2), + "BFD sourced %U via %U", + format_fib_prefix, &pfx_5_5_5_5_s_32, + format_ip_adjacency, ai_10_10_10_2, FORMAT_IP_ADJACENCY_NONE); + + /* + * Bring up the down session - should now LB + */ + bfd_10_10_10_1.local_state = BFD_STATE_up; + adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &adj_o_10_10_10_1, + &adj_o_10_10_10_2), + "BFD sourced %U via noth adjs", + format_fib_prefix, &pfx_5_5_5_5_s_32); + + /* + * remove the BFD session state from the adj + */ + adj_bfd_notify(BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + /* + * clean-up + */ + fib_table_entry_delete(0, &pfx_5_5_5_5_s_32, FIB_SOURCE_CLI); + adj_unlock(ai_10_10_10_1); + adj_unlock(ai_10_10_10_2); + + /* + * test no-one left behind + */ + FIB_TEST((n_feis == fib_entry_pool_size()), "Entries gone"); + FIB_TEST(0 == adj_nbr_db_size(), "All adjacencies removed"); + return (0); +} + static int lfib_test (void) { @@ -7119,6 +7631,11 @@ fib_test (vlib_main_t * vm, res = 0; fib_test_mk_intf(4); + if (unformat (input, "debug")) + { + fib_test_do_debug = 1; + } + if (unformat (input, "ip")) { res += fib_test_v4(); @@ -7140,6 +7657,10 @@ fib_test (vlib_main_t * vm, { res += fib_test_walk(); } + else if (unformat (input, "bfd")) + { + res += fib_test_bfd(); + } else { /* @@ -7151,6 +7672,7 @@ fib_test (vlib_main_t * vm, res += fib_test_v4(); res += fib_test_v6(); res += fib_test_ae(); + res += fib_test_bfd(); res += fib_test_label(); res += lfib_test(); } diff --git a/src/vnet/fib/ip4_fib.c b/src/vnet/fib/ip4_fib.c index 98d4e52f..b03186e8 100644 --- a/src/vnet/fib/ip4_fib.c +++ b/src/vnet/fib/ip4_fib.c @@ -477,12 +477,15 @@ static void ip4_fib_table_show_one (ip4_fib_t *fib, vlib_main_t * vm, ip4_address_t *address, - u32 mask_len) + u32 mask_len, + int detail) { vlib_cli_output(vm, "%U", format_fib_entry, ip4_fib_table_lookup(fib, address, mask_len), - FIB_ENTRY_FORMAT_DETAIL); + (detail ? + FIB_ENTRY_FORMAT_DETAIL2 : + FIB_ENTRY_FORMAT_DETAIL)); } static clib_error_t * @@ -496,6 +499,7 @@ ip4_show_fib (vlib_main_t * vm, ip4_address_t matching_address; u32 matching_mask = 32; int i, table_id = -1, fib_index = ~0; + int detail = 0; verbose = 1; matching = 0; @@ -506,6 +510,9 @@ ip4_show_fib (vlib_main_t * vm, || unformat (input, "sum")) verbose = 0; + else if (unformat (input, "detail") || unformat (input, "det")) + detail = 1; + else if (unformat (input, "mtrie")) mtrie = 1; @@ -563,7 +570,8 @@ ip4_show_fib (vlib_main_t * vm, } else { - ip4_fib_table_show_one(fib, vm, &matching_address, matching_mask); + ip4_fib_table_show_one(fib, vm, &matching_address, + matching_mask, detail); } })); @@ -717,7 +725,7 @@ ip4_show_fib (vlib_main_t * vm, /* *INDENT-OFF* */ VLIB_CLI_COMMAND (ip4_show_fib_command, static) = { .path = "show ip fib", - .short_help = "show ip fib [summary] [table ] [index ] [[/]] [mtrie]", + .short_help = "show ip fib [summary] [table ] [index ] [[/]] [mtrie] [detail]", .function = ip4_show_fib, }; /* *INDENT-ON* */ diff --git a/src/vnet/fib/ip6_fib.c b/src/vnet/fib/ip6_fib.c index 0ee029d3..00297140 100644 --- a/src/vnet/fib/ip6_fib.c +++ b/src/vnet/fib/ip6_fib.c @@ -560,12 +560,15 @@ static void ip6_fib_table_show_one (ip6_fib_t *fib, vlib_main_t * vm, ip6_address_t *address, - u32 mask_len) + u32 mask_len, + int detail) { vlib_cli_output(vm, "%U", format_fib_entry, ip6_fib_table_lookup(fib->index, address, mask_len), - FIB_ENTRY_FORMAT_DETAIL); + (detail ? + FIB_ENTRY_FORMAT_DETAIL2: + FIB_ENTRY_FORMAT_DETAIL)); } typedef struct { @@ -573,8 +576,9 @@ typedef struct { u64 count_by_prefix_length[129]; } count_routes_in_fib_at_prefix_length_arg_t; -static void count_routes_in_fib_at_prefix_length -(BVT(clib_bihash_kv) * kvp, void *arg) +static void +count_routes_in_fib_at_prefix_length (BVT(clib_bihash_kv) * kvp, + void *arg) { count_routes_in_fib_at_prefix_length_arg_t * ap = arg; int mask_width; @@ -600,6 +604,7 @@ ip6_show_fib (vlib_main_t * vm, ip6_address_t matching_address; u32 mask_len = 128; int table_id = -1, fib_index = ~0; + int detail = 0; verbose = 1; matching = 0; @@ -610,6 +615,10 @@ ip6_show_fib (vlib_main_t * vm, unformat (input, "summary") || unformat (input, "sum")) verbose = 0; + + else if (unformat (input, "detail") || + unformat (input, "det")) + detail = 1; else if (unformat (input, "%U/%d", unformat_ip6_address, &matching_address, &mask_len)) @@ -667,7 +676,7 @@ ip6_show_fib (vlib_main_t * vm, } else { - ip6_fib_table_show_one(fib, vm, &matching_address, mask_len); + ip6_fib_table_show_one(fib, vm, &matching_address, mask_len, detail); } })); @@ -771,7 +780,7 @@ ip6_show_fib (vlib_main_t * vm, /* *INDENT-OFF* */ VLIB_CLI_COMMAND (ip6_show_fib_command, static) = { .path = "show ip6 fib", - .short_help = "show ip6 fib [summary] [table ] [index ] [[/]]", + .short_help = "show ip6 fib [summary] [table ] [index ] [[/]] [detail]", .function = ip6_show_fib, }; /* *INDENT-ON* */ diff --git a/test/framework.py b/test/framework.py index fbd21d23..ce70af2e 100644 --- a/test/framework.py +++ b/test/framework.py @@ -359,7 +359,7 @@ class VppTestCase(unittest.TestCase): self._testMethodDoc)) if not self.vpp_dead: self.logger.debug(self.vapi.cli("show trace")) - self.logger.info(self.vapi.ppcli("show interfaces")) + self.logger.info(self.vapi.ppcli("show interface")) self.logger.info(self.vapi.ppcli("show hardware")) self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show run")) diff --git a/test/test_bfd.py b/test/test_bfd.py index c9d0abdd..e8f8f338 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -16,9 +16,10 @@ from scapy.layers.inet6 import IPv6 from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \ BFDDiagCode, BFDState, BFD_vpp_echo from framework import VppTestCase, VppTestRunner, running_extended_tests -from vpp_pg_interface import CaptureTimeoutError +from vpp_pg_interface import CaptureTimeoutError, is_ipv6_misc from util import ppp from vpp_papi_provider import UnexpectedApiReturnValueError +from vpp_ip_route import VppIpRoute, VppRoutePath USEC_IN_SEC = 1000000 @@ -1582,6 +1583,107 @@ class BFD6TestCase(VppTestCase): self.test_session.send_packet() +class BFDFIBTestCase(VppTestCase): + """ BFD-FIB interactions (IPv6) """ + + vpp_session = None + test_session = None + + def setUp(self): + super(BFDFIBTestCase, self).setUp() + self.create_pg_interfaces(range(1)) + + self.vapi.want_bfd_events() + self.pg0.enable_capture() + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.configure_ipv6_neighbors() + + def tearDown(self): + if not self.vpp_dead: + self.vapi.want_bfd_events(enable_disable=0) + + super(BFDFIBTestCase, self).tearDown() + + @staticmethod + def pkt_is_not_data_traffic(p): + """ not data traffic implies BFD or the usual IPv6 ND/RA""" + if p.haslayer(BFD) or is_ipv6_misc(p): + return True + return False + + def test_session_with_fib(self): + """ BFD-FIB interactions """ + + # packets to match against both of the routes + p = [(Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(src="3001::1", dst="2001::1") / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)), + (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(src="3001::1", dst="2002::1") / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100))] + + # A recursive and a non-recursive route via a next-hop that + # will have a BFD session + ip_2001_s_64 = VppIpRoute(self, "2001::", 64, + [VppRoutePath(self.pg0.remote_ip6, + self.pg0.sw_if_index, + is_ip6=1)], + is_ip6=1) + ip_2002_s_64 = VppIpRoute(self, "2002::", 64, + [VppRoutePath(self.pg0.remote_ip6, + 0xffffffff, + is_ip6=1)], + is_ip6=1) + ip_2001_s_64.add_vpp_config() + ip_2002_s_64.add_vpp_config() + + # bring the session up now the routes are present + self.vpp_session = VppBFDUDPSession(self, + self.pg0, + self.pg0.remote_ip6, + af=AF_INET6) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET6) + + # session is up - traffic passes + bfd_session_up(self) + + self.pg0.add_stream(p) + self.pg_start() + for packet in p: + captured = self.pg0.wait_for_packet( + 1, + filter_out_fn=self.pkt_is_not_data_traffic) + self.assertEqual(captured[IPv6].dst, + packet[IPv6].dst) + + # session is up - traffic is dropped + bfd_session_down(self) + + self.pg0.add_stream(p) + self.pg_start() + with self.assertRaises(CaptureTimeoutError): + self.pg0.wait_for_packet(1, self.pkt_is_not_data_traffic) + + # session is up - traffic passes + bfd_session_up(self) + + self.pg0.add_stream(p) + self.pg_start() + for packet in p: + captured = self.pg0.wait_for_packet( + 1, + filter_out_fn=self.pkt_is_not_data_traffic) + self.assertEqual(captured[IPv6].dst, + packet[IPv6].dst) + + class BFDSHA1TestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """ diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 7f9e2ae1..e8025dff 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -152,8 +152,9 @@ class VppPapiProvider(object): raise UnexpectedApiReturnValueError(msg) elif self._expect_api_retval == self._zero: if hasattr(reply, 'retval') and reply.retval != expected_retval: - msg = "API call failed, expected zero return value instead "\ - "of %d in %s" % (expected_retval, repr(reply)) + msg = "API call failed, expected %d return value instead "\ + "of %d in %s" % (expected_retval, reply.retval, + repr(reply)) self.test_class.logger.info(msg) raise UnexpectedApiReturnValueError(msg) else: -- cgit 1.2.3-korg