diff options
-rw-r--r-- | src/vnet/api_errno.h | 6 | ||||
-rw-r--r-- | src/vnet/bfd/bfd.api | 207 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_api.c | 177 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_api.h | 31 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_main.c | 628 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_main.h | 79 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_protocol.c | 127 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_protocol.h | 119 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_udp.c | 343 | ||||
-rw-r--r-- | test/bfd.py | 265 | ||||
-rw-r--r-- | test/framework.py | 15 | ||||
-rw-r--r-- | test/test_bfd.py | 733 | ||||
-rw-r--r-- | test/vpp_papi_provider.py | 77 |
13 files changed, 2437 insertions, 370 deletions
diff --git a/src/vnet/api_errno.h b/src/vnet/api_errno.h index 7166da6716c..192bfaa4bc2 100644 --- a/src/vnet/api_errno.h +++ b/src/vnet/api_errno.h @@ -91,8 +91,10 @@ _(INVALID_ADDRESS_FAMILY, -97, "Invalid address family") \ _(INVALID_SUB_SW_IF_INDEX, -98, "Invalid sub-interface sw_if_index") \ _(TABLE_TOO_BIG, -99, "Table too big") \ _(CANNOT_ENABLE_DISABLE_FEATURE, -100, "Cannot enable/disable feature") \ -_(BFD_EEXIST, -101, "Duplicate BFD session") \ -_(BFD_NOENT, -102, "No such BFD session") +_(BFD_EEXIST, -101, "Duplicate BFD object") \ +_(BFD_ENOENT, -102, "No such BFD object") \ +_(BFD_EINUSE, -103, "BFD object in use") \ +_(BFD_NOTSUPP, -104, "BFD feature not supported") typedef enum { diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api index 5798ee698ce..17ca35b6004 100644 --- a/src/vnet/bfd/bfd.api +++ b/src/vnet/bfd/bfd.api @@ -21,7 +21,8 @@ @param min_rx - desired min rx interval @param detect_mult - desired detection multiplier */ -define bfd_set_config { +define bfd_set_config +{ u32 client_index; u32 context; u32 slow_timer; @@ -34,14 +35,16 @@ define bfd_set_config { @param context - sender context, to match reply w/ request @param retval - return code for the request */ -define bfd_set_config_reply { +define bfd_set_config_reply +{ u32 context; i32 retval; }; /** \brief Get BFD configuration */ -define bfd_get_config { +define bfd_get_config +{ u32 client_index; u32 context; }; @@ -54,7 +57,8 @@ define bfd_get_config { @param min_rx - desired min rx interval @param detect_mult - desired detection multiplier */ -define bfd_get_config_reply { +define bfd_get_config_reply +{ u32 client_index; u32 context; u32 slow_timer; @@ -69,12 +73,16 @@ define bfd_get_config_reply { @param sw_if_index - sw index of the interface @param desired_min_tx - desired min transmit interval (microseconds) @param required_min_rx - required min receive interval (microseconds) - @param detect_mult - detect multiplier (# of packets missed between connection goes down) @param local_addr - local address @param peer_addr - peer address @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param detect_mult - detect multiplier (# of packets missed before connection goes down) + @param is_authenticated - non-zero if authentication is required + @param bfd_key_id - key id sent out in BFD packets (if is_authenticated) + @param conf_key_id - id of already configured key (if is_authenticated) */ -define bfd_udp_add { +define bfd_udp_add +{ u32 client_index; u32 context; u32 sw_if_index; @@ -84,17 +92,19 @@ define bfd_udp_add { u8 peer_addr[16]; u8 is_ipv6; u8 detect_mult; + u8 is_authenticated; + u8 bfd_key_id; + u32 conf_key_id; }; /** \brief Add UDP BFD session response @param context - sender context, to match reply w/ request @param retval - return code for the request - @param bs_index - index of the session created */ -define bfd_udp_add_reply { +define bfd_udp_add_reply +{ u32 context; i32 retval; - u32 bs_index; }; /** \brief Delete UDP BFD session on interface @@ -105,7 +115,8 @@ define bfd_udp_add_reply { @param peer_addr - peer address @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 */ -define bfd_udp_del { +define bfd_udp_del +{ u32 client_index; u32 context; u32 sw_if_index; @@ -118,7 +129,8 @@ define bfd_udp_del { @param context - sender context, to match reply w/ request @param retval - return code for the request */ -define bfd_udp_del_reply { +define bfd_udp_del_reply +{ u32 context; i32 retval; }; @@ -127,48 +139,61 @@ define bfd_udp_del_reply { @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request */ -define bfd_udp_session_dump { +define bfd_udp_session_dump +{ u32 client_index; u32 context; }; /** \brief BFD session details structure @param context - sender context, to match reply w/ request - @param bs_index - index of the session @param sw_if_index - sw index of the interface @param local_addr - local address @param peer_addr - peer address @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 @param state - session state + @param is_authenticated - non-zero if authentication in-use, zero otherwise + @param bfd_key_id - ID of key currently in-use if auth is on + @param conf_key_id - configured key ID for this session */ -define bfd_udp_session_details { +define bfd_udp_session_details +{ u32 context; - u32 bs_index; u32 sw_if_index; u8 local_addr[16]; u8 peer_addr[16]; u8 is_ipv6; u8 state; + u8 is_authenticated; + u8 bfd_key_id; + u32 conf_key_id; }; -/** \brief Set flags of BFD session +/** \brief Set flags of BFD UDP session @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request - @param bs_index - index of the bfd session to set flags on + @param sw_if_index - sw index of the interface + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 @param admin_up_down - set the admin state, 1 = up, 0 = down */ -define bfd_session_set_flags { +define bfd_udp_session_set_flags +{ u32 client_index; u32 context; - u32 bs_index; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; u8 admin_up_down; }; -/** \brief Reply to bfd_session_set_flags +/** \brief Reply to bfd_udp_session_set_flags @param context - sender context which was passed in the request @param retval - return code of the set flags request */ -define bfd_session_set_flags_reply +define bfd_udp_session_set_flags_reply { u32 context; i32 retval; @@ -198,6 +223,146 @@ define want_bfd_events_reply i32 retval; }; +/** \brief BFD UDP - add/replace key to configuration + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param conf_key_id - key ID to add/replace/delete + @param key_len - length of key (must be non-zero) + @param auth_type - authentication type (RFC 5880/4.1/Auth Type) + @param key - key data +*/ +define bfd_auth_set_key +{ + u32 client_index; + u32 context; + u32 conf_key_id; + u8 key_len; + u8 auth_type; + u8 key[20]; +}; + +/** \brief BFD UDP - add/replace key reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_auth_set_key_reply +{ + u32 context; + i32 retval; +}; + +/** \brief BFD UDP - delete key from configuration + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param conf_key_id - key ID to add/replace/delete + @param key_len - length of key (must be non-zero) + @param key - key data +*/ +define bfd_auth_del_key +{ + u32 client_index; + u32 context; + u32 conf_key_id; +}; + +/** \brief BFD UDP - delete key reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_auth_del_key_reply +{ + u32 context; + i32 retval; +}; + +/** \brief Get a list of configured authentication keys + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define bfd_auth_keys_dump +{ + u32 client_index; + u32 context; +}; + +/** \brief BFD authentication key details + @param context - sender context, to match reply w/ request + @param conf_key_id - configured key ID + @param use_count - how many BFD sessions currently use this key + @param auth_type - authentication type (RFC 5880/4.1/Auth Type) +*/ +define bfd_auth_keys_details +{ + u32 context; + u32 conf_key_id; + u32 use_count; + u8 auth_type; +}; + +/** \brief BFD UDP - activate/change authentication + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - sw index of the interface + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param is_delayed - change is applied once peer applies the change (on first received packet with this auth) + @param bfd_key_id - key id sent out in BFD packets + @param conf_key_id - id of already configured key +*/ +define bfd_udp_auth_activate +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; + u8 is_delayed; + u8 bfd_key_id; + u32 conf_key_id; +}; + +/** \brief BFD UDP - activate/change authentication reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_udp_auth_activate_reply +{ + u32 context; + i32 retval; +}; + +/** \brief BFD UDP - deactivate authentication + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - sw index of the interface + @param local_addr - local address + @param peer_addr - peer address + @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4 + @param is_delayed - change is applied once peer applies the change (on first received non-authenticated packet) +*/ +define bfd_udp_auth_deactivate +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 local_addr[16]; + u8 peer_addr[16]; + u8 is_ipv6; + u8 is_delayed; +}; + +/** \brief BFD UDP - deactivate authentication reply + @param context - returned sender context, to match reply w/ request + @param retval - return code +*/ +define bfd_udp_auth_deactivate_reply +{ + u32 context; + i32 retval; +}; + /* * Local Variables: * eval: (c-set-style "gnu") diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c index 2e63fe90d78..cfc3a38d575 100644 --- a/src/vnet/bfd/bfd_api.c +++ b/src/vnet/bfd/bfd_api.c @@ -43,15 +43,39 @@ #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) +#define foreach_vpe_api_msg \ + _ (BFD_UDP_ADD, bfd_udp_add) \ + _ (BFD_UDP_DEL, bfd_udp_del) \ + _ (BFD_UDP_SESSION_DUMP, bfd_udp_session_dump) \ + _ (BFD_UDP_SESSION_SET_FLAGS, bfd_udp_session_set_flags) \ + _ (WANT_BFD_EVENTS, want_bfd_events) \ + _ (BFD_AUTH_SET_KEY, bfd_auth_set_key) \ + _ (BFD_AUTH_DEL_KEY, bfd_auth_del_key) \ + _ (BFD_AUTH_KEYS_DUMP, bfd_auth_keys_dump) \ + _ (BFD_UDP_AUTH_ACTIVATE, bfd_udp_auth_activate) \ + _ (BFD_UDP_AUTH_DEACTIVATE, bfd_udp_auth_deactivate) pub_sub_handler (bfd_events, BFD_EVENTS); +#define BFD_UDP_API_PARAM_COMMON_CODE \ + ip46_address_t local_addr; \ + memset (&local_addr, 0, sizeof (local_addr)); \ + ip46_address_t peer_addr; \ + memset (&peer_addr, 0, sizeof (peer_addr)); \ + if (mp->is_ipv6) \ + { \ + clib_memcpy (&local_addr.ip6, mp->local_addr, sizeof (local_addr.ip6)); \ + clib_memcpy (&peer_addr.ip6, mp->peer_addr, sizeof (peer_addr.ip6)); \ + } \ + else \ + { \ + clib_memcpy (&local_addr.ip4, mp->local_addr, sizeof (local_addr.ip4)); \ + clib_memcpy (&peer_addr.ip4, mp->peer_addr, sizeof (peer_addr.ip4)); \ + } + +#define BFD_UDP_API_PARAM_FROM_MP(mp) \ + clib_net_to_host_u32 (mp->sw_if_index), &local_addr, &peer_addr + static void vl_api_bfd_udp_add_t_handler (vl_api_bfd_udp_add_t * mp) { @@ -60,31 +84,17 @@ vl_api_bfd_udp_add_t_handler (vl_api_bfd_udp_add_t * mp) VALIDATE_SW_IF_INDEX (mp); - ip46_address_t local_addr; - memset (&local_addr, 0, sizeof (local_addr)); - ip46_address_t peer_addr; - memset (&peer_addr, 0, sizeof (peer_addr)); - if (mp->is_ipv6) - { - clib_memcpy (&local_addr.ip6, mp->local_addr, sizeof (local_addr.ip6)); - clib_memcpy (&peer_addr.ip6, mp->peer_addr, sizeof (peer_addr.ip6)); - } - else - { - clib_memcpy (&local_addr.ip4, mp->local_addr, sizeof (local_addr.ip4)); - clib_memcpy (&peer_addr.ip4, mp->peer_addr, sizeof (peer_addr.ip4)); - } + BFD_UDP_API_PARAM_COMMON_CODE; - u32 bs_index = 0; - rv = bfd_udp_add_session (clib_net_to_host_u32 (mp->sw_if_index), + rv = bfd_udp_add_session (BFD_UDP_API_PARAM_FROM_MP (mp), clib_net_to_host_u32 (mp->desired_min_tx), clib_net_to_host_u32 (mp->required_min_rx), - mp->detect_mult, &local_addr, &peer_addr, - &bs_index); + mp->detect_mult, mp->is_authenticated, + clib_net_to_host_u32 (mp->conf_key_id), + mp->bfd_key_id); BAD_SW_IF_INDEX_LABEL; - REPLY_MACRO2 (VL_API_BFD_UDP_ADD_REPLY, - rmp->bs_index = clib_host_to_net_u32 (bs_index)); + REPLY_MACRO (VL_API_BFD_UDP_ADD_REPLY); } static void @@ -95,23 +105,9 @@ vl_api_bfd_udp_del_t_handler (vl_api_bfd_udp_del_t * mp) VALIDATE_SW_IF_INDEX (mp); - ip46_address_t local_addr; - memset (&local_addr, 0, sizeof (local_addr)); - ip46_address_t peer_addr; - memset (&peer_addr, 0, sizeof (peer_addr)); - if (mp->is_ipv6) - { - clib_memcpy (&local_addr.ip6, mp->local_addr, sizeof (local_addr.ip6)); - clib_memcpy (&peer_addr.ip6, mp->peer_addr, sizeof (peer_addr.ip6)); - } - else - { - clib_memcpy (&local_addr.ip4, mp->local_addr, sizeof (local_addr.ip4)); - clib_memcpy (&peer_addr.ip4, mp->peer_addr, sizeof (peer_addr.ip4)); - } + BFD_UDP_API_PARAM_COMMON_CODE; - rv = bfd_udp_del_session (clib_net_to_host_u32 (mp->sw_if_index), - &local_addr, &peer_addr); + rv = bfd_udp_del_session (BFD_UDP_API_PARAM_FROM_MP (mp)); BAD_SW_IF_INDEX_LABEL; REPLY_MACRO (VL_API_BFD_UDP_DEL_REPLY); @@ -131,7 +127,6 @@ send_bfd_udp_session_details (unix_shared_memory_queue_t * q, u32 context, memset (mp, 0, sizeof (*mp)); mp->_vl_msg_id = ntohs (VL_API_BFD_UDP_SESSION_DETAILS); mp->context = context; - mp->bs_index = clib_host_to_net_u32 (bs->bs_idx); mp->state = bs->local_state; bfd_udp_session_t *bus = &bs->udp; bfd_udp_key_t *key = &bus->key; @@ -198,15 +193,101 @@ vl_api_bfd_udp_session_dump_t_handler (vl_api_bfd_udp_session_dump_t * mp) } static void -vl_api_bfd_session_set_flags_t_handler (vl_api_bfd_session_set_flags_t * mp) +vl_api_bfd_udp_session_set_flags_t_handler (vl_api_bfd_udp_session_set_flags_t + * mp) { - vl_api_bfd_session_set_flags_reply_t *rmp; + vl_api_bfd_udp_session_set_flags_reply_t *rmp; int rv; - rv = bfd_session_set_flags (clib_net_to_host_u32 (mp->bs_index), - mp->admin_up_down); + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = bfd_udp_session_set_flags (BFD_UDP_API_PARAM_FROM_MP (mp), + mp->admin_up_down); + + REPLY_MACRO (VL_API_BFD_UDP_SESSION_SET_FLAGS_REPLY); +} + +static void +vl_api_bfd_auth_set_key_t_handler (vl_api_bfd_auth_set_key_t * mp) +{ + vl_api_bfd_auth_set_key_reply_t *rmp; + int rv = bfd_auth_set_key (clib_net_to_host_u32 (mp->conf_key_id), + mp->auth_type, mp->key_len, mp->key); + + REPLY_MACRO (VL_API_BFD_AUTH_SET_KEY_REPLY); +} + +static void +vl_api_bfd_auth_del_key_t_handler (vl_api_bfd_auth_del_key_t * mp) +{ + vl_api_bfd_auth_del_key_reply_t *rmp; + int rv = bfd_auth_del_key (clib_net_to_host_u32 (mp->conf_key_id)); + + REPLY_MACRO (VL_API_BFD_AUTH_DEL_KEY_REPLY); +} + +static void +vl_api_bfd_auth_keys_dump_t_handler (vl_api_bfd_auth_keys_dump_t * mp) +{ + unix_shared_memory_queue_t *q; - REPLY_MACRO (VL_API_BFD_SESSION_SET_FLAGS_REPLY); + q = vl_api_client_index_to_input_queue (mp->client_index); + + if (q == 0) + return; + + bfd_auth_key_t *key = NULL; + vl_api_bfd_auth_keys_details_t *rmp = NULL; + + /* *INDENT-OFF* */ + pool_foreach (key, bfd_main.auth_keys, ({ + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_BFD_AUTH_KEYS_DETAILS); + rmp->context = mp->context; + rmp->conf_key_id = clib_host_to_net_u32 (key->conf_key_id); + rmp->auth_type = key->auth_type; + rmp->use_count = clib_host_to_net_u32 (key->use_count); + vl_msg_api_send_shmem (q, (u8 *)&rmp); + })); + /* *INDENT-ON* */ +} + +static void +vl_api_bfd_udp_auth_activate_t_handler (vl_api_bfd_udp_auth_activate_t * mp) +{ + vl_api_bfd_udp_auth_activate_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = + bfd_udp_auth_activate (BFD_UDP_API_PARAM_FROM_MP (mp), + clib_net_to_host_u32 (mp->conf_key_id), + mp->bfd_key_id, mp->is_delayed); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_AUTH_ACTIVATE_REPLY); +} + +static void +vl_api_bfd_udp_auth_deactivate_t_handler (vl_api_bfd_udp_auth_deactivate_t * + mp) +{ + vl_api_bfd_udp_auth_deactivate_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + BFD_UDP_API_PARAM_COMMON_CODE; + + rv = + bfd_udp_auth_deactivate (BFD_UDP_API_PARAM_FROM_MP (mp), mp->is_delayed); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_BFD_UDP_AUTH_DEACTIVATE_REPLY); } /* diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h index a9bc5a1f40c..128a3dc42bf 100644 --- a/src/vnet/bfd/bfd_api.h +++ b/src/vnet/bfd/bfd_api.h @@ -24,17 +24,36 @@ #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, - u32 * bs_index); +vnet_api_error_t +bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u32 desired_min_tx_us, + u32 required_min_rx_us, u8 detect_mult, + u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id); vnet_api_error_t bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr); -vnet_api_error_t bfd_session_set_flags (u32 bs_index, u8 admin_up_down); +vnet_api_error_t bfd_udp_session_set_flags (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u8 admin_up_down); + +vnet_api_error_t bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, + const u8 * key); + +vnet_api_error_t bfd_auth_del_key (u32 conf_key_id); + +vnet_api_error_t bfd_udp_auth_activate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 conf_key_id, u8 bfd_key_id, + u8 is_delayed); + +vnet_api_error_t bfd_udp_auth_deactivate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u8 is_delayed); #endif /* __included_bfd_api_h__ */ diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 7e1a2ef24a7..8f2fae2b795 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -25,6 +25,9 @@ #include <vnet/bfd/bfd_debug.h> #include <vnet/bfd/bfd_protocol.h> #include <vnet/bfd/bfd_main.h> +#if WITH_LIBSSL > 0 +#include <openssl/sha.h> +#endif static u64 bfd_us_to_clocks (bfd_main_t * bm, u64 us) @@ -41,6 +44,23 @@ static u32 bfd_node_index_by_transport[] = { #undef F }; +static u8 * +format_bfd_auth_key (u8 * s, va_list * args) +{ + const bfd_auth_key_t *key = va_arg (*args, bfd_auth_key_t *); + if (key) + { + s = format (s, "{auth-type=%u:%s, conf-key-id=%u, use-count=%u}, ", + key->auth_type, bfd_auth_type_str (key->auth_type), + key->conf_key_id, key->use_count); + } + else + { + s = format (s, "{none}"); + } + return s; +} + /* * We actually send all bfd pkts to the "error" node after scanning * them, so the graph node has only one next-index. The "error-drop" @@ -67,6 +87,9 @@ bfd_set_defaults (bfd_main_t * bm, bfd_session_t * bs) bs->desired_min_tx_clocks = bfd_us_to_clocks (bm, bs->desired_min_tx_us); bs->remote_min_rx_us = 1; bs->remote_demand = 0; + bs->auth.remote_seq_number = 0; + bs->auth.remote_seq_number_known = 0; + bs->auth.local_seq_number = random_u32 (&bm->random_seed); } static void @@ -288,7 +311,7 @@ bfd_del_session (uword bs_idx) else { BFD_ERR ("no such session"); - return VNET_API_ERROR_BFD_NOENT; + return VNET_API_ERROR_BFD_ENOENT; } return 0; } @@ -319,16 +342,10 @@ bfd_state_string (bfd_state_e state) #undef F } -vnet_api_error_t -bfd_session_set_flags (u32 bs_idx, u8 admin_up_down) +void +bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down) { bfd_main_t *bm = &bfd_main; - if (pool_is_free_index (bm->sessions, bs_idx)) - { - BFD_ERR ("invalid bs_idx=%u", bs_idx); - return VNET_API_ERROR_BFD_NOENT; - } - bfd_session_t *bs = pool_elt_at_index (bm->sessions, bs_idx); if (admin_up_down) { bfd_set_state (bm, bs, BFD_STATE_down, 0); @@ -338,7 +355,6 @@ bfd_session_set_flags (u32 bs_idx, u8 admin_up_down) bfd_set_diag (bs, BFD_DIAG_CODE_neighbor_sig_down); bfd_set_state (bm, bs, BFD_STATE_admin_down, 0); } - return 0; } u8 * @@ -351,8 +367,8 @@ bfd_input_format_trace (u8 * s, va_list * args) if (t->len > STRUCT_SIZE_OF (bfd_pkt_t, head)) { s = format (s, "BFD v%u, diag=%u(%s), state=%u(%s),\n" - " flags=(P:%u, F:%u, C:%u, A:%u, D:%u, M:%u), detect_mult=%u, " - "length=%u\n", + " flags=(P:%u, F:%u, C:%u, A:%u, D:%u, M:%u), " + "detect_mult=%u, length=%u\n", bfd_pkt_get_version (pkt), bfd_pkt_get_diag_code (pkt), bfd_diag_code_string (bfd_pkt_get_diag_code (pkt)), bfd_pkt_get_state (pkt), @@ -362,8 +378,8 @@ bfd_input_format_trace (u8 * s, va_list * args) bfd_pkt_get_auth_present (pkt), bfd_pkt_get_demand (pkt), bfd_pkt_get_multipoint (pkt), pkt->head.detect_mult, pkt->head.length); - if (t->len >= sizeof (bfd_pkt_t) - && pkt->head.length >= sizeof (bfd_pkt_t)) + if (t->len >= sizeof (bfd_pkt_t) && + pkt->head.length >= sizeof (bfd_pkt_t)) { s = format (s, " my discriminator: %u\n", pkt->my_disc); s = format (s, " your discriminator: %u\n", pkt->your_disc); @@ -430,8 +446,7 @@ bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, } static vlib_buffer_t * -bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, - bfd_session_t * bs) +bfd_create_frame_to_next_node (vlib_main_t * vm, bfd_session_t * bs) { u32 bi; if (vlib_buffer_alloc (vm, &bi, 1) != 1) @@ -454,13 +469,82 @@ bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, return b; } +#if WITH_LIBSSL > 0 +static void +bfd_add_sha1_auth_section (vlib_buffer_t * b, bfd_session_t * bs) +{ + bfd_pkt_with_sha1_auth_t *pkt = vlib_buffer_get_current (b); + bfd_auth_sha1_t *auth = &pkt->sha1_auth; + b->current_length += sizeof (*auth); + pkt->pkt.head.length += sizeof (*auth); + bfd_pkt_set_auth_present (&pkt->pkt); + memset (auth, 0, sizeof (*auth)); + auth->type_len.type = bs->auth.curr_key->auth_type; + /* + * only meticulous authentication types require incrementing seq number + * for every message, but doing so doesn't violate the RFC + */ + ++bs->auth.local_seq_number; + auth->type_len.len = sizeof (bfd_auth_sha1_t); + auth->key_id = bs->auth.curr_bfd_key_id; + auth->seq_num = clib_host_to_net_u32 (bs->auth.local_seq_number); + /* + * first copy the password into the packet, then calculate the hash + * and finally replace the password with the calculated hash + */ + clib_memcpy (auth->hash, bs->auth.curr_key->key, + sizeof (bs->auth.curr_key->key)); + unsigned char hash[sizeof (auth->hash)]; + SHA1 ((unsigned char *) pkt, sizeof (*pkt), hash); + BFD_DBG ("hashing: %U", format_hex_bytes, pkt, sizeof (*pkt)); + clib_memcpy (auth->hash, hash, sizeof (hash)); +#endif +} + +static void +bfd_add_auth_section (vlib_buffer_t * b, bfd_session_t * bs) +{ + if (bs->auth.curr_key) + { + const bfd_auth_type_e auth_type = bs->auth.curr_key->auth_type; + switch (auth_type) + { + case BFD_AUTH_TYPE_reserved: + /* fallthrough */ + case BFD_AUTH_TYPE_simple_password: + /* fallthrough */ + case BFD_AUTH_TYPE_keyed_md5: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_md5: + clib_warning ("Internal error, unexpected BFD auth type '%d'", + auth_type); + break; +#if WITH_LIBSSL > 0 + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: + bfd_add_sha1_auth_section (b, bs); + break; +#else + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: + clib_warning ("Internal error, unexpected BFD auth type '%d'", + auth_type); + break; +#endif + } + } +} + static void bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) { bfd_pkt_t *pkt = vlib_buffer_get_current (b); - const u32 bfd_length = 24; - memset (pkt, 0, sizeof (*pkt)); + u32 bfd_length = 0; + bfd_length = sizeof (bfd_pkt_t); + memset (pkt, 0, sizeof (*pkt)); bfd_pkt_set_version (pkt, 1); bfd_pkt_set_diag_code (pkt, bs->local_diag); bfd_pkt_set_state (pkt, bs->local_state); @@ -477,6 +561,7 @@ bfd_init_control_frame (vlib_buffer_t * b, bfd_session_t * bs) pkt->req_min_rx = clib_host_to_net_u32 (bs->required_min_rx_us); pkt->req_min_echo_rx = clib_host_to_net_u32 (bs->required_min_echo_rx_us); b->current_length = bfd_length; + bfd_add_auth_section (b, bs); } static void @@ -502,7 +587,7 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, if (now + bm->wheel_inaccuracy >= bs->tx_timeout_clocks) { BFD_DBG ("Send periodic control frame for bs_idx=%lu", bs->bs_idx); - vlib_buffer_t *b = bfd_create_frame (vm, rt, bs); + vlib_buffer_t *b = bfd_create_frame_to_next_node (vm, bs); if (!b) { return; @@ -522,7 +607,8 @@ bfd_send_periodic (vlib_main_t * vm, vlib_node_runtime_t * rt, } void -bfd_send_final (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs) +bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, + bfd_session_t * bs) { BFD_DBG ("Send final control frame for bs_idx=%lu", bs->bs_idx); bfd_init_control_frame (b, bs); @@ -624,13 +710,17 @@ bfd_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) * each event or timeout */ break; case BFD_EVENT_NEW_SESSION: - do + if (!pool_is_free_index (bm->sessions, *event_data)) { bfd_session_t *bs = pool_elt_at_index (bm->sessions, *event_data); bfd_send_periodic (vm, rt, bm, bs, now, 1); } - while (0); + else + { + BFD_DBG ("Ignoring event for non-existent session index %u", + (u32) * event_data); + } break; default: clib_warning ("BUG: event type 0x%wx", event_type); @@ -710,22 +800,25 @@ VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (bfd_hw_interface_up_down); static clib_error_t * bfd_main_init (vlib_main_t * vm) { +#if BFD_DEBUG + setbuf (stdout, NULL); +#endif bfd_main_t *bm = &bfd_main; bm->random_seed = random_default_seed (); bm->vlib_main = vm; bm->vnet_main = vnet_get_main (); memset (&bm->wheel, 0, sizeof (bm->wheel)); - bm->cpu_cps = 2590000000; // vm->clib_time.clocks_per_second; + bm->cpu_cps = vm->clib_time.clocks_per_second; BFD_DBG ("cps is %.2f", bm->cpu_cps); const u64 now = clib_cpu_time_now (); timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; vlib_node_t *node = NULL; -#define F(t, n) \ - node = vlib_get_node_by_name (vm, (u8 *)n); \ - bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index;\ - BFD_DBG("node '%s' has index %u", n, node->index); +#define F(t, n) \ + node = vlib_get_node_by_name (vm, (u8 *)n); \ + bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index; \ + BFD_DBG ("node '%s' has index %u", n, node->index); foreach_bfd_transport (F); #undef F return 0; @@ -750,6 +843,14 @@ bfd_get_session (bfd_main_t * bm, bfd_transport_t t) void bfd_put_session (bfd_main_t * bm, bfd_session_t * bs) { + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + } + if (bs->auth.next_key) + { + --bs->auth.next_key->use_count; + } hash_unset (bm->session_by_disc, bs->local_discr); pool_put (bm->sessions, bs); } @@ -793,7 +894,7 @@ bfd_verify_pkt_common (const bfd_pkt_t * pkt) } if (pkt->head.length < sizeof (bfd_pkt_t) || (bfd_pkt_get_auth_present (pkt) && - pkt->head.length < sizeof (bfd_pkt_with_auth_t))) + pkt->head.length < sizeof (bfd_pkt_with_common_auth_t))) { BFD_ERR ("BFD verification failed - unexpected length: '%d' (auth " "present: %d)", @@ -831,6 +932,226 @@ bfd_verify_pkt_common (const bfd_pkt_t * pkt) return 1; } +static void +bfd_session_switch_auth_to_next (bfd_session_t * bs) +{ + BFD_DBG ("Switching authentication key from %U to %U for bs_idx=%u", + format_bfd_auth_key, bs->auth.curr_key, format_bfd_auth_key, + bs->auth.next_key, bs->bs_idx); + bs->auth.is_delayed = 0; + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + } + bs->auth.curr_key = bs->auth.next_key; + bs->auth.next_key = NULL; + bs->auth.curr_bfd_key_id = bs->auth.next_bfd_key_id; +} + +static int +bfd_auth_type_is_meticulous (bfd_auth_type_e auth_type) +{ + if (BFD_AUTH_TYPE_meticulous_keyed_md5 == auth_type || + BFD_AUTH_TYPE_meticulous_keyed_sha1 == auth_type) + { + return 1; + } + return 0; +} + +static int +bfd_verify_pkt_auth_seq_num (bfd_session_t * bs, + u32 received_seq_num, int is_meticulous) +{ + /* + * RFC 5880 6.8.1: + * + * This variable MUST be set to zero after no packets have been + * received on this session for at least twice the Detection Time. + */ + u64 now = clib_cpu_time_now (); + if (now - bs->last_rx_clocks > bs->detection_time_clocks * 2) + { + BFD_DBG ("BFD peer unresponsive for %lu clocks, which is > 2 * " + "detection_time=%u clocks, resetting remote_seq_number_known " + "flag", + now - bs->last_rx_clocks, bs->detection_time_clocks * 2); + bs->auth.remote_seq_number_known = 0; + } + if (bs->auth.remote_seq_number_known) + { + /* remote sequence number is known, verify its validity */ + const u32 max_u32 = 0xffffffff; + /* the calculation might wrap, account for the special case... */ + if (bs->auth.remote_seq_number > max_u32 - 3 * bs->local_detect_mult) + { + /* + * special case + * + * x y z + * |----------+----------------------------+-----------| + * 0 ^ ^ 0xffffffff + * | remote_seq_num------+ + * | + * +-----(remote_seq_num + 3*detect_mult) % * 0xffffffff + * + * x + y + z = 0xffffffff + * x + z = 3 * detect_mult + */ + const u32 z = max_u32 - bs->auth.remote_seq_number; + const u32 x = 3 * bs->local_detect_mult - z; + if (received_seq_num > x && + received_seq_num < bs->auth.remote_seq_number + is_meticulous) + { + BFD_ERR + ("Recvd sequence number=%u out of ranges <0, %u>, <%u, %u>", + received_seq_num, x, + bs->auth.remote_seq_number + is_meticulous, max_u32); + return 0; + } + } + else + { + /* regular case */ + const u32 min = bs->auth.remote_seq_number + is_meticulous; + const u32 max = + bs->auth.remote_seq_number + 3 * bs->local_detect_mult; + if (received_seq_num < min || received_seq_num > max) + { + BFD_ERR ("Recvd sequence number=%u out of range <%u, %u>", + received_seq_num, min, max); + return 0; + } + } + } + return 1; +} + +static int +bfd_verify_pkt_auth_key_sha1 (const bfd_pkt_t * pkt, u32 pkt_size, + bfd_session_t * bs, u8 bfd_key_id, + bfd_auth_key_t * auth_key) +{ + ASSERT (auth_key->auth_type == BFD_AUTH_TYPE_keyed_sha1 || + auth_key->auth_type == BFD_AUTH_TYPE_meticulous_keyed_sha1); + + u8 result[SHA_DIGEST_LENGTH]; + bfd_pkt_with_common_auth_t *with_common = (void *) pkt; + if (pkt_size < sizeof (*with_common)) + { + BFD_ERR ("Packet size too small to hold authentication common header"); + return 0; + } + if (with_common->common_auth.type != auth_key->auth_type) + { + BFD_ERR ("BFD auth type mismatch, packet auth=%d:%s doesn't match " + "in-use auth=%d:%s", + with_common->common_auth.type, + bfd_auth_type_str (with_common->common_auth.type), + auth_key->auth_type, bfd_auth_type_str (auth_key->auth_type)); + return 0; + } + bfd_pkt_with_sha1_auth_t *with_sha1 = (void *) pkt; + if (pkt_size < sizeof (*with_sha1) || + with_sha1->sha1_auth.type_len.len < sizeof (with_sha1->sha1_auth)) + { + BFD_ERR + ("BFD size mismatch, payload size=%u, expected=%u, auth_len=%u, " + "expected=%u", pkt_size, sizeof (*with_sha1), + with_sha1->sha1_auth.type_len.len, sizeof (with_sha1->sha1_auth)); + return 0; + } + if (with_sha1->sha1_auth.key_id != bfd_key_id) + { + BFD_ERR + ("BFD key ID mismatch, packet key ID=%u doesn't match key ID=%u%s", + with_sha1->sha1_auth.key_id, bfd_key_id, + bs-> + auth.is_delayed ? " (but a delayed auth change is scheduled)" : ""); + return 0; + } + SHA_CTX ctx; + if (!SHA1_Init (&ctx)) + { + BFD_ERR ("SHA1_Init failed"); + return 0; + } + /* ignore last 20 bytes - use the actual key data instead pkt data */ + if (!SHA1_Update (&ctx, with_sha1, + sizeof (*with_sha1) - sizeof (with_sha1->sha1_auth.hash))) + { + BFD_ERR ("SHA1_Update failed"); + return 0; + } + if (!SHA1_Update (&ctx, auth_key->key, sizeof (auth_key->key))) + { + BFD_ERR ("SHA1_Update failed"); + return 0; + } + if (!SHA1_Final (result, &ctx)) + { + BFD_ERR ("SHA1_Final failed"); + return 0; + } + if (0 == memcmp (result, with_sha1->sha1_auth.hash, SHA_DIGEST_LENGTH)) + { + return 1; + } + BFD_ERR ("SHA1 hash: %U doesn't match the expected value: %U", + format_hex_bytes, with_sha1->sha1_auth.hash, SHA_DIGEST_LENGTH, + format_hex_bytes, result, SHA_DIGEST_LENGTH); + return 0; +} + +static int +bfd_verify_pkt_auth_key (const bfd_pkt_t * pkt, u32 pkt_size, + bfd_session_t * bs, u8 bfd_key_id, + bfd_auth_key_t * auth_key) +{ + switch (auth_key->auth_type) + { + case BFD_AUTH_TYPE_reserved: + clib_warning ("Internal error, unexpected auth_type=%d:%s", + auth_key->auth_type, + bfd_auth_type_str (auth_key->auth_type)); + return 0; + case BFD_AUTH_TYPE_simple_password: + clib_warning + ("Internal error, not implemented, unexpected auth_type=%d:%s", + auth_key->auth_type, bfd_auth_type_str (auth_key->auth_type)); + return 0; + case BFD_AUTH_TYPE_keyed_md5: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_md5: + clib_warning + ("Internal error, not implemented, unexpected auth_type=%d:%s", + auth_key->auth_type, bfd_auth_type_str (auth_key->auth_type)); + return 0; + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: +#if WITH_LIBSSL > 0 + do + { + const u32 seq_num = clib_net_to_host_u32 (((bfd_pkt_with_sha1_auth_t + *) pkt)-> + sha1_auth.seq_num); + return bfd_verify_pkt_auth_seq_num (bs, seq_num, + bfd_auth_type_is_meticulous + (auth_key->auth_type)) + && bfd_verify_pkt_auth_key_sha1 (pkt, pkt_size, bs, bfd_key_id, + auth_key); + } + while (0); +#else + clib_warning + ("Internal error, attempt to use SHA1 without SSL support"); + return 0; +#endif + } + return 0; +} + /** * @brief verify bfd packet - authentication * @@ -839,30 +1160,81 @@ bfd_verify_pkt_common (const bfd_pkt_t * pkt) * @return 1 if bfd packet is valid */ int -bfd_verify_pkt_session (const bfd_pkt_t * pkt, u16 pkt_size, - const bfd_session_t * bs) +bfd_verify_pkt_auth (const bfd_pkt_t * pkt, u16 pkt_size, bfd_session_t * bs) { - const bfd_pkt_with_auth_t *with_auth = (bfd_pkt_with_auth_t *) pkt; - if (!bfd_pkt_get_auth_present (pkt)) + if (bfd_pkt_get_auth_present (pkt)) { - if (pkt_size > sizeof (*pkt)) + /* authentication present in packet */ + if (!bs->auth.curr_key) { - BFD_ERR ("BFD verification failed - unexpected packet size '%d' " - "(auth not present)", pkt_size); - return 0; + /* currently not using authentication - can we turn it on? */ + if (bs->auth.is_delayed && bs->auth.next_key) + { + /* yes, switch is scheduled - make sure the auth is valid */ + if (bfd_verify_pkt_auth_key (pkt, pkt_size, bs, + bs->auth.next_bfd_key_id, + bs->auth.next_key)) + { + /* auth matches next key, do the switch, packet is valid */ + bfd_session_switch_auth_to_next (bs); + return 1; + } + } + } + else + { + /* yes, using authentication, verify the key */ + if (bfd_verify_pkt_auth_key (pkt, pkt_size, bs, + bs->auth.curr_bfd_key_id, + bs->auth.curr_key)) + { + /* verification passed, packet is valid */ + return 1; + } + else + { + /* verification failed - but maybe we need to switch key */ + if (bs->auth.is_delayed && bs->auth.next_key) + { + /* delayed switch present, verify if that key works */ + if (bfd_verify_pkt_auth_key (pkt, pkt_size, bs, + bs->auth.next_bfd_key_id, + bs->auth.next_key)) + { + /* auth matches next key, switch key, packet is valid */ + bfd_session_switch_auth_to_next (bs); + return 1; + } + } + } } } else { - if (!with_auth->auth.type) + /* authentication in packet not present */ + if (pkt_size > sizeof (*pkt)) { - BFD_ERR ("BFD verification failed - unexpected auth type: '%d'", - with_auth->auth.type); + BFD_ERR ("BFD verification failed - unexpected packet size '%d' " + "(auth not present)", pkt_size); return 0; } - /* TODO FIXME - implement the actual verification */ + if (bs->auth.curr_key) + { + /* currently authenticating - could we turn it off? */ + if (bs->auth.is_delayed && !bs->auth.next_key) + { + /* yes, delayed switch to NULL key is scheduled */ + bfd_session_switch_auth_to_next (bs); + return 1; + } + } + else + { + /* no auth in packet, no auth in use - packet is valid */ + return 1; + } } - return 1; + return 0; } void @@ -879,6 +1251,38 @@ bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * pkt, u32 bs_idx) bs->remote_demand = bfd_pkt_get_demand (pkt); u64 now = clib_cpu_time_now (); bs->last_rx_clocks = now; + if (bfd_pkt_get_auth_present (pkt)) + { + bfd_auth_type_e auth_type = + ((bfd_pkt_with_common_auth_t *) (pkt))->common_auth.type; + switch (auth_type) + { + case BFD_AUTH_TYPE_reserved: + /* fallthrough */ + case BFD_AUTH_TYPE_simple_password: + /* fallthrough */ + case BFD_AUTH_TYPE_keyed_md5: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_md5: + clib_warning ("Internal error, unexpected auth_type=%d:%s", + auth_type, bfd_auth_type_str (auth_type)); + break; + case BFD_AUTH_TYPE_keyed_sha1: + /* fallthrough */ + case BFD_AUTH_TYPE_meticulous_keyed_sha1: + do + { + bfd_pkt_with_sha1_auth_t *with_sha1 = + (bfd_pkt_with_sha1_auth_t *) pkt; + bs->auth.remote_seq_number = + clib_net_to_host_u32 (with_sha1->sha1_auth.seq_num); + bs->auth.remote_seq_number_known = 1; + BFD_DBG ("Received sequence number %u", + bs->auth.remote_seq_number); + } + while (0); + } + } bs->remote_desired_min_tx_us = clib_net_to_host_u32 (pkt->des_min_tx); bs->remote_detect_mult = pkt->head.detect_mult; bfd_set_remote_required_min_rx (bm, bs, now, @@ -933,25 +1337,129 @@ u8 * format_bfd_session (u8 * s, va_list * args) { const bfd_session_t *bs = va_arg (*args, bfd_session_t *); - return format (s, "BFD(%u): bfd.SessionState=%s, " - "bfd.RemoteSessionState=%s, " - "bfd.LocalDiscr=%u, " - "bfd.RemoteDiscr=%u, " - "bfd.LocalDiag=%s, " - "bfd.DesiredMinTxInterval=%u, " - "bfd.RequiredMinRxInterval=%u, " - "bfd.RequiredMinEchoRxInterval=%u, " - "bfd.RemoteMinRxInterval=%u, " - "bfd.DemandMode=%s, " - "bfd.RemoteDemandMode=%s, " - "bfd.DetectMult=%u, ", - bs->bs_idx, bfd_state_string (bs->local_state), - bfd_state_string (bs->remote_state), bs->local_discr, - bs->remote_discr, bfd_diag_code_string (bs->local_diag), - bs->desired_min_tx_us, bs->required_min_rx_us, - bs->required_min_echo_rx_us, bs->remote_min_rx_us, - (bs->local_demand ? "yes" : "no"), - (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult); + s = format (s, "BFD(%u): bfd.SessionState=%s, " + "bfd.RemoteSessionState=%s, " + "bfd.LocalDiscr=%u, " + "bfd.RemoteDiscr=%u, " + "bfd.LocalDiag=%s, " + "bfd.DesiredMinTxInterval=%u, " + "bfd.RequiredMinRxInterval=%u, " + "bfd.RequiredMinEchoRxInterval=%u, " + "bfd.RemoteMinRxInterval=%u, " + "bfd.DemandMode=%s, " + "bfd.RemoteDemandMode=%s, " + "bfd.DetectMult=%u, " + "Auth: {local-seq-num=%u, " + "remote-seq-num=%u, " + "is-delayed=%s, " + "curr-key=%U, " + "next-key=%U}", + bs->bs_idx, bfd_state_string (bs->local_state), + bfd_state_string (bs->remote_state), bs->local_discr, + bs->remote_discr, bfd_diag_code_string (bs->local_diag), + bs->desired_min_tx_us, bs->required_min_rx_us, + bs->required_min_echo_rx_us, bs->remote_min_rx_us, + (bs->local_demand ? "yes" : "no"), + (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult, + bs->auth.local_seq_number, bs->auth.remote_seq_number, + (bs->auth.is_delayed ? "yes" : "no"), format_bfd_auth_key, + bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key); + return s; +} + +unsigned +bfd_auth_type_supported (bfd_auth_type_e auth_type) +{ + if (auth_type == BFD_AUTH_TYPE_keyed_sha1 || + auth_type == BFD_AUTH_TYPE_meticulous_keyed_sha1) + { + return 1; + } + return 0; +} + +vnet_api_error_t +bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, + u8 bfd_key_id, u8 is_delayed) +{ + bfd_main_t *bm = &bfd_main; + const uword *key_idx_p = + hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (!key_idx_p) + { + clib_warning ("Authentication key with config ID %u doesn't exist)", + conf_key_id); + return VNET_API_ERROR_BFD_ENOENT; + } + const uword key_idx = *key_idx_p; + bfd_auth_key_t *key = pool_elt_at_index (bm->auth_keys, key_idx); + if (is_delayed) + { + if (bs->auth.next_key == key) + { + /* already using this key, no changes required */ + return 0; + } + bs->auth.next_key = key; + bs->auth.next_bfd_key_id = bfd_key_id; + bs->auth.is_delayed = 1; + } + else + { + if (bs->auth.curr_key == key) + { + /* already using this key, no changes required */ + return 0; + } + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + } + bs->auth.curr_key = key; + bs->auth.curr_bfd_key_id = bfd_key_id; + bs->auth.is_delayed = 0; + } + ++key->use_count; + BFD_DBG ("Session auth modified: %U", format_bfd_session, bs); + return 0; +} + +vnet_api_error_t +bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed) +{ +#if WITH_LIBSSL > 0 + if (!is_delayed) + { + /* not delayed - deactivate the current key right now */ + if (bs->auth.curr_key) + { + --bs->auth.curr_key->use_count; + bs->auth.curr_key = NULL; + } + bs->auth.is_delayed = 0; + } + else + { + /* delayed - mark as so */ + bs->auth.is_delayed = 1; + } + /* + * clear the next key unconditionally - either the auth change is not delayed + * in which case the caller expects the session to not use authentication + * from this point forward, or it is delayed, in which case the next_key + * needs to be set to NULL to make it so in the future + */ + if (bs->auth.next_key) + { + --bs->auth.next_key->use_count; + bs->auth.next_key = NULL; + } + BFD_DBG ("Session auth modified: %U", format_bfd_session, bs); + return 0; +#else + clib_warning ("SSL missing, cannot deactivate BFD authentication"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif } bfd_main_t bfd_main; diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index 20da381ac23..b66b79e71a0 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -25,7 +25,7 @@ #include <vnet/bfd/bfd_udp.h> #define foreach_bfd_transport(F) \ - F (UDP4, "ip4-rewrite") \ + F (UDP4, "ip4-rewrite") \ F (UDP6, "ip6-rewrite") typedef enum @@ -48,6 +48,24 @@ typedef enum typedef struct { + /* global configuration key ID */ + u32 conf_key_id; + + /* keeps track of how many sessions reference this key */ + u32 use_count; + + /* + * key data directly usable for bfd purposes - already padded with zeroes + * (so we don't need the actual length) + */ + u8 key[20]; + + /* authentication type for this key */ + bfd_auth_type_e auth_type; +} bfd_auth_key_t; + +typedef struct +{ /* index in bfd_main.sessions pool */ u32 bs_idx; @@ -120,6 +138,40 @@ typedef struct /* detection time */ u64 detection_time_clocks; + /* authentication information */ + struct + { + /* current key in use */ + bfd_auth_key_t *curr_key; + + /* + * set to next key to use if delayed switch is enabled - in that case + * the key is switched when first incoming packet is signed with next_key + */ + bfd_auth_key_t *next_key; + + /* sequence number incremented occasionally or always (if meticulous) */ + u32 local_seq_number; + + /* remote sequence number */ + u32 remote_seq_number; + + /* set to 1 if remote sequence number is known */ + u8 remote_seq_number_known; + + /* current key ID sent out in bfd packet */ + u8 curr_bfd_key_id; + + /* key ID to use when switched to next_key */ + u8 next_bfd_key_id; + + /* + * set to 1 if delayed action is pending, which might be activation + * of authentication, change of key or deactivation + */ + u8 is_delayed; + } auth; + /* transport type for this session */ bfd_transport_t transport; @@ -131,12 +183,6 @@ typedef struct typedef struct { - u32 client_index; - u32 client_pid; -} event_subscriber_t; - -typedef struct -{ /* pool of bfd sessions context data */ bfd_session_t *sessions; @@ -162,6 +208,12 @@ typedef struct /* for generating random numbers */ u32 random_seed; + /* pool of authentication keys */ + bfd_auth_key_t *auth_keys; + + /* hashmap - index in pool auth_keys by conf_key_id */ + u32 *auth_key_by_conf_key_id; + } bfd_main_t; extern bfd_main_t bfd_main; @@ -202,12 +254,17 @@ bfd_session_t *bfd_find_session_by_disc (bfd_main_t * bm, u32 disc); void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs); void bfd_consume_pkt (bfd_main_t * bm, const bfd_pkt_t * bfd, u32 bs_idx); int bfd_verify_pkt_common (const bfd_pkt_t * pkt); -int bfd_verify_pkt_session (const bfd_pkt_t * pkt, u16 pkt_size, - const bfd_session_t * bs); +int bfd_verify_pkt_auth (const bfd_pkt_t * pkt, u16 pkt_size, + bfd_session_t * bs); void bfd_event (bfd_main_t * bm, bfd_session_t * bs); -void bfd_send_final (vlib_main_t * vm, vlib_buffer_t * b, bfd_session_t * bs); +void bfd_init_final_control_frame (vlib_main_t * vm, vlib_buffer_t * b, + bfd_session_t * bs); u8 *format_bfd_session (u8 * s, va_list * args); - +void bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down); +unsigned bfd_auth_type_supported (bfd_auth_type_e auth_type); +vnet_api_error_t bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id, + u8 bfd_key_id, u8 is_delayed); +vnet_api_error_t bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed); #define USEC_PER_MS 1000LL #define USEC_PER_SECOND (1000 * USEC_PER_MS) diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c index ede9536f3cf..180fc6df8fd 100644 --- a/src/vnet/bfd/bfd_protocol.c +++ b/src/vnet/bfd/bfd_protocol.c @@ -1,74 +1,159 @@ +/* + * Copyright (c) 2011-2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include <vnet/bfd/bfd_protocol.h> -u8 bfd_pkt_get_version (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_version (const bfd_pkt_t * pkt) { return pkt->head.vers_diag >> 5; } -void bfd_pkt_set_version (bfd_pkt_t *pkt, int version) +void +bfd_pkt_set_version (bfd_pkt_t * pkt, int version) { pkt->head.vers_diag = - (version << 5) | (pkt->head.vers_diag & ((1 << 5) - 1)); + (version << 5) | (pkt->head.vers_diag & ((1 << 5) - 1)); } -u8 bfd_pkt_get_diag_code (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_diag_code (const bfd_pkt_t * pkt) { return pkt->head.vers_diag & ((1 << 5) - 1); } -void bfd_pkt_set_diag_code (bfd_pkt_t *pkt, int value) +void +bfd_pkt_set_diag_code (bfd_pkt_t * pkt, int value) { pkt->head.vers_diag = - (pkt->head.vers_diag & ~((1 << 5) - 1)) | (value & ((1 << 5) - 1)); + (pkt->head.vers_diag & ~((1 << 5) - 1)) | (value & ((1 << 5) - 1)); } -u8 bfd_pkt_get_state (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_state (const bfd_pkt_t * pkt) { return pkt->head.sta_flags >> 6; } -void bfd_pkt_set_state (bfd_pkt_t *pkt, int value) +void +bfd_pkt_set_state (bfd_pkt_t * pkt, int value) { pkt->head.sta_flags = (value << 6) | (pkt->head.sta_flags & ((1 << 6) - 1)); } -u8 bfd_pkt_get_poll (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_poll (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 5) & 1; } -void bfd_pkt_set_final (bfd_pkt_t *pkt) { pkt->head.sta_flags |= 1 << 5; } +void +bfd_pkt_set_poll (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 5; +} -u8 bfd_pkt_get_final (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_final (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 4) & 1; } -void bfd_pkt_set_poll (bfd_pkt_t *pkt); -u8 bfd_pkt_get_control_plane_independent (const bfd_pkt_t *pkt) +void +bfd_pkt_set_final (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 4; +} + +u8 +bfd_pkt_get_control_plane_independent (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 3) & 1; } -void bfd_pkt_set_control_plane_independent (bfd_pkt_t *pkt); +void +bfd_pkt_set_control_plane_independent (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 3; +} -u8 bfd_pkt_get_auth_present (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_auth_present (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 2) & 1; } -void bfd_pkt_set_auth_present (bfd_pkt_t *pkt); +void +bfd_pkt_set_auth_present (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 2; +} -u8 bfd_pkt_get_demand (const bfd_pkt_t *pkt) +u8 +bfd_pkt_get_demand (const bfd_pkt_t * pkt) { return (pkt->head.sta_flags >> 1) & 1; } -void bfd_pkt_set_demand (bfd_pkt_t *pkt) { pkt->head.sta_flags |= 1 << 1; } +void +bfd_pkt_set_demand (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 1; +} + +u8 +bfd_pkt_get_multipoint (const bfd_pkt_t * pkt) +{ + return (pkt->head.sta_flags >> 0) & 1; +} + +void +bfd_pkt_set_multipoint (bfd_pkt_t * pkt) +{ + pkt->head.sta_flags |= 1 << 0; +} + +u32 +bfd_max_len_for_auth_type (bfd_auth_type_e auth_type) +{ +#define F(t, l, n, s) \ + if (auth_type == t) \ + { \ + return l; \ + } + foreach_bfd_auth_type (F); +#undef F + return 0; +} -u8 bfd_pkt_get_multipoint (const bfd_pkt_t *pkt) +const char * +bfd_auth_type_str (bfd_auth_type_e auth_type) { - return pkt->head.sta_flags & 1; +#define F(t, l, n, s) \ + if (auth_type == t) \ + { \ + return s; \ + } + foreach_bfd_auth_type (F); +#undef F + return "UNKNOWN"; } -void bfd_pkt_set_multipoint (bfd_pkt_t *pkt); +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/bfd/bfd_protocol.h b/src/vnet/bfd/bfd_protocol.h index cf751b3b89a..cdbb8fa7bd3 100644 --- a/src/vnet/bfd/bfd_protocol.h +++ b/src/vnet/bfd/bfd_protocol.h @@ -22,45 +22,93 @@ #include <vppinfra/types.h> #include <vppinfra/clib.h> +/* auth type value, max key length, name, description */ +#define foreach_bfd_auth_type(F) \ + F (0, 0, reserved, "Reserved") \ + F (1, 16, simple_password, "Simple Password") \ + F (2, 16, keyed_md5, "Keyed MD5") \ + F (3, 16, meticulous_keyed_md5, "Meticulous Keyed MD5") \ + F (4, 20, keyed_sha1, "Keyed SHA1") \ + F (5, 20, meticulous_keyed_sha1, "Meticulous Keyed SHA1") + +#define BFD_AUTH_TYPE_NAME(t) BFD_AUTH_TYPE_##t + +typedef enum +{ +#define F(n, l, t, s) BFD_AUTH_TYPE_NAME (t) = n, + foreach_bfd_auth_type (F) +#undef F +} bfd_auth_type_e; + +u32 bfd_max_len_for_auth_type (bfd_auth_type_e auth_type); +const char *bfd_auth_type_str (bfd_auth_type_e auth_type); + /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { - /* - An optional Authentication Section MAY be present: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Auth Type | Auth Len | Authentication Data... | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ u8 type; u8 len; - u8 data[0]; -}) bfd_auth_t; +}) bfd_auth_common_t; /* *INDENT-ON* */ /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { /* - The Mandatory Section of a BFD Control packet has the following - format: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | My Discriminator | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Your Discriminator | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Desired Min TX Interval | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Required Min RX Interval | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Required Min Echo RX Interval | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ + * 4.4. Keyed SHA1 and Meticulous Keyed SHA1 Authentication Section Format + + * If the Authentication Present (A) bit is set in the header, and the + * Authentication Type field contains 4 (Keyed SHA1) or 5 (Meticulous + * Keyed SHA1), the Authentication Section has the following format: + + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Auth Type | Auth Len | Auth Key ID | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Auth Key/Hash... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | ... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + bfd_auth_common_t type_len; + u8 key_id; + u8 reserved; + u32 seq_num; + /* + * Auth Key/Hash + + * This field carries the 20-byte SHA1 hash for the packet. When the + * hash is calculated, the shared SHA1 key is stored in this field, + * padded to a length of 20 bytes with trailing zero bytes if needed. + * The shared key MUST be encoded and configured to section 6.7.4. + */ + u8 hash[20]; +}) bfd_auth_sha1_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + /* + * The Mandatory Section of a BFD Control packet has the following + * format: + + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | My Discriminator | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Your Discriminator | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Desired Min TX Interval | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Required Min RX Interval | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Required Min Echo RX Interval | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ struct { u8 vers_diag; @@ -79,8 +127,15 @@ typedef CLIB_PACKED (struct { /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { bfd_pkt_t pkt; - bfd_auth_t auth; -}) bfd_pkt_with_auth_t; + bfd_auth_common_t common_auth; +}) bfd_pkt_with_common_auth_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + bfd_pkt_t pkt; + bfd_auth_sha1_t sha1_auth; +}) bfd_pkt_with_sha1_auth_t; /* *INDENT-ON* */ u8 bfd_pkt_get_version (const bfd_pkt_t * pkt); diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index dfd030aeccb..443f4253b96 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -45,7 +45,7 @@ bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, ip4_header_t ip4; udp_header_t udp; } ip4_udp_headers; - ip4_udp_headers *headers = vlib_buffer_get_current (b); + ip4_udp_headers *headers = NULL; vlib_buffer_advance (b, -sizeof (*headers)); headers = vlib_buffer_get_current (b); memset (headers, 0, sizeof (*headers)); @@ -82,8 +82,9 @@ bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, ip6_header_t ip6; udp_header_t udp; } ip6_udp_headers; - vlib_buffer_advance (b, -sizeof (ip6_udp_headers)); - ip6_udp_headers *headers = vlib_buffer_get_current (b); + ip6_udp_headers *headers = NULL; + vlib_buffer_advance (b, -sizeof (*headers)); + headers = vlib_buffer_get_current (b); memset (headers, 0, sizeof (*headers)); headers->ip6.ip_version_traffic_class_and_flow_label = clib_host_to_net_u32 (0x6 << 28); @@ -125,16 +126,27 @@ bfd_lookup_session (bfd_udp_main_t * bum, const bfd_udp_key_t * key) return 0; } +static void +bfd_udp_key_init (bfd_udp_key_t * key, u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr) +{ + memset (key, 0, sizeof (*key)); + key->sw_if_index = sw_if_index; + key->local_addr.as_u64[0] = local_addr->as_u64[0]; + key->local_addr.as_u64[1] = local_addr->as_u64[1]; + key->peer_addr.as_u64[0] = peer_addr->as_u64[0]; + key->peer_addr.as_u64[1] = peer_addr->as_u64[1]; +} + static vnet_api_error_t bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, u32 desired_min_tx_us, u32 required_min_rx_us, u8 detect_mult, const ip46_address_t * local_addr, const ip46_address_t * peer_addr, - u32 * bs_index) + bfd_session_t ** bs_out) { - vnet_sw_interface_t *sw_if = - vnet_get_sw_interface (vnet_get_main (), sw_if_index); /* get a pool entry and if we end up not needing it, give it back */ bfd_transport_t t = BFD_TRANSPORT_UDP4; if (!ip46_address_is_ip4 (local_addr)) @@ -145,19 +157,15 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, bfd_udp_session_t *bus = &bs->udp; memset (bus, 0, sizeof (*bus)); bfd_udp_key_t *key = &bus->key; - key->sw_if_index = sw_if->sw_if_index; - key->local_addr.as_u64[0] = local_addr->as_u64[0]; - key->local_addr.as_u64[1] = local_addr->as_u64[1]; - key->peer_addr.as_u64[0] = peer_addr->as_u64[0]; - key->peer_addr.as_u64[1] = peer_addr->as_u64[1]; + bfd_udp_key_init (key, sw_if_index, local_addr, peer_addr); const bfd_session_t *tmp = bfd_lookup_session (bum, key); if (tmp) { - BFD_ERR ("duplicate bfd-udp session, existing bs_idx=%d", tmp->bs_idx); + clib_warning ("duplicate bfd-udp session, existing bs_idx=%d", + tmp->bs_idx); bfd_put_session (bum->bfd_main, bs); return VNET_API_ERROR_BFD_EEXIST; } - key->sw_if_index = sw_if->sw_if_index; mhash_set (&bum->bfd_session_idx_by_bfd_key, key, bs->bs_idx, NULL); BFD_DBG ("session created, bs_idx=%u, sw_if_index=%d, local=%U, peer=%U", bs->bs_idx, key->sw_if_index, format_ip46_address, @@ -185,8 +193,7 @@ bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index, bs->required_min_rx_us = required_min_rx_us; bs->required_min_echo_rx_us = required_min_rx_us; /* FIXME */ bs->local_detect_mult = detect_mult; - bfd_session_start (bum->bfd_main, bs); - *bs_index = bs->bs_idx; + *bs_out = bs; return 0; } @@ -201,14 +208,14 @@ bfd_udp_validate_api_input (u32 sw_if_index, ip_interface_address_t *ia = NULL; if (!sw_if) { - BFD_ERR ("got NULL sw_if"); + clib_warning ("got NULL sw_if"); return VNET_API_ERROR_INVALID_SW_IF_INDEX; } if (ip46_address_is_ip4 (local_addr)) { if (!ip46_address_is_ip4 (peer_addr)) { - BFD_ERR ("IP family mismatch"); + clib_warning ("IP family mismatch"); return VNET_API_ERROR_INVALID_ARGUMENT; } ip4_main_t *im = &ip4_main; @@ -231,7 +238,7 @@ bfd_udp_validate_api_input (u32 sw_if_index, { if (ip46_address_is_ip4 (peer_addr)) { - BFD_ERR ("IP family mismatch"); + clib_warning ("IP family mismatch"); return VNET_API_ERROR_INVALID_ARGUMENT; } ip6_main_t *im = &ip6_main; @@ -241,7 +248,7 @@ bfd_udp_validate_api_input (u32 sw_if_index, ip6_address_t *x = ip_interface_address_get_address (&im->lookup_main, ia); if (local_addr->ip6.as_u64[0] == x->as_u64[0] && - local_addr->ip6.as_u64[1] == x->as_u64[1]) + local_addr->ip6.as_u64[1] == x->as_u64[1]) { /* valid address for this interface */ local_ip_valid = 1; @@ -253,18 +260,48 @@ bfd_udp_validate_api_input (u32 sw_if_index, if (!local_ip_valid) { - BFD_ERR ("address not found on interface"); + clib_warning ("address not found on interface"); return VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE; } return 0; } -vnet_api_error_t -bfd_udp_add_session (u32 sw_if_index, u32 desired_min_tx_us, - u32 required_min_rx_us, u8 detect_mult, - const ip46_address_t * local_addr, - const ip46_address_t * peer_addr, u32 * bs_index) +static vnet_api_error_t +bfd_udp_find_session_by_api_input (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + bfd_session_t ** bs_out) +{ + vnet_api_error_t rv = + bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); + if (!rv) + { + bfd_udp_main_t *bum = &bfd_udp_main; + bfd_udp_key_t key; + bfd_udp_key_init (&key, sw_if_index, local_addr, peer_addr); + bfd_session_t *bs = bfd_lookup_session (bum, &key); + if (bs) + { + *bs_out = bs; + } + else + { + clib_warning + ("BFD session not found (sw_if_index=%u, local=%U, peer=%U", + sw_if_index, format_ip46_address, local_addr, IP46_TYPE_ANY, + format_ip46_address, peer_addr, IP46_TYPE_ANY); + return VNET_API_ERROR_BFD_ENOENT; + } + } + return rv; +} + +static vnet_api_error_t +bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_us, + u32 required_min_rx_us, u8 detect_mult, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr) { vnet_api_error_t rv = bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); @@ -274,18 +311,66 @@ bfd_udp_add_session (u32 sw_if_index, u32 desired_min_tx_us, } if (detect_mult < 1) { - BFD_ERR ("detect_mult < 1"); + clib_warning ("detect_mult < 1"); return VNET_API_ERROR_INVALID_ARGUMENT; } if (desired_min_tx_us < 1) { - BFD_ERR ("desired_min_tx_us < 1"); + clib_warning ("desired_min_tx_us < 1"); return VNET_API_ERROR_INVALID_ARGUMENT; } - return bfd_udp_add_session_internal (&bfd_udp_main, sw_if_index, - desired_min_tx_us, required_min_rx_us, - detect_mult, local_addr, peer_addr, - bs_index); + return 0; +} + +static void +bfd_udp_del_session_internal (bfd_session_t * bs) +{ + bfd_udp_main_t *bum = &bfd_udp_main; + BFD_DBG ("free bfd-udp session, bs_idx=%d", bs->bs_idx); + mhash_unset (&bum->bfd_session_idx_by_bfd_key, &bs->udp.key, NULL); + adj_unlock (bs->udp.adj_index); + bfd_put_session (bum->bfd_main, bs); +} + +vnet_api_error_t +bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u32 desired_min_tx_us, + u32 required_min_rx_us, u8 detect_mult, + u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id) +{ + vnet_api_error_t rv = bfd_api_verify_common (sw_if_index, desired_min_tx_us, + required_min_rx_us, + detect_mult, + local_addr, peer_addr); + bfd_session_t *bs = NULL; + if (!rv) + { + rv = + bfd_udp_add_session_internal (&bfd_udp_main, sw_if_index, + desired_min_tx_us, required_min_rx_us, + detect_mult, local_addr, peer_addr, + &bs); + } + if (!rv && is_authenticated) + { +#if WITH_LIBSSL > 0 + rv = bfd_auth_activate (bs, conf_key_id, bfd_key_id, + 0 /* is not delayed */ ); +#else + clib_warning ("SSL missing, cannot add authenticated BFD session"); + rv = VNET_API_ERROR_BFD_NOTSUPP; +#endif + if (rv) + { + bfd_udp_del_session_internal (bs); + } + } + if (!rv) + { + bfd_session_start (bfd_udp_main.bfd_main, bs); + } + + return rv; } vnet_api_error_t @@ -293,36 +378,162 @@ bfd_udp_del_session (u32 sw_if_index, const ip46_address_t * local_addr, const ip46_address_t * peer_addr) { + bfd_session_t *bs = NULL; vnet_api_error_t rv = - bfd_udp_validate_api_input (sw_if_index, local_addr, peer_addr); + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); if (rv) { return rv; } - bfd_udp_main_t *bum = &bfd_udp_main; - vnet_sw_interface_t *sw_if = - vnet_get_sw_interface (vnet_get_main (), sw_if_index); - bfd_udp_key_t key; - memset (&key, 0, sizeof (key)); - key.sw_if_index = sw_if->sw_if_index; - key.local_addr.as_u64[0] = local_addr->as_u64[0]; - key.local_addr.as_u64[1] = local_addr->as_u64[1]; - key.peer_addr.as_u64[0] = peer_addr->as_u64[0]; - key.peer_addr.as_u64[1] = peer_addr->as_u64[1]; - bfd_session_t *tmp = bfd_lookup_session (bum, &key); - if (tmp) + bfd_udp_del_session_internal (bs); + return 0; +} + +vnet_api_error_t +bfd_udp_session_set_flags (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u8 admin_up_down) +{ + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + bfd_session_set_flags (bs, admin_up_down); + return 0; +} + +vnet_api_error_t +bfd_auth_set_key (u32 conf_key_id, u8 auth_type, u8 key_len, + const u8 * key_data) +{ +#if WITH_LIBSSL > 0 + bfd_auth_key_t *auth_key = NULL; + if (!key_len || key_len > bfd_max_len_for_auth_type (auth_type)) + { + clib_warning ("Invalid authentication key length for auth_type=%d:%s " + "(key_len=%u, must be " + "non-zero, expected max=%u)", + auth_type, bfd_auth_type_str (auth_type), key_len, + (u32) bfd_max_len_for_auth_type (auth_type)); + return VNET_API_ERROR_INVALID_VALUE; + } + if (!bfd_auth_type_supported (auth_type)) + { + clib_warning ("Unsupported auth type=%d:%s", auth_type, + bfd_auth_type_str (auth_type)); + return VNET_API_ERROR_BFD_NOTSUPP; + } + bfd_main_t *bm = bfd_udp_main.bfd_main; + uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (key_idx_p) + { + /* modifying existing key - must not be used */ + const uword key_idx = *key_idx_p; + auth_key = pool_elt_at_index (bm->auth_keys, key_idx); + if (auth_key->use_count > 0) + { + clib_warning ("Authentication key with conf ID %u in use by %u BFD " + "sessions - cannot modify", + conf_key_id, auth_key->use_count); + return VNET_API_ERROR_BFD_EINUSE; + } + } + else + { + /* adding new key */ + pool_get (bm->auth_keys, auth_key); + auth_key->conf_key_id = conf_key_id; + hash_set (bm->auth_key_by_conf_key_id, conf_key_id, + auth_key - bm->auth_keys); + } + auth_key->auth_type = auth_type; + memset (auth_key->key, 0, sizeof (auth_key->key)); + clib_memcpy (auth_key->key, key_data, key_len); + return 0; +#else + clib_warning ("SSL missing, cannot manipulate authentication keys"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_auth_del_key (u32 conf_key_id) +{ +#if WITH_LIBSSL > 0 + bfd_auth_key_t *auth_key = NULL; + bfd_main_t *bm = bfd_udp_main.bfd_main; + uword *key_idx_p = hash_get (bm->auth_key_by_conf_key_id, conf_key_id); + if (key_idx_p) { - BFD_DBG ("free bfd-udp session, bs_idx=%d", tmp->bs_idx); - mhash_unset (&bum->bfd_session_idx_by_bfd_key, &key, NULL); - adj_unlock (tmp->udp.adj_index); - bfd_put_session (bum->bfd_main, tmp); + /* deleting existing key - must not be used */ + const uword key_idx = *key_idx_p; + auth_key = pool_elt_at_index (bm->auth_keys, key_idx); + if (auth_key->use_count > 0) + { + clib_warning ("Authentication key with conf ID %u in use by %u BFD " + "sessions - cannot delete", + conf_key_id, auth_key->use_count); + return VNET_API_ERROR_BFD_EINUSE; + } + hash_unset (bm->auth_key_by_conf_key_id, conf_key_id); + memset (auth_key, 0, sizeof (*auth_key)); + pool_put (bm->auth_keys, auth_key); } else { - BFD_ERR ("no such session"); - return VNET_API_ERROR_BFD_NOENT; + /* no such key */ + clib_warning ("Authentication key with conf ID %u does not exist", + conf_key_id); + return VNET_API_ERROR_BFD_ENOENT; } return 0; +#else + clib_warning ("SSL missing, cannot manipulate authentication keys"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_udp_auth_activate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, + u32 conf_key_id, u8 key_id, u8 is_delayed) +{ +#if WITH_LIBSSL > 0 + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + return bfd_auth_activate (bs, conf_key_id, key_id, is_delayed); +#else + clib_warning ("SSL missing, cannot activate BFD authentication"); + return VNET_API_ERROR_BFD_NOTSUPP; +#endif +} + +vnet_api_error_t +bfd_udp_auth_deactivate (u32 sw_if_index, + const ip46_address_t * local_addr, + const ip46_address_t * peer_addr, u8 is_delayed) +{ + bfd_session_t *bs = NULL; + vnet_api_error_t rv = + bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr, + &bs); + if (rv) + { + return rv; + } + return bfd_auth_deactivate (bs, is_delayed); } typedef enum @@ -461,6 +672,14 @@ bfd_udp4_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, BFD_ERR ("Couldn't find ip4 or udp header"); return BFD_UDP_ERROR_BAD; } + const u32 udp_payload_length = udp->length - sizeof (*udp); + if (pkt->head.length > udp_payload_length) + { + BFD_ERR + ("BFD packet length is larger than udp payload length (%u > %u)", + pkt->head.length, udp_payload_length); + return BFD_UDP_ERROR_BAD; + } if (!bfd_verify_pkt_common (pkt)) { return BFD_UDP_ERROR_BAD; @@ -491,8 +710,9 @@ bfd_udp4_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, return BFD_UDP_ERROR_BAD; } BFD_DBG ("BFD session found, bs_idx=%u", bs->bs_idx); - if (!bfd_verify_pkt_session (pkt, b->current_length, bs)) + if (!bfd_verify_pkt_auth (pkt, b->current_length, bs)) { + BFD_ERR ("Packet verification failed, dropping packet"); return BFD_UDP_ERROR_BAD; } bfd_udp_error_t err; @@ -526,11 +746,10 @@ bfd_udp6_find_headers (vlib_buffer_t * b, const ip6_header_t ** ip6, *udp = NULL; return; } - /* FIXME skip extra headers when searching for UDP ? */ if ((*ip6)->protocol != IP_PROTOCOL_UDP) { BFD_ERR ("Unexpected protocol in IPv6 header '%u', expected '%u' (== " - "IP_PROTOCOL_UDP)" (*ip6)->protocol, IP_PROTOCOL_UDP); + "IP_PROTOCOL_UDP)", (*ip6)->protocol, IP_PROTOCOL_UDP); *ip6 = NULL; *udp = NULL; return; @@ -596,6 +815,14 @@ bfd_udp6_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, BFD_ERR ("Couldn't find ip6 or udp header"); return BFD_UDP_ERROR_BAD; } + const u32 udp_payload_length = udp->length - sizeof (*udp); + if (pkt->head.length > udp_payload_length) + { + BFD_ERR + ("BFD packet length is larger than udp payload length (%u > %u)", + pkt->head.length, udp_payload_length); + return BFD_UDP_ERROR_BAD; + } if (!bfd_verify_pkt_common (pkt)) { return BFD_UDP_ERROR_BAD; @@ -617,8 +844,9 @@ bfd_udp6_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, key.peer_addr.ip6.as_u64[0] = ip6->src_address.as_u64[0]; key.peer_addr.ip6.as_u64[1] = ip6->src_address.as_u64[1]; BFD_DBG ("Looking up BFD session using key (sw_if_index=%u, local=%U, " - "peer=%U)", key.sw_if_index, format_ip6_address, - &key.local_addr, format_ip6_address, &key.peer_addr); + "peer=%U)", + key.sw_if_index, format_ip6_address, &key.local_addr, + format_ip6_address, &key.peer_addr); bs = bfd_lookup_session (&bfd_udp_main, &key); } if (!bs) @@ -627,8 +855,9 @@ bfd_udp6_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, return BFD_UDP_ERROR_BAD; } BFD_DBG ("BFD session found, bs_idx=%u", bs->bs_idx); - if (!bfd_verify_pkt_session (pkt, b->current_length, bs)) + if (!bfd_verify_pkt_auth (pkt, b->current_length, bs)) { + BFD_ERR ("Packet verification failed, dropping packet"); return BFD_UDP_ERROR_BAD; } bfd_udp_error_t err; @@ -699,7 +928,7 @@ bfd_udp_input (vlib_main_t * vm, vlib_node_runtime_t * rt, const bfd_pkt_t *pkt = vlib_buffer_get_current (b0); if (bfd_pkt_get_poll (pkt)) { - bfd_send_final (vm, b0, bs); + bfd_init_final_control_frame (vm, b0, bs); if (is_ipv6) { vlib_node_increment_counter (vm, bfd_udp6_input_node.index, diff --git a/test/bfd.py b/test/bfd.py index 5171681369c..475a1707b40 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -1,3 +1,4 @@ +from random import randint from socket import AF_INET, AF_INET6 from scapy.all import * from scapy.packet import * @@ -53,11 +54,57 @@ class BFDState(NumericConstant): NumericConstant.__init__(self, value) +class BFDAuthType(NumericConstant): + """ BFD Authentication Type """ + no_auth = 0 + simple_pwd = 1 + keyed_md5 = 2 + meticulous_keyed_md5 = 3 + keyed_sha1 = 4 + meticulous_keyed_sha1 = 5 + + desc_dict = { + no_auth: "No authentication", + simple_pwd: "Simple Password", + keyed_md5: "Keyed MD5", + meticulous_keyed_md5: "Meticulous Keyed MD5", + keyed_sha1: "Keyed SHA1", + meticulous_keyed_sha1: "Meticulous Keyed SHA1", + } + + def __init__(self, value): + NumericConstant.__init__(self, value) + + +def bfd_is_auth_used(pkt): + return "A" in pkt.sprintf("%BFD.flags%") + + +def bfd_is_simple_pwd_used(pkt): + return bfd_is_auth_used(pkt) and pkt.auth_type == BFDAuthType.simple_pwd + + +def bfd_is_sha1_used(pkt): + return bfd_is_auth_used(pkt) and pkt.auth_type in \ + (BFDAuthType.keyed_sha1, BFDAuthType.meticulous_keyed_sha1) + + +def bfd_is_md5_used(pkt): + return bfd_is_auth_used(pkt) and pkt.auth_type in \ + (BFDAuthType.keyed_md5, BFDAuthType.meticulous_keyed_md5) + + +def bfd_is_md5_or_sha1_used(pkt): + return bfd_is_md5_used(pkt) or bfd_is_sha1_used(pkt) + + class BFD(Packet): udp_dport = 3784 #: BFD destination port per RFC 5881 udp_sport_min = 49152 #: BFD source port min value per RFC 5881 udp_sport_max = 65535 #: BFD source port max value per RFC 5881 + bfd_pkt_len = 24 # : length of BFD pkt without authentication section + sha1_auth_len = 28 # : length of authentication section if SHA1 used name = "BFD" @@ -65,14 +112,27 @@ class BFD(Packet): BitField("version", 1, 3), BitEnumField("diag", 0, 5, BFDDiagCode.desc_dict), BitEnumField("state", 0, 2, BFDState.desc_dict), - FlagsField("flags", 0, 6, ['P', 'F', 'C', 'A', 'D', 'M']), + FlagsField("flags", 0, 6, ['M', 'D', 'A', 'C', 'F', 'P']), XByteField("detect_mult", 0), - XByteField("length", 24), + BitField("length", bfd_pkt_len, 8), BitField("my_discriminator", 0, 32), BitField("your_discriminator", 0, 32), BitField("desired_min_tx_interval", 0, 32), BitField("required_min_rx_interval", 0, 32), - BitField("required_min_echo_rx_interval", 0, 32)] + BitField("required_min_echo_rx_interval", 0, 32), + ConditionalField( + BitEnumField("auth_type", 0, 8, BFDAuthType.desc_dict), + bfd_is_auth_used), + ConditionalField(BitField("auth_len", 0, 8), bfd_is_auth_used), + ConditionalField(BitField("auth_key_id", 0, 8), bfd_is_auth_used), + ConditionalField(BitField("auth_reserved", 0, 8), + bfd_is_md5_or_sha1_used), + ConditionalField( + BitField("auth_seq_num", 0, 32), bfd_is_md5_or_sha1_used), + ConditionalField(StrField("auth_key_hash", "0" * 16), bfd_is_md5_used), + ConditionalField( + StrField("auth_key_hash", "0" * 20), bfd_is_sha1_used), + ] def mysummary(self): return self.sprintf("BFD(my_disc=%BFD.my_discriminator%," @@ -82,9 +142,78 @@ class BFD(Packet): bind_layers(UDP, BFD, dport=BFD.udp_dport) +class VppBFDAuthKey(VppObject): + """ Represents BFD authentication key in VPP """ + + def __init__(self, test, conf_key_id, auth_type, key): + self._test = test + self._key = key + self._auth_type = auth_type + test.assertIn(auth_type, BFDAuthType.desc_dict) + self._conf_key_id = conf_key_id + + @property + def test(self): + """ Test which created this key """ + return self._test + + @property + def auth_type(self): + """ Authentication type for this key """ + return self._auth_type + + @property + def key(self): + return self._key + + @property + def conf_key_id(self): + return self._conf_key_id + + def add_vpp_config(self): + self.test.vapi.bfd_auth_set_key( + self._conf_key_id, self._auth_type, self._key) + self._test.registry.register(self, self.test.logger) + + def get_bfd_auth_keys_dump_entry(self): + """ get the entry in the auth keys dump corresponding to this key """ + result = self.test.vapi.bfd_auth_keys_dump() + for k in result: + if k.conf_key_id == self._conf_key_id: + return k + return None + + def query_vpp_config(self): + return self.get_bfd_auth_keys_dump_entry() is not None + + def remove_vpp_config(self): + self.test.vapi.bfd_auth_del_key(self._conf_key_id) + + def object_id(self): + return "bfd-auth-key-%s" % self._conf_key_id + + def __str__(self): + return self.object_id() + + class VppBFDUDPSession(VppObject): """ Represents BFD UDP session in VPP """ + def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET, + desired_min_tx=100000, required_min_rx=100000, detect_mult=3, + sha1_key=None, bfd_key_id=None): + self._test = test + self._interface = interface + self._af = af + self._local_addr = local_addr + self._peer_addr = peer_addr + self._peer_addr_n = socket.inet_pton(af, peer_addr) + self._desired_min_tx = desired_min_tx + self._required_min_rx = required_min_rx + self._detect_mult = detect_mult + self._sha1_key = sha1_key + self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255) + @property def test(self): """ Test which created this session """ @@ -101,13 +230,6 @@ class VppBFDUDPSession(VppObject): return self._af @property - def bs_index(self): - """ BFD session index from VPP """ - if self._bs_index is not None: - return self._bs_index - raise NotConfiguredException("not configured") - - @property def local_addr(self): """ BFD session local address (VPP address) """ if self._local_addr is None: @@ -141,19 +263,27 @@ class VppBFDUDPSession(VppObject): """ BFD session peer address - raw, suitable for API """ return self._peer_addr_n - @property - def state(self): - """ BFD session state """ + def get_bfd_udp_session_dump_entry(self): result = self.test.vapi.bfd_udp_session_dump() - session = None for s in result: + self.test.logger.debug("session entry: %s" % str(s)) if s.sw_if_index == self.interface.sw_if_index: if self.af == AF_INET \ and s.is_ipv6 == 0 \ and self.interface.local_ip4n == s.local_addr[:4] \ and self.interface.remote_ip4n == s.peer_addr[:4]: - session = s - break + return s + if self.af == AF_INET6 \ + and s.is_ipv6 == 1 \ + and self.interface.local_ip6n == s.local_addr \ + and self.interface.remote_ip6n == s.peer_addr: + return s + return None + + @property + def state(self): + """ BFD session state """ + session = self.get_bfd_udp_session_dump_entry() if session is None: raise Exception("Could not find BFD session in VPP response: %s" % repr(result)) @@ -171,61 +301,78 @@ class VppBFDUDPSession(VppObject): def detect_mult(self): return self._detect_mult - def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET, - desired_min_tx=100000, required_min_rx=100000, detect_mult=3): - self._test = test - self._interface = interface - self._af = af - self._local_addr = local_addr - self._peer_addr = peer_addr - self._peer_addr_n = socket.inet_pton(af, peer_addr) - self._bs_index = None - self._desired_min_tx = desired_min_tx - self._required_min_rx = required_min_rx - self._detect_mult = detect_mult + @property + def sha1_key(self): + return self._sha1_key + + @property + def bfd_key_id(self): + return self._bfd_key_id + + def activate_auth(self, key, bfd_key_id=None, delayed=False): + self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255) + self._sha1_key = key + is_ipv6 = 1 if AF_INET6 == self.af else 0 + conf_key_id = self._sha1_key.conf_key_id + is_delayed = 1 if delayed else 0 + self.test.vapi.bfd_udp_auth_activate(self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6, + bfd_key_id=self._bfd_key_id, + conf_key_id=conf_key_id, + is_delayed=is_delayed) + + def deactivate_auth(self, delayed=False): + self._bfd_key_id = None + self._sha1_key = None + is_delayed = 1 if delayed else 0 + is_ipv6 = 1 if AF_INET6 == self.af else 0 + self.test.vapi.bfd_udp_auth_deactivate(self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6, + is_delayed=is_delayed) def add_vpp_config(self): is_ipv6 = 1 if AF_INET6 == self.af else 0 - result = self.test.vapi.bfd_udp_add( - self._interface.sw_if_index, - self.desired_min_tx, - self.required_min_rx, - self.detect_mult, - self.local_addr_n, - self.peer_addr_n, - is_ipv6=is_ipv6) - self._bs_index = result.bs_index + bfd_key_id = self._bfd_key_id if self._sha1_key else None + conf_key_id = self._sha1_key.conf_key_id if self._sha1_key else None + self.test.vapi.bfd_udp_add(self._interface.sw_if_index, + self.desired_min_tx, + self.required_min_rx, + self.detect_mult, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6, + bfd_key_id=bfd_key_id, + conf_key_id=conf_key_id) self._test.registry.register(self, self.test.logger) def query_vpp_config(self): - result = self.test.vapi.bfd_udp_session_dump() - session = None - for s in result: - if s.sw_if_index == self.interface.sw_if_index: - if self.af == AF_INET \ - and s.is_ipv6 == 0 \ - and self.interface.local_ip4n == s.local_addr[:4] \ - and self.interface.remote_ip4n == s.peer_addr[:4]: - session = s - break - if session is None: - return False - return True + session = self.get_bfd_udp_session_dump_entry() + return session is not None def remove_vpp_config(self): - if self._bs_index is not None: - is_ipv6 = 1 if AF_INET6 == self._af else 0 - self.test.vapi.bfd_udp_del( - self._interface.sw_if_index, - self.local_addr_n, - self.peer_addr_n, - is_ipv6=is_ipv6) + is_ipv6 = 1 if AF_INET6 == self._af else 0 + self.test.vapi.bfd_udp_del(self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) def object_id(self): - return "bfd-udp-%d" % self.bs_index + return "bfd-udp-%s-%s-%s-%s" % (self._interface.sw_if_index, + self.local_addr, + self.peer_addr, + self.af) def __str__(self): return self.object_id() def admin_up(self): - self.test.vapi.bfd_session_set_flags(self.bs_index, 1) + is_ipv6 = 1 if AF_INET6 == self._af else 0 + self.test.vapi.bfd_udp_session_set_flags(1, + self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) diff --git a/test/framework.py b/test/framework.py index 02935604f26..8ceb33c3a44 100644 --- a/test/framework.py +++ b/test/framework.py @@ -89,8 +89,8 @@ class VppTestCase(unittest.TestCase): if dl == "core": if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0: # give a heads up if this is actually useless - cls.logger.critical("WARNING: core size limit is set 0, core " - "files will NOT be created") + print(colorize("WARNING: core size limit is set 0, core files " + "will NOT be created", RED)) cls.debug_core = True elif dl == "gdb": cls.debug_gdb = True @@ -533,12 +533,11 @@ class VppTestCase(unittest.TestCase): self.assertEqual(real_value, expected_value, msg) - def assert_in_range( - self, - real_value, - expected_min, - expected_max, - name=None): + def assert_in_range(self, + real_value, + expected_min, + expected_max, + name=None): if name is None: msg = None else: diff --git a/test/test_bfd.py b/test/test_bfd.py index d047b5a3b99..5f8614777ca 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -1,6 +1,8 @@ #!/usr/bin/env python import unittest +import hashlib +import binascii import time from random import randint from bfd import * @@ -10,6 +12,22 @@ from util import ppp us_in_sec = 1000000 +class AuthKeyFactory(object): + """Factory class for creating auth keys with unique conf key ID""" + + def __init__(self): + self._conf_key_ids = {} + + def create_random_key(self, test, auth_type=BFDAuthType.keyed_sha1): + conf_key_id = randint(0, 0xFFFFFFFF) + while conf_key_id in self._conf_key_ids: + conf_key_id = randint(0, 0xFFFFFFFF) + self._conf_key_ids[conf_key_id] = 1 + key = str(bytearray([randint(0, 255) for j in range(randint(1, 20))])) + return VppBFDAuthKey(test=test, auth_type=auth_type, + conf_key_id=conf_key_id, key=key) + + class BFDAPITestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) - API""" @@ -21,19 +39,23 @@ class BFDAPITestCase(VppTestCase): cls.create_pg_interfaces(range(2)) for i in cls.pg_interfaces: i.config_ip4() + i.config_ip6() i.resolve_arp() except Exception: super(BFDAPITestCase, cls).tearDownClass() raise + def setUp(self): + super(BFDAPITestCase, self).setUp() + self.factory = AuthKeyFactory() + def test_add_bfd(self): """ create a BFD session """ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() self.logger.debug("Session state is %s" % str(session.state)) session.remove_vpp_config() - session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() self.logger.debug("Session state is %s" % str(session.state)) session.remove_vpp_config() @@ -48,25 +70,155 @@ class BFDAPITestCase(VppTestCase): session.remove_vpp_config() - def test_add_two(self): - """ create two BFD sessions """ - session1 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) - session1.add_vpp_config() - session2 = VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4) - session2.add_vpp_config() - self.assertNotEqual(session1.bs_index, session2.bs_index, - "Different BFD sessions share bs_index (%s)" % - session1.bs_index) + def test_add_bfd6(self): + """ create IPv6 BFD session """ + session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip6, af=AF_INET6) + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + + def test_add_sha1_keys(self): + """ add SHA1 keys """ + key_count = 10 + keys = [self.factory.create_random_key( + self) for i in range(0, key_count)] + for key in keys: + self.assertFalse(key.query_vpp_config()) + for key in keys: + key.add_vpp_config() + for key in keys: + self.assertTrue(key.query_vpp_config()) + # remove randomly + indexes = range(key_count) + random.shuffle(indexes) + removed = [] + for i in indexes: + key = keys[i] + key.remove_vpp_config() + removed.append(i) + for j in range(key_count): + key = keys[j] + if j in removed: + self.assertFalse(key.query_vpp_config()) + else: + self.assertTrue(key.query_vpp_config()) + # should be removed now + for key in keys: + self.assertFalse(key.query_vpp_config()) + # add back and remove again + for key in keys: + key.add_vpp_config() + for key in keys: + self.assertTrue(key.query_vpp_config()) + for key in keys: + key.remove_vpp_config() + for key in keys: + self.assertFalse(key.query_vpp_config()) + + def test_add_bfd_sha1(self): + """ create a BFD session (SHA1) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key) + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + + def test_double_add_sha1(self): + """ create the same BFD session twice (negative case) (SHA1) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key) + session.add_vpp_config() + with self.assertRaises(Exception): + session.add_vpp_config() + + def test_add_authenticated_with_nonexistent_key(self): + """ create BFD session using non-existent SHA1 (negative case) """ + session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, + sha1_key=self.factory.create_random_key(self)) + with self.assertRaises(Exception): + session.add_vpp_config() + + def test_shared_sha1_key(self): + """ share single SHA1 key between multiple BFD sessions """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + sessions = [ + VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key), + VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, + sha1_key=key, af=AF_INET6), + VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4, + sha1_key=key), + VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6, + sha1_key=key, af=AF_INET6)] + for s in sessions: + s.add_vpp_config() + removed = 0 + for s in sessions: + e = key.get_bfd_auth_keys_dump_entry() + self.assert_equal(e.use_count, len(sessions) - removed, + "Use count for shared key") + s.remove_vpp_config() + removed += 1 + e = key.get_bfd_auth_keys_dump_entry() + self.assert_equal(e.use_count, len(sessions) - removed, + "Use count for shared key") + + def test_activate_auth(self): + """ activate SHA1 authentication """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + session.activate_auth(key) + + def test_deactivate_auth(self): + """ deactivate SHA1 authentication """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + session.activate_auth(key) + session.deactivate_auth() + + def test_change_key(self): + key1 = self.factory.create_random_key(self) + key2 = self.factory.create_random_key(self) + while key2.conf_key_id == key1.conf_key_id: + key2 = self.factory.create_random_key(self) + key1.add_vpp_config() + key2.add_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, + sha1_key=key1) + session.add_vpp_config() + session.activate_auth(key2) class BFDTestSession(object): """ BFD session as seen from test framework side """ - def __init__(self, test, interface, af, detect_mult=3): + def __init__(self, test, interface, af, detect_mult=3, sha1_key=None, + bfd_key_id=None, our_seq_number=0xFFFFFFFF - 4): self.test = test self.af = af + self.sha1_key = sha1_key + self.bfd_key_id = bfd_key_id self.interface = interface self.udp_sport = 50000 + self.our_seq_number = our_seq_number + self.vpp_seq_number = None self.bfd_values = { 'my_discriminator': 0, 'desired_min_tx_interval': 100000, @@ -74,10 +226,25 @@ class BFDTestSession(object): 'diag': BFDDiagCode.no_diagnostic, } + def inc_seq_num(self): + if self.our_seq_number == 0xFFFFFFFF: + self.our_seq_number = 0 + else: + self.our_seq_number += 1 + def update(self, **kwargs): self.bfd_values.update(kwargs) def create_packet(self): + if self.sha1_key: + bfd = BFD(flags="A") + bfd.auth_type = self.sha1_key.auth_type + bfd.auth_len = BFD.sha1_auth_len + bfd.auth_key_id = self.bfd_key_id + bfd.auth_seq_num = self.our_seq_number + bfd.length = BFD.sha1_auth_len + BFD.bfd_pkt_len + else: + bfd = BFD() if self.af == AF_INET6: packet = (Ether(src=self.interface.remote_mac, dst=self.interface.local_mac) / @@ -85,7 +252,7 @@ class BFDTestSession(object): dst=self.interface.local_ip6, hlim=255) / UDP(sport=self.udp_sport, dport=BFD.udp_dport) / - BFD()) + bfd) else: packet = (Ether(src=self.interface.remote_mac, dst=self.interface.local_mac) / @@ -93,11 +260,17 @@ class BFDTestSession(object): dst=self.interface.local_ip4, ttl=255) / UDP(sport=self.udp_sport, dport=BFD.udp_dport) / - BFD()) + bfd) self.test.logger.debug("BFD: Creating packet") for name, value in self.bfd_values.iteritems(): self.test.logger.debug("BFD: setting packet.%s=%s", name, value) packet[BFD].setfieldval(name, value) + if self.sha1_key: + hash_material = str(packet[BFD])[:32] + self.sha1_key.key + \ + "\0" * (20 - len(self.sha1_key.key)) + self.test.logger.debug("BFD: Calculated SHA1 hash: %s" % + hashlib.sha1(hash_material).hexdigest()) + packet[BFD].auth_key_hash = hashlib.sha1(hash_material).digest() return packet def send_packet(self): @@ -106,13 +279,60 @@ class BFDTestSession(object): self.test.pg0.add_stream([p]) self.test.pg_start() - def verify_packet(self, packet): + def verify_sha1_auth(self, packet): + """ Verify correctness of authentication in BFD layer. """ + bfd = packet[BFD] + self.test.assert_equal(bfd.auth_len, 28, "Auth section length") + self.test.assert_equal(bfd.auth_type, self.sha1_key.auth_type, + BFDAuthType) + self.test.assert_equal(bfd.auth_key_id, self.bfd_key_id, "Key ID") + self.test.assert_equal(bfd.auth_reserved, 0, "Reserved") + if self.vpp_seq_number is None: + self.vpp_seq_number = bfd.auth_seq_num + self.test.logger.debug("Received initial sequence number: %s" % + self.vpp_seq_number) + else: + recvd_seq_num = bfd.auth_seq_num + self.test.logger.debug("Received followup sequence number: %s" % + recvd_seq_num) + if self.vpp_seq_number < 0xffffffff: + if self.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + self.test.assert_equal(recvd_seq_num, + self.vpp_seq_number + 1, + "BFD sequence number") + else: + self.test.assert_in_range(recvd_seq_num, + self.vpp_seq_number, + self.vpp_seq_number + 1, + "BFD sequence number") + else: + if self.sha1_key.auth_type == \ + BFDAuthType.meticulous_keyed_sha1: + self.test.assert_equal(recvd_seq_num, 0, + "BFD sequence number") + else: + self.test.assertIn(recvd_seq_num, (self.vpp_seq_number, 0), + "BFD sequence number not one of " + "(%s, 0)" % self.vpp_seq_number) + self.vpp_seq_number = recvd_seq_num + # last 20 bytes represent the hash - so replace them with the key, + # pad the result with zeros and hash the result + hash_material = bfd.original[:-20] + self.sha1_key.key + \ + "\0" * (20 - len(self.sha1_key.key)) + expected_hash = hashlib.sha1(hash_material).hexdigest() + self.test.assert_equal(binascii.hexlify(bfd.auth_key_hash), + expected_hash, "Auth key hash") + + def verify_bfd(self, packet): """ Verify correctness of BFD layer. """ bfd = packet[BFD] self.test.assert_equal(bfd.version, 1, "BFD version") self.test.assert_equal(bfd.your_discriminator, self.bfd_values['my_discriminator'], "BFD - your discriminator") + if self.sha1_key: + self.verify_sha1_auth(packet) class BFDCommonCode: @@ -122,9 +342,9 @@ class BFDCommonCode: self.vapi.collect_events() # clear the event queue if not self.vpp_dead: self.vapi.want_bfd_events(enable_disable=0) - self.vpp_session.remove_vpp_config() def bfd_session_up(self): + """ Bring BFD session up """ self.pg_enable_capture([self.pg0]) self.logger.info("BFD: Waiting for slow hello") p, timeout = self.wait_for_bfd_packet(2) @@ -139,6 +359,18 @@ class BFDCommonCode: self.verify_event(e, expected_state=BFDState.up) self.logger.info("BFD: Session is Up") self.test_session.update(state=BFDState.up) + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def bfd_session_down(self): + """ Bring BFD session down """ + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.test_session.update(state=BFDState.down) + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.down) + self.logger.info("BFD: Session is Down") + self.assert_equal(self.vpp_session.state, BFDState.down, BFDState) def verify_ip(self, packet): """ Verify correctness of IP layer. """ @@ -166,12 +398,9 @@ class BFDCommonCode: """ Verify correctness of event values. """ e = event self.logger.debug("BFD: Event: %s" % repr(e)) - self.assert_equal(e.bs_index, self.vpp_session.bs_index, - "BFD session index") - self.assert_equal( - e.sw_if_index, - self.vpp_session.interface.sw_if_index, - "BFD interface index") + self.assert_equal(e.sw_if_index, + self.vpp_session.interface.sw_if_index, + "BFD interface index") is_ipv6 = 0 if self.vpp_session.af == AF_INET6: is_ipv6 = 1 @@ -199,7 +428,7 @@ class BFDCommonCode: before = time.time() p = self.pg0.wait_for_packet(timeout=timeout) after = time.time() - self.logger.debug(ppp("Got packet:", p)) + self.logger.debug(ppp("BFD: Got packet:", p)) bfd = p[BFD] if bfd is None: raise Exception(ppp("Unexpected or invalid BFD packet:", p)) @@ -207,20 +436,9 @@ class BFDCommonCode: raise Exception(ppp("Unexpected payload in BFD packet:", bfd)) self.verify_ip(p) self.verify_udp(p) - self.test_session.verify_packet(p) + self.test_session.verify_bfd(p) return p, after - before - def test_session_up(self): - """ bring BFD session up """ - self.bfd_session_up() - - def test_hold_up(self): - """ hold BFD session up """ - self.bfd_session_up() - for i in range(5): - self.wait_for_bfd_packet() - self.test_session.send_packet() - class BFD4TestCase(VppTestCase, BFDCommonCode): """Bidirectional Forwarding Detection (BFD)""" @@ -231,7 +449,6 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): try: cls.create_pg_interfaces([0]) cls.pg0.config_ip4() - cls.pg0.generate_remote_hosts() cls.pg0.configure_ipv4_neighbors() cls.pg0.admin_up() cls.pg0.resolve_arp() @@ -242,6 +459,7 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): def setUp(self): super(BFD4TestCase, self).setUp() + self.factory = AuthKeyFactory() self.vapi.want_bfd_events() try: self.vpp_session = VppBFDUDPSession(self, self.pg0, @@ -255,7 +473,25 @@ class BFD4TestCase(VppTestCase, BFDCommonCode): def tearDown(self): BFDCommonCode.tearDown(self) - super(BFD4TestCase, self).tearDown() + VppTestCase.tearDown(self) + + def test_session_up(self): + """ bring BFD session up """ + self.bfd_session_up() + + def test_session_down(self): + """ bring BFD session down """ + self.bfd_session_up() + self.bfd_session_down() + + def test_hold_up(self): + """ hold BFD session up """ + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") def test_slow_timer(self): """ verify slow periodic control frames while session down """ @@ -367,6 +603,7 @@ class BFD6TestCase(VppTestCase, BFDCommonCode): def setUp(self): super(BFD6TestCase, self).setUp() + self.factory = AuthKeyFactory() self.vapi.want_bfd_events() try: self.vpp_session = VppBFDUDPSession(self, self.pg0, @@ -382,7 +619,429 @@ class BFD6TestCase(VppTestCase, BFDCommonCode): def tearDown(self): BFDCommonCode.tearDown(self) - super(BFD6TestCase, self).tearDown() + VppTestCase.tearDown(self) + + def test_session_up(self): + """ bring BFD session up """ + self.bfd_session_up() + + def test_hold_up(self): + """ hold BFD session up """ + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + +class BFDSHA1TestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """ + + @classmethod + def setUpClass(cls): + super(BFDSHA1TestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFDSHA1TestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFDSHA1TestCase, self).setUp() + self.factory = AuthKeyFactory() + self.vapi.want_bfd_events() + + def tearDown(self): + BFDCommonCode.tearDown(self) + VppTestCase.tearDown(self) + + def test_session_up(self): + """ bring BFD session up """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, + sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + + def test_hold_up(self): + """ hold BFD session up """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, + sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def test_hold_up_meticulous(self): + """ hold BFD session up - meticulous auth """ + key = self.factory.create_random_key( + self, BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.inc_seq_num() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def test_send_bad_seq_number(self): + """ session is not kept alive by msgs with bad seq numbers""" + key = self.factory.create_random_key( + self, BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.wait_for_bfd_packet() + self.test_session.send_packet() + e = self.vapi.collect_events() + # session should be down now, because the sequence numbers weren't + # updated + self.assert_equal(len(e), 1, "number of bfd events") + self.verify_event(e[0], expected_state=BFDState.down) + + def execute_rogue_session_scenario(self, vpp_bfd_udp_session, + legitimate_test_session, + rogue_test_session, + rogue_bfd_values=None): + """ execute a rogue session interaction scenario + + 1. create vpp session, add config + 2. bring the legitimate session up + 3. copy the bfd values from legitimate session to rogue session + 4. apply rogue_bfd_values to rogue session + 5. set rogue session state to down + 6. send message to take the session down from the rogue session + 7. assert that the legitimate session is unaffected + """ + + self.vpp_session = vpp_bfd_udp_session + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = legitimate_test_session + # bring vpp session up + self.bfd_session_up() + # send packet from rogue session + rogue_test_session.bfd_values = self.test_session.bfd_values.copy() + if rogue_bfd_values: + rogue_test_session.update(**rogue_bfd_values) + rogue_test_session.update(state=BFDState.down) + rogue_test_session.send_packet() + self.wait_for_bfd_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + + def test_mismatch_auth(self): + """ session is not brought down by unauthenticated msg """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + legitimate_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + rogue_test_session = BFDTestSession(self, self.pg0, AF_INET) + self.execute_rogue_session_scenario(vpp_session, + legitimate_test_session, + rogue_test_session) + + def test_mismatch_bfd_key_id(self): + """ session is not brought down by msg with non-existent key-id """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + # pick a different random bfd key id + x = randint(0, 255) + while x == vpp_session.bfd_key_id: + x = randint(0, 255) + legitimate_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + rogue_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=x) + self.execute_rogue_session_scenario(vpp_session, + legitimate_test_session, + rogue_test_session) + + def test_mismatched_auth_type(self): + """ session is not brought down by msg with wrong auth type """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + vpp_session = VppBFDUDPSession( + self, self.pg0, self.pg0.remote_ip4, sha1_key=key) + legitimate_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + rogue_test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=vpp_session.bfd_key_id) + self.execute_rogue_session_scenario( + vpp_session, legitimate_test_session, rogue_test_session, + {'auth_type': BFDAuthType.keyed_md5}) + + def test_restart(self): + """ simulate remote peer restart and resynchronization """ + key = self.factory.create_random_key( + self, BFDAuthType.meticulous_keyed_sha1) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0) + self.bfd_session_up() + # now we need to not respond for 2*detection_time (4 packets) + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.down) + self.test_session.update(state=BFDState.down) + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + self.wait_for_bfd_packet() + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + # reset sequence number + self.test_session.our_seq_number = 0 + self.bfd_session_up() + + +class BFDAuthOnOffTestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD) (changing auth) """ + + @classmethod + def setUpClass(cls): + super(BFDAuthOnOffTestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFDAuthOnOffTestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFDAuthOnOffTestCase, self).setUp() + self.factory = AuthKeyFactory() + self.vapi.want_bfd_events() + + def tearDown(self): + BFDCommonCode.tearDown(self) + VppTestCase.tearDown(self) + + def test_auth_on_immediate(self): + """ turn auth on without disturbing session state (immediate) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key) + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_off_immediate(self): + """ turn auth off without disturbing session state (immediate) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.deactivate_auth() + self.test_session.bfd_key_id = None + self.test_session.sha1_key = None + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_change_key_immediate(self): + """ change auth key without disturbing session state (immediate) """ + key1 = self.factory.create_random_key(self) + key1.add_vpp_config() + key2 = self.factory.create_random_key(self) + key2.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key1) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key1, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key2) + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key2 + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_on_delayed(self): + """ turn auth on without disturbing session state (delayed) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key, delayed=True) + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key + self.test_session.send_packet() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_off_delayed(self): + """ turn auth off without disturbing session state (delayed) """ + key = self.factory.create_random_key(self) + key.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.deactivate_auth(delayed=True) + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.test_session.bfd_key_id = None + self.test_session.sha1_key = None + self.test_session.send_packet() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") + + def test_auth_change_key_delayed(self): + """ change auth key without disturbing session state (delayed) """ + key1 = self.factory.create_random_key(self) + key1.add_vpp_config() + key2 = self.factory.create_random_key(self) + key2.add_vpp_config() + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4, sha1_key=key1) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession( + self, self.pg0, AF_INET, sha1_key=key1, + bfd_key_id=self.vpp_session.bfd_key_id) + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.vpp_session.activate_auth(key2, delayed=True) + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.test_session.bfd_key_id = self.vpp_session.bfd_key_id + self.test_session.sha1_key = key2 + self.test_session.send_packet() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + self.assert_equal(self.vpp_session.state, BFDState.up, BFDState) + self.assert_equal(len(self.vapi.collect_events()), 0, + "number of bfd events") if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 90c954dc0b3..72c18e6cef7 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -992,16 +992,57 @@ class VppPapiProvider(object): self.api(self.papi.control_ping) def bfd_udp_add(self, sw_if_index, desired_min_tx, required_min_rx, - detect_mult, local_addr, peer_addr, is_ipv6=0): - return self.api(self.papi.bfd_udp_add, + detect_mult, local_addr, peer_addr, is_ipv6=0, + bfd_key_id=None, conf_key_id=None): + if bfd_key_id is None: + return self.api(self.papi.bfd_udp_add, + { + 'sw_if_index': sw_if_index, + 'desired_min_tx': desired_min_tx, + 'required_min_rx': required_min_rx, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'detect_mult': detect_mult, + }) + else: + return self.api(self.papi.bfd_udp_add, + { + 'sw_if_index': sw_if_index, + 'desired_min_tx': desired_min_tx, + 'required_min_rx': required_min_rx, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'detect_mult': detect_mult, + 'is_authenticated': 1, + 'bfd_key_id': bfd_key_id, + 'conf_key_id': conf_key_id, + }) + + def bfd_udp_auth_activate(self, sw_if_index, local_addr, peer_addr, + is_ipv6=0, bfd_key_id=None, conf_key_id=None, + is_delayed=False): + return self.api(self.papi.bfd_udp_auth_activate, { 'sw_if_index': sw_if_index, - 'desired_min_tx': desired_min_tx, - 'required_min_rx': required_min_rx, 'local_addr': local_addr, 'peer_addr': peer_addr, 'is_ipv6': is_ipv6, - 'detect_mult': detect_mult, + 'is_delayed': 1 if is_delayed else 0, + 'bfd_key_id': bfd_key_id, + 'conf_key_id': conf_key_id, + }) + + def bfd_udp_auth_deactivate(self, sw_if_index, local_addr, peer_addr, + is_ipv6=0, is_delayed=False): + return self.api(self.papi.bfd_udp_auth_deactivate, + { + 'sw_if_index': sw_if_index, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'is_delayed': 1 if is_delayed else 0, }) def bfd_udp_del(self, sw_if_index, local_addr, peer_addr, is_ipv6=0): @@ -1016,10 +1057,14 @@ class VppPapiProvider(object): def bfd_udp_session_dump(self): return self.api(self.papi.bfd_udp_session_dump, {}) - def bfd_session_set_flags(self, bs_idx, admin_up_down): - return self.api(self.papi.bfd_session_set_flags, { - 'bs_index': bs_idx, + def bfd_udp_session_set_flags(self, admin_up_down, sw_if_index, local_addr, + peer_addr, is_ipv6=0): + return self.api(self.papi.bfd_udp_session_set_flags, { 'admin_up_down': admin_up_down, + 'sw_if_index': sw_if_index, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, }) def want_bfd_events(self, enable_disable=1): @@ -1028,6 +1073,22 @@ class VppPapiProvider(object): 'pid': os.getpid(), }) + def bfd_auth_set_key(self, conf_key_id, auth_type, key): + return self.api(self.papi.bfd_auth_set_key, { + 'conf_key_id': conf_key_id, + 'auth_type': auth_type, + 'key': key, + 'key_len': len(key), + }) + + def bfd_auth_del_key(self, conf_key_id): + return self.api(self.papi.bfd_auth_del_key, { + 'conf_key_id': conf_key_id, + }) + + def bfd_auth_keys_dump(self): + return self.api(self.papi.bfd_auth_keys_dump, {}) + def classify_add_del_table( self, is_add, |