diff options
author | Klement Sekera <ksekera@cisco.com> | 2017-01-09 07:43:48 +0100 |
---|---|---|
committer | Damjan Marion <dmarion.lists@gmail.com> | 2017-02-02 12:04:30 +0000 |
commit | b17dd9607ee8ecba5ae3ef69c7b4915b57de292a (patch) | |
tree | aa8d22ac4a9ea3adc2f96c4d754273af505950d5 | |
parent | 402ed3128512efc091a560729ce1e772a86e9f74 (diff) |
BFD: SHA1 authentication
Add authentication support to BFD feature. Out of three existing
authentication types, implement SHA1 (sole RFC requirement). Simple
password is insecure and MD5 is discouraged by the RFC, so ignore
those.
Add/change APIs to allow configuring BFD authentication keys
and their usage with BFD sessions.
Change-Id: Ifb0fb5b19c2e72196d84c1cde919bd4c074ea415
Signed-off-by: Klement Sekera <ksekera@cisco.com>
-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, |