diff options
author | Klement Sekera <ksekera@cisco.com> | 2016-09-29 14:43:44 +0200 |
---|---|---|
committer | Dave Barach <openvpp@barachs.net> | 2016-12-07 19:54:12 +0000 |
commit | 0e3c0de1ed87f3cdf16e26e05e39ea6eebeafb18 (patch) | |
tree | dd5f11c08311a95ce56eb8c33d4e8681940bf877 /vnet | |
parent | d171d48edc1672564db8bb920586b8ea220df14c (diff) |
BFD: basic asynchronous session up/down
This is a work-in-progress basic BFD session handling. Only
asynchronous mode is supported at the moment. Setting the session flags
doesn't work.
Change-Id: Idba27f721b5c35be5a66a6d202a63d23ff7ecf6f
Signed-off-by: Klement Sekera <ksekera@cisco.com>
Diffstat (limited to 'vnet')
-rw-r--r-- | vnet/Makefile.am | 16 | ||||
-rw-r--r-- | vnet/vnet/api_errno.h | 4 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_api.h | 46 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_debug.h | 80 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_doc.md | 1 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_main.c | 928 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_main.h | 218 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_protocol.c | 74 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_protocol.h | 154 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_udp.c | 610 | ||||
-rw-r--r-- | vnet/vnet/bfd/bfd_udp.h | 56 | ||||
-rw-r--r-- | vnet/vnet/bfd/dir.dox | 18 | ||||
-rw-r--r-- | vnet/vnet/buffer.h | 4 | ||||
-rw-r--r-- | vnet/vnet/ip/ip4_forward.c | 12 | ||||
-rw-r--r-- | vnet/vnet/ip/udp.h | 2 |
15 files changed, 2218 insertions, 5 deletions
diff --git a/vnet/Makefile.am b/vnet/Makefile.am index eb7f8ef1a01..4e4b2c0acb3 100644 --- a/vnet/Makefile.am +++ b/vnet/Makefile.am @@ -350,6 +350,22 @@ nobase_include_HEADERS += \ vnet/ip/udp_packet.h ######################################## +# Bidirectional Forwarding Detection +######################################## + +nobase_include_HEADERS += \ + vnet/bfd/bfd_protocol.h \ + vnet/bfd/bfd_main.h \ + vnet/bfd/bfd_api.h \ + vnet/bfd/bfd_udp.h + +libvnet_la_SOURCES += \ + vnet/bfd/bfd_api.h \ + vnet/bfd/bfd_udp.c \ + vnet/bfd/bfd_main.c \ + vnet/bfd/bfd_protocol.c + +######################################## # Layer 3 protocol: IPSec ######################################## if WITH_IPSEC diff --git a/vnet/vnet/api_errno.h b/vnet/vnet/api_errno.h index 50d6f731035..65e3e59121e 100644 --- a/vnet/vnet/api_errno.h +++ b/vnet/vnet/api_errno.h @@ -90,7 +90,9 @@ _(EXCEEDED_NUMBER_OF_PORTS_CAPACITY, -96, "Operation would exceed capacity of nu _(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") +_(CANNOT_ENABLE_DISABLE_FEATURE, -100, "Cannot enable/disable feature") \ +_(BFD_EEXIST, -101, "Duplicate BFD session") \ +_(BFD_NOENT, -102, "No such BFD session") typedef enum { diff --git a/vnet/vnet/bfd/bfd_api.h b/vnet/vnet/bfd/bfd_api.h new file mode 100644 index 00000000000..cfcd04f3f50 --- /dev/null +++ b/vnet/vnet/bfd/bfd_api.h @@ -0,0 +1,46 @@ +/* + * 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_api_h__ +#define __included_bfd_api_h__ + +#include <vnet/api_errno.h> +#include <vnet/vnet.h> +#include <vnet/ip/ip6_packet.h> +#include <vnet/bfd/bfd_udp.h> + +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); + +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); + +#endif /* __included_bfd_api_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/vnet/vnet/bfd/bfd_debug.h b/vnet/vnet/bfd/bfd_debug.h new file mode 100644 index 00000000000..c11e6d9f499 --- /dev/null +++ b/vnet/vnet/bfd/bfd_debug.h @@ -0,0 +1,80 @@ +/* + * 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_debug_h__ +#define __included_bfd_debug_h__ + +/* controls debug prints */ +#define BFD_DEBUG (0) + +#if BFD_DEBUG +#define BFD_DEBUG_FILE_DEF \ + static const char *__file = NULL; \ + if (!__file) \ + { \ + __file = strrchr (__FILE__, '/'); \ + if (__file) \ + { \ + ++__file; \ + } \ + else \ + { \ + __file = __FILE__; \ + } \ + } + +#define BFD_DBG(fmt, ...) \ + do \ + { \ + BFD_DEBUG_FILE_DEF \ + u8 *_s = NULL; \ + vlib_main_t *vm = vlib_get_main (); \ + _s = format (_s, "%6.02f:DBG:%s:%d:%s():" fmt, vlib_time_now (vm), \ + __file, __LINE__, __func__, ##__VA_ARGS__); \ + printf ("%s\n", _s); \ + vec_free (_s); \ + } \ + while (0); + +#define BFD_ERR(fmt, ...) \ + do \ + { \ + BFD_DEBUG_FILE_DEF \ + u8 *_s = NULL; \ + vlib_main_t *vm = vlib_get_main (); \ + _s = format (_s, "%6.02f:ERR:%s:%d:%s():" fmt, vlib_time_now (vm), \ + __file, __LINE__, __func__, ##__VA_ARGS__); \ + printf ("%s\n", _s); \ + vec_free (_s); \ + } \ + while (0); + +#else +#define BFD_DBG(...) +#define BFD_ERR(...) +#endif + +#endif /* __included_bfd_debug_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/vnet/vnet/bfd/bfd_doc.md b/vnet/vnet/bfd/bfd_doc.md new file mode 100644 index 00000000000..1333ed77b7e --- /dev/null +++ b/vnet/vnet/bfd/bfd_doc.md @@ -0,0 +1 @@ +TODO diff --git a/vnet/vnet/bfd/bfd_main.c b/vnet/vnet/bfd/bfd_main.c new file mode 100644 index 00000000000..a72d6fed9bb --- /dev/null +++ b/vnet/vnet/bfd/bfd_main.c @@ -0,0 +1,928 @@ +/* + * 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 nodes implementation + */ + +#include <vppinfra/random.h> +#include <vppinfra/error.h> +#include <vppinfra/hash.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/ethernet/packet.h> +#include <vnet/bfd/bfd_debug.h> +#include <vnet/bfd/bfd_protocol.h> +#include <vnet/bfd/bfd_main.h> + +static u64 +bfd_us_to_clocks (bfd_main_t * bm, u64 us) +{ + return bm->cpu_cps * ((f64) us / USEC_PER_SECOND); +} + +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, + foreach_bfd_transport (F) +#undef F +}; + +/* + * 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" + * node automatically bumps our per-node packet counters for us. + */ +typedef enum +{ + BFD_INPUT_NEXT_NORMAL, + BFD_INPUT_N_NEXT, +} bfd_input_next_t; + +static void bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, + u64 now); + +static void +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->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->remote_demand = 0; +} + +static void +bfd_set_diag (bfd_session_t * bs, bfd_diag_code_e code) +{ + if (bs->local_diag != code) + { + BFD_DBG ("set local_diag, bs_idx=%d: '%d:%s'", bs->bs_idx, code, + bfd_diag_code_string (code)); + bs->local_diag = code; + } +} + +static void +bfd_set_state (bfd_main_t * bm, bfd_session_t * bs, bfd_state_e new_state) +{ + if (bs->local_state != new_state) + { + BFD_DBG ("Change state, bs_idx=%d: %s->%s", bs->bs_idx, + bfd_state_string (bs->local_state), + bfd_state_string (new_state)); + bs->local_state = new_state; + bfd_on_state_change (bm, bs, clib_cpu_time_now ()); + } +} + +static void +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); + } + else + { + /* TODO */ + } + BFD_DBG ("Recalculated transmit interval %lu clocks/%.2fs", + bs->transmit_interval_clocks, + bs->transmit_interval_clocks / bm->cpu_cps); +} + +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) + { + /* common case - 75-100% of transmit interval */ + bs->tx_timeout_clocks = now + + (1 - .25 * (random_f64 (&bm->random_seed))) * + bs->transmit_interval_clocks; + } + else + { + /* special case - 75-90% of transmit interval */ + bs->tx_timeout_clocks = + now + + (.9 - .15 * (random_f64 (&bm->random_seed))) * + bs->transmit_interval_clocks; + } + } + else + { + /* TODO */ + } + if (bs->tx_timeout_clocks) + { + BFD_DBG ("Next transmit in %lu clocks/%.02fs@%lu", + bs->tx_timeout_clocks - now, + (bs->tx_timeout_clocks - now) / bm->cpu_cps, + bs->tx_timeout_clocks); + } +} + +static void +bfd_recalc_detection_time (bfd_main_t * bm, bfd_session_t * bs) +{ + if (!bs->local_demand) + { + 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)); + } + else + { + 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)); + } + BFD_DBG ("Recalculated detection time %lu clocks/%.2fs", + bs->detection_time_clocks, + bs->detection_time_clocks / bm->cpu_cps); +} + +static void +bfd_set_timer (bfd_main_t * bm, bfd_session_t * bs, u64 now) +{ + u64 next = 0; + u64 rx_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) + { + next = clib_min (bs->tx_timeout_clocks, rx_timeout); + } + else if (bs->tx_timeout_clocks) + { + next = bs->tx_timeout_clocks; + } + 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 (next && (next < bs->wheel_time_clocks || !bs->wheel_time_clocks)) + { + if (bs->wheel_time_clocks) + { + timing_wheel_delete (&bm->wheel, bs->bs_idx); + } + bs->wheel_time_clocks = next; + BFD_DBG ("timing_wheel_insert(%p, %lu (%ld clocks/%.2fs in the " + "future), %u);", + &bm->wheel, bs->wheel_time_clocks, + (i64) bs->wheel_time_clocks - clib_cpu_time_now (), + (i64) (bs->wheel_time_clocks - clib_cpu_time_now ()) / + bm->cpu_cps, bs->bs_idx); + timing_wheel_insert (&bm->wheel, bs->wheel_time_clocks, bs->bs_idx); + } +} + +static void +bfd_set_desired_min_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now, + u32 desired_min_tx_us) +{ + 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); + 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); +} + +void +bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) +{ + BFD_DBG ("%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); +} + +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_NOENT; + } + 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 +} + +vnet_api_error_t +bfd_session_set_flags (u32 bs_idx, 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); + } + else + { + bfd_set_diag (bs, BFD_DIAG_CODE_neighbor_sig_down); + bfd_set_state (bm, bs, BFD_STATE_admin_down); + } + return 0; +} + +u8 * +bfd_input_format_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + const bfd_input_trace_t *t = va_arg (*args, bfd_input_trace_t *); + const bfd_pkt_t *pkt = (bfd_pkt_t *) t->data; + 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", + 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), + bfd_state_string (bfd_pkt_get_state (pkt)), + bfd_pkt_get_poll (pkt), bfd_pkt_get_final (pkt), + bfd_pkt_get_control_plane_independent (pkt), + 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)) + { + s = format (s, " my discriminator: %u\n", pkt->my_disc); + s = format (s, " your discriminator: %u\n", pkt->your_disc); + s = format (s, " desired min tx interval: %u\n", + 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", + clib_net_to_host_u32 (pkt->req_min_echo_rx)); + } + } + + return s; +} + +static void +bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now) +{ + BFD_DBG ("State changed: %U", format_bfd_session, bs); + bfd_event (bm, bs); + 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)); + 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)); + 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)); + break; + case BFD_STATE_up: + bfd_set_desired_min_tx (bm, bs, now, bs->config_desired_min_tx_us); + break; + } +} + +static void +bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, + bfd_session_t * bs) +{ + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + /* fallthrough */ + case BFD_TRANSPORT_UDP6: + BFD_DBG ("Transport bfd via udp, bs_idx=%u", bs->bs_idx); + bfd_add_udp_transport (vm, b, &bs->udp); + break; + } +} + +static vlib_buffer_t * +bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, + bfd_session_t * bs) +{ + u32 bi; + if (vlib_buffer_alloc (vm, &bi, 1) != 1) + { + clib_warning ("buffer allocation failure"); + return NULL; + } + + 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); + + to_next[0] = bi; + n_left_to_next -= 1; + + vlib_put_next_frame (vm, rt, bfd_next_index_by_transport[bs->transport], + n_left_to_next); + return b; +} + +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)); + + 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->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 */ + b->current_length = bfd_length; +} + +static void +bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, + bfd_main_t * bm, bfd_session_t * bs, u64 now) +{ + if (!bs->remote_min_rx_us) + { + BFD_DBG + ("bfd.RemoteMinRxInterval is zero, not sending periodic control " + "frame"); + return; + } + /* FIXME + A system MUST NOT periodically transmit BFD Control packets if Demand + mode is active on the remote system (bfd.RemoteDemandMode is 1, + bfd.SessionState is Up, and bfd.RemoteSessionState is Up) and a Poll + Sequence is not being transmitted. + */ + if (now >= 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); + if (!b) + { + return; + } + bfd_init_control_frame (b, bs); + bfd_add_transport_layer (vm, b, bs); + bfd_calc_next_tx (bm, bs, now); + } + else + { + BFD_DBG ("No need to send control frame now"); + } + bfd_set_timer (bm, bs, now); +} + +void +bfd_send_final (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); + bfd_pkt_set_final (vlib_buffer_get_current (b)); + bfd_add_transport_layer (vm, b, bs); +} + +static void +bfd_check_rx_timeout (bfd_main_t * bm, bfd_session_t * bs, u64 now) +{ + if (bs->last_rx_clocks + bs->detection_time_clocks < 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); + } +} + +void +bfd_on_timeout (vlib_main_t * vm, vlib_node_runtime_t * rt, bfd_main_t * bm, + bfd_session_t * bs, u64 now) +{ + BFD_DBG ("Timeout for bs_idx=%lu", bs->bs_idx); + switch (bs->local_state) + { + case BFD_STATE_admin_down: + BFD_ERR ("Unexpected timeout when in %s state", + bfd_state_string (bs->local_state)); + abort (); + break; + case BFD_STATE_down: + bfd_send_periodic (vm, rt, bm, bs, now); + break; + case BFD_STATE_init: + BFD_ERR ("Unexpected timeout when in %s state", + bfd_state_string (bs->local_state)); + abort (); + break; + case BFD_STATE_up: + bfd_check_rx_timeout (bm, bs, now); + bfd_send_periodic (vm, rt, bm, bs, now); + break; + } +} + +/* + * bfd input routine + */ +bfd_error_t +bfd_input (vlib_main_t * vm, vlib_buffer_t * b0, u32 bi0) +{ + // bfd_main_t *bm = &bfd_main; + bfd_error_t e; + + /* find our interface */ + bfd_session_t *s = NULL; + // bfd_get_intf (lm, vnet_buffer (b0)->sw_if_index[VLIB_RX]); + + if (!s) + { + /* bfd disabled on this interface, we're done */ + return BFD_ERROR_DISABLED; + } + + /* Actually scan the packet */ + e = BFD_ERROR_NONE; // bfd_packet_scan (lm, n, vlib_buffer_get_current (b0)); + + return e; +} + +/* + * bfd process node function + */ +static uword +bfd_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + bfd_main_t *bm = &bfd_main; + u32 *expired = 0; + uword event_type, *event_data = 0; + + /* So we can send events to the bfd process */ + bm->bfd_process_node_index = bfd_process_node.index; + + while (1) + { + u64 now = clib_cpu_time_now (); + u64 next_expire = timing_wheel_next_expiring_elt_time (&bm->wheel); + BFD_DBG ("timing_wheel_next_expiring_elt_time(%p) returns %lu", + &bm->wheel, next_expire); + if ((i64) next_expire < 0) + { + BFD_DBG ("wait for event without timeout"); + (void) vlib_process_wait_for_event (vm); + } + else + { + f64 timeout = ((i64) next_expire - (i64) now) / bm->cpu_cps; + BFD_DBG ("wait for event with timeout %.02f", timeout); + ASSERT (timeout > 0); + (void) vlib_process_wait_for_event_or_clock (vm, timeout); + } + event_type = vlib_process_get_events (vm, &event_data); + now = clib_cpu_time_now (); + switch (event_type) + { + case ~0: /* no events => timeout */ + /* nothing to do here */ + break; + case BFD_EVENT_RESCHEDULE: + /* nothing to do here - reschedule is done automatically after + * each event or timeout */ + break; + case BFD_EVENT_NEW_SESSION: + do + { + bfd_session_t *bs = + pool_elt_at_index (bm->sessions, *event_data); + bfd_send_periodic (vm, rt, bm, bs, now); + } + while (0); + break; + default: + clib_warning ("BUG: event type 0x%wx", event_type); + break; + } + BFD_DBG ("advancing wheel, now is %lu", now); + BFD_DBG ("timing_wheel_advance (%p, %lu, %p, 0);", &bm->wheel, now, + expired); + expired = timing_wheel_advance (&bm->wheel, now, expired, 0); + BFD_DBG ("Expired %d elements", vec_len (expired)); + u32 *p = NULL; + vec_foreach (p, expired) + { + const u32 bs_idx = *p; + if (!pool_is_free_index (bm->sessions, bs_idx)) + { + bfd_session_t *bs = pool_elt_at_index (bm->sessions, bs_idx); + bs->wheel_time_clocks = 0; /* no longer scheduled */ + bfd_on_timeout (vm, rt, bm, bs, now); + } + } + if (expired) + { + _vec_len (expired) = 0; + } + if (event_data) + { + _vec_len (event_data) = 0; + } + } + + return 0; +} + +/* + * bfd process node declaration + */ +/* *INDENT-OFF* */ +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 + }, +}; +/* *INDENT-ON* */ + +static clib_error_t * +bfd_sw_interface_up_down (vnet_main_t * vnm, u32 sw_if_index, u32 flags) +{ + // bfd_main_t *bm = &bfd_main; + // vnet_hw_interface_t *hi = vnet_get_sup_hw_interface (vnm, sw_if_index); + if (!(flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP)) + { + /* TODO */ + } + return 0; +} + +VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (bfd_sw_interface_up_down); + +static clib_error_t * +bfd_hw_interface_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) +{ + // bfd_main_t *bm = &bfd_main; + if (flags & VNET_HW_INTERFACE_FLAG_LINK_UP) + { + /* TODO */ + } + return 0; +} + +VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (bfd_hw_interface_up_down); + +/* + * setup function + */ +static clib_error_t * +bfd_main_init (vlib_main_t * vm) +{ + 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; + BFD_DBG ("cps is %.2f", bm->cpu_cps); + const u64 now = clib_cpu_time_now (); + timing_wheel_init (&bm->wheel, now, bm->cpu_cps); + + return 0; +} + +VLIB_INIT_FUNCTION (bfd_main_init); + +bfd_session_t * +bfd_get_session (bfd_main_t * bm, bfd_transport_t t) +{ + bfd_session_t *result; + pool_get (bm->sessions, result); + result->bs_idx = result - bm->sessions; + result->transport = t; + result->local_discr = random_u32 (&bm->random_seed); + bfd_set_defaults (bm, result); + hash_set (bm->session_by_disc, result->local_discr, result->bs_idx); + return result; +} + +void +bfd_put_session (bfd_main_t * bm, bfd_session_t * bs) +{ + hash_unset (bm->session_by_disc, bs->local_discr); + pool_put (bm->sessions, bs); +} + +bfd_session_t * +bfd_find_session_by_idx (bfd_main_t * bm, uword bs_idx) +{ + if (!pool_is_free_index (bm->sessions, bs_idx)) + { + return pool_elt_at_index (bm->sessions, bs_idx); + } + return NULL; +} + +bfd_session_t * +bfd_find_session_by_disc (bfd_main_t * bm, u32 disc) +{ + uword *p = hash_get (bfd_main.session_by_disc, disc); + if (p) + { + return pool_elt_at_index (bfd_main.sessions, *p); + } + return NULL; +} + +/** + * @brief verify bfd packet - common checks + * + * @param pkt + * + * @return 1 if bfd packet is valid + */ +int +bfd_verify_pkt_common (const bfd_pkt_t * pkt) +{ + if (1 != bfd_pkt_get_version (pkt)) + { + BFD_ERR ("BFD verification failed - unexpected version: '%d'", + bfd_pkt_get_version (pkt)); + return 0; + } + if (pkt->head.length < sizeof (bfd_pkt_t) || + (bfd_pkt_get_auth_present (pkt) && + pkt->head.length < sizeof (bfd_pkt_with_auth_t))) + { + BFD_ERR ("BFD verification failed - unexpected length: '%d' (auth " + "present: %d)", + pkt->head.length, bfd_pkt_get_auth_present (pkt)); + return 0; + } + if (!pkt->head.detect_mult) + { + BFD_ERR ("BFD verification failed - unexpected detect-mult: '%d'", + pkt->head.detect_mult); + return 0; + } + if (bfd_pkt_get_multipoint (pkt)) + { + BFD_ERR ("BFD verification failed - unexpected multipoint: '%d'", + bfd_pkt_get_multipoint (pkt)); + return 0; + } + if (!pkt->my_disc) + { + BFD_ERR ("BFD verification failed - unexpected my-disc: '%d'", + pkt->my_disc); + return 0; + } + if (!pkt->your_disc) + { + const u8 pkt_state = bfd_pkt_get_state (pkt); + if (pkt_state != BFD_STATE_down && pkt_state != BFD_STATE_admin_down) + { + BFD_ERR ("BFD verification failed - unexpected state: '%s' " + "(your-disc is zero)", bfd_state_string (pkt_state)); + return 0; + } + } + return 1; +} + +/** + * @brief verify bfd packet - authentication + * + * @param 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) +{ + const bfd_pkt_with_auth_t *with_auth = (bfd_pkt_with_auth_t *) pkt; + if (!bfd_pkt_get_auth_present (pkt)) + { + if (pkt_size > sizeof (*pkt)) + { + BFD_ERR ("BFD verification failed - unexpected packet size '%d' " + "(auth not present)", pkt_size); + return 0; + } + } + else + { + if (!with_auth->auth.type) + { + BFD_ERR ("BFD verification failed - unexpected auth type: '%d'", + with_auth->auth.type); + return 0; + } + /* TODO FIXME - implement the actual verification */ + } + return 1; +} + +void +bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) +{ + bfd_session_t *bs = bfd_find_session_by_idx (bm, bs_idx); + if (!bs) + { + return; + } + BFD_DBG ("Scanning bfd packet, bs_idx=%d", bs->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_min_rx_us = clib_net_to_host_u32 (pkt->req_min_rx); + bs->remote_min_rx_clocks = bfd_us_to_clocks (bm, bs->remote_min_rx_us); + BFD_DBG ("Set remote min rx to %lu clocks/%.2fs", bs->remote_min_rx_clocks, + bs->remote_min_rx_clocks / bm->cpu_cps); + bs->remote_desired_min_tx_us = clib_net_to_host_u32 (pkt->des_min_tx); + bs->remote_detect_mult = pkt->head.detect_mult; + bfd_recalc_detection_time (bm, bs); + bs->last_rx_clocks = clib_cpu_time_now (); + /* 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. + */ + /* FIXME 6.8.2 */ + /* FIXME 6.8.4 */ + if (BFD_STATE_admin_down == bs->local_state) + return; + if (BFD_STATE_admin_down == bs->remote_state) + { + bfd_set_diag (bs, BFD_DIAG_CODE_neighbor_sig_down); + bfd_set_state (bm, bs, BFD_STATE_down); + } + else if (BFD_STATE_down == bs->local_state) + { + if (BFD_STATE_down == bs->remote_state) + { + bfd_set_state (bm, bs, BFD_STATE_init); + } + else if (BFD_STATE_init == bs->remote_state) + { + bfd_set_state (bm, bs, BFD_STATE_up); + } + } + else if (BFD_STATE_init == bs->local_state) + { + if (BFD_STATE_up == bs->remote_state || + BFD_STATE_init == bs->remote_state) + { + bfd_set_state (bm, bs, BFD_STATE_up); + } + } + else /* BFD_STATE_up == bs->local_state */ + { + if (BFD_STATE_down == bs->remote_state) + { + bfd_set_diag (bs, BFD_DIAG_CODE_neighbor_sig_down); + bfd_set_state (bm, bs, BFD_STATE_down); + } + } +} + +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.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->remote_min_rx_us, (bs->local_demand ? "yes" : "no"), + (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult); +} + +bfd_main_t bfd_main; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/vnet/vnet/bfd/bfd_main.h b/vnet/vnet/bfd/bfd_main.h new file mode 100644 index 00000000000..727903bd286 --- /dev/null +++ b/vnet/vnet/bfd/bfd_main.h @@ -0,0 +1,218 @@ +/* + * 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 <vppinfra/timing_wheel.h> +#include <vnet/vnet.h> +#include <vnet/bfd/bfd_protocol.h> +#include <vnet/bfd/bfd_udp.h> + +#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 */ + uword 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; + + u8 local_detect_mult; + u8 remote_detect_mult; + + /* set to value of timer in timing wheel, 0 if not 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; + + /* 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; + + /* pool of event subscribers */ + //event_subscriber_t *subscribers; + +} 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; + +bfd_error_t bfd_input (vlib_main_t * vm, vlib_buffer_t * b0, u32 bi0); +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: + */ diff --git a/vnet/vnet/bfd/bfd_protocol.c b/vnet/vnet/bfd/bfd_protocol.c new file mode 100644 index 00000000000..ede9536f3cf --- /dev/null +++ b/vnet/vnet/bfd/bfd_protocol.c @@ -0,0 +1,74 @@ +#include <vnet/bfd/bfd_protocol.h> + +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) +{ + pkt->head.vers_diag = + (version << 5) | (pkt->head.vers_diag & ((1 << 5) - 1)); +} + +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) +{ + pkt->head.vers_diag = + (pkt->head.vers_diag & ~((1 << 5) - 1)) | (value & ((1 << 5) - 1)); +} + +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) +{ + pkt->head.sta_flags = (value << 6) | (pkt->head.sta_flags & ((1 << 6) - 1)); +} + +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; } + +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) +{ + return (pkt->head.sta_flags >> 3) & 1; +} + +void bfd_pkt_set_control_plane_independent (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); + +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; } + +u8 bfd_pkt_get_multipoint (const bfd_pkt_t *pkt) +{ + return pkt->head.sta_flags & 1; +} + +void bfd_pkt_set_multipoint (bfd_pkt_t *pkt); diff --git a/vnet/vnet/bfd/bfd_protocol.h b/vnet/vnet/bfd/bfd_protocol.h new file mode 100644 index 00000000000..cf751b3b89a --- /dev/null +++ b/vnet/vnet/bfd/bfd_protocol.h @@ -0,0 +1,154 @@ +/* + * 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. + */ +#ifndef __included_bfd_protocol_h__ +#define __included_bfd_protocol_h__ +/** + * @file + * @brief BFD protocol declarations + */ + +#include <vppinfra/types.h> +#include <vppinfra/clib.h> + +/* *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; +/* *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; + u8 sta_flags; + u8 detect_mult; + u8 length; + } head; + u32 my_disc; + u32 your_disc; + u32 des_min_tx; + u32 req_min_rx; + u32 req_min_echo_rx; +}) bfd_pkt_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + bfd_pkt_t pkt; + bfd_auth_t auth; +}) bfd_pkt_with_auth_t; +/* *INDENT-ON* */ + +u8 bfd_pkt_get_version (const bfd_pkt_t * pkt); +void bfd_pkt_set_version (bfd_pkt_t * pkt, int version); +u8 bfd_pkt_get_diag_code (const bfd_pkt_t * pkt); +void bfd_pkt_set_diag_code (bfd_pkt_t * pkt, int value); +u8 bfd_pkt_get_state (const bfd_pkt_t * pkt); +void bfd_pkt_set_state (bfd_pkt_t * pkt, int value); +u8 bfd_pkt_get_poll (const bfd_pkt_t * pkt); +void bfd_pkt_set_final (bfd_pkt_t * pkt); +u8 bfd_pkt_get_final (const bfd_pkt_t * pkt); +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_control_plane_independent (bfd_pkt_t * pkt); +u8 bfd_pkt_get_auth_present (const bfd_pkt_t * pkt); +void bfd_pkt_set_auth_present (bfd_pkt_t * pkt); +u8 bfd_pkt_get_demand (const bfd_pkt_t * pkt); +void bfd_pkt_set_demand (bfd_pkt_t * pkt); +u8 bfd_pkt_get_multipoint (const bfd_pkt_t * pkt); +void bfd_pkt_set_multipoint (bfd_pkt_t * pkt); + +/* BFD diagnostic codes */ +#define foreach_bfd_diag_code(F) \ + F (0, no_diag, "No Diagnostic") \ + F (1, det_time_exp, "Control Detection Time Expired") \ + F (2, echo_failed, "Echo Function Failed") \ + F (3, neighbor_sig_down, "Neighbor Signaled Session Down") \ + F (4, fwd_plain_reset, "Forwarding Plane Reset") \ + F (5, path_down, "Path Down") \ + F (6, concat_path_down, "Concatenated Path Down") \ + F (7, admin_down, "Administratively Down") \ + F (8, reverse_concat_path_down, "Reverse Concatenated Path Down") + +#define BFD_DIAG_CODE_NAME(t) BFD_DIAG_CODE_##t + +typedef enum +{ +#define F(n, t, s) BFD_DIAG_CODE_NAME (t) = n, + foreach_bfd_diag_code (F) +#undef F +} bfd_diag_code_e; + +const char *bfd_diag_code_string (bfd_diag_code_e diag); + +/* BFD state values */ +#define foreach_bfd_state(F) \ + F (0, admin_down, "AdminDown") \ + F (1, down, "Down") \ + F (2, init, "Init") \ + F (3, up, "Up") + +#define BFD_STATE_NAME(t) BFD_STATE_##t + +typedef enum +{ +#define F(n, t, s) BFD_STATE_NAME (t) = n, + foreach_bfd_state (F) +#undef F +} bfd_state_e; + +const char *bfd_state_string (bfd_state_e state); + +#endif /* __included_bfd_protocol_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/vnet/vnet/bfd/bfd_udp.c b/vnet/vnet/bfd/bfd_udp.c new file mode 100644 index 00000000000..ded3342559c --- /dev/null +++ b/vnet/vnet/bfd/bfd_udp.c @@ -0,0 +1,610 @@ +#include <vppinfra/types.h> +#include <vlibmemory/api.h> +#include <vlib/vlib.h> +#include <vlib/buffer.h> +#include <vnet/ip/format.h> +#include <vnet/ethernet/packet.h> +#include <vnet/ip/udp_packet.h> +#include <vnet/ip/lookup.h> +#include <vnet/ip/icmp46_packet.h> +#include <vnet/ip/ip4.h> +#include <vnet/ip/ip6.h> +#include <vnet/ip/udp.h> +#include <vnet/ip/ip6_packet.h> +#include <vnet/adj/adj.h> +#include <vnet/adj/adj_nbr.h> +#include <vnet/bfd/bfd_debug.h> +#include <vnet/bfd/bfd_udp.h> +#include <vnet/bfd/bfd_main.h> +#include <vnet/bfd/bfd_api.h> + +typedef struct +{ + bfd_main_t *bfd_main; + /* hashmap - bfd session index by bfd key - used for CLI/API lookup, where + * discriminator is unknown */ + mhash_t bfd_session_idx_by_bfd_key; +} bfd_udp_main_t; + +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) +{ + udp_header_t *udp; + u16 udp_length, ip_length; + 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 (); + } +} + +void bfd_add_udp_transport (vlib_main_t *vm, vlib_buffer_t *b, + bfd_udp_session_t *bus) +{ + 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); +} + +static bfd_session_t *bfd_lookup_session (bfd_udp_main_t *bum, + const bfd_udp_key_t *key) +{ + uword *p = mhash_get (&bum->bfd_session_idx_by_bfd_key, key); + if (p) + { + return bfd_find_session_by_idx (bum->bfd_main, *p); + } + return 0; +} + +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) +{ + 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)) + { + t = BFD_TRANSPORT_UDP6; + } + bfd_session_t *bs = bfd_get_session (bum->bfd_main, t); + 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]; + 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); + 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, &key->local_addr, + IP46_TYPE_ANY, format_ip46_address, &key->peer_addr, IP46_TYPE_ANY); + if (BFD_TRANSPORT_UDP4 == t) + { + bus->adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4, + &key->peer_addr, key->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); + } + else + { + bus->adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP6, VNET_LINK_IP6, + &key->peer_addr, key->sw_if_index); + BFD_DBG ("adj_nbr_add_or_lock(FIB_PROTOCOL_IP6, VNET_LINK_IP6, %U, %d) " + "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->local_detect_mult = detect_mult; + bfd_session_start (bum->bfd_main, bs); + return 0; +} + +static vnet_api_error_t +bfd_udp_validate_api_input (u32 sw_if_index, const ip46_address_t *local_addr, + const ip46_address_t *peer_addr) +{ + vnet_sw_interface_t *sw_if = + vnet_get_sw_interface (vnet_get_main (), sw_if_index); + u8 local_ip_valid = 0; + ip_interface_address_t *ia = NULL; + if (!sw_if) + { + BFD_ERR ("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"); + return VNET_API_ERROR_INVALID_ARGUMENT; + } + ip4_main_t *im = &ip4_main; + + /* *INDENT-OFF* */ + foreach_ip_interface_address ( + &im->lookup_main, ia, sw_if_index, 0 /* honor unnumbered */, ({ + ip4_address_t *x = + ip_interface_address_get_address (&im->lookup_main, ia); + if (x->as_u32 == local_addr->ip4.as_u32) + { + /* valid address for this interface */ + local_ip_valid = 1; + break; + } + })); + /* *INDENT-ON* */ + } + else + { + if (ip46_address_is_ip4 (peer_addr)) + { + BFD_ERR ("IP family mismatch"); + return VNET_API_ERROR_INVALID_ARGUMENT; + } + ip6_main_t *im = &ip6_main; + /* *INDENT-OFF* */ + foreach_ip_interface_address ( + &im->lookup_main, ia, sw_if_index, 0 /* honor unnumbered */, ({ + 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]) + { + /* valid address for this interface */ + local_ip_valid = 1; + break; + } + })); + /* *INDENT-ON* */ + } + + if (!local_ip_valid) + { + BFD_ERR ("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) +{ + vnet_api_error_t rv = + bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); + if (rv) + { + return rv; + } + if (detect_mult < 1) + { + BFD_ERR ("detect_mult < 1"); + return VNET_API_ERROR_INVALID_ARGUMENT; + } + if (desired_min_tx_us < 1) + { + BFD_ERR ("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); +} + +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 rv = + bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); + 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_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); + } + else + { + BFD_ERR ("no such session"); + return VNET_API_ERROR_BFD_NOENT; + } + return 0; +} + +typedef enum { + BFD_UDP_INPUT_NEXT_NORMAL, + BFD_UDP_INPUT_NEXT_REPLY, + BFD_UDP_INPUT_N_NEXT, +} bfd_udp_input_next_t; + +/* Packet counters */ +#define foreach_bfd_udp_error(F) \ + F (NONE, "good bfd packets (processed)") \ + F (BAD, "invalid bfd packets") \ + F (DISABLED, "bfd packets received on disabled interfaces") + +#define F(sym, string) static char BFD_UDP_ERR_##sym##_STR[] = string; +foreach_bfd_udp_error (F); +#undef F + +static char *bfd_udp_error_strings[] = { +#define F(sym, string) BFD_UDP_ERR_##sym##_STR, + foreach_bfd_udp_error (F) +#undef F +}; + +typedef enum { +#define F(sym, str) BFD_UDP_ERROR_##sym, + foreach_bfd_udp_error (F) +#undef F + BFD_UDP_N_ERROR, +} bfd_udp_error_t; + +static void bfd_udp4_find_headers (vlib_buffer_t *b, const ip4_header_t **ip4, + const udp_header_t **udp) +{ + *ip4 = vnet_buffer (b)->ip.header; + *udp = (udp_header_t *)((*ip4) + 1); +} + +static bfd_udp_error_t bfd_udp4_verify_transport (const ip4_header_t *ip4, + const udp_header_t *udp, + const bfd_session_t *bs) +{ + const bfd_udp_session_t *bus = &bs->udp; + 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); + 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); + 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, + 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>", + udp->src_port); + } + return BFD_UDP_ERROR_NONE; +} + +typedef struct +{ + u32 bs_idx; + bfd_pkt_t pkt; +} bfd_rpc_update_t; + +static void bfd_rpc_update_session_cb (const bfd_rpc_update_t *a) +{ + bfd_consume_pkt (bfd_udp_main.bfd_main, &a->pkt, a->bs_idx); +} + +static void bfd_rpc_update_session (u32 bs_idx, const bfd_pkt_t *pkt) +{ + /* packet length was already verified to be correct by the caller */ + const u32 data_size = sizeof (bfd_rpc_update_t) - + STRUCT_SIZE_OF (bfd_rpc_update_t, pkt) + + pkt->head.length; + u8 data[data_size]; + bfd_rpc_update_t *update = (bfd_rpc_update_t *)data; + update->bs_idx = bs_idx; + clib_memcpy (&update->pkt, pkt, pkt->head.length); + vl_api_rpc_call_main_thread (bfd_rpc_update_session_cb, data, data_size); +} + +static bfd_udp_error_t bfd_udp4_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 ip4_header_t *ip4; + const udp_header_t *udp; + bfd_udp4_find_headers (b, &ip4, &udp); + if (!ip4 || !udp) + { + BFD_ERR ("Couldn't find ip4 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.ip4.as_u32 = ip4->dst_address.as_u32; + key.peer_addr.ip4.as_u32 = ip4->src_address.as_u32; + BFD_DBG ("Looking up BFD session using key (sw_if_index=%u, local=%U, " + "peer=%U)", + key.sw_if_index, format_ip4_address, key.local_addr.ip4.as_u8, + format_ip4_address, key.peer_addr.ip4.as_u8); + 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=%d", 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_udp4_verify_transport (ip4, udp, bs))) + { + return err; + } + bfd_rpc_update_session (bs->bs_idx, pkt); + *bs_out = bs; + return BFD_UDP_ERROR_NONE; +} + +static bfd_udp_error_t bfd_udp6_scan (vlib_main_t *vm, vlib_buffer_t *b) +{ + /* TODO */ + return BFD_UDP_ERROR_BAD; +} + +/* + * Process a frame of bfd packets + * Expect 1 packet / frame + */ +static uword bfd_udp_input (vlib_main_t *vm, vlib_node_runtime_t *rt, + vlib_frame_t *f, int is_ipv6) +{ + u32 n_left_from, *from; + bfd_input_trace_t *t0; + + from = vlib_frame_vector_args (f); /* array of buffer indices */ + n_left_from = f->n_vectors; /* number of buffer indices */ + + while (n_left_from > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 next0, error0; + + bi0 = from[0]; + b0 = vlib_get_buffer (vm, bi0); + + bfd_session_t *bs = NULL; + + /* If this pkt is traced, snapshot the data */ + if (b0->flags & VLIB_BUFFER_IS_TRACED) + { + int len; + t0 = vlib_add_trace (vm, rt, b0, sizeof (*t0)); + len = (b0->current_length < sizeof (t0->data)) ? b0->current_length + : sizeof (t0->data); + t0->len = len; + clib_memcpy (t0->data, vlib_buffer_get_current (b0), len); + } + + /* scan this bfd pkt. error0 is the counter index to bmp */ + if (is_ipv6) + { + error0 = bfd_udp6_scan (vm, b0); + } + else + { + error0 = bfd_udp4_scan (vm, rt, b0, &bs); + } + b0->error = rt->errors[error0]; + + next0 = BFD_UDP_INPUT_NEXT_NORMAL; + if (BFD_UDP_ERROR_NONE == error0) + { + /* if everything went fine, check for poll bit, if present, re-use + the buffer and based on (now update) session parameters, send the + final packet back */ + const bfd_pkt_t *pkt = vlib_buffer_get_current (b0); + if (bfd_pkt_get_poll (pkt)) + { + bfd_send_final (vm, b0, bs); + next0 = BFD_UDP_INPUT_NEXT_REPLY; + } + } + vlib_set_next_frame_buffer (vm, rt, next0, bi0); + + from += 1; + n_left_from -= 1; + } + + return f->n_vectors; +} + +static uword bfd_udp4_input (vlib_main_t *vm, vlib_node_runtime_t *rt, + vlib_frame_t *f) +{ + return bfd_udp_input (vm, rt, f, 0); +} + +/* + * bfd input graph node declaration + */ +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (bfd_udp4_input_node, static) = { + .function = bfd_udp4_input, + .name = "bfd-udp4-input", + .vector_size = sizeof (u32), + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = BFD_UDP_N_ERROR, + .error_strings = bfd_udp_error_strings, + + .format_trace = bfd_input_format_trace, + + .n_next_nodes = BFD_UDP_INPUT_N_NEXT, + .next_nodes = + { + [BFD_UDP_INPUT_NEXT_NORMAL] = "error-drop", + [BFD_UDP_INPUT_NEXT_REPLY] = "ip4-lookup", + }, +}; +/* *INDENT-ON* */ + +static uword bfd_udp6_input (vlib_main_t *vm, vlib_node_runtime_t *rt, + vlib_frame_t *f) +{ + return bfd_udp_input (vm, rt, f, 1); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (bfd_udp6_input_node, static) = { + .function = bfd_udp6_input, + .name = "bfd-udp6-input", + .vector_size = sizeof (u32), + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = BFD_UDP_N_ERROR, + .error_strings = bfd_udp_error_strings, + + .format_trace = bfd_input_format_trace, + + .n_next_nodes = BFD_UDP_INPUT_N_NEXT, + .next_nodes = + { + [BFD_UDP_INPUT_NEXT_NORMAL] = "error-drop", + [BFD_UDP_INPUT_NEXT_REPLY] = "ip6-lookup", + }, +}; +/* *INDENT-ON* */ + +static clib_error_t *bfd_sw_interface_up_down (vnet_main_t *vnm, + u32 sw_if_index, u32 flags) +{ + // vnet_hw_interface_t *hi = vnet_get_sup_hw_interface (vnm, sw_if_index); + if (!(flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP)) + { + /* TODO */ + } + return 0; +} + +VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (bfd_sw_interface_up_down); + +static clib_error_t *bfd_hw_interface_up_down (vnet_main_t *vnm, + u32 hw_if_index, u32 flags) +{ + if (flags & VNET_HW_INTERFACE_FLAG_LINK_UP) + { + /* TODO */ + } + return 0; +} + +VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (bfd_hw_interface_up_down); + +/* + * setup function + */ +static clib_error_t *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; + 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); + return 0; +} + +VLIB_INIT_FUNCTION (bfd_udp_init); diff --git a/vnet/vnet/bfd/bfd_udp.h b/vnet/vnet/bfd/bfd_udp.h new file mode 100644 index 00000000000..51f5327be01 --- /dev/null +++ b/vnet/vnet/bfd/bfd_udp.h @@ -0,0 +1,56 @@ +/* * 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_udp_h__ +#define __included_bfd_udp_h__ + +#include <vppinfra/clib.h> +#include <vnet/adj/adj_types.h> +#include <vnet/ip/ip6_packet.h> + +#define BFD_UDP_KEY_BODY + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + + u32 sw_if_index; + ip46_address_t local_addr; + ip46_address_t peer_addr; + +}) bfd_udp_key_t; +/* *INDENT-ON* */ + +typedef struct +{ + bfd_udp_key_t key; + + 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); + +#endif /* __included_bfd_udp_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/vnet/vnet/bfd/dir.dox b/vnet/vnet/bfd/dir.dox new file mode 100644 index 00000000000..ed656b52074 --- /dev/null +++ b/vnet/vnet/bfd/dir.dox @@ -0,0 +1,18 @@ +/* + * 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. + */ +/** + @dir vnet/vnet/bfd + @brief Bidirectional Forwarding Detection (BFD) implementation +*/ diff --git a/vnet/vnet/buffer.h b/vnet/vnet/buffer.h index 898c94ee7b9..82806bba986 100644 --- a/vnet/vnet/buffer.h +++ b/vnet/vnet/buffer.h @@ -143,7 +143,11 @@ typedef struct u8 code; u32 data; } icmp; + + /* IP header - saved by ip*_local nodes */ + void *header; }; + } ip; /* diff --git a/vnet/vnet/ip/ip4_forward.c b/vnet/vnet/ip/ip4_forward.c index fc7b34963fe..38729c8ecfb 100644 --- a/vnet/vnet/ip/ip4_forward.c +++ b/vnet/vnet/ip/ip4_forward.c @@ -1472,9 +1472,12 @@ ip4_local (vlib_main_t * vm, ip0 = vlib_buffer_get_current (p0); ip1 = vlib_buffer_get_current (p1); - fib_index0 = vec_elt (im->fib_index_by_sw_if_index, - vnet_buffer(p0)->sw_if_index[VLIB_RX]); - fib_index1 = vec_elt (im->fib_index_by_sw_if_index, + vnet_buffer (p0)->ip.header = ip0; + vnet_buffer (p1)->ip.header = ip1; + + fib_index0 = vec_elt (im->fib_index_by_sw_if_index, + vnet_buffer (p0)->sw_if_index[VLIB_RX]); + fib_index1 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer(p1)->sw_if_index[VLIB_RX]); mtrie0 = &ip4_fib_get (fib_index0)->mtrie; @@ -1679,6 +1682,8 @@ ip4_local (vlib_main_t * vm, ip0 = vlib_buffer_get_current (p0); + vnet_buffer (p0)->ip.header = ip0; + fib_index0 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer(p0)->sw_if_index[VLIB_RX]); @@ -3294,4 +3299,3 @@ VLIB_CLI_COMMAND (set_ip_classify_command, static) = { .function = set_ip_classify_command_fn, }; /* *INDENT-ON* */ - diff --git a/vnet/vnet/ip/udp.h b/vnet/vnet/ip/udp.h index 4de30f1d16f..f8ff777e275 100644 --- a/vnet/vnet/ip/udp.h +++ b/vnet/vnet/ip/udp.h @@ -37,6 +37,7 @@ typedef enum { _ (67, dhcp_to_server) \ _ (68, dhcp_to_client) \ _ (500, ikev2) \ +_ (3784, bfd4) \ _ (4341, lisp_gpe) \ _ (4342, lisp_cp) \ _ (4739, ipfix) \ @@ -49,6 +50,7 @@ _ (6633, vpath_3) #define foreach_udp6_dst_port \ _ (547, dhcpv6_to_server) \ _ (546, dhcpv6_to_client) \ +_ (3784, bfd6) \ _ (4341, lisp_gpe6) \ _ (4342, lisp_cp6) \ _ (4790, vxlan6_gpe) \ |