diff options
Diffstat (limited to 'src/vnet/bfd')
-rw-r--r-- | src/vnet/bfd/bfd.api | 205 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_api.c | 262 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_api.h | 46 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_debug.h | 79 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_doc.md | 1 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_main.c | 969 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_main.h | 220 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_protocol.c | 74 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_protocol.h | 154 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_udp.c | 639 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_udp.h | 56 | ||||
-rw-r--r-- | src/vnet/bfd/dir.dox | 18 |
12 files changed, 2723 insertions, 0 deletions
diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api new file mode 100644 index 00000000000..5798ee698ce --- /dev/null +++ b/src/vnet/bfd/bfd.api @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2015-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. + */ + +/** \brief Configure BFD feature + @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 +*/ +define bfd_set_config { + u32 client_index; + u32 context; + u32 slow_timer; + u32 min_tx; + u32 min_rx; + u8 detect_mult; +}; + +/** \brief Configure BFD feature response + @param context - sender context, to match reply w/ request + @param retval - return code for the request +*/ +define bfd_set_config_reply { + u32 context; + 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 + @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 +*/ +define bfd_udp_add { + 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 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 { + u32 context; + i32 retval; + u32 bs_index; +}; + +/** \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 + @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 +*/ +define bfd_udp_del { + u32 client_index; + u32 context; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; +}; + +/** \brief Delete UDP BFD session response + @param context - sender context, to match reply w/ request + @param retval - return code for the request +*/ +define bfd_udp_del_reply { + u32 context; + i32 retval; +}; + +/** \brief Get all BFD sessions + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +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 +*/ +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; +}; + +/** \brief Set flags of BFD 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 admin_up_down - set the admin state, 1 = up, 0 = down +*/ +define bfd_session_set_flags { + u32 client_index; + u32 context; + u32 bs_index; + u8 admin_up_down; +}; + +/** \brief Reply to bfd_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 +{ + u32 context; + i32 retval; +}; + +/** \brief Register for BFD events + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param enable_disable - 1 => register for events, 0 => cancel registration + @param pid - sender's pid +*/ +define want_bfd_events +{ + u32 client_index; + u32 context; + u32 enable_disable; + u32 pid; +}; + +/** \brief Reply for BFD events registration + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define want_bfd_events_reply +{ + u32 context; + i32 retval; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c new file mode 100644 index 00000000000..126cf29a801 --- /dev/null +++ b/src/vnet/bfd/bfd_api.c @@ -0,0 +1,262 @@ +/* + *------------------------------------------------------------------ + * bfd_api.c - bfd api + * + * 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 <vnet/vnet.h> +#include <vlibmemory/api.h> + +#include <vnet/interface.h> +#include <vnet/api_errno.h> +#include <vnet/bfd/bfd_main.h> +#include <vnet/bfd/bfd_api.h> + +#include <vnet/vnet_msg_enum.h> + +#define vl_typedefs /* define message structures */ +#include <vnet/vnet_all_api_h.h> +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include <vnet/vnet_all_api_h.h> +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include <vnet/vnet_all_api_h.h> +#undef vl_printfun + +#include <vlibapi/api_helper_macros.h> + +#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) + +pub_sub_handler (bfd_events, BFD_EVENTS); + +static void +vl_api_bfd_udp_add_t_handler (vl_api_bfd_udp_add_t * mp) +{ + vl_api_bfd_udp_add_reply_t *rmp; + int rv; + + 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)); + } + + rv = bfd_udp_add_session (clib_net_to_host_u32 (mp->sw_if_index), + 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); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_ADD_REPLY); +} + +static void +vl_api_bfd_udp_del_t_handler (vl_api_bfd_udp_del_t * mp) +{ + vl_api_bfd_udp_del_reply_t *rmp; + int rv; + + 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)); + } + + rv = + bfd_udp_del_session (clib_net_to_host_u32 (mp->sw_if_index), &local_addr, + &peer_addr); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_DEL_REPLY); +} + +void +send_bfd_udp_session_details (unix_shared_memory_queue_t * q, u32 context, + bfd_session_t * bs) +{ + if (bs->transport != BFD_TRANSPORT_UDP4 && + bs->transport != BFD_TRANSPORT_UDP6) + { + return; + } + + vl_api_bfd_udp_session_details_t *mp = vl_msg_api_alloc (sizeof (*mp)); + 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; + mp->sw_if_index = clib_host_to_net_u32 (key->sw_if_index); + mp->is_ipv6 = !(ip46_address_is_ip4 (&key->local_addr)); + if (mp->is_ipv6) + { + clib_memcpy (mp->local_addr, &key->local_addr, + sizeof (key->local_addr)); + clib_memcpy (mp->peer_addr, &key->peer_addr, sizeof (key->peer_addr)); + } + else + { + clib_memcpy (mp->local_addr, key->local_addr.ip4.data, + sizeof (key->local_addr.ip4.data)); + clib_memcpy (mp->peer_addr, key->peer_addr.ip4.data, + sizeof (key->peer_addr.ip4.data)); + } + + vl_msg_api_send_shmem (q, (u8 *) & mp); +} + +void +bfd_event (bfd_main_t * bm, bfd_session_t * bs) +{ + vpe_api_main_t *vam = &vpe_api_main; + vpe_client_registration_t *reg; + unix_shared_memory_queue_t *q; + /* *INDENT-OFF* */ + pool_foreach (reg, vam->bfd_events_registrations, ({ + q = vl_api_client_index_to_input_queue (reg->client_index); + if (q) + { + switch (bs->transport) + { + case BFD_TRANSPORT_UDP4: + /* fallthrough */ + case BFD_TRANSPORT_UDP6: + send_bfd_udp_session_details (q, 0, bs); + } + } + })); + /* *INDENT-ON* */ +} + +static void +vl_api_bfd_udp_session_dump_t_handler (vl_api_bfd_udp_session_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + + q = vl_api_client_index_to_input_queue (mp->client_index); + + if (q == 0) + return; + + bfd_session_t *bs = NULL; + /* *INDENT-OFF* */ + pool_foreach (bs, bfd_main.sessions, ({ + if (bs->transport == BFD_TRANSPORT_UDP4 || + bs->transport == BFD_TRANSPORT_UDP6) + send_bfd_udp_session_details (q, mp->context, bs); + })); + /* *INDENT-ON* */ +} + +static void +vl_api_bfd_session_set_flags_t_handler (vl_api_bfd_session_set_flags_t * mp) +{ + vl_api_bfd_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); + + REPLY_MACRO (VL_API_BFD_SESSION_SET_FLAGS_REPLY); +} + + +/* + * bfd_api_hookup + * Add vpe's API message handlers to the table. + * vlib has alread mapped shared memory and + * added the client registration handlers. + * See .../vlib-api/vlibmemory/memclnt_vlib.c:memclnt_process() + */ +#define vl_msg_name_crc_list +#include <vnet/vnet_all_api_h.h> +#undef vl_msg_name_crc_list + +static void +setup_message_id_table (api_main_t * am) +{ +#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id); + foreach_vl_msg_name_crc_bfd; +#undef _ +} + +static clib_error_t * +bfd_api_hookup (vlib_main_t * vm) +{ + api_main_t *am = &api_main; + +#define _(N,n) \ + vl_msg_api_set_handlers(VL_API_##N, #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_vpe_api_msg; +#undef _ + + /* + * Set up the (msg_name, crc, message-id) table + */ + setup_message_id_table (am); + + return 0; +} + +VLIB_API_INIT_FUNCTION (bfd_api_hookup); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h new file mode 100644 index 00000000000..cfcd04f3f50 --- /dev/null +++ b/src/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/src/vnet/bfd/bfd_debug.h b/src/vnet/bfd/bfd_debug.h new file mode 100644 index 00000000000..707ebab2ddd --- /dev/null +++ b/src/vnet/bfd/bfd_debug.h @@ -0,0 +1,79 @@ +/* + * 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; \ + { \ + __file = strrchr (__FILE__, '/'); \ + if (__file) \ + { \ + ++__file; \ + } \ + else \ + { \ + __file = __FILE__; \ + } \ + } + +#define BFD_DBG(fmt, ...) \ + do \ + { \ + BFD_DEBUG_FILE_DEF \ + static 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", vec_len (_s), _s); \ + vec_reset_length (_s); \ + } \ + while (0); + +#define BFD_ERR(fmt, ...) \ + do \ + { \ + BFD_DEBUG_FILE_DEF \ + static 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", vec_len (_s), _s); \ + vec_reset_length (_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/src/vnet/bfd/bfd_doc.md b/src/vnet/bfd/bfd_doc.md new file mode 100644 index 00000000000..1333ed77b7e --- /dev/null +++ b/src/vnet/bfd/bfd_doc.md @@ -0,0 +1 @@ +TODO diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c new file mode 100644 index 00000000000..e25eadfc510 --- /dev/null +++ b/src/vnet/bfd/bfd_main.c @@ -0,0 +1,969 @@ +/* + * 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, + int handling_wakeup); + +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, int handling_wakeup) +{ + 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 (), handling_wakeup); + } +} + +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; + 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; + } + } + else + { + /* special case - 75-90% of transmit interval */ + bs->tx_timeout_clocks = + now + + (.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; + } + } + } + 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, + int handling_wakeup) +{ + 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"); + /* 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 || + next < bs->wheel_time_clocks || !bs->wheel_time_clocks)) + { + 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); + if (!handling_wakeup) + { + vlib_process_signal_event (bm->vlib_main, + bm->bfd_process_node_index, + BFD_EVENT_RESCHEDULE, 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, 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); + 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_remote_required_min_rx (bfd_main_t * bm, bfd_session_t * bs, + u64 now, + u32 remote_required_min_rx_us, + 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); + 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); +} + +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, 0); + } + else + { + bfd_set_diag (bs, BFD_DIAG_CODE_neighbor_sig_down); + bfd_set_state (bm, bs, BFD_STATE_admin_down, 0); + } + 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, + int handling_wakeup) +{ + 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), + 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); + 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); + break; + case BFD_STATE_up: + bfd_set_desired_min_tx (bm, bs, now, bs->config_desired_min_tx_us, + handling_wakeup); + 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, + int handling_wakeup) +{ + 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. + */ + /* 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", 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, now is %lu, tx_timeout is %lu", + now, bs->tx_timeout_clocks); + } + bfd_set_timer (bm, bs, now, handling_wakeup); +} + +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, + int handling_wakeup) +{ + /* 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) + { + 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); + } +} + +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, 1); + 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, 1); + bfd_send_periodic (vm, rt, bm, bs, now, 1); + break; + } +} + +/* + * 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); + event_type = vlib_process_get_events (vm, &event_data); + } + else + { + f64 timeout = ((i64) next_expire - (i64) now) / bm->cpu_cps; + BFD_DBG ("wait for event with timeout %.02f", timeout); + if (timeout < 0) + { + BFD_DBG ("negative timeout, already expired, skipping wait"); + event_type = ~0; + } + else + { + (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, 1); + } + 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); + 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); + bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; + + 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); + memset (result, 0, sizeof (*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); + u64 now = clib_cpu_time_now (); + bs->last_rx_clocks = now; + 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, + 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. + */ + /* 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, 0); + } + else if (BFD_STATE_down == bs->local_state) + { + if (BFD_STATE_down == bs->remote_state) + { + bfd_set_state (bm, bs, BFD_STATE_init, 0); + } + else if (BFD_STATE_init == bs->remote_state) + { + bfd_set_state (bm, bs, BFD_STATE_up, 0); + } + } + 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, 0); + } + } + 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, 0); + } + } +} + +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/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h new file mode 100644 index 00000000000..c72ea92a70f --- /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 <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 */ + 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: + */ diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c new file mode 100644 index 00000000000..ede9536f3cf --- /dev/null +++ b/src/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/src/vnet/bfd/bfd_protocol.h b/src/vnet/bfd/bfd_protocol.h new file mode 100644 index 00000000000..cf751b3b89a --- /dev/null +++ b/src/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/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c new file mode 100644 index 00000000000..3c747d86a10 --- /dev/null +++ b/src/vnet/bfd/bfd_udp.c @@ -0,0 +1,639 @@ +#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; + +static vlib_node_registration_t bfd_udp4_input_node; +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) +{ + 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) +{ + /* 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"); + *ip4 = NULL; + *udp = NULL; + return; + } + *ip4 = (ip4_header_t *)(b->data + start); + if ((u8 *)*ip4 > (u8 *)vlib_buffer_get_current (b)) + { + BFD_ERR ("Start of ip header is beyond current data, ignoring"); + *ip4 = NULL; + *udp = NULL; + return; + } + *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=%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_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 updated) 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); + if (is_ipv6) + { + vlib_node_increment_counter (vm, bfd_udp6_input_node.index, + b0->error, 1); + } + else + { + vlib_node_increment_counter (vm, bfd_udp4_input_node.index, + b0->error, 1); + } + 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/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h new file mode 100644 index 00000000000..51f5327be01 --- /dev/null +++ b/src/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/src/vnet/bfd/dir.dox b/src/vnet/bfd/dir.dox new file mode 100644 index 00000000000..ed656b52074 --- /dev/null +++ b/src/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 +*/ |