From 18322efd130a7ce60a2c8b731494d12edb1c937e Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Tue, 19 May 2020 19:30:21 -0300 Subject: [PATCH 1/9] bfdd,lib: implement protocol profile selection Implement the infrastructure for other protocols daemon (e.g. `bgpd`, `ospfd`, `isisd` etc...) to communicate to BFD daemon which profile they want to use with their peers. It was also added the ability for protocols to change profile while running (no need to remove the registration and then register again). The protocols message building function was rewritten to support multiple arguments through `struct bfd_session_arg`, so we can implement new features without the need of changing function prototypes. The old function was also rewritten to keep compatibility. The profile message part is only available for BFD daemon at the moment. Signed-off-by: Rafael Zalamena --- bfdd/bfd.c | 9 +++ bfdd/bfdctl.h | 3 + bfdd/ptm_adapter.c | 35 ++++++-- lib/bfd.c | 193 ++++++++++++++++++++++++++------------------- lib/bfd.h | 85 ++++++++++++++++++++ 5 files changed, 239 insertions(+), 86 deletions(-) diff --git a/bfdd/bfd.c b/bfdd/bfd.c index a021b5cabc..f51136e980 100644 --- a/bfdd/bfd.c +++ b/bfdd/bfd.c @@ -765,6 +765,15 @@ static void _bfd_session_update(struct bfd_session *bs, */ bs->peer_profile.admin_shutdown = bpc->bpc_shutdown; bfd_set_shutdown(bs, bpc->bpc_shutdown); + + /* + * Apply profile last: it also calls `bfd_set_shutdown`. + * + * There is no problem calling `shutdown` twice if the value doesn't + * change or if it is overriden by peer specific configuration. + */ + if (bpc->bpc_has_profile) + bfd_profile_apply(bpc->bpc_profile, bs); } static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h index 4ce23a8f27..95cfcb1105 100644 --- a/bfdd/bfdctl.h +++ b/bfdd/bfdctl.h @@ -90,6 +90,9 @@ struct bfd_peer_cfg { bool bpc_cbit; + bool bpc_has_profile; + char bpc_profile[64]; + /* Status information */ enum bfd_peer_status bpc_bps; uint32_t bpc_id; diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c index 25938dd9f5..0abbdf3dcb 100644 --- a/bfdd/ptm_adapter.c +++ b/bfdd/ptm_adapter.c @@ -85,6 +85,7 @@ static void debug_printbpc(const struct bfd_peer_cfg *bpc, const char *fmt, ...) { char timers[3][128] = {}; char addr[3][128] = {}; + char profile[128] = {}; char cbit_str[32]; char msgbuf[256]; va_list vl; @@ -119,13 +120,17 @@ static void debug_printbpc(const struct bfd_peer_cfg *bpc, const char *fmt, ...) snprintf(cbit_str, sizeof(cbit_str), " cbit:0x%02x", bpc->bpc_cbit); + if (bpc->bpc_has_profile) + snprintf(profile, sizeof(profile), " profile:%s", + bpc->bpc_profile); + va_start(vl, fmt); vsnprintf(msgbuf, sizeof(msgbuf), fmt, vl); va_end(vl); - zlog_debug("%s [mhop:%s %s%s%s%s%s%s%s]", msgbuf, + zlog_debug("%s [mhop:%s %s%s%s%s%s%s%s%s]", msgbuf, bpc->bpc_mhop ? "yes" : "no", addr[0], addr[1], addr[2], - timers[0], timers[1], timers[2], cbit_str); + timers[0], timers[1], timers[2], cbit_str, profile); } static int _ptm_msg_address(struct stream *msg, int family, const void *addr) @@ -301,6 +306,8 @@ static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id, * - c: ifname length * - X bytes: interface name * - c: bfd_cbit + * - c: profile name length. + * - X bytes: profile name. * * q(64), l(32), w(16), c(8) */ @@ -381,6 +388,14 @@ static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id, STREAM_GETC(msg, bpc->bpc_cbit); + /* Handle profile names. */ + STREAM_GETC(msg, ifnamelen); + bpc->bpc_has_profile = ifnamelen > 0; + if (bpc->bpc_has_profile) { + STREAM_GET(bpc->bpc_profile, msg, ifnamelen); + bpc->bpc_profile[ifnamelen] = 0; + } + /* Sanity check: peer and local address must match IP types. */ if (bpc->bpc_local.sa_sin.sin_family != 0 && (bpc->bpc_local.sa_sin.sin_family @@ -421,10 +436,18 @@ static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id) /* Protocol created peers are 'no shutdown' by default. */ bs->peer_profile.admin_shutdown = false; } else { - /* Don't try to change echo/shutdown state. */ - bpc.bpc_echo = CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); - bpc.bpc_shutdown = - CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + /* + * BFD session was already created, we are just updating the + * current peer. + * + * `ptm-bfd` (or `HAVE_BFDD == 0`) is the only implementation + * that allow users to set peer specific timers via protocol. + * BFD daemon (this code) on the other hand only supports + * changing peer configuration manually (through `peer` node) + * or via profiles. + */ + if (bpc.bpc_has_profile) + bfd_profile_apply(bpc.bpc_profile, bs); } /* Create client peer notification register. */ diff --git a/lib/bfd.c b/lib/bfd.c index 5f2d2f0eda..2d86acbbf4 100644 --- a/lib/bfd.c +++ b/lib/bfd.c @@ -127,9 +127,8 @@ void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info, int ttl, int multihop, int cbit, int command, int set_flag, vrf_id_t vrf_id) { - struct stream *s; - int ret; - int len; + struct bfd_session_arg args = {}; + size_t addrlen; /* Individual reg/dereg messages are suppressed during shutdown. */ if (CHECK_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN)) { @@ -150,86 +149,30 @@ void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info, return; } - s = zclient->obuf; - stream_reset(s); - zclient_create_header(s, command, vrf_id); + /* Fill in all arguments. */ + args.ttl = ttl; + args.cbit = cbit; + args.family = family; + args.mhop = multihop; + args.vrf_id = vrf_id; + args.command = command; + args.set_flag = set_flag; + args.bfd_info = bfd_info; + args.min_rx = bfd_info->required_min_rx; + args.min_tx = bfd_info->desired_min_tx; + args.detection_multiplier = bfd_info->detect_mult; - stream_putl(s, getpid()); + addrlen = family == AF_INET ? sizeof(struct in_addr) + : sizeof(struct in6_addr); + memcpy(&args.dst, dst_ip, addrlen); + if (src_ip) + memcpy(&args.src, src_ip, addrlen); - stream_putw(s, family); - switch (family) { - case AF_INET: - stream_put_in_addr(s, (struct in_addr *)dst_ip); - break; - case AF_INET6: - stream_put(s, dst_ip, 16); - break; - default: - break; - } + if (if_name) + args.ifnamelen = + strlcpy(args.ifname, if_name, sizeof(args.ifname)); - if (command != ZEBRA_BFD_DEST_DEREGISTER) { - stream_putl(s, bfd_info->required_min_rx); - stream_putl(s, bfd_info->desired_min_tx); - stream_putc(s, bfd_info->detect_mult); - } - - if (multihop) { - stream_putc(s, 1); - /* Multi-hop destination send the source IP address to BFD */ - if (src_ip) { - stream_putw(s, family); - switch (family) { - case AF_INET: - stream_put_in_addr(s, (struct in_addr *)src_ip); - break; - case AF_INET6: - stream_put(s, src_ip, 16); - break; - default: - break; - } - } - stream_putc(s, ttl); - } else { - stream_putc(s, 0); - if ((family == AF_INET6) && (src_ip)) { - stream_putw(s, family); - stream_put(s, src_ip, 16); - } - if (if_name) { - len = strlen(if_name); - stream_putc(s, len); - stream_put(s, if_name, len); - } else { - stream_putc(s, 0); - } - } - /* cbit */ - if (cbit) - stream_putc(s, 1); - else - stream_putc(s, 0); - - stream_putw_at(s, 0, stream_get_endp(s)); - - ret = zclient_send_message(zclient); - - if (ret < 0) { - if (bfd_debug) - zlog_debug( - "bfd_peer_sendmsg: zclient_send_message() failed"); - return; - } - - if (set_flag) { - if (command == ZEBRA_BFD_DEST_REGISTER) - SET_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG); - else if (command == ZEBRA_BFD_DEST_DEREGISTER) - UNSET_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG); - } - - return; + zclient_bfd_command(zclient, &args); } /* @@ -478,3 +421,93 @@ void bfd_client_sendmsg(struct zclient *zclient, int command, return; } + +int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args) +{ + struct stream *s; + size_t addrlen; + + /* Check socket. */ + if (!zc || zc->sock < 0) { + if (bfd_debug) + zlog_debug("%s: zclient unavailable", __func__); + return -1; + } + + s = zc->obuf; + stream_reset(s); + + /* Create new message. */ + zclient_create_header(s, args->command, args->vrf_id); + stream_putl(s, getpid()); + + /* Encode destination address. */ + stream_putw(s, args->family); + addrlen = (args->family == AF_INET) ? sizeof(struct in_addr) + : sizeof(struct in6_addr); + stream_put(s, &args->dst, addrlen); + + /* Encode timers if this is a registration message. */ + if (args->command != ZEBRA_BFD_DEST_DEREGISTER) { + stream_putl(s, args->min_rx); + stream_putl(s, args->min_tx); + stream_putc(s, args->detection_multiplier); + } + + if (args->mhop) { + /* Multi hop indicator. */ + stream_putc(s, 1); + + /* Multi hop always sends the source address. */ + stream_putw(s, args->family); + stream_put(s, &args->src, addrlen); + + /* Send the expected TTL. */ + stream_putc(s, args->ttl); + } else { + /* Multi hop indicator. */ + stream_putc(s, 0); + + /* Single hop only sends the source address when IPv6. */ + if (args->family == AF_INET6) { + stream_putw(s, args->family); + stream_put(s, &args->src, addrlen); + } + + /* Send interface name if any. */ + stream_putc(s, args->ifnamelen); + if (args->ifnamelen) + stream_put(s, args->ifname, args->ifnamelen); + } + + /* Send the C bit indicator. */ + stream_putc(s, args->cbit); + + /* `ptm-bfd` doesn't support profiles yet. */ +#if HAVE_BFDD > 0 + /* Send profile name if any. */ + stream_putc(s, args->profilelen); + if (args->profilelen) + stream_put(s, args->profile, args->profilelen); +#endif /* HAVE_BFDD */ + + /* Finish the message by writing the size. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + /* Send message to zebra. */ + if (zclient_send_message(zc) == -1) { + if (bfd_debug) + zlog_debug("%s: zclient_send_message failed", __func__); + return -1; + } + + /* Write registration indicator into data structure. */ + if (args->set_flag) { + if (args->command == ZEBRA_BFD_DEST_REGISTER) + SET_FLAG(args->bfd_info->flags, BFD_FLAG_BFD_REG); + else if (args->command == ZEBRA_BFD_DEST_DEREGISTER) + UNSET_FLAG(args->bfd_info->flags, BFD_FLAG_BFD_REG); + } + + return 0; +} diff --git a/lib/bfd.h b/lib/bfd.h index 7f5d111504..d7d4b5fe35 100644 --- a/lib/bfd.h +++ b/lib/bfd.h @@ -56,6 +56,8 @@ struct bfd_gbl { #define BFD_STATUS_UP (1 << 2) /* BFD session status is up */ #define BFD_STATUS_ADMIN_DOWN (1 << 3) /* BFD session is admin down */ +#define BFD_PROFILE_NAME_LEN 64 + #define BFD_SET_CLIENT_STATUS(current_status, new_status) \ do { \ (current_status) = \ @@ -77,6 +79,7 @@ struct bfd_info { time_t last_update; uint8_t status; enum bfd_sess_type type; + char profile[BFD_PROFILE_NAME_LEN]; }; extern struct bfd_info *bfd_info_create(void); @@ -120,6 +123,88 @@ extern void bfd_gbl_init(void); extern void bfd_gbl_exit(void); + +/* + * BFD new API. + */ + +/** + * BFD session registration arguments. + */ +struct bfd_session_arg { + /** + * BFD command. + * + * Valid commands: + * - `ZEBRA_BFD_DEST_REGISTER` + * - `ZEBRA_BFD_DEST_DEREGISTER` + */ + int32_t command; + + /** + * BFD family type. + * + * Supported types: + * - `AF_INET` + * - `AF_INET6`. + */ + uint32_t family; + /** Source address. */ + struct in6_addr src; + /** Source address. */ + struct in6_addr dst; + + /** Multi hop indicator. */ + uint8_t mhop; + /** Expected TTL. */ + uint8_t ttl; + /** C bit (Control Plane Independent bit) indicator. */ + uint8_t cbit; + + /** Interface name size. */ + uint8_t ifnamelen; + /** Interface name. */ + char ifname[64]; + + /** Daemon or session VRF. */ + vrf_id_t vrf_id; + + /** Profile name length. */ + uint8_t profilelen; + /** Profile name. */ + char profile[BFD_PROFILE_NAME_LEN]; + + /* + * Deprecation fields: these fields should be removed once `ptm-bfd` + * no longer uses this interface. + */ + + /** Minimum required receive interval (in microseconds). */ + uint32_t min_rx; + /** Minimum desired transmission interval (in microseconds). */ + uint32_t min_tx; + /** Detection multiplier. */ + uint32_t detection_multiplier; + + /** BFD client information output. */ + struct bfd_info *bfd_info; + + /** Write registration indicator. */ + uint8_t set_flag; +}; + +/** + * Send a message to BFD daemon through the zebra client. + * + * \param zc the zebra client context. + * \param arg the BFD session command arguments. + * + * \returns `-1` on failure otherwise `0`. + * + * \see bfd_session_arg. + */ +extern int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *arg); + #ifdef __cplusplus } #endif From 02012befe8cf5589ba4190c832b6b2488d0ab79b Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Tue, 19 May 2020 20:15:20 -0300 Subject: [PATCH 2/9] bgpd: support BFD profiles configuration Allow BGP to use the new API to configure BFD session profiles. Now it is possible to preconfigure BFD sessions without needing to create the peers. Signed-off-by: Rafael Zalamena --- bgpd/bgp_bfd.c | 180 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 163 insertions(+), 17 deletions(-) diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c index a200589bd3..4e237120a1 100644 --- a/bgpd/bgp_bfd.c +++ b/bgpd/bgp_bfd.c @@ -95,9 +95,11 @@ bool bgp_bfd_is_peer_multihop(struct peer *peer) */ static void bgp_bfd_peer_sendmsg(struct peer *peer, int command) { + struct bfd_session_arg arg = {}; struct bfd_info *bfd_info; - int multihop, cbit = 0; + int multihop; vrf_id_t vrf_id; + size_t addrlen; bfd_info = (struct bfd_info *)peer->bfd_info; @@ -121,21 +123,49 @@ static void bgp_bfd_peer_sendmsg(struct peer *peer, int command) && !CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE)) SET_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); - cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); + /* Set all message arguments. */ + arg.family = peer->su.sa.sa_family; + addrlen = arg.family == AF_INET ? sizeof(struct in_addr) + : sizeof(struct in6_addr); - if (peer->su.sa.sa_family == AF_INET) - bfd_peer_sendmsg( - zclient, bfd_info, AF_INET, &peer->su.sin.sin_addr, - (peer->su_local) ? &peer->su_local->sin.sin_addr : NULL, - (peer->nexthop.ifp) ? peer->nexthop.ifp->name : NULL, - peer->ttl, multihop, cbit, command, 1, vrf_id); - else if (peer->su.sa.sa_family == AF_INET6) - bfd_peer_sendmsg( - zclient, bfd_info, AF_INET6, &peer->su.sin6.sin6_addr, - (peer->su_local) ? &peer->su_local->sin6.sin6_addr - : NULL, - (peer->nexthop.ifp) ? peer->nexthop.ifp->name : NULL, - peer->ttl, multihop, cbit, command, 1, vrf_id); + if (arg.family == AF_INET) + memcpy(&arg.dst, &peer->su.sin.sin_addr, addrlen); + else + memcpy(&arg.dst, &peer->su.sin6.sin6_addr, addrlen); + + if (peer->su_local) { + if (arg.family == AF_INET) + memcpy(&arg.src, &peer->su_local->sin.sin_addr, + addrlen); + else + memcpy(&arg.src, &peer->su_local->sin6.sin6_addr, + addrlen); + } + + if (peer->nexthop.ifp) { + arg.ifnamelen = strlen(peer->nexthop.ifp->name); + strlcpy(arg.ifname, peer->nexthop.ifp->name, + sizeof(arg.ifname)); + } + + if (bfd_info->profile[0]) { + arg.profilelen = strlen(bfd_info->profile); + strlcpy(arg.profile, bfd_info->profile, sizeof(arg.profile)); + } + + arg.set_flag = 1; + arg.mhop = multihop; + arg.ttl = peer->ttl; + arg.vrf_id = vrf_id; + arg.command = command; + arg.bfd_info = bfd_info; + arg.min_tx = bfd_info->desired_min_tx; + arg.min_rx = bfd_info->required_min_rx; + arg.detection_multiplier = bfd_info->detect_mult; + arg.cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); + + /* Send message. */ + zclient_bfd_command(zclient, &arg); } /* @@ -553,6 +583,61 @@ static int bgp_bfd_peer_param_type_set(struct peer *peer, return 0; } +/** + * Set peer BFD profile configuration. + */ +static int bgp_bfd_peer_set_profile(struct peer *peer, const char *profile) +{ + struct peer_group *group; + struct listnode *node, *nnode; + int command = 0; + struct bfd_info *bfd_info; + + bfd_set_param((struct bfd_info **)&(peer->bfd_info), BFD_DEF_MIN_RX, + BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1, &command); + + bfd_info = (struct bfd_info *)peer->bfd_info; + + /* If profile was specified, then copy string. */ + if (profile) + strlcpy(bfd_info->profile, profile, sizeof(bfd_info->profile)); + else /* Otherwise just initialize it empty. */ + bfd_info->profile[0] = 0; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + group = peer->group; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + command = 0; + bfd_set_param((struct bfd_info **)&(peer->bfd_info), + BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, + BFD_DEF_DETECT_MULT, 1, &command); + + bfd_info = (struct bfd_info *)peer->bfd_info; + + /* If profile was specified, then copy string. */ + if (profile) + strlcpy(bfd_info->profile, profile, + sizeof(bfd_info->profile)); + else /* Otherwise just initialize it empty. */ + bfd_info->profile[0] = 0; + + if (peer->status == Established + && command == ZEBRA_BFD_DEST_REGISTER) + bgp_bfd_register_peer(peer); + else if (command == ZEBRA_BFD_DEST_UPDATE) + bgp_bfd_update_peer(peer); + } + } else { + if (peer->status == Established + && command == ZEBRA_BFD_DEST_REGISTER) + bgp_bfd_register_peer(peer); + else if (command == ZEBRA_BFD_DEST_UPDATE) + bgp_bfd_update_peer(peer); + } + + return 0; +} + /* * bgp_bfd_peer_config_write - Write the peer BFD configuration. */ @@ -580,8 +665,12 @@ void bgp_bfd_peer_config_write(struct vty *vty, struct peer *peer, char *addr) : "singlehop"); if (!CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG) - && (bfd_info->type == BFD_TYPE_NOT_CONFIGURED)) - vty_out(vty, " neighbor %s bfd\n", addr); + && (bfd_info->type == BFD_TYPE_NOT_CONFIGURED)) { + vty_out(vty, " neighbor %s bfd", addr); + if (bfd_info->profile[0]) + vty_out(vty, " profile %s", bfd_info->profile); + vty_out(vty, "\n"); + } if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE)) vty_out(vty, " neighbor %s bfd check-control-plane-failure\n", addr); @@ -824,6 +913,58 @@ DEFUN_HIDDEN (no_neighbor_bfd_type, return CMD_SUCCESS; } +#if HAVE_BFDD > 0 +DEFUN(neighbor_bfd_profile, neighbor_bfd_profile_cmd, + "neighbor bfd profile BFDPROF", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BFD integration\n" + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + int idx_peer = 1, idx_prof = 4; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = bgp_bfd_peer_set_profile(peer, argv[idx_prof]->arg); + if (ret != 0) + return bgp_vty_return(vty, ret); + + return CMD_SUCCESS; +} + +DEFUN(no_neighbor_bfd_profile, no_neighbor_bfd_profile_cmd, + "no neighbor bfd profile [BFDPROF]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BFD integration\n" + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + int idx_peer = 2; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!peer->bfd_info) + return 0; + + ret = bgp_bfd_peer_set_profile(peer, NULL); + if (ret != 0) + return bgp_vty_return(vty, ret); + + return CMD_SUCCESS; +} +#endif /* HAVE_BFDD */ + void bgp_bfd_init(void) { bfd_gbl_init(); @@ -839,4 +980,9 @@ void bgp_bfd_init(void) install_element(BGP_NODE, &neighbor_bfd_check_controlplane_failure_cmd); install_element(BGP_NODE, &no_neighbor_bfd_cmd); install_element(BGP_NODE, &no_neighbor_bfd_type_cmd); + +#if HAVE_BFDD > 0 + install_element(BGP_NODE, &neighbor_bfd_profile_cmd); + install_element(BGP_NODE, &no_neighbor_bfd_profile_cmd); +#endif /* HAVE_BFDD */ } From 55d1a98423a789f85acae911926e1d5b67c3d8c7 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Thu, 21 May 2020 08:26:27 -0300 Subject: [PATCH 3/9] doc: document BGP BFD command with profile Let the user know its possible to create BFD sessions with profiles applied to it or apply profile to already created sessions. Signed-off-by: Rafael Zalamena --- doc/user/bfd.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/user/bfd.rst b/doc/user/bfd.rst index 40c2247b5b..b34bcba34d 100644 --- a/doc/user/bfd.rst +++ b/doc/user/bfd.rst @@ -232,6 +232,20 @@ The following commands are available inside the BGP configuration node. Disallow to write CBIT independence in BFD outgoing packets. Also disallow to ignore BFD down notification. This is the default behaviour. + +.. index:: neighbor bfd profile BFDPROF +.. clicmd:: neighbor bfd profile BFDPROF + + Same as command ``neighbor bfd``, but applies the + BFD profile to the sessions it creates or that already exist. + + +.. index:: no neighbor bfd profile BFDPROF +.. clicmd:: no neighbor bfd profile BFDPROF + + Removes the BFD profile configuration from peer session(s). + + .. _bfd-ospf-peer-config: OSPF BFD Configuration From 68286f96197c7d1775eb75acc6713b05e57a95d7 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Thu, 21 May 2020 16:00:12 -0300 Subject: [PATCH 4/9] bgpd: command to remove profile configuration To remove a BFD profile without removing the BFD configuration just call `neighbor bfd`. Signed-off-by: Rafael Zalamena --- bgpd/bgp_bfd.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c index 4e237120a1..74ff6f4472 100644 --- a/bgpd/bgp_bfd.c +++ b/bgpd/bgp_bfd.c @@ -474,6 +474,7 @@ static int bgp_bfd_peer_param_set(struct peer *peer, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults) { + struct bfd_info *bi; struct peer_group *group; struct listnode *node, *nnode; int command = 0; @@ -481,6 +482,10 @@ static int bgp_bfd_peer_param_set(struct peer *peer, uint32_t min_rx, bfd_set_param((struct bfd_info **)&(peer->bfd_info), min_rx, min_tx, detect_mult, defaults, &command); + /* This command overrides profile if it was previously applied. */ + bi = peer->bfd_info; + bi->profile[0] = 0; + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { @@ -489,6 +494,13 @@ static int bgp_bfd_peer_param_set(struct peer *peer, uint32_t min_rx, min_rx, min_tx, detect_mult, defaults, &command); + /* + * This command overrides profile if it was previously + * applied. + */ + bi = peer->bfd_info; + bi->profile[0] = 0; + if ((peer->status == Established) && (command == ZEBRA_BFD_DEST_REGISTER)) bgp_bfd_register_peer(peer); From 95a99382cbeb756344c02aef44f862abee53c3b8 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Thu, 21 May 2020 16:02:44 -0300 Subject: [PATCH 5/9] bgpd: fix crash on daemon exit Don't attempt to send BFD daemon a message to remove the peer registration on daemon exit, otherwise we'll access a dangling interface pointer and we'll crash. This crash was not previosly possible because the function that built the message was passing the interface pointer but not using it due to the exit condition. In `lib/bfd.c`: ``` void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info, int family, void *dst_ip, void *src_ip, char *if_name, int ttl, int multihop, int cbit, int command, int set_flag, vrf_id_t vrf_id) { struct bfd_session_arg args = {}; size_t addrlen; /* Individual reg/dereg messages are suppressed during shutdown. */ if (CHECK_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN)) { if (bfd_debug) zlog_debug( "%s: Suppressing BFD peer reg/dereg messages", __func__); return; } ``` Signed-off-by: Rafael Zalamena --- bgpd/bgp_bfd.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c index 74ff6f4472..ad31fd6418 100644 --- a/bgpd/bgp_bfd.c +++ b/bgpd/bgp_bfd.c @@ -101,6 +101,19 @@ static void bgp_bfd_peer_sendmsg(struct peer *peer, int command) vrf_id_t vrf_id; size_t addrlen; + /* + * XXX: some pointers are dangling during shutdown, so instead of + * trying to send a message during signal handlers lets just wait BGP + * to terminate zebra's connection and BFD will automatically find + * out that we are no longer expecting notifications. + * + * The pointer that is causing a crash here is `peer->nexthop.ifp`. + * That happens because at this point of the shutdown all interfaces are + * already `free()`d. + */ + if (bm->terminating) + return; + bfd_info = (struct bfd_info *)peer->bfd_info; vrf_id = peer->bgp->vrf_id; From f3e1d2241e0e96de95640bd8ecef5ff61843f3a5 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Fri, 29 May 2020 17:44:54 -0300 Subject: [PATCH 6/9] bfdd: don't update peers settings on shutdown During the shutdown phase don't attempt to apply settings to peers as it is useless and will crash if the peer hash is gone. Signed-off-by: Rafael Zalamena --- bfdd/bfd.c | 3 ++- bfdd/bfd.h | 6 ++++++ bfdd/bfdd.c | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bfdd/bfd.c b/bfdd/bfd.c index f51136e980..b8f25710dc 100644 --- a/bfdd/bfd.c +++ b/bfdd/bfd.c @@ -115,7 +115,8 @@ struct bfd_profile *bfd_profile_new(const char *name) void bfd_profile_free(struct bfd_profile *bp) { /* Detach from any session. */ - bfd_profile_detach(bp); + if (bglobal.bg_shutdown == false) + bfd_profile_detach(bp); /* Remove from global list. */ TAILQ_REMOVE(&bplist, bp, entry); diff --git a/bfdd/bfd.h b/bfdd/bfd.h index 5984662a01..492334a670 100644 --- a/bfdd/bfd.h +++ b/bfdd/bfd.h @@ -429,6 +429,12 @@ struct bfd_global { struct zebra_privs_t bfdd_privs; + /** + * Daemon is exit()ing? Use this to avoid actions that expect a + * running system or to avoid unnecessary operations when quitting. + */ + bool bg_shutdown; + /* Debug options. */ /* Show all peer state changes events. */ bool debug_peer_event; diff --git a/bfdd/bfdd.c b/bfdd/bfdd.c index 39d51eb649..9131417f17 100644 --- a/bfdd/bfdd.c +++ b/bfdd/bfdd.c @@ -63,6 +63,8 @@ static void sigusr1_handler(void) static void sigterm_handler(void) { + bglobal.bg_shutdown = true; + /* Signalize shutdown. */ frr_early_fini(); From c2afd32f36d1529d2e3f42b7fbf46e2efadb387b Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Fri, 29 May 2020 17:46:29 -0300 Subject: [PATCH 7/9] isisd: check interface pointer before accessing On some cases (protocol convergence down or daemon exit) we'll have the interface pointer in the circuit as `NULL`, so don't attempt to access it. Signed-off-by: Rafael Zalamena --- isisd/isis_bfd.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/isisd/isis_bfd.c b/isisd/isis_bfd.c index 0f0d20e393..a9a1ec7c47 100644 --- a/isisd/isis_bfd.c +++ b/isisd/isis_bfd.c @@ -263,9 +263,10 @@ static void bfd_handle_adj_down(struct isis_adjacency *adj) ZEBRA_BFD_DEST_DEREGISTER); bfd_peer_sendmsg(zclient, NULL, adj->bfd_session->family, - &adj->bfd_session->dst_ip, - &adj->bfd_session->src_ip, - adj->circuit->interface->name, + &adj->bfd_session->dst_ip, &adj->bfd_session->src_ip, + (adj->circuit->interface) + ? adj->circuit->interface->name + : NULL, 0, /* ttl */ 0, /* multihop */ 1, /* control plane independent bit is on */ @@ -324,7 +325,9 @@ static void bfd_handle_adj_up(struct isis_adjacency *adj, int command) bfd_peer_sendmsg(zclient, circuit->bfd_info, adj->bfd_session->family, &adj->bfd_session->dst_ip, &adj->bfd_session->src_ip, - circuit->interface->name, + (adj->circuit->interface) + ? adj->circuit->interface->name + : NULL, 0, /* ttl */ 0, /* multihop */ 1, /* control plane independent bit is on */ From 6a6b1036579bf2ed1e8c71957e4dce2e4b09ae2c Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Sun, 31 May 2020 08:28:05 -0300 Subject: [PATCH 8/9] lib: permit BFD library users to pass NULL Add the proper handling for cases where user forgets or doesn't have the pointer needed to call the library function. Signed-off-by: Rafael Zalamena --- lib/bfd.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/bfd.c b/lib/bfd.c index 2d86acbbf4..7c84648d91 100644 --- a/lib/bfd.c +++ b/lib/bfd.c @@ -158,9 +158,11 @@ void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info, args.command = command; args.set_flag = set_flag; args.bfd_info = bfd_info; - args.min_rx = bfd_info->required_min_rx; - args.min_tx = bfd_info->desired_min_tx; - args.detection_multiplier = bfd_info->detect_mult; + if (args.bfd_info) { + args.min_rx = bfd_info->required_min_rx; + args.min_tx = bfd_info->desired_min_tx; + args.detection_multiplier = bfd_info->detect_mult; + } addrlen = family == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr); @@ -502,7 +504,7 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args) } /* Write registration indicator into data structure. */ - if (args->set_flag) { + if (args->bfd_info && args->set_flag) { if (args->command == ZEBRA_BFD_DEST_REGISTER) SET_FLAG(args->bfd_info->flags, BFD_FLAG_BFD_REG); else if (args->command == ZEBRA_BFD_DEST_DEREGISTER) From a1e0142dced66e806586856b969fd81e19c9756e Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Fri, 29 May 2020 17:44:02 -0300 Subject: [PATCH 9/9] topotest: add tests for BFD profiles Import a topology with some protocols that integrate with BFD. As other daemons get the new BFD profile support we can update the test to cover them. Signed-off-by: Rafael Zalamena --- .../topotests/bfd-profiles-topo1/__init__.py | 0 .../r1/bfd-peers-initial.json | 38 ++++ .../topotests/bfd-profiles-topo1/r1/bfdd.conf | 14 ++ .../bfd-profiles-topo1/r1/ospfd.conf | 8 + .../bfd-profiles-topo1/r1/zebra.conf | 9 + .../r2/bfd-peers-initial.json | 39 ++++ .../topotests/bfd-profiles-topo1/r2/bfdd.conf | 18 ++ .../topotests/bfd-profiles-topo1/r2/bgpd.conf | 19 ++ .../bfd-profiles-topo1/r2/zebra.conf | 10 + .../r3/bfd-peers-initial.json | 39 ++++ .../topotests/bfd-profiles-topo1/r3/bfdd.conf | 11 + .../topotests/bfd-profiles-topo1/r3/bgpd.conf | 13 ++ .../bfd-profiles-topo1/r3/isisd.conf | 15 ++ .../bfd-profiles-topo1/r3/zebra.conf | 12 ++ .../r4/bfd-peers-initial.json | 41 ++++ .../topotests/bfd-profiles-topo1/r4/bfdd.conf | 6 + .../topotests/bfd-profiles-topo1/r4/bgpd.conf | 17 ++ .../bfd-profiles-topo1/r4/isisd.conf | 15 ++ .../bfd-profiles-topo1/r4/ospf6d.conf | 8 + .../bfd-profiles-topo1/r4/zebra.conf | 12 ++ .../r5/bfd-peers-initial.json | 21 ++ .../topotests/bfd-profiles-topo1/r5/bfdd.conf | 11 + .../bfd-profiles-topo1/r5/ospf6d.conf | 8 + .../bfd-profiles-topo1/r5/zebra.conf | 6 + .../r6/bfd-peers-initial.json | 20 ++ .../topotests/bfd-profiles-topo1/r6/bfdd.conf | 11 + .../bfd-profiles-topo1/r6/ospfd.conf | 8 + .../bfd-profiles-topo1/r6/zebra.conf | 6 + .../test_bfd_profiles_topo1.dot | 97 +++++++++ .../test_bfd_profiles_topo1.png | Bin 0 -> 43508 bytes .../test_bfd_profiles_topo1.py | 198 ++++++++++++++++++ 31 files changed, 730 insertions(+) create mode 100644 tests/topotests/bfd-profiles-topo1/__init__.py create mode 100644 tests/topotests/bfd-profiles-topo1/r1/bfd-peers-initial.json create mode 100644 tests/topotests/bfd-profiles-topo1/r1/bfdd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r1/ospfd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r1/zebra.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r2/bfd-peers-initial.json create mode 100644 tests/topotests/bfd-profiles-topo1/r2/bfdd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r2/bgpd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r2/zebra.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r3/bfd-peers-initial.json create mode 100644 tests/topotests/bfd-profiles-topo1/r3/bfdd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r3/bgpd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r3/isisd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r3/zebra.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r4/bfd-peers-initial.json create mode 100644 tests/topotests/bfd-profiles-topo1/r4/bfdd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r4/bgpd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r4/isisd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r4/ospf6d.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r4/zebra.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r5/bfd-peers-initial.json create mode 100644 tests/topotests/bfd-profiles-topo1/r5/bfdd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r5/ospf6d.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r5/zebra.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r6/bfd-peers-initial.json create mode 100644 tests/topotests/bfd-profiles-topo1/r6/bfdd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r6/ospfd.conf create mode 100644 tests/topotests/bfd-profiles-topo1/r6/zebra.conf create mode 100644 tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.dot create mode 100644 tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.png create mode 100644 tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.py diff --git a/tests/topotests/bfd-profiles-topo1/__init__.py b/tests/topotests/bfd-profiles-topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bfd-profiles-topo1/r1/bfd-peers-initial.json b/tests/topotests/bfd-profiles-topo1/r1/bfd-peers-initial.json new file mode 100644 index 0000000000..bab24c4fa0 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r1/bfd-peers-initial.json @@ -0,0 +1,38 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r1-eth1", + "multihop": false, + "peer": "172.16.100.2", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r1-eth0", + "multihop": false, + "peer": "172.16.0.1", + "receive-interval": 800, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 800, + "remote-transmit-interval": 800, + "status": "up", + "transmit-interval": 800, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd-profiles-topo1/r1/bfdd.conf b/tests/topotests/bfd-profiles-topo1/r1/bfdd.conf new file mode 100644 index 0000000000..4d636ab052 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r1/bfdd.conf @@ -0,0 +1,14 @@ +debug bfd peer +debug bfd network +debug bfd zebra +! +bfd + profile slowtx + receive-interval 800 + transmit-interval 800 + ! + peer 172.16.0.1 interface r1-eth0 + profile slowtx + no shutdown + ! +! diff --git a/tests/topotests/bfd-profiles-topo1/r1/ospfd.conf b/tests/topotests/bfd-profiles-topo1/r1/ospfd.conf new file mode 100644 index 0000000000..c0896353ae --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r1/ospfd.conf @@ -0,0 +1,8 @@ +interface r1-eth1 + ip ospf area 0 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.1 + redistribute connected +! diff --git a/tests/topotests/bfd-profiles-topo1/r1/zebra.conf b/tests/topotests/bfd-profiles-topo1/r1/zebra.conf new file mode 100644 index 0000000000..4b7982b235 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r1/zebra.conf @@ -0,0 +1,9 @@ +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ip address 172.16.0.2/24 +! +interface r1-eth1 + ip address 172.16.100.1/24 +! diff --git a/tests/topotests/bfd-profiles-topo1/r2/bfd-peers-initial.json b/tests/topotests/bfd-profiles-topo1/r2/bfd-peers-initial.json new file mode 100644 index 0000000000..3df9ec9c9d --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r2/bfd-peers-initial.json @@ -0,0 +1,39 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r2-eth0", + "multihop": false, + "peer": "172.16.0.2", + "receive-interval": 800, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 800, + "remote-transmit-interval": 800, + "status": "up", + "transmit-interval": 800, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r2-eth1", + "multihop": false, + "peer": "172.16.1.1", + "receive-interval": 250, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-interval": 50, + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 250, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd-profiles-topo1/r2/bfdd.conf b/tests/topotests/bfd-profiles-topo1/r2/bfdd.conf new file mode 100644 index 0000000000..23a39a6ee0 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r2/bfdd.conf @@ -0,0 +1,18 @@ +debug bfd peer +debug bfd network +debug bfd zebra +! +bfd + profile slowtx + receive-interval 800 + transmit-interval 800 + ! + profile fasttx + receive-interval 250 + transmit-interval 250 + ! + peer 172.16.0.2 interface r2-eth0 + profile slowtx + no shutdown + ! +! diff --git a/tests/topotests/bfd-profiles-topo1/r2/bgpd.conf b/tests/topotests/bfd-profiles-topo1/r2/bgpd.conf new file mode 100644 index 0000000000..824d01983f --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r2/bgpd.conf @@ -0,0 +1,19 @@ +debug bgp neighbor-events +! +router bgp 100 + bgp router-id 10.254.254.2 + no bgp ebgp-requires-policy + neighbor 172.16.1.1 remote-as 100 + neighbor 172.16.1.1 bfd profile fasttx + neighbor 2001:db8:2::2 remote-as 200 + neighbor 2001:db8:2::2 ebgp-multihop 2 + neighbor 2001:db8:2::2 bfd profile slowtx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 172.16.1.1 activate + neighbor 2001:db8:2::2 activate + exit-address-family +! diff --git a/tests/topotests/bfd-profiles-topo1/r2/zebra.conf b/tests/topotests/bfd-profiles-topo1/r2/zebra.conf new file mode 100644 index 0000000000..6acef139b9 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r2/zebra.conf @@ -0,0 +1,10 @@ +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ip address 172.16.0.1/24 +! +interface r2-eth1 + ip address 172.16.1.2/24 + ipv6 address 2001:db8:1::2/64 +! diff --git a/tests/topotests/bfd-profiles-topo1/r3/bfd-peers-initial.json b/tests/topotests/bfd-profiles-topo1/r3/bfd-peers-initial.json new file mode 100644 index 0000000000..abca1ed131 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r3/bfd-peers-initial.json @@ -0,0 +1,39 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r3-eth0", + "multihop": false, + "peer": "172.16.1.2", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 250, + "remote-transmit-interval": 250, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r3-eth1", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd-profiles-topo1/r3/bfdd.conf b/tests/topotests/bfd-profiles-topo1/r3/bfdd.conf new file mode 100644 index 0000000000..74dae5a60d --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r3/bfdd.conf @@ -0,0 +1,11 @@ +debug bfd peer +debug bfd network +debug bfd zebra +! +bfd + ! profile is commented out on purpose. + !profile fasttx + ! receive-interval 250 + ! transmit-interval 250 + !! +! diff --git a/tests/topotests/bfd-profiles-topo1/r3/bgpd.conf b/tests/topotests/bfd-profiles-topo1/r3/bgpd.conf new file mode 100644 index 0000000000..9c56a349ed --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r3/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 100 + bgp router-id 10.254.254.3 + neighbor 172.16.1.2 remote-as 100 + neighbor 172.16.1.2 bfd profile fasttx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 172.16.1.2 activate + exit-address-family + ! +! diff --git a/tests/topotests/bfd-profiles-topo1/r3/isisd.conf b/tests/topotests/bfd-profiles-topo1/r3/isisd.conf new file mode 100644 index 0000000000..5d774a356b --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r3/isisd.conf @@ -0,0 +1,15 @@ +hostname r3 +! +debug isis adj-packets +debug isis events +debug isis update-packets +! +interface r3-eth1 + ipv6 router isis lan + isis circuit-type level-1 + isis bfd +! +router isis lan + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0001.00 + redistribute ipv6 connected level-1 +! diff --git a/tests/topotests/bfd-profiles-topo1/r3/zebra.conf b/tests/topotests/bfd-profiles-topo1/r3/zebra.conf new file mode 100644 index 0000000000..2297bfafe9 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r3/zebra.conf @@ -0,0 +1,12 @@ +ipv6 forwarding +! +interface lo + ip address 10.254.254.3/32 +! +interface r3-eth0 + ip address 172.16.1.1/24 + ipv6 address 2001:db8:1::1/64 +! +interface r3-eth1 + ipv6 address 2001:db8:2::1/64 +! diff --git a/tests/topotests/bfd-profiles-topo1/r4/bfd-peers-initial.json b/tests/topotests/bfd-profiles-topo1/r4/bfd-peers-initial.json new file mode 100644 index 0000000000..c8bc4c20e9 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r4/bfd-peers-initial.json @@ -0,0 +1,41 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r4-eth0", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r4-eth1", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-interval": 50, + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd-profiles-topo1/r4/bfdd.conf b/tests/topotests/bfd-profiles-topo1/r4/bfdd.conf new file mode 100644 index 0000000000..36ef4f0403 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r4/bfdd.conf @@ -0,0 +1,6 @@ +debug bfd peer +debug bfd network +debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd-profiles-topo1/r4/bgpd.conf b/tests/topotests/bfd-profiles-topo1/r4/bgpd.conf new file mode 100644 index 0000000000..7c4b39b020 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r4/bgpd.conf @@ -0,0 +1,17 @@ +debug bgp neighbor-events +! +router bgp 200 + bgp router-id 10.254.254.4 + no bgp ebgp-requires-policy + neighbor 2001:db8:1::2 remote-as 100 + neighbor 2001:db8:1::2 ebgp-multihop 2 + neighbor 2001:db8:1::2 bfd profile fasttx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::2 activate + exit-address-family + ! +! diff --git a/tests/topotests/bfd-profiles-topo1/r4/isisd.conf b/tests/topotests/bfd-profiles-topo1/r4/isisd.conf new file mode 100644 index 0000000000..477740087d --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r4/isisd.conf @@ -0,0 +1,15 @@ +hostname r4 +! +debug isis adj-packets +debug isis events +debug isis update-packets +! +interface r4-eth0 + ipv6 router isis lan + isis circuit-type level-1 + isis bfd +! +router isis lan + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0002.00 + redistribute ipv6 connected level-1 +! diff --git a/tests/topotests/bfd-profiles-topo1/r4/ospf6d.conf b/tests/topotests/bfd-profiles-topo1/r4/ospf6d.conf new file mode 100644 index 0000000000..84157de24d --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r4/ospf6d.conf @@ -0,0 +1,8 @@ +interface r4-eth1 + ipv6 ospf6 bfd +! +router ospf6 + ospf6 router-id 10.254.254.4 + redistribute connected + interface r4-eth1 area 0.0.0.0 +! diff --git a/tests/topotests/bfd-profiles-topo1/r4/zebra.conf b/tests/topotests/bfd-profiles-topo1/r4/zebra.conf new file mode 100644 index 0000000000..753041f952 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r4/zebra.conf @@ -0,0 +1,12 @@ +ipv6 forwarding +! +interface lo + ip address 10.254.254.4/32 +! +interface r4-eth0 + ipv6 address 2001:db8:2::2/64 +! +interface r4-eth1 + ip address 172.16.3.1/24 + ipv6 address 2001:db8:3::1/64 +! diff --git a/tests/topotests/bfd-profiles-topo1/r5/bfd-peers-initial.json b/tests/topotests/bfd-profiles-topo1/r5/bfd-peers-initial.json new file mode 100644 index 0000000000..fcb090959e --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r5/bfd-peers-initial.json @@ -0,0 +1,21 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r5-eth0", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd-profiles-topo1/r5/bfdd.conf b/tests/topotests/bfd-profiles-topo1/r5/bfdd.conf new file mode 100644 index 0000000000..74dae5a60d --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r5/bfdd.conf @@ -0,0 +1,11 @@ +debug bfd peer +debug bfd network +debug bfd zebra +! +bfd + ! profile is commented out on purpose. + !profile fasttx + ! receive-interval 250 + ! transmit-interval 250 + !! +! diff --git a/tests/topotests/bfd-profiles-topo1/r5/ospf6d.conf b/tests/topotests/bfd-profiles-topo1/r5/ospf6d.conf new file mode 100644 index 0000000000..970c713558 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r5/ospf6d.conf @@ -0,0 +1,8 @@ +interface r5-eth0 + ipv6 ospf6 bfd +! +router ospf6 + ospf6 router-id 10.254.254.5 + redistribute connected + interface r5-eth0 area 0.0.0.0 +! diff --git a/tests/topotests/bfd-profiles-topo1/r5/zebra.conf b/tests/topotests/bfd-profiles-topo1/r5/zebra.conf new file mode 100644 index 0000000000..de8ae1644b --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r5/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.5/32 +! +interface r5-eth0 + ipv6 address 2001:db8:3::2/64 +! diff --git a/tests/topotests/bfd-profiles-topo1/r6/bfd-peers-initial.json b/tests/topotests/bfd-profiles-topo1/r6/bfd-peers-initial.json new file mode 100644 index 0000000000..4e6fa869ba --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r6/bfd-peers-initial.json @@ -0,0 +1,20 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r6-eth0", + "multihop": false, + "peer": "172.16.100.1", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd-profiles-topo1/r6/bfdd.conf b/tests/topotests/bfd-profiles-topo1/r6/bfdd.conf new file mode 100644 index 0000000000..74dae5a60d --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r6/bfdd.conf @@ -0,0 +1,11 @@ +debug bfd peer +debug bfd network +debug bfd zebra +! +bfd + ! profile is commented out on purpose. + !profile fasttx + ! receive-interval 250 + ! transmit-interval 250 + !! +! diff --git a/tests/topotests/bfd-profiles-topo1/r6/ospfd.conf b/tests/topotests/bfd-profiles-topo1/r6/ospfd.conf new file mode 100644 index 0000000000..f16844401e --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r6/ospfd.conf @@ -0,0 +1,8 @@ +interface r6-eth0 + ip ospf area 0 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.6 + redistribute connected +! diff --git a/tests/topotests/bfd-profiles-topo1/r6/zebra.conf b/tests/topotests/bfd-profiles-topo1/r6/zebra.conf new file mode 100644 index 0000000000..c0804b94a7 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/r6/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.6/32 +! +interface r6-eth0 + ip address 172.16.100.2/24 +! diff --git a/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.dot b/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.dot new file mode 100644 index 0000000000..a3936093aa --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.dot @@ -0,0 +1,97 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-profiles-topo1"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + r5 [ + shape=doubleoctagon + label="r5", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n172.16.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n172.16.1.0/24\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n2001:db8:2::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw4 [ + shape=oval, + label="sw4\n2001:db8:3::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw5 [ + shape=oval, + label="sw5\n172.16.100.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + + r2 -- sw2 [label="eth1"]; + r3 -- sw2 [label="eth0"]; + + r3 -- sw3 [label="eth1"]; + r4 -- sw3 [label="eth0"]; + + r4 -- sw4 [label="eth1"]; + r5 -- sw4 [label="eth0"]; + + r1 -- sw5 [label="eth1"]; + r6 -- sw5 [label="eth0"]; +} diff --git a/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.png b/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.png new file mode 100644 index 0000000000000000000000000000000000000000..775fae13f108b4b0ceb4cb941fa384c1fe299a08 GIT binary patch literal 43508 zcmZ_01z1&W+bs%$lys*^BcL=A(k&>Bpduw*q9_g0UDBbXl+q%gNSBDxAl=<5&AI3M ze*f9u-us+4F1(g&tvToO+%d+u$3uwv14TR>Y8(_46g*`mIZYH4G*1*1R9q|!c;&gx z)IR*cFi}yIL%Br$&G?=di-K|kMOjW-+a+y%!u3gn6%FS0l*mRv&JXgDHfo|Q_5k$= z+xTeDo2)g)U%3v}06EY_H*P{LVM;WXFk%4`pe_oaerp?Zc z`p@h0Nqk!Cn==8MW0w)n%pD{3rpGN#J86vTX(#}BO*oyKR$GoyoKY3zx64rJ&NA6C739Q&kRQ)>Uu>Wh(jZ%G+h+1^Z9O#h&uJ+DwDysQ(Z(D2oCCE`Jn80QZkK5Xyq`i6vHYwPI5 zynWmBy=`ma8>XF|-Trbfno0uKjKp3(A!Whmh6bsj3~98XLLE+WI=WwCzun(Ucy^|x zrBR|R&d;~-@$8sD3VeQO2#l3vN3KI8+>bXJ6!)UFgTc;m&e9=>(&C@%B?cJ zE)Nfnq=W?WwZ0G6R8&>x>6D8#`X6FPT^BcL4kEC);`65F>(_F4fXNsM4j!K9m6eq; z*83@?rKLQRj=|{a|GlVh-x4pbto#@l7)XLOS5a1;pc1uTN3DKV@;(Z9h1Il6STu z+h=BG=9AT2+mi?WI=Y%BQ{I>2oLpR7cW5GrJ8u_CW_9FWgfMG}6S+R@9r^zGvw*p{ z5lSL8W&?(vei_`^i;CV)hQEk-+9;K3e0_a+Y=*c7K0YKEt8!4hyg2`98yg=V;IR0s znf{xzsnc+Asm*Zl(92v43sXB=u1A&D0q?xeeBO(@CyXqPR9HNBc2-nZCxnHco|{9X zqkH$L`yNN+{}v={Y^J;OKdQaXMRh;v`N)TnODij{e7B=C{qx0+rp}<6@Z{CnEDb+@ zKIib}s+~L|(NKHqO#1kfPv_m2y2 zupd9SGuNIZXhV)qC+bhdZ*Da4^;N!B-hzP zZq+oXUUKw{-DC4_0|VNssX}-b-S1nQ0>+9=171C&FsUop!nCK{!10_*CJ99hqVq&(thYC^l6VGRw z0{F&6L_`=wM5q=Q7b%5oDUrnc^oa{UMAn5vK#e}pt+L;UsCO~LYy~H9+gse>3(*^s}MZhez1Cm94y?~+auO62VZN<$gV5+j)b-$05fa0=xQwV|sst{5U)a9C zU;6v|WsQt(TtP!~JlVIRq^9l@l}gh%*EqikHK@>AIm<1}?X9!J(8OAAHfCIWyyNk% znTv}{oJbnKxHz3(QPIrB!ElnfZFtg3f3K`zy4GsB=pFm;Wvl+uKjXAN6K)552)MdA zUb8nTbj1gyxVTv9-aX&q;@hhC?^_wvb7bWb#M9t?uB?nt>1~M0)Q}m0m^jFT{3^I$ zfg-x9Jo#gFeX?Eq^^n6{Tg0DgM zp)$_c{*SSLOMp|>i97vZx=H4VH~paZfx7tjs8!{QjxqQ^4vfNw0Wd=&G#-KbR$9nWNLv!qdG@u=kd_x%=07+ospn!sM@!OV!&O*p^o% z{xmPSJ4`Rx_NkO-{AX876sPX=m_vPl`q9N%ydGB@bhn*m0?7>1_Q-0N9Y*vksCg}Y z@|&r3)6+)6kVM?vC0&{H4Qss(RQj4ma(ntp-1sPyJ;WUa$Z0`4P*OSi=qR z*`N2IbtQ?iONhVOSy%UkBfEuJT(I09r%hTfiB~w^-T$`XdwYMj8~f7r`kV7sTT2KD z193>FKw#)STPrFdi*Xto>Cqe0g?Fl5%dHxdf{wp@3$kF zs`4*7A+0F$FMh&m32Lh?WV^kkr=p@NwHwC&SyWUo;~tb!&gVrdOlVl?5EUMdJL#D7 zzDdXvMJn%ALevA7BD#I9#Hdv@-ky(7BnmGJGcAfd;4WpAN){A*Uuy9RJh^edPIx}?9(J9)# zNA>vA?VM`cq}Dwe#xDAw{Dth?rXPRYT+Wm>WL6R+`(RpNi1OfdT4)7YN>Ac##<`Yi z*ES0B-YE6mMsbdaQ>L~-;&;p6J`QC4rN6ne>dH6duV}nB#m!*Zm*h;F#S`jyxTRI{`bU=TSR>qu91gmc??;ybonUkowfc2V)&-> z{JbWtNf~5=k_8;P!ToH_r5iO^b}kkSawc&-1!G3l0-p5&gx0J5f3MzDyq`|W?wR(+ z!6ZFPHmdRYEeUkPNuOXDqQW)PH;rq71Sp@W|N1}vrWalx6TE#_wkuz8YgR6@K%n)F zZV8P9in@)F|ILjDjH|1!Xn(AP=pPhZDC_V%n-oXwrWifaOuAb0lqQUw67|vNP1J|k z?Uqc7wk~{f&ijml)E9eAx*p$ihNZkj^PoHQIrkn* zMlbq=(=yZBxPuWl6IU#8YD}^{>C)b;zv_%O5(n?%7eAR%d1ejbFg*IQ6(nM}Cas=? zUtdub{b3A$$gR_<1G9iEzEE<9pV^#LHd|g1H!C-)!zXm0zWOs(a!vR1MHyzV`~7W{ zk2&=|z6evz`IEO6GGp9FWUEur68a@&pCzm#qubMhjA3)&fu8+d^Np!n%PZ$%7@K0g z69m_!`YHP&06N>rhzma^@U0M4ADu)gDBS;$GLY3hL@#TOeX3wogO-#w4JH?;%wxjDORb^dO+7W>D2OmibPGi;j;dsQ~@L2&h&6xuhrFYyJA z45Rv84R+U1{V%;wpS5bIvo;;&X1A_ z-k6fKHDSMnBK30A)&vQU51yW1OKtE>qnTa0-7p~%S^DE{i1g!&mcu2+I);!g_2OZ| z4eg3)dRMBVPp8@Ro?l2r_726hwjG)=QLYSf^L(wx4p*T!8f&fB=;CHP#s8b-Ml=hM|i(UC=*_{hnHjRAc&(Rs&o`sTAk z>T8#`@$zSwaPH^p6WovUIZC6Q)Cle(oO{s5^eiWDc5VN~W}PFgmi?;9q=56MfjoUF zMh8ktH1vC&`{&Z38vTmFzMMbmeB?2S)u*hvguWEW_Fu{wmnMcJksDy9237{OiB*N| zRqUu?s~hG?RN2Z6v72^a5j=}{i8>m`0S zbzFX<(dHwbE~M zbUf>Mx9exqLY_(RA2Kd1w@}?ztBtja-r-iN+$06>qXyI2w}@_=QA{hC3*0UCI$~$p zrydW4wBD0Ax%ikBCgB#MS82+x`_V>5gj3L%Y%wO@grh(H5KCo+qOLy=h@%Yc5eHNzspcdKDZN^R(8jRg9b>ocD;06$(m`OL3NO^~b>X;zP)a3@IjkdTntR) zHms|PV2mKvbrRtD5AO@OM)7?84=?b(4Eu?%w8TG!3hVy@*C8*wgdYzH2K!F|+(%_* zGR)4-BJ2%eqd@hPjx)T-tcFUmD%=YS3YMqpCC$6vM*umW1C9w)SuAg$PnjV_0>yH? zniv22-Qe@nzjJUOJbZkb)f!Emkr<+4Vy5#QG1ad(s6Q1I0i_`LzbFNW7kBU8jZR9! zic#_hMT2wxlcAaU-|+D8XwVCej$B;a-1;)#EvP5uZsEz(vE=khtw{Z!%;)CnC;DHT zKuc^+P+F<~d5`S>^`6mx-a{K;domgQS3`yxFV0@uy9CyOweWH{|ANJDbw#j{?U-e& z!b{-W!#$u{X!9@Qq7~50Fb9D<0nN=jNd(2Gkj<5?AbaHdsQmpYF%HptmNqt=ud@9GsM!Je{x9}C z5mez-{-qds4hid%P6nH7e;q13P-oa9Fz(&_ANl2nyEqUud{I%+($AlB+%(n8h1L`+ z+-Z4tm3@4CR3AP}th62sl~+_u{C3~moHbS0_zGIXMwD@T+({K=}gq)q7a|;TTK@*j^KBeX`S^Gou)oRQB{{A13m25;$9xyUOH)`t1X$jF3j|L`Qd zduQOM9KsddrZ1T?@zK-ifuZ1Ck0U~Rd;8$fP~W1WTd=D&iz1#U0`SlD(vsp*WJl7& zlBdQX$qK`(um%tXe@EK4KS`G9Jz2c>7#_KAbMkvwo#$y&OAC5tW+o_WUhe6i>n;lm z3!54mP$0IrE20QjSc(p`-~H|9xA@&KGD3B*I@s)khS^i|(gHh=xg3dw_wV29Ra%q5 zu4tRP`j5W;yJ^78#x@s3@73^@MI|FEt4T15F(X&)%Il5MfZ*U@>+^6Dwg(w1iMK5W z@-^bU1ATl@z0P(NLE$WUq_4Em+1cs){kyoPrY7O_>yZ+kC)%q!Z}aOrt6le4`S|#F zj2hnjwa+^NiVzdy1NWb0+#DDj85x+6pj$IG`1;(tEA^z_dFwhTL}li`sJWh0M>}(A zDyJ9bQE)$_=A=PzC~RoV=fdY--xzb^hr20P@_S17WzwYBO` zo;)c^Z>02tOoH1iEaV{PehRNha|ESG++-ld*;z{WZWh&o9ng zoi;|}ooN{v!pLvQ=;={HSNtV*??YTd!YnN1o5VzlCk6&(j&lkrIt+ue>+85>df^!v zHzqZdSbECX{GY#=Yr{)QNs-|qpIaZP;OpH+ee>Z13oahs%-meF7*33AYisNC=g%9U zi8`JgIVRg2p&0#m%fcfl*jnv4r>?HvAch|k?T1Ow)Y}{59DYv=lvdE-uUxrOYSDci z7nnWB?Xz@YgqlC|SD5_$pr7oo4HKN4oCJr2NLQHua#1WKefaR<`ouSinwpy0Y(f!6 z^01lNS>J0Ew{JinGasfV!3jWg+3R;*-q3p;`M}enf+i(nZOuVTOY5Roz(+xmShYrQ zxIRMSeSSc(rQ{#|NbWv8LnUbExM&!-Z)$2pva+)?KYsivrupH1iU4*vFFHEcTW*mM(-)Qw1I?hy3{Tv>CvKXA9<`Y(4=ig zKVze!qCzN5(6J<^-nzoa*RFJ#1Ov?UHu-*2Tr74^k28ki(O6o7DIUg3c zKpeTY#|YEW(b-Pb(J?SE{Hd@Y*b>gcftf^4z6MiLa`H|jqa$FI_SP_p%%Y+QP|^|D zQp-dnV~r)~=|2GJ&sbeuZ3KPhXs=t67ls9sG0dI6e&Ionbvaz8+G3Z(Xl-vd?MmWJ z60)O43c;kKbytS8ACLQ?ZS}WrOyc72oM|1_H8p4HJ}3w{Ow#i5@*;C9Xu3QO zlXoFWcHt3Ycz_ucFaU1zhXN9LT|5-D!^R&!LY$dHUOGDZ1O{S24QhpC-~~Cgw4%bV zva(VpjRx}l5hW$1QIkJ5VTc@T9}YC+=6KGB^Q%3-cVYa{I5tKDGmY)NJ@|y+`@619 zV&n{RzDU4e-1R!6JU=;ThI;L|I&c$m0=f=$W$5fw1?*-HX$yONdeqb_f$ZYJoTL@nl=b9{ z>w$0=wco$Pn9g4b1$I-Z2fokP+#CZUA`PGu0F)WJg0@%de~3I!4_k+d9>Ya7i!(A% zU@E8h^yzg_7&Ei8o5eJX9@o0?!a#!uLZ6fr2%4Ee339Of=-M$#jDcz~Lr@g5>43!*@EqeA%2S8??PWBMS;a9C3&o zXGniO-GFLoWz{%3n&M1)dy;Dn zX*0pWSo<4e6ada^O^IX*gOLR`0uEAIS=m}@+)PMC6#^@~8>#&fLaqnKn(Kec{G3S{ z11L?Ozk1aQdO9IFc_2)C;E%95=T&hNq) zcLutUx`u{NSs5>2EM$GPQDJ?xW1(cRK1d7JuP`@;z<{^F`VPTO8TaKVZyy|d;3Rp= zS9f{gzCQV#1|S(s1Rx(ZjUh}Z#oYuD35f~)@vc;bc<%h>8mx}v%@h5N>L|vWX#(R z&lf%GCCidS4T%Nx4fkyKCpQ)rR>N#-n5*J!@%>aGO5x`>V9JYJ&ID$hN&FW0NL4iJ zBn2h;0VEhQvY@@|LI_nCGSu1M-`}VsnxUt!Z7)jd*bqwM+?Vac=oLJWT zO`r5Dwl}J#b|aZ8fpPI#{HE=CFHU1+Wd%AH3QX~YU3Z!PXPbzaiHelc($Y7fp*X+ai-$ph-HBwbL_!hfny_KffR28`FdF0WX9y?aHu-IM=T6dK^8U)Ohv)v=K)@VK9GdZfgrncy&Xr z6ZWrz%|9@(X=sQX&WFllsEe2Wc3u;7RYc}-2?E%qF8UHv`YBJJQj?te~P_TwHf+E z1;x6|xEVuEUVbMsTOD?rkcdb+==Hrfkf;yd-wkPLk=tJ#48uUdj=6%M0enzLkYP6< zw3fCAniUh$cxFN@WTFoRCRM?8VH}ulnZuL>Iwoee``^vlxsCvA5>|u=0E}!8qu_RF z*Pvv3kS1kse_J7(vQaGb3D2E7?JyoUHZppH%+=uup=D+DET?|rGEEj;t<62z-922R6;^xdw>7ak>ARJ zOZWVbSO!9@Qun|1KhAsZNw#0IxrJ8Li)4o_a4ThU&Z>k1Vnr4K-hg}kZnX(P)_IQ1 z7u%65W@cu9FtW`rP)8Fx0h~#1O@8mKb=?b<3|>8s1vZI!O2b0%UQUY2+V zZA2!lO;sNyw{Onkb+)#z2C1Hu6bcCz`g+xr*T<%8E6IXjXle*Kh6K}*y}Yua;sB_J zPh4F53%tT{H)XYk5rm@-2vB|KTl$Y4Q3s)GZ@uMn+qYa?Ue*gx?5k@c`L+KerIx*m zdY6e7ID(#h^5LH)FZ?7gZ(ikmnT|r}F`IeSg2?Ocwb6;M zb1EOa>H9OB4V|716J2*KVZHLxALM|>&Q2V-+s2jtJY!>HjYh@3wH=Ybz(6EWbML1X z9+4F8(4wht3elkYJl>wDmaxp-NsdqsU}k1U0v(t@Bj6{_ZLCqX!sh;d60lXYA#q5o zvlNEEp_2Xr@C0x-GC)JeT?yLVgS~a0?k(-@&2U{IAt7j-S4_>)f2?M^EkzxYS;^i{ z|9n$fu?2^1f72vfVo|(zq=S0%VmP{+kLa71Xj^flwEgI9)fJo2I@2%Hi>X^EzB1!t zwd56nuY-KPhziiWYId1=MtX5+uQ4jfyoA%ESHI)V{q=1f>VCY6Bj<|3t;?*wnZ;kJ z!0hB59eHQx=8T^`+rPlrcheplHuR1FQ&dh~UShp8o_M?5E{FivKyrn)Wdf;6h;?yU zaQ{>}LIe5%q;E`8Qm`RVv3{EVI&BtcLeT1gM7nsQ4Ze&hd|Y+=aBC_GWGCa1ax+lI z(ORr;;NuI^TM_rU86?qJE$#vG8i(b1d zrt4&5Tlv4^#Jt*X$JM&dUq(gg@)}=jCjRqlUy*BW^XB1R1BR-e1Nzwc*?~`YYu*wM z8Vi~E;Tj#Q+h6+Ice_|uu#!eIiYiY|@A*OZ$^~o)bgLCQj_z-A?v;b+H?y-QL!Yg* z4#7+i_tN2)c>5O7gz4HzVV^72AOaqT%FR@u^Kc)6qUF4qd7dNm$>3W;L4JNIfQ%?x z=~GRe^MheST$FEMkji!|HURSR$#Jd?-t~HYw9+Qtg?Ug+t)zQ}M*1`~JTqCsO7l5c z>Y2T?MUL2$M+GQ^Qr&G-XlrK?4dNCO;y51$&?iokGrmik3Rsz5{j0pF?)i$K>e5yG zQ&T98Mhm*Srmowi;}sTm&T9=gWH#Yz;3`1Sk=x(R2(V4fK>ug|rZK7ECeAD+FMkzY z3IJdR#eTBB0wdj&Q3Tlc(dx&NjDmuY{CswRy65g}8*G&(U_XLJ-2ieQu>Ic)YnFzE z<Df2mri#!PH{cC=INcEDj`V$zc(AI3M<P}L01r>VI92Vec=Jb zY<9Ufdl@3V26={WAd3O6@PT?d2e0)E4qlal6ajFPVfHhja`(O5!I|sr+qaSN4J{pA zZ_(pB>tj^}fUdbK=%Ay*KooWYmu~W=d>nO^)RHLUcwLy%dLj zPSMWCjP~CRxC3TfJ!tK*MNRWJmTqg^`UagDM5QYZ4i2C+RmtGdqp*9A#6Km#riL8( zSMJ(7{6g&+sa*LqO(GJNdT|PyT8aE#e`I34@{1{Q9XU+h5|uMZPE0 z>~H*b)7R(KG9?k}VhWz{YTf!OVZ_} zo?o})j{|YrAL~|KX4>9Eb#>9$Yn5gROzmX_hTP`gib;1}aB?Pr{v)2<$CcJPt@LuI z*c8{uxr|<|4j4njclAWuP>Choem zUIE?A7$`M}cim9BP$5a6o8W<>Ft;(Ni%tLg_iw_mRxBKxS-Jr2D|C~}^S?o$HZnH$ zfom~JNW7<(xCwGR?f7?h3b1EEJ*%AqX%OkZfw)w@K&E#0hzB z0~4~#*xvp@_L}B=qaP-spT)!w9d3*%@+;5NA0;!o^U)}botg#*$?EFr*txjofhUH@ zF^1FJ!LM?dY6ITeQ|tP2GDb-d$g?Tbfj_mb{D>~o9%GHH0=VKqoq!b{L{zE&T||@O zpYZp%;wi?;r@~C!2%0OhL0{~~kij&3BMTyYjEszc?Ua6t9Vdu45Zu|*f67du96?8; zgNG%CaCrlQjnV2rfm3lm8=|@__hdTB1=9j0+TPxl0yzNgrU#s>C+j!JlCfV)6rLqw$UnwLS&v<0A1U4fzk=xira z#`?|wpKvC}0qOA!xcMC;YwJ)r5CX&^8(K8P6n2uB^KHZ{0N?=1)G$nb13(-_2Euxu zEr~#<4=j56o!Zdrm<5?n#3;3hMQV5a_|XV&TIqRkb$WF*w7Oat#se83=Y0-jVaNcl zTri-V=Dt2+nCl3?T*Ni7QjZiC70ptPW!>J{nW2-nN8~CnB2Wrilm05?z!DS`L?%99 zmT2RyKWlU{iXET4`L*;VaO;aEseUR=l@(f{X(vA14>V}LPQgkJ4ZwUwzS-il(T*VAQ1-# zr__1t>7>SdCsYa}s6$X4XF$zU*V2MIG*EuLx6}+_1`NC%2FI@LGmCH&NW{^N7QBJJ zfFu;?M2vUuB4!UKQB(~jkXH&878U|-YULUM@J1d0Ccq%|3d*Ue5df+50j-zP6j4+V zq3X{cDQ4oC?U`mc8G#RTFEk%16iG=*5D#Io{G4g+;lb4P^nzj53GpIaR#k<}+5z1f znV9%yD@N@`$|)6r*HMYCjS^5@@CTJd6Ob}MIhltC@C7j(IdqWs;2o%?i}9dW_4X27 zp6wC*zj_sxBJ4PF+ym4SUy$$jBAZ*G8Y5i?CIh>0O>1a;z|0ANEkYXgmE+50LTu*T z1CH4iNf!of#~4Hv1b>FgA5Nz2lKp~H^{JNbNu+Veh@q}11Dz~)D~-6NP6p9k7MG) z!$TM)Bh?2^Q2c@%G<&)98__0_lRRRfU5{XlfN)dric`|i>_y7vbO6`S$jn3qY&EH2 zJqWsKbYfx9sO`{=W!4;7-(UZAl3QwrKN>Sux&J`iCLY=#K8aAs$TE^i15XNL3En%4Z#Ty-99HSAmA!Fm);xL=o#odwQ~c)U;^6Se*V>(?U)@@BA7~}Lm_O2q(jV2 zz={pe4ap zDg?KhK(6<3(HF!4_L+lvj`+E3ZEfM;jr>|WagEe{`hr)`3mW{fN&eANq3{6dfK9$> zYH1maiiXj%iU`8i8t}ixfzKRPmr}rz5IENM?t;psBQv0dmmM7)86T*X zJ`5H;rhsbS4mhyXc7!jSlJ^P>BA_{G-Wu4CdDKkV97@Ky{p-$@5%>zW4-W&?l~|NI z?>KKh0ZACqgrKv&OG~3gP7<-k+{aOQ;(GH8+Xx(l@+@ZXaSL_qkUn}3iM3z0*{6M%_9J)q<_$AxAz zf6-YbcL7z-S{oc-7zEeD!Sz5Z=|%hH%NMvADn7t95GZgK6N&&?-SHv~LYAS5!=Q4O zE~q00q56*=J%SM-z)41AO+jn=ry$A9x1^fUfz!cH-2D)lv$*Up6G4J#Q@%S^vsQ&{ zfrvsXKD6l14pZ-8xlC7wio!sCpZ+`LjR+B)X=sl$HGLs{Z}9Vza05I({R;?zl!Agu zQW6fvj4a*>+W-fX6c>5D0lo}7De@m6AC!#3!jnT<5wMiy7Z%FEczLIq^%*s_uX?nv=XJESN= z+_q7+nDFiz*4Y!PgsOnDg@OVwTF%j!_n!O*hb`}i@4mju7?Ywv&;bZr;2jBr-3LL2 z+;XIx1vxEAe(RC8yD~97J^d%ZO8~>2j$JiPVaQcx*~{1kSQ2bImczw#TS_{J$!5IP zl>{gZ;shBiGgSmYX4)Bd6B)DXmOQ;b%yB~#0XdE^t4HRaU!7Kb;e<~WKf8Y|9D!3r z44mNBh)zfdgoBCW_1^SwShb_G)9sr9+60E>a3)>LJ(GdnVn5cR6e&eT%u&(N{1Pqt zgVkYKVEj`T-uG`AsKc(Ot<%mwTeu-6MpH9V)A74g;ejqb%#fmE3`f&an>SWP(&~SpgAMJ2(TX0&|wlFi)r96BYYRKDj@3x0zqMfSVqY>4Y<9Z zx|Ut$Wh!7dM0)=qH7mp`cG%lth?F{|TwnHT2~PCR4KykOzU}21rn?^7U`~=~nhT2B z*-j5~**Uw4BK7bgg@S?tB%%!H+P#%F522J{LoR|_$p@4-fW+ajBF`@(g0qyAm8HSl zG(9upv+y%i-ojqk&&Jysch#o}L0wo4lwQpL^} zPXq|JBTw*Fvmd5ET^`IE9vg#Pynb;`SmiL;Hu2aw=7spxD*Gv4fENOl0=++TD8R|p zlcz?HV1I<=AJGuH3IQuZ;0Xe+Vf{aPKDD;P{%`RBCl?G6uy2iE7vP0?7AP&4;6Tl8 z6lD~&%vHPBv8tM`#M}gSCB)haKoq!cgfp|IA7Fv!;K_mJhiDxzs0J#hrWl0xKVAUK zk*_S_Uuhcuv`|jCy_WqsbZe#1mAHvgcJ#Zz6XjappA@&5Ry2wGH2Y!UjV&#)KtzGw z2@|DI{5)3T_?Q?}CMG68tPSn$LC$G7y!`yKfUyxTAvjX5j6ul+nFyPNgakoBpe@YO zDQCeQ3u*MTu{oAJW-?$d>O8F2f7)p!k~2iP)@ms(R|D$oR&kqx?e z6B}6WBHdCps4~H@3fI-rB|N`9xyEqU3|LTW>s44;U$8*}4rv#AD~CjG{z@!y`M^H# zOe_!yT)>cx@EOQ;XfNnsLS+KmKEMGba3=HnnJ?Q-(wt-L`{_&aL*LttRN|1*=8)-I zU``8>@Z)FuJw5r(9r0y=!w0-Fa@W4a-g7_RWdXa?&O)LgBUl)%2__(`BoWvGT^ndh z$K!Bbqy{1f{GmExLJzUDw%(1@0X+7D6(%PBp9DM|$babS=(NT#DH<6WH6VI{_r)=C0~!R0j|d+?J2L{++MqNd?rI($o;Te>^s3L_y_v&qFEc*Hz&){!*7kH{ zs+7%VmUGyf)H|OV6n?RZmD`o#+mIoJP^Jgj?+7U<+Qk@SWWenFr_zSPe!SWT1j=OR zw5k^`UOb0iEdW!AP-jvZ2^Bc843K2|@85QKcq16lKr;ffBPq(S{Xr1>iRTv=8Q~DO zzP`SLLpKPOK*!$eeidqSKF7ctYJ zHrH$Wi4abTl7>HjD5pI3@Sw@7prOr+*I!VTvcJAK9OOAlik+Jt-uW$8s^iR1 zmptElswXACT--bZPp$h_jag^#U*Q;!W)Rgug^*&6Edzry2%NEN63^t*L1L99L{EG7 z&L{k!AT!ek&JM_jl1i(osSV`e5XDMdReJD%m6_QeF(^VQ8c2sD3E(jDg}4|%=N1)B z$zNtCo+XLfOoZqHO$BXtY&|TuOLV7Xvc=O+OoGjW zT`LzC^u|ok7mn?*-YWej1{*c?sX_#G02q6L?Xn06#DV?O#Kbf>B$5GCRd^>1BxIjF zp~c4Tj0#H62J3EQBt8h=q_-ZCf*Esv|DdOTFdL9KDfiPfu<@I~Ne>XVS?rf->>>s=OfX^63#2YCs&_;5frAQMnGpk=j8ONr~h)>!9aXs zpk6tLe)0{J)~tdL$!=WYp5GnZy5Wdb^oNc&1U9*?t!*DJZb{s}@KZOP4GxP?Id_I% zd{Ne01{O$S3KB&elZ5j6$;YfSK%OMfM6EsLUVGMnwac%;zmh!C(E)d zi&T*7p7FaTONt={#fO+Mzs6Z3eQ*wSIScL*`3usMk|jOGhf86C%&!id>RoGYB>oJE zI4=3qGwjGKax?j;+Qr0FqNU-e`e;B6p4zd@k-3G9ke;IZ+qQFUm<#goyuG}Je|m3! zx{p0a9LJ?ktfi&ZnQ-g0wS9b?*3NE&*2;k>raR`xGw|jlBC5#xdKX9xb=5<1o}njQ z*k1WL+|VUJ%6hprbPEKD*pz8;?~Am!I8u~Qa?a3#`*|Q1- zl9FnpyP_|W(}K5y36zbLELXdGqSc??b(h}WGkRL@@h*junuf;I-u^deuE?MXfC|W< z>R+9#$kIcU+(1X*U8uU5Ch7d1^;Zhdi&-TO?ZOOLbR0ryH9x)YE^tQbd%nRpAsG2J z{Qmu5&_3kZTQGot&$E^AVpP^P~!Hf|VA zuEbqxYAO@G@#w5T`MvYecOVwX`707$2q8B;5cF%i=lNu)IHrN*1q`o&{#?ZDmvb+k1O95B6U;$1-j`n1FQ6ib!^X@ON8(4tUqQ9Qs=L zbSpi$r@#@f3CF$)wF};!WLQC`ijUW+GYo#rY;jyHAt=DZ$+oJcA?L~Qz(?r>C~!5L z=RqVjuo!}v5eNkeB{Y#eQiqJQ^Kn96n!L!TkB?04jc@NjFXN4eGxX zP7ojaZ2#S}81Xf``}HyybUr@tIy%+`ghu~(2IS}sP^=dRt2(z0Yq|zGA1dFwCu1P{ z4l1_s)5g1k7ztHeYOX(b3tF4x%7eJ%+twbIxb7}1Tc_cA(00JRfnzKqH#ZQD-AG=Z z@x$h1=@va2C^mR(dr6P3kzOc2u#`CeGlv_xo1vj0K(L0Iy`}0G$2jmLl+;w3MY=GU zF7~;PN5jRTA56e_Pbz_NPJfzzbMouYgP+4Sjt3Y%QD~^8<>gs0ITx_~6Ag0;ja*eK zGWW-&!X8IFpxd)SqH4q{_EuUv+_@SUd@uUv!umvAV!U^;9N1(9ZH9Wlou>0mXWA81 z769>cFt9x`JX|`dT!Y(J*URG>(kUvi)k@|VEHtvRQ>NoTAhpm%IFpbEL&LXmq6`*}5pUOL@XXhlUysp03( zFc9(`;cPG%&lF-9<@1MguH&T&FYkbz{?er5}ok2E6+7^%Wf*VBL8o8@{9YL)_|N>Bgk&MMx#yqxES9;txmVnL1?YpmnWto#@I ziOxg1v5=6+OpkzF0jpkrH@r2CkN*!_Zy8o)*mY~G7#MVScXuzOQ;=>DknV0!y1P@l zyIbjAq%=r(ckhd5?|1L#JC5&%|4>}(j%&^_$2g}posC?t`@kRu!_Cz~yJnO1`}S(j zuHJ5ia!)?oW}a0LHUP3gk=Br1--a!i(L;fq-gqvHYT_wTW=v0{y**p}5>fA12a={F zmX1io5UQ=q_5R4A(tK^`c!iFV1|3W|oRE$Z@8&f}Tr&)SP%53oEQ0G<@&cGRarsae4F_AT{2D7pJh z0DA|23DVzM?z+i?fw!nP!k*Ll12K?B%@N?=pK0^pX?8p!Cgy#WlFVLnIGU_{prQMn z7X2-iF)2Cu>egOwrB5%X+b%70QYeV(jsw%+^F$H|K1EuJT%cG6A)OeOHY3cddCt^Z zo*-ju$u*qZRE*qoXgV5WSYeD*58iYWm$BX|2Md5_@w(2w~0VYr=9r#_JiG zfng92rMjZK+6kL%I`1D~H-$c=-@BdYfP`n^at_3?bl-TPgh?bAU9L8S*yTrGp!i4{ zq<1GKztQ=U7^qS zDYA;Q2cPHdH8qO0Qq&Y627rKoz;3lP1S%~m`5M?S$!vehYVj^`rp2T*q^9VD*L9aD zTDDdjLsB6&$<+{}Aa{%)Alv~-^4Y@K# z@&mYP&*4BIV)+Ut`x#HQp;_?M<{BO(>s>&iXo(W~8+X0p^xuWsoc2jXBgYsoXY#?^ zBK!iQk5xs?H%J#$S)WdeAa-OsQZh0>i`bGwVB?iI=uPd7%h?U^`a*w zN6XnD@Gb~Ory2kex#!RinN+-Dt(oL=><;v!K6!L0%uXi@1mt7G__iG*55ojfGH1#{ z?`p4MiE|yWnrDMz;Re;JX9GT$4%F=Yjj|MKR;Zqt7W%FL%X^E^?1;bek47q$BHTcg zMxpJXS89F#S~--@ynE8lY}=3)zE&x=>ncc^fIqEz$!HdIu3!*blx!szT4S!ZaWe*SZntn%0@c(U3J%rgkUqm%9Ka^mNz4xYq* zy3=S#T=IGUcV=KjOyYB6|HI&8GF67gcYFCeFE6Z0IvABvP)KmL*@MBY{(ua0F*>66 z9l*?bD$L;`v%0b(07QtL{h6e~v%KJL22|ld&K}>*5GqzLhO>EWLZqydr2BVBYg-eg z!nhE99>x`WElnuhuiVD~VY(%^w`otThC5MFs>LvU4fPcaPq7;PdI4EENh7(u-;|n{ zoMRJ*AoYi_sO{;}Lz?-Kqp zmYuE?G;5wOwA-BfGg|{VYpxKjv|BU5^9Qn`L7?MTn=0FQ531|}WTia?st9`9#?(as zb+oV;{`JSFKr@^9uI2LNZhX1hSC&)(l-8-b`T;iQ9dhLNR%$^xY|u(`-i(Qj~q8ZIB$g(gci5}FYXncY^*;nTRfDk?j>-bqGjX4jaQ_=n;3 z5G@PQs@407$>Pg3J6d3~oAv=;P@mA7c{?<@3}Tm;N_#B51UfRQoB~rtN;bB(S$b2! z&gsBC6YY|ij21ezFQVuJECKMXxSYvEOz2`nqafRFV zi-|!2&%Hve6qh$p%fP@S7btMTJ)%{f^RAA#vp-vXS~&^Cg$=}W<&*1-gXbX}f2}kS zc1H^pD2kMdLAE--XmOAYT>Etq#at+^)GcUIfNL2Hr`Qt{lPb2>@i*zyX~^A(xq=;a zEp8F^peuF(J1h+dK=#W9=FU`pFYT!^gJfXd87s7-P_HrjvpyT6bRhF6T+S534(%zN za~mm7{PV3@*&eShPJ@YHIo@RImxTt(QU?E@Stnb;aJQ&+#*m-G=gw~TunDW*Ups4M zOEcdoOTFBgg^vhFI^pl`o|iY7nfRv}my`gi@#F4a?dB zKK&4!u{RAeSv8PJZ|M(0*{v~F`AM#2L3KHjkPVsUJkaq+4{O)Lk;mWd)$vI{oU_9E z=%zs^a{$H4k*BJDOcgOm834&J6m0Aw(*$M_5YGn=o=gzD@fGv0wwV^yRB6yNA)}Wd zimtD$sp@j@qdB}=3<`?K1PuVll_mgEZ1G$z?teZt8OM22sUZEc;c#a@z>DEgs{Cau zCd86+&ac*Ph%2f*QpDQh?3E|KT$JDZJ3abvY4MHg%r)BNW>_lu=Oi$<<9>l@!ng>1 z!bFzHM|?g{-`1G`Ln>B*@m%(^w%c(3jL3@ax7UjXFEY#AxAhjRL&v`_= zG}f$OMXjd13Q{jFkKF;%g?!= zo}6{sMKiXr3fgxeMdN1c`TWXQIbWzZL;OQ*_G>s7TZs9@C^T#1aJX&d0Hkgw&zIt4+J=eHV|plZ!y8lvq zFVWI~Eqo)9GV5hZF%N&JH=TD_fOngwOX74USZ4Zk`VKkB7aZ|b%WnK0@2(7h5Xove zo(N2Dg>$viB)cpOM7t~$U*jPxY3J{~pObvxabvAA^93&yYNfJRItpQCQAewHb{Kd1 z|5)Mk2Ufj{cQW(Kd-p}EZ|tTr?jNAbGjp@2mj@b3%@<%Ax2OmDi%$d`xS;VVa->)$ zp;G>7ON4_pp2+~?2A8D+-Dt673%G$9^l-F`k+1W>DT=ogy%Xgo@X8N&*s4>cwGi8D z(t%%yZEuAx{-eY{)C$@gdP94?S{+G;LEM$Y5KvKOItONj^<%?HW!SG z@Y1__X7DiEc&MR~jMe3N!jM2jIt2|6cK)`&o+O0ZUB+E2{%2B%#cfDAk6=Z?&7KUT zj%q-?P-@O)1a}EOxJ$xQ{~G1_JCT!-J-Y}U50<(oxfFJGC)o!5MXG-ACYFY4sm620 zF5bNh>f~YhchcD#ipBc2R8aldxd9YErDk73SkMauUSFMkP}Da^cmQ` z<%IvNqLE-{1l>rgKLbJxV1gtA33bfqD?yJmK8f<(V0DhQnJ31r746Vy=RB6WL`%J) zA5g;L=~Vsi-Z}x1z6S(aM@RcoHj0HkHYvcL*Ib#he$8P^?Ow9=hl|g)%3}7?EX(Ys zB*%t-BozA#%lDP*tX6FHUqd1Y<<%1^Q_{p|D^l4~JYXk_q*E;McGzop*g`xWtYZZK zjd{(~p25D;QCsD;q3u*p3O);}`XM6nvF>nrz&K%y)p7yz*~w6%IA6VacsSCI@nlfE zrOdQ~f^-w8_+`M#&a<;In+2%cowKuQQ}jr9cws(1FrF9>sx^j4!}F{vps|eA3=R^+ zg~D`Zihn8j((PuZET^JkbpO<&@UXS}T>ODJAW-e_&o3^N4(2dGmrwQlQEde?KGb@G zdKlEwwvP)gf1AqDP}=MfiDiy^w--^dtHSA;*(+`$ak6PdkmB!A=*UMl6XM@S%40X{ zJfg_9Tzyin)$VUaec>=P57*Nc3~>l}h*;6s;73tsrx9GjT#s!Z8=KPI*o?zWV17RMsc8_} zL3mzp$)C}9oiJ_&1F3)S6BeIuWIa(sb93=f0GR3lv{L_kMSa781IpH-AxdsfSL{3I zPXxYF&`fE~+d$g~|FPoC%IhrFfQll$?oGZFm$OSaz(aW+U{28o-45x352aI81U;&I z0)>b*peQ)x7S9Ns;7tw{>niNirR%?AvX~_*;|&qg`})Zz!pFFFufBVP6TZC@BM=>; z_@4K1!S^(o;bhJ$O!)KVKUNiKjq$IvW-0opf2oG5;0DE|WpY4pt8b%`C+b?^gygR5 zGM1Gpsklx=`1B*0WZW;I1bAB2iXY}Bq_GA-2?SG^e^YxZCChdAG0fzRhe85Eg-mj- z$R6zGMn_38sgMK7m|tk;7JSKCvhubMydSA^zerVsGRs@%?F5Jy$;&HF*MS!UzFfvl zO35}$XU-9r%mDY-&p+93CmPh&aP4Y-XH6$cq!_I!9`J3c41KH6qG^Jk_eK?c_#ZF8 zKxtwG0;4*c@%&%kt1VgU%e2(UHEmL^A-!)bd`tNJ9l4xXFSVyBC(2&l>VdKWs5+-x zMf_YqPXY>bpVB<|#Lvb|BaA7i5;9W%AZIft3L@ff0Q<39yu3bHy8*T>wOgqCz`Y{* z^Z|yrEfu*W9PJKoDZ>=WgKG1o4zk_Wz9+2ou=Mj!0gOT-r%l$tJ>o^-YjDBbn2VwI z{}C!ib%iRyg^iRGvYqVr4tb47<3?^d7f}C1A0JvrG-5`@qOvUX_A`+=cWoEz2{VQ4 z(t6i>2lblb&oO0nsBT+>av5f+mkdH7Jx$b*2=5>VB{NYO+Rd`?KMBlI8docqIX4^~Pe!N-tO7O;meJU*kK1 zV8jB9;vb9KPh;(Qu_`|qt`(C%x7y;UYmlzAJHm^>sp}h&=2{E6smF$PUooiM52=4u zN!>1DO?}4+6ui*NwVuu3R`3o6p*fTJMvJl;1@0=?GOI*kj$XLe18uh{_jJv!Q+ckl zyC$BdkGu(9F)xYUJ{jomOb_7Sq4RA|Rq|-5!+krAR(MUe9?6g)?%A1p!Gg6=iQ&=A zxi(Y6hc_qv=Pb!v$Q`Zn1kQFz%hAX+Li^ki_j5UWD`xBOkgcPM>Jwa!UV#Sw5Mbr% z(lW3abmgRNi|DZ2l$&#~?0wGC_SrztL9dbX))O z4N?TR;k_&5^Dk1_6ydS`HsEiHh1AhKr$3LD^PN}*U4VR!0F=jfA{R-9>=d981rW!B z=9=EB=-lyCT4PwXWk=T*g8nF_@DbzR{)X-?CrAzJIOdBC?Tmh|5_UWAV;ut#T;d zDTRW?c=de;YF{tvsmNutQ1&g;&UXpXhQ{jd2_G#;bD}0maOCw-yQyNIC^jkoOvlVm z9yy$rXmD1nRNC=w8s)LL`!gJkbB5kGBD9&-&BV(LsW-ziY1*a@U-tHYxJyhh8O?bK z(0ZD!u52p%r69ZuGzjwH)L5EJPg3yxlBuhc>OclJ0->*?u91)0>+FiH;cgKMd>UBm zZ4-ITm_e!;xH#CS&4X_hL51-VJ$RdC*x?p*qkn+6UbTYTX$!L9MA}XLmHs=75cmG? z3RR-$Y!pW0|6M1=Ye`Yfx4|hn@n(Bu%a)5xRvs?I2M2T-YpLv|tPSAf>Bj}|TM2qM zUWxl_dPcgCq<=Hj^j2>b{br>^nJ`lEZ4`XVw57CaZSGva5T{r~2Tt%%DQqzPC{yGa z_)m4G!k5`H!I`>hR7KJXcVRka(17v<(jFRfRB3u;(3{10iwFR;p#JknsG6NxG zg4hP*w^v8d;bxB679%^$YbxUCy#_1mt)mSu6l7%Yyu8yTo(rJBj`n?xFVVY+I92oD9}aW zzV~JlsZv@uNY9Ihyw=I%!0f$Irs3R~LkyD*aH)!dl&X+mwI|SZ4nKY@s3K&)NR`bb zGqaJg8=DKA7lGv;F41k98L2cBqfqJaZ>lzHK~v*fZ0P_BBBfl(YcNe!DM}*|3;zau z8$cb=vw=lIdgF|CAAA`$Z@1ouG*_o|Xe|ZA5#ppZw_E8mhg&eOs<6;4HeVJNRurxN zI}G4|tAHmaS!?(7=rIsaQ>=*Nf<4P}Y32Oc=aG(C{QAXrVX+?XWG1pOyKnF|9TsH^ zlA_I7WOu&?U~6MAXh8w)B!~!@O${t z&RGgY81OBC5x>+_@+$E@@f!^AW2hYTW#=wJvM?F)J^Q_IswbZK`)J$~*(3Fx@$H0U zk@NLyqeT;`qN201-`1^sT+(2ZE1hQ8^*=q#aedRNr}~0(u~J{F68LpoKI}Bmn)vnm zIc~pp%>rd146QHX!LUyyVB~B>F(O~!N4FDh_@!QJy=C?&IAG~(&kWYr3i&n!gMy5= z5TzzO5U()>Y$bahV&XSJ*tKTXVAzp655!pFXcy-3Esh9m*$hwE8h-mD-#&z&YFsRU z`;}F*Hu#-+Rc~#SV^k$Mj5q8aPV5OH+x?ivI9O5th48NxhnTkdsGFdezhz= zXYMuoYD-y!c|Qob16uk0?;G0uX66R#PGO=gYu_C)euY^KK3Ig;<>xbM_T4Sc*Dnu*w1 zN1CMiRa*}H3rMejf}vIJEO2u^gAu{pJSqYyihmIT`Nxo>wCw@$gib z6QO=MenFf-V{#u8D2^{cpOCM8H1L`D=J<2EXlagj#4&tEdxTuq4S_jx-0q)=K2&B_VU<^e6vtg^HR=s{v(-tS&3!H_Oc7y~Q&>ow0n=jr33XktMJ|+hGqCqD)dMd$RI*Tsb)#Gd ziC7`qoa_u?i|S)nuNcle8d;l3M(8}5->-QD*pv3IPfx74oR=6eEkoPwP6YbJ8_U?~ zHcU3+q{zS~53UL@pM!(|@v;c@uz#Sqz`9l!PfbnD=$PCRCHnupLNVuR>Yl2h4-&s) zT3YFYX}$MSFplqnf@v2Z$Hle>R%VtHCJOWMeV$HtO1MvbVO@03 zKMYDZmxmbO82KYu38;yvNI?4%WJ%js#`D3R4x{Z{JF~n;o87Cg#@ieJ)EGyRRAzn1c^2)qm-2RMZx3Fz)Bf z2PSgY-U$4Y_>FRVahT7FBVUNh=fRgn636kH+zPq38_rF9yCsl(=bSya_y!-`h2E0w>_l**>f_>7d^5^LpvopUwhYlX&W;N6f`j7-5RNrNx)2BCM znc#(91(%x7S4O0Ci@AGApb`bo0nIYjjc6HzTk3$(-KNF;sI#xf{+VD8UP zY&7O;^@=v68`+9Uyx~GP;_Dh1klb+uOQ<%6`vz zj%6hS&5~c5-By9kM|0K}F0TYswB=m#cu=rCRS~%!+i>*Vqzy+WZuV7|u(OYN!>HFo z_Q?W78!ISFSfmyWJvAx0!|mF?{IhiL5qgRyFxTDx-I~Z?qc0VrC!SSSRnyDxDT30s z*-RnW@K?alA&u9ePkAz(og~F-LYyO#Uc)+f%0Wa7)8KK5BJ}LnmT+504p3nwYZclw zUDEm8%D^EBcM~FHe!`r+MYPB2cI11^GGv$D`=IWzeLoqZrGsh`tEbq^H(crv8QE8w z3xn0llwL=}&Qe(DNnWH_B$v62*@TM;-OSxGW-fm(YHBMe>uSqB9y&t*@lnrLKO0>A z>4)-(8XJY@!|n!VJBCzS6E5pMXV~1_#Fsnrd?=sSd0?`}4p7*zQb~~&DM#1x=NYTkfj&W6i%4Abg{L1KZtNc< z&Wp$Y8Tm~5|EC~j!RwGc{KG@KR=NJ)#b^r=0R!s`JnW}Re_ds)77j3vsy1c08sZ+C zF2j`ntoJqM(P!^?#1764-{7B4-c{V$YM0ui`kWr|uw*l=aGF|A@^ATp9f5vb89W|8 zV3qz)35o}H<9Or0qnZ-6By_V6-C!|dap*F?ghUe|@b_C0cF2vG)G)jD3;n#-vI@Ya4h|6J#Z>fr{Tl`(;wK0j=5z&nrd zNB#13#sv3FE$idz+tflws(-Kf-e-X)A6$AsdpTTcllm9)Id^^l{=;GfTI-e(F*_x$ zRkerrvP&G1m){k{YjtSfSVQ5eGbBQAbB5npzgAHPzA9STCnb2i}(A7D(hiDFbn?)Fmo2*NCBilLjN5{w1f zcy_vMZ4d@b)iK(GA-?Lnf%LVt((nUhlpJ}JyywcfGkS{ZJY^ykFRctQzL8~E2{>T` zuJC(6XxYJmY>DzGHk+w+$Hi-JLdsf7zbd(E^TF*|3$z*e80-lIYvOEuH8`WIwUrNe zgNCW^+SaOeKN`oH_Wjy`!^%O$F**7uA3Sx?d(XG?2~WBB_vnLJ`$KKV!6{ud@}k;R zfm{6Luo)Otq@_4*GdB8+TL9D4^BSulJWHNyixDfy`UEo6u>U-pNYBq^(&uRH71~1e z9-)?vBiN4#L{wS5TW`#2gSRuOx=`@rVNoTwW@baqc1~S}h6GcgvP@@oyzH*u0AZ=L zv~=78D01I;J_JI>vO7;MAi8+fzUtl;fx`&H#f^vmj#gZ&rYm9j!?$yShm%Kqy-xSBULl`90 zt5AD(nZ5CO$qv*)l9pBQ*L})G70f4g()ug3)^(kS9fUltIg`-`?}CQ0%2{|ny7z#b ze4J%5NZtTT70NN=XHeM-e|Qyf$WP2mn+o(bn#FiU>gREV^WpKj(lJf${2*6bD*=rw z6e^=Bf%9{l<>R)VzSQ!DlY~b;1H#s>zuG6$#iT>6x6DHZpHTiT5t5aX2y5w|F;dJa z7E9sLXzb(VuN^)ewhuI$<}z|j2*Z9PyKmQhA>96cqqT<9&~WbQfu2qW)wsRp>dTX} zPS?;-&y71e3;zl5Bc}84C2>0x$Lkjn_p3WQAD zmkJZ>g7HTxuY2@7hb7Ha{{rz6)TdR@$aV5Y<{~Ecv*mKQP_c|1HkQHDV0=5=rUS8Y z&ywzZDAT#w)(?-xJXVXzfaSm1M8rPnW-h{&&iOJ3O-IL3PZ{({WX@3Dy?z^?Ko2 zDoSIsIcYnsi(nXkUDco+j2Q>L{t9ABO#p_37^fg{PT%hrEMI4--sLu1CHLMj8yeD&Y?UY8 zyca;ce(-A7*Z!Ld63pw#T5=gCRtZ$6L68jz8{yXXU0!?ce|4yTK9sDm-nYESEr8m~ zDQ4`1TY#h%4iMCbo|fI~oGk@}NGLuQZ84v6;6B{(qTN~b(st77{NKh&x0iJqIzS89 z21hR^!wUJ_Upz?e5TYSF??M1%yoXuIilR78DnH5NqeKwtN94)L_S<6N;HKo)L`+fN zUye6Xv(%RFV=XQD88?+Ea%)}xzwW835&stX^gcBku9*FpeVq>2V29A$LuA(|ce z{2*`D#ckIMUo^uYWL~I7ok92Wm)0YE$t;D`aWea{NN;}$t+!CyP3dN~{W%AW{6YeJ z;o4$*1L25?du}hz5shFXkE`fu?29DUxx?zj^HPT8Xl-68vt|46E2W`W1LBdb`ox*s{g01U4`)bJ_Bx+BY>V9W}$726YXq%~jCm!`)oLSY3mgR0B!_`h^5S=H+UcT*oj%?$N zo8f-1{VDA3_Qt~%(e!KP@;Xb`ynu~L%{c6sQTThm;ucJje`&LQD)DF&`b{lDu7O|P{kMip*G;*8UWW}N zqkBkJ^Q^t6gX?1w#Dg2;PN2`~bo(Mb&$@(1kxCZS|6rfbE_xzg%LKW`yRB5LdQUNTTW(fZG|JQwvW35uLvF0QS6VkIk4%Wj-RCcul2>SbP=h< zMll*Q)lD#j8r~idZ~67|viB(W5)UDiXKOp-U984JJ&4y|*+e{?Kn}$|D=s<9p5AVl z<%K2--oYPWaoK(^n=4Z($5$#+{G;z9RyK!*)Nj@Bv?c?5HH9FwQZ}E?Wk(KRC~MQ1 z?Wfl(Ynt`tuvpzxB1bo~K{Ua{5#hw5^dc&<6lP}DlB{FnZ^4sA`f^?1oof3X8Nb7! zL^d+PJ4y+-0!UZVZU6Ml^%a?)sp`6P>Jz&mHTr*Q58>ZFFM1GgWC24ck!fx3l}lp) z7yy(*WySw{-mxtGHy`OD32B*5YX&4j0k^w9G>C1s}?Esxk#zfz%%c=|YFPfVP z5?8D70k@U*UDU*Pbo#nt6Sz{>FG!of`9ssqu#H8;3`r5Dc1N^-Yh{r`jz(F|V14O8 z$xNL~|AETuAs8_U+khr1f3LXXZs66)$ykr0NRZVW#hRl8(e@>b;5`FVCqLY~@P}?v z2A12S#oigyqEaUuG_3BvM-nPsO>a6&4MlkoI}q}Irr2ca`=7?R99}R72d-m)IK!WQ z5(;2550TuL%9XEkG0z56$WIRV1Gg4?R90%*WNkU!SF5pESNHo}Xd+R@CfQU`F^CyI za9fA?RR{Ef!e3tKiHyZ~(_X(R|1M9BiS}Sy6CRJ$^A_>!{>{<)cTO^r-c@&OCWT7Q z3@^P+3VeR|p^Bx>0z(D3tI|X2($9229o;)K%jpiKDxXuJ*s#wVrWg|uqPQG8%&Dpd{6LV7rn+< z1HZphN)!Ihou~p)yEQ6XtDpLT?Q>1hL`@Xr#OVk*cCM4|`0y6an-rb3lI_dWM@4I$ z|I_JS{&%PQ3uoba-|k_Ybr)Se`@Y~n)5HDq#;P;MNjOofe`IuQ$1~h&dRirj;29JI zg5?f_(}i80{;*8f3^-El%9k_s+XCg`i#y|{FJcl>i78k1!Zuo(_eak0TelB5e`s5{ zGP(Z5fG!(3G)MFiH?-*@FXD0_CXQHdg+hsfHF#9c#4;QwYqkdl=Bp6aZWBiIzqKac zn?FIXB4~td<~{I*=6O~Z7X2U=xZ8Jw>LYKBvXs>wp=&8?e7(aqh=>iFi%jA@w(7QYtRMwth+- z>F3Na98#nl*@^(~eVQk2-Ct{;sa48`fOVEilXG#Z)(*&HZ)7tBx3$S-xjLsK-kw%W zw|FuLEZ49mVVQu$i9TwRBXBKg)R_Z9{Le8MF}vNFLUXPZjXoV*y^sVfJ~xegDnX+A zPiLuHHq$NmWT1;5w0OrO&)>}Hz;L+sCKWWjt(CPq!Npeg_)=u zbY*HI($yl)tgAIrDam5es1?`rYP**&E>}c(tLuNng}Pi6w8!wa&w(q^d**fgBbrf1 z3!6Q0IJi8{q2Rv_Z1;1&iojBmeiRCmsUoz_yHCA#`T3F$a@U?E#w}kt%P9AW#@fp4 zYb$f1RXn6G$B~Dvjg>+RNNCjDx2Y!*^ZtfJBj zAaO7-Fys%+K~X4TtIP{zQt(FaX0!E6PLH>~h%;W{_eTyQFk5w@NQX&%EuMzmFNM&( zNCLc~c^AVgnuBpQH<`yvh?7q?EUbFa-ctEf(a+(UTh{8oH=|l~V7~0GjixN8Ln?y9 zY%{O7@OYgPrL{OZElnt?>|bW4%u;Y@H0y%R&Psiuzr!z&PKPV99L6oKk4$OEkx;8| zPIY@Dk{st@AbZSJ>a%FN#!&SnT`PsZu{o7;&wZ{UnG(ARx)$m$*N|s+YBGn_NXP8Q z*(NWu^rAqTv2sbD3^@66pAO}4PPX~q|BHL&F#|vP1m~>&7 zr&WnCk3E_5^pAT#y~>{t28X3VkGu}%`;{Z3(-g`8Pgp0FCpk~7qxW*7`bPQj(w1zh z8voqi$p>o-4CdaY6{C1r&Y{e~?1EX=)^3A12Yg%YRL&6fL9Rq`w0m$j5$+B#|9u^w zRWdiodzVGd#g)WwcrP#*PqzcIs6~q}KTAo4fJinY*q5*=ozw}cq9kr7YV|oaDz`{J zW;=dmR&C0rw2l+yNRr=Pogt+DHTU{&m|R0_Qg(b7h3P_Rrn0s8JU+qxyXQ|Sfb{!` z<~kJ4R86xjP$O_VH@pw?6B@V)zMatIS~g!dhcz) zjh#kYJbK3rCBplGgrV-6@{zT@<;&vMgD<~Q=n==ed z1<%_%zG$!-{LQEy<@h=r$%PbE4(0_GDRq_hf8?JN?CcR1AQ@RSSE>ls5*27?bGP1J z>8*Dy&{4*4{eSKHrjh>!&)=?bE}Oog(89^PEa=ZL2g^~X#CwjwwgHx$V9TpUE*C}( zyygm88_MP@gbv=2G`=7)im~tK`nS%9hToOg5Ibh)>*>6@LIJbnafe|x+BT(K{jxK= z50m%mTng_jW1S@p9Y$8(V~i@EXWL`>CoVU_i|+#FyIdK9Qh!&?lmCYCg&l3P_#RIT zVQ+>U;F>ww+me|V@HF>mL~*>$zwZS-;Qm5W49>I_;H+huI<>9zMUenk=sOP>4?~93 zFBcib#dBmxQMg;T8+${koG~-osvwUe2{K5)b1n!bJvT^wR33&qXd__R5CA@HK9=5b zI#_osGnA@D5EC~J&U08{@fg#34L^mbu^3e@+j*r+UjGvkSs)8Zc-9ljZgdoYdy$ZY zn;CsFl*;6Og`Efi{+~qX>+EPzodwPuGAZ2>dXnuw3ehZ;X{)fp8ET&~a5Qe|+wW{t z-x2=*b@ipMpn{WZ>n-(_`afWZVW5wwiQ9ub%OY^zAY~C}Tc>Rd{KkhVlyCe1Fq$JA?+L({MK!Zt z1F2rHS_U+5P;N?;Cf8!_^2TDJ7$RaJB^ihb3s(he(i?5Xr`cP~#$o(q0Ldy{AaJ7fHf;tKLkde6Fxi2z*R1EQMg?uTq8U!OT|drfpL} zd0|c_@6~yO4+8MsKQync+I}w@klYybqnO@T#%ZPFs5aYReGHh%;WN~d58kYP+8n(I zNxg8)^||GW|8$KO(MuqeA|7hYH{wS?oNQk2$VBv@Pa!Z<-UKu?Y2Ym}PAko(lyW&wwf}g7Gi?}W(0!Q!h6b5PkZ*$*d zA)ZrEEP;rOB7u&&EMr5uJ^!Wqtd8MOzvwG0VivTj`+!Kj^d0lByd@_ToFEw+$%Ii6 zA!?Dq9C{xkE(N9&@@B`yF9%qYW#e&3e4)n~{J2|=;phA8aUOCGSe&cjS~3}crW{W5dQ7z4l%^+W@d=7w1L#i^AQso7CO6{mrRCT)$!vc zjPva|mA+_%?hb*}&fg{+ZLt=`gZM!4yaxM6WSN6B)zdrV+shp(z&Qoa;eYl^4ft}VT&6BWTtgb3;J6=cs}zYM<*1% zm3_w-1lu!#a%cl~0h%h8_jl=#GnZU3g6v@lh{FgocP&FRGDn24=G-XRmCTGyh+wl^!)pj1z*Ee^d?YKYY-u0W7 zW01L7pb(@-yqY06TJwA@Txdz^PY64d#+@LAL2q;X`wuTqge(%+4b=-mq|Y|tWYgqN zj@Y4gVn!|D&O`}?+JnTzFMz)xCO~#|VJr!>QNMD_{L0H2K;|tIMAC~KWlg|)t{-8& z4uYm#B_Icgaf5zm7|)?sh5RqMD!tm1W{MF^3O(PHx~cPrgYF3d$E4D1keCMAZ36D@$iuLw7itoE24B1LEr_)xX<{5uxcQ$vVn*m7BfC;Gu}{3!{RKyo^@}! zHR~_RA>+`C4n{jquWvbd{_j5x-rYE- zzFSM`OugD-@#Ng{7};`%q|Jbn@8&YaDhX_LGc{wAil_V2G0qDmP9AVNJql2k_qh6? z-PrfK8hz;j)=~NT<~x_DpoZvA)->A8A!CWp#)P{xGl!-H<_8sWNd@J_y}lE16rQ4d zY}^&LcHRB2Lc2v`G3mCq4qZJ}g=rr_G9RBCu@UVmDl3pP=pR`#3f;({~lLe72%NAaC9B1BZO^_uWw&U^Ub< z3hjLzhlm(`FT0$z%x44(eePQxjF3@KObRUiT^81#jV>aboJH_E1@mBMnQ^AOr5Z@Z zGkLB%Lp+WY($()#-@JJPpkEb(6z=WVpn7`Fb>4LyxtvB(Fky0g5+CHDq_`aZL8x?I zhf8l1?|Ap)0C9CKwQz8VwA~<3+OMd_XT4ak!7)_lj3D-}r4t&h&mB?@yDckS$SwcL zUC+WN`02v;1Q0}?-yMs193N5G=8``i$|x)3!k8gArE*hZaBMt^S3O|%Xfk>a5_pOI z4fS;3F}Zoi#5C@z*Kc3s_FQAtVt zw_O)}^ea3GHL_z^iOn8$cP2xzR0@FPxB-?eb!uBcy}3R%oHXIwtUo~nG)gUw7DMR! zzO$Z8An-F?jWjOATBF=4TvngY<^tMqHP}Bzf3~m`_q-YkywLU~m%6?0Fn&$%@R%{#$R_1b-ZE4r3cpTq#UNrh^}Gt5LQ&8NZ^rUrR!TXqCbpX~hoApJw$?uRU7A!0Ag zu5n}EcdvA*>;g%?`hm<6o7KNr`THoWsJ`bjb+K z9O6?!}t_Rp600F;&UUTOXHbxeebx$(L`j@7rh;R#G;b69GnEnMf%_MtHpGF2<{ za3xyR@#E3*yav0p^w&?7Q4g+gCez{NVoJG4GMlvtQrnu-lB= zI`i780PB2G9rCzW?zAPXcz6@5qBAR;83%*jtq?WYJt#EncX>U9Ts?o+B%_&U+Th`#SX-~jh~$zNX_m`tSr2wI9n}$v6nLAe zk@%&|G3j8-e<@cAHW}_5XXy<@)ru zb5SvA1Y>a7VDTlLV;THc4T&%Z#_8oJ#;Y&q`7RByA-$(tgEoJf0!nX;H*Hz1TDwcP zKCRjsERWs9Rxu>;+=^67k7z4??E?3fb>8785Z49^%W7b*mR$A$R3!p(8~EgTq669} zq5BzUa+wryb9Y+D%f;+o?`_o!hl~At7Yd1GvPzFZN3e%436PermfRS?HkwWNa;d0G zsyTHdiwwN<^9RXtt8}GDu)J;)RISc;&M}N+KZ6t~2CBXI#6?`*q;QyT-d!xmq$h`i zIMLOD9RdR1IU-PtHsPVP0Gp5m7IEIZxuMml4BZ~%tCtGjZgPBViRu1`j)IEW1;{X< zE@pBPH&oHk7zBv^VKD*^VOv{f)7s=H8= zM~b7KZ;a36E03$iUI6iHHeg5$Cp}Qm3@aOj49B0R(U6e%fN5(5Slj!&EAN{%AxfUi zxBK;z*B5(j*-{sZQ%Y0!=bf_d8P|Mwjt8A1OW{}(M%Lg~jE;=_q3%)J>2B`+MDZalhNsqW;NU~aaSB8)|Zxtqxzd~8}Ale_exv9cLySUDLf@I`7JG}Amej# z;Iv$2aEAWrElgA=agc1F(NF`^^_%M?9^tX~s2doS7K7|{7E3*U9^O^TYeX^9Kd(Sx zc?MYX@($tY)q_t`BdTJSkw(13qXKxb&tne&)qIZ4U0>fI@^L3`&lu~i>RVg0dgalU zEmWrJ-`}r6_ZJ%zJesd`O>`x|*sFfQRw}>o)i_q`*$5Bs<&6HPP~5(8m4wxeHG{)Q z--ev%Rpj`4kM~&bSX3+Z-XQQE|7aB2Mc&=p10wWCfR@<;+qN=WT5u6$5&S>ljN9(V z)Gf25mT?aM6yFp=N4o6!~Wb7tWR8g+xG2oz@m!E0$pvb3@ zSvxow2AOOST_6Vkd|H#PJvktLtcUX){;DX5?9K2@#i}+;4n#fZ^57yv3|K>`NgL!68fKmtmUkMpGBUiHhS$GXOaV;u7NQte*tNFY}|) zlVu!5*{S1#{|LQhaTrj{1K|enbQx5n^Y4KB4Zt&fr?uU}w#UY3)td^0-nt;IMxdgh zR+{%SU+&Lzu0`;V0I%-Gku^?Hc{v3e8-TE*0{nqAcyPkP!knF55!netkAJTC!oiZ* zI+KoIH%9(Vnwt7+L%Ij67EskU(zj{dqYM1`0N45kv;gE_h4{>jDj@i5WEo=+9aP_K zzdxuRB=y*0#tsA7?1-@eZpe*{TT(*NPdsqxUmFp&&F*F zzZH2Qg>K?DAP7Xja&!ym)@erP6eHB=f@OV4j9-WZTo9bfM zaQCNq14~>N9sJkbPY)^FD^6j+DhHRN)+<2ni*4-Oy{u8E11lB`0q*trKht`5DX*;l zjK&do@P;M8lGa|DI-zZiz_BYarH3D#Lna5fBZplK{dc*Zuu}{v= zzh4KggknOx!CH5~Y$%|2L#7&`IH)$;EugC?x-^0T9IG1a@{w4|* zB7o&4(aFgJj}LdxNQAh!xEIE(*xt8qkS6&9e?<}s-Uf^^lw{cRn~}JtrFYy5hHv1K zFmOb)r1GBHo>9`%iprtl;^I9cggdkRk#NnH-!8s)ePnupfT&|0dA!(z$6MJGibr$yP>k8<;?ls-mlVKcc}pM zTxjM8)m-zdpW;$n>Kg>{yW$Ut1su!hG?Ik?E2&?iH&T}xL~D(#-`Ke z<}|F|=N9;8br8$MrN8?9XY6OiDC1jrTyc=J2AektX=y_Nabw^Omkqv2hK1PNR@_b< z0DfSGiGnT9k^q(sHl6|yVze7=0@%P_R9y7(t48t3$;F@gat7@s?eo)Z5i96Y!w)}@n^wMktW>okeMgXs3?Zf5#VV~0X!PZUBRU@SL0s}Z9_p>C{ z%P*lEtgWq~7pZ_PE$pJq&&+&9BKc4NS$lnTE)?3w?*(c4=VDlJK2@1hS$#v(%=d8? zz)Vc|G(1bOmuJ)Z=i!T(A2kL;MPjZ$*B*l91@1_P&oW@ zIJL}t2bLbdX#1~|Cn21$99q|u#^bhjnV)9-LaSW&OVTwqRs~A|z!=!?8f$CcdHVDQ zMDy_QaJ7T|lP7nDqLKB!^=Mb@^!#cdI*x-TisrVIBx)NE&Mqq4DtedtkH)~kO#7jP zR{*f5+rQ6j6Ai`0#oh7_&wklgxnqd7_bxB1rGZdG=I0xjhHK`d4NTP{YbpISh*Hf+ zEpKD@HzV+p1)&LE5P9z7GyCgTBUU0od?7DkM|!JBZp4(U|C<R>SFZu-AnXGNJ-5uuZA=? z`}N+U``A5JxL~>Z;IpEw>=qcF9BY*mWMVoujKgJ>W6>ikg~g@z$e%w8u-~-vonJmS zHPnJWSEOmE@t9!1vL{4Wg z-TO=EqY7>-TIgxhL6?zav!6v84UgkE91c82A0UyMKo{*@ArRa)ePpE4Y0@=S^z|8} z(#yQlkt0LsoMuTW$=UhkK%fYNyAOCVlj%=!x*y6%bYc%fchi7$BryyZA?Pr=2`kVxWwObUzY8L zDPhjT2wQrn$+CwO2)p(1TLi--DK2>;W^Lki`+$sK5zY<}F7uzXLyFlTiN@td1`ii2&bR?CY?!XBA`i<0ch<$`{@)VElXd$>Rht4pRk~l+sQI+?8+ut$vTYH z2eFKMPQ$iH@Oem)aJ~k_&qW$7YW6@nNA*})cAqN&Z69Gv-tKDEpi%2fnXqg+IYCsn z2p2tq>k_=<>K@F4zY8#N#7lfzm$O7A%nh{Lz!0HfWuU2k<}I@&N(N4q%}lP>wUWlh z_vJ05FfF;gc$RL&mwMYPa#qq3>P@fjz6)qvbs5Y~>9+m*;Eq)41&NIg66+TO`_S-k z3xID;ydd^NfWg%Q>EuIb*i&pG_2 zQ{uBNu36O>>qW)OI80Th6Lc3~LMaT^zCfDQ(AJJ=P`C(9rbq?FquxMxdwTa;xb8ZUzaDL6xw3w)Oh3$$U z)uFcX$?xD6g!B&C)$Ff#(1J0`Ygl=PIF5?WpauCDaupt9K2e}POC#!)Eb}&-XP&ip z$?PY{yj+a`_RITWYT#^T28LqlDCrc~Y_2wZW-;RCH?NRIOu!y(Y^_}6=CB8h+7(_EHlNndgJXaQK(B!m z@J(1KmK-vl8aRK@%kMwc(9j^WMnM?@p8YOtqzBt#c=7Y5**b+hRp13T4eA4hJ^EoG zYJ@KWL`@-X1Q$ovOdXQ**SfDGI8ysI)q!&c1b0qB!6DFpzVK9*W?g&KKONr7~~%5ri7 zhllkbH~iDrX9;|!`T2P>Q6`u@%|MD%ai)E-@3;PEyK`jlGAI}FAB3&;p`M`$rFwHQ zJX6@2a7mr#Z)V%g!@}R28m1LZ!$b^bI>(mhaKTS>Ism!LCM}%?Ca3ZW3Yh9uAFE5` zU|QmY=xpbQFLw9zkli?7g(su#A_D^!2yCws@TY~Gg{1eM&z86<&lTOBD{2E~e`GP` zWy8y9VgvQ{jiRkf*-wd1(Qr2)dTooV^$hiO(WP09MNEzfiA6=c)%yKwI9q#2Cv%qF zs_)NgP{2)^URqN5Z?+`^UtoxbGKGeOeEjyUR>Oh;$`x22Lyk&K6`hMPws%8Z-QeK? zm~{pW_Pl(2;5VC@o&5$diMq!6p~(E!Wt)Oh-01k&2{Rk#V0@#u86&q1w;nG8xGELs z@kS_fgk@(IV~?5HifWsVytrIe)U|EoLTrw1BIFmzNh9nZTn#GIbX; z)CC}qCM6}QJCh@tx+<0&5xJ1F#kpT4BnZfzqdY+waICpSk{w*-axs|muYUftyQtJ= zW~;e4Y$pa;^YZ1(6!4j=iwgzWM5@Ll(xkMjzf`hq1y_6-Y>Hr>n3`2ht}mUTCJbQY z%diJ7clmsa@&5OyJTD^G-C9347^ zhHZ#H6zD^lS$Zjs?i|(@ZtQO--1qda2=tyifq9^^u4kv)^ywh~K=6=~m!6f)i2jq` zltHH1{Lb5?-OuS98x_^pXC@!`OswO&bXTAIT{KLziF>NzP+wZ(qxlK@Sbj_B-9SB^* z+y@66;$3J5^}crD64sZAybew;ll)U1@N%`aZrB}1jDD+vW^Ri4(DjYFRIgh$9S^em zUQ|0u?Qqey#HfBcQGT;^OZK~{^KEERsec<5J85h}2$rZ-XGkl8-H#bE{6=_l-%!)a zis~GOrGtv{JhIg>ubyI-1|}-1kAH%xoEXU%zQH@Ymb7n_bxNk(r}S#+a4O zv85jo^QFFt`u8~k>H1w2zy3JTM7r0vYDH?tZlt zNjo0wNEEvuZ`zj`;%*t?!eh>a`>{td!Q-fLO^rkD31()E)lz5o(R+M7U-3SB&xmp) zX=sh(+7KijT6qr^CO5>xaALH4%%_VIDA78F0;Ac|zRgYMKQ89;4+`(sI?jn(wX`P8 zdJg(5sQ30rS*^upg*DIJTlkT#ANFGvj~}$*tu>hGnLVIfATY)EyEk7{8~M@W``Ip@ zt21Yq!Aa+kVJ*+7QB(VSzX)n_ATvjnCCkV-h-Yr;P{I4j0-}e0uG|oX$kE*rWGuS9 zi7NV#>MqW!fT`RWNO>Qo3 zw|o#ajgkpyiPy4BB?9~v+pN91&irQUDUUxK%(V1=$LJP~RPFbBtlIEf&+sJ4P3G!a z{d(P6PM=s%L1gazS}voQ{9DD~rCSKG>!|wlQrTrwYpen-VV}zRrY=#Yyfr1tx{G6C za&46uWFV{lP&%pL>&o*#9sGDpiu;q>B`=Ps%S5g%M%@w1Y`2)@?~=Mi==_;cDDV%P zT7FkIHm`w3uT0(V&oieVenDan;yjcVb~}8U+6U>Ohr0heGRZ_(wa%PI1HaCYP)yup7g^n*csBes^*+pJ&d&0gHdC6|fi|srU z2W(==%sWS!-!>(dnUCLMxm4Ed z+SHyun_9X3NZ(tqq^|gY9Dp*xl^^>rah6vuT2GFXy~k!VRP zv&0z&UR-(j$@fj)urA#Ph*{h6cD-WsmM|?o#jdFER$)a|9pXq3-E)nKih(Qr(qm>$ zu`r}csrmdrdtM@*eOJ077(0?(kIa&6_|YA{7KJH&VUeuD{)#fHgOeS94*aMycA1)L zdKeN8ihAAeHb2ulAdmAT%S}!A^)$kysQR>>7wsd#xc|Gve+PRWw*n=hEKfViBv!^nBK_E4)|tr9K;@T~J=C@_On5b1scv>6@S`%6#!F z%6bdS5$K-NQw_yqB%|rhUdiX;<<80dy$r#l>x)y~I}Je-d$YDQvZJI*(`Vj-1$9wH ztk77GGyZL?4pFPM>l)4)!^`h(Ke9#?f4Lj@P~u&SuUrl7?zBhfFa8r<8zaG=AJ3Zx`Voo#mQK)b{ zq}`Vnc-_jEUpP$_TfL!v%`yP*$M!YpgTi5S7JrYVvhK&yB%ZyLMb6bUgrng@)R~q3 z?0}ytFy@bNPa=ZovbhW8+JgsMw4A1!`B5w`V*#&%w$C0)HA^Iz%J$D?*kp;l!Tjbl zR?&L=k`&N;>ms4^46DoNo;WI*#bY-h=Ic3WDQ9Xi`>E-;i7FPZ)cZz9vrSiBG6tsB z&g{+YQa$YOK2H0cLGe%08SkBdgLpBXSMNw)PhQj$HRs(6O-l6Z46Q;B@Q|dVP}Szs z6?aFG1O)+?q1)MM(*GQqp9srBiFg|7pu%wvRdCUeAqBO5Z=PaT^vZSPA`N&?R9cI; ze=fOm3}>rCfrG*;&ou$8vNtng8U@nANnLA{0y(-NsVK5 z>to;i-NNfSt!DiLLu-n??f)dri!x$%2EX##rFG87D3t8I5cnLaVdP(%tYqV4bGYyN zXuF52#lt(+YEX;Q7ReEXYWeHjwZSQr?`wn%mM4Lnc_SVeAj&6Z+jWC z57O_SFO+~!9ko#Z>mSH>lPtj?$8&*r{_lxAEMos34;$P`XbAY9D^X)*59R1E2DiBF SVj3ynM^#Bvu}t3V&3^z% literal 0 HcmV?d00001 diff --git a/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.py b/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.py new file mode 100644 index 0000000000..02385b32e5 --- /dev/null +++ b/tests/topotests/bfd-profiles-topo1/test_bfd_profiles_topo1.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python + +# +# test_bfd_profiles_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_bfd_profiles_topo1.py: Test the FRR BFD profile protocol integration. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + + +class BFDProfTopo(Topo): + "Test topology builder" + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # Create 6 routers + for routern in range(1, 7): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r6"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(BFDProfTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.iteritems(): + daemon_file = "{}/{}/bfdd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BFD, daemon_file) + + daemon_file = "{}/{}/bgpd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BGP, daemon_file) + + daemon_file = "{}/{}/isisd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_ISIS, daemon_file) + + daemon_file = "{}/{}/ospfd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_OSPF, daemon_file) + + daemon_file = "{}/{}/ospf6d.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_OSPF6, daemon_file) + + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_ZEBRA, daemon_file) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + +def test_wait_protocols_convergence(): + "Wait for all protocols to converge" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for protocols to converge") + + def expect_loopback_route(router, iptype, route, proto): + "Wait until route is present on RIB for protocol." + logger.info('waiting route {} in {}'.format(route, router)) + test_func = partial( + topotest.router_json_cmp, + tgen.gears[router], + 'show {} route json'.format(iptype), + { route: [{ 'protocol': proto }] } + ) + _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + assertmsg = '"{}" OSPF convergence failure'.format(router) + assert result is None, assertmsg + + + # Wait for R1 <-> R6 convergence. + expect_loopback_route('r1', 'ip', '10.254.254.6/32', 'ospf') + + # Wait for R6 <-> R1 convergence. + expect_loopback_route('r6', 'ip', '10.254.254.1/32', 'ospf') + + # Wait for R2 <-> R3 convergence. + expect_loopback_route('r2', 'ip', '10.254.254.3/32', 'bgp') + + # Wait for R3 <-> R2 convergence. + expect_loopback_route('r3', 'ip', '10.254.254.2/32', 'bgp') + + # Wait for R3 <-> R4 convergence. + expect_loopback_route('r3', 'ipv6', '2001:db8:3::/64', 'isis') + + # Wait for R4 <-> R3 convergence. + expect_loopback_route('r4', 'ipv6', '2001:db8:1::/64', 'isis') + + # Wait for R4 <-> R5 convergence. + expect_loopback_route('r4', 'ipv6', '2001:db8:3::/64', 'ospf6') + + # Wait for R5 <-> R4 convergence. + expect_loopback_route('r5', 'ipv6', '2001:db8:2::/64', 'ospf6') + + +def test_bfd_profile_values(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bfd peers to go up and checking profile values") + + for router in tgen.routers().values(): + json_file = "{}/{}/bfd-peers-initial.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=12, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args))