From 2435b7defe734ab42f8b30368ff842aa0a5b6c42 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Wed, 6 Mar 2019 17:50:04 -0300 Subject: [PATCH 1/4] bfdd: fix single hop IPv6 configurations Don't assume IPv6 will always be multi hop and handle the single hop link-local address case. Signed-off-by: Rafael Zalamena --- bfdd/bfd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bfdd/bfd.c b/bfdd/bfd.c index be6f2caa44..36133f5727 100644 --- a/bfdd/bfd.c +++ b/bfdd/bfd.c @@ -208,13 +208,17 @@ int bfd_session_enable(struct bfd_session *bs) /* Set the IPv6 scope id for link-local addresses. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { - sin6 = &bs->mhop.peer.sa_sin6; + sin6 = (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? &bs->mhop.peer.sa_sin6 + : &bs->shop.peer.sa_sin6; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) sin6->sin6_scope_id = bs->ifp != NULL ? bs->ifp->ifindex : IFINDEX_INTERNAL; - sin6 = &bs->mhop.local.sa_sin6; + sin6 = (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? &bs->mhop.local.sa_sin6 + : &bs->local_address.sa_sin6; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) sin6->sin6_scope_id = bs->ifp != NULL ? bs->ifp->ifindex From 79b4a6fcebd7cca550c9661f54f48f74df12e5ea Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Mon, 11 Mar 2019 15:09:15 -0300 Subject: [PATCH 2/4] bfdd: change session lookup data structure Use simplier data structure key to avoid having to do complex and error-prone key building (e.g. avoid expecting caller to know IPv6 scope id, interface index, vrf index etc...). Signed-off-by: Rafael Zalamena --- bfdd/bfd.c | 526 +++++++++++++++++---------------------------- bfdd/bfd.h | 53 ++--- bfdd/bfd_packet.c | 60 +++--- bfdd/bfdd_vty.c | 105 +++++---- bfdd/config.c | 46 ++-- bfdd/ptm_adapter.c | 83 ++----- 6 files changed, 340 insertions(+), 533 deletions(-) diff --git a/bfdd/bfd.c b/bfdd/bfd.c index 36133f5727..ea059cc1c2 100644 --- a/bfdd/bfd.c +++ b/bfdd/bfd.c @@ -36,7 +36,9 @@ DEFINE_QOBJ_TYPE(bfd_session); /* * Prototypes */ -static struct bfd_session *bs_peer_waiting_find(struct bfd_peer_cfg *bpc); +void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer, + struct sockaddr_any *local, bool mhop, const char *ifname, + const char *vrfname); static uint32_t ptm_bfd_gen_ID(void); static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd); @@ -52,66 +54,47 @@ static void bs_down_handler(struct bfd_session *bs, int nstate); static void bs_init_handler(struct bfd_session *bs, int nstate); static void bs_up_handler(struct bfd_session *bs, int nstate); +/* Zeroed array with the size of an IPv6 address. */ +struct in6_addr zero_addr; /* * Functions */ -static struct bfd_session *bs_peer_waiting_find(struct bfd_peer_cfg *bpc) +void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer, + struct sockaddr_any *local, bool mhop, const char *ifname, + const char *vrfname) { - struct bfd_session_observer *bso; - struct bfd_session *bs = NULL; - bool is_shop, is_ipv4; - - TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { - bs = bso->bso_bs; - - is_shop = !BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH); - is_ipv4 = !BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6); - /* Quick checks first. */ - if (is_shop != (!bpc->bpc_mhop)) - continue; - if (is_ipv4 != bpc->bpc_ipv4) - continue; - - /* - * Slow lookup without hash because we don't have all - * information yet. - */ - if (is_shop) { - if (strcmp(bs->ifname, bpc->bpc_localif)) - continue; - if (memcmp(&bs->shop.peer, &bpc->bpc_peer, - sizeof(bs->shop.peer))) - continue; - - break; - } - - if (strcmp(bs->vrfname, bpc->bpc_vrfname)) - continue; - if (memcmp(&bs->mhop.peer, &bpc->bpc_peer, - sizeof(bs->mhop.peer))) - continue; - if (memcmp(&bs->mhop.local, &bpc->bpc_local, - sizeof(bs->mhop.local))) - continue; + memset(key, 0, sizeof(*key)); + switch (peer->sa_sin.sin_family) { + case AF_INET: + key->family = AF_INET; + memcpy(&key->peer, &peer->sa_sin.sin_addr, + sizeof(peer->sa_sin.sin_addr)); + memcpy(&key->local, &local->sa_sin.sin_addr, + sizeof(local->sa_sin.sin_addr)); + break; + case AF_INET6: + key->family = AF_INET6; + memcpy(&key->peer, &peer->sa_sin6.sin6_addr, + sizeof(peer->sa_sin6.sin6_addr)); + memcpy(&key->local, &local->sa_sin6.sin6_addr, + sizeof(local->sa_sin6.sin6_addr)); break; } - if (bso == NULL) - bs = NULL; - return bs; + key->mhop = mhop; + if (ifname && ifname[0]) + strlcpy(key->ifname, ifname, sizeof(key->ifname)); + if (vrfname && vrfname[0]) + strlcpy(key->vrfname, vrfname, sizeof(key->vrfname)); } struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) { struct bfd_session *bs; struct peer_label *pl; - struct interface *ifp; - struct vrf *vrf; - struct bfd_mhop_key mhop; - struct bfd_shop_key shop; + struct bfd_key key; /* Try to find label first. */ if (bpc->bpc_has_label) { @@ -123,38 +106,10 @@ struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) } /* Otherwise fallback to peer/local hash lookup. */ - if (bpc->bpc_mhop) { - memset(&mhop, 0, sizeof(mhop)); - mhop.peer = bpc->bpc_peer; - mhop.local = bpc->bpc_local; - if (bpc->bpc_has_vrfname) { - vrf = vrf_lookup_by_name(bpc->bpc_vrfname); - if (vrf == NULL) - return NULL; + gen_bfd_key(&key, &bpc->bpc_peer, &bpc->bpc_local, bpc->bpc_mhop, + bpc->bpc_localif, bpc->bpc_vrfname); - mhop.vrfid = vrf->vrf_id; - } - - bs = bfd_mhop_lookup(mhop); - } else { - memset(&shop, 0, sizeof(shop)); - shop.peer = bpc->bpc_peer; - if (bpc->bpc_has_localif) { - ifp = if_lookup_by_name_all_vrf(bpc->bpc_localif); - if (ifp == NULL) - return NULL; - - shop.ifindex = ifp->ifindex; - } - - bs = bfd_shop_lookup(shop); - } - - if (bs != NULL) - return bs; - - /* Search for entries that are incomplete. */ - return bs_peer_waiting_find(bpc); + return bfd_key_lookup(key); } /* @@ -165,7 +120,6 @@ struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) */ int bfd_session_enable(struct bfd_session *bs) { - struct sockaddr_in6 *sin6; struct interface *ifp = NULL; struct vrf *vrf = NULL; int psock; @@ -174,8 +128,8 @@ int bfd_session_enable(struct bfd_session *bs) * If the interface or VRF doesn't exist, then we must register * the session but delay its start. */ - if (bs->ifname[0] != 0) { - ifp = if_lookup_by_name_all_vrf(bs->ifname); + if (bs->key.ifname[0]) { + ifp = if_lookup_by_name_all_vrf(bs->key.ifname); if (ifp == NULL) { log_error( "session-enable: specified interface doesn't exists."); @@ -184,15 +138,17 @@ int bfd_session_enable(struct bfd_session *bs) vrf = vrf_lookup_by_id(ifp->vrf_id); if (vrf == NULL) { - log_error("session-enable: specified VRF doesn't exists."); + log_error( + "session-enable: specified VRF doesn't exists."); return 0; } } - if (bs->vrfname[0] != 0) { - vrf = vrf_lookup_by_name(bs->vrfname); + if (bs->key.vrfname[0]) { + vrf = vrf_lookup_by_name(bs->key.vrfname); if (vrf == NULL) { - log_error("session-enable: specified VRF doesn't exists."); + log_error( + "session-enable: specified VRF doesn't exists."); return 0; } } @@ -202,32 +158,10 @@ int bfd_session_enable(struct bfd_session *bs) if (bs->vrf == NULL) bs->vrf = vrf_lookup_by_id(VRF_DEFAULT); - if (bs->ifname[0] != 0 && - BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0) + if (bs->key.ifname[0] + && BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0) bs->ifp = ifp; - /* Set the IPv6 scope id for link-local addresses. */ - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { - sin6 = (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) - ? &bs->mhop.peer.sa_sin6 - : &bs->shop.peer.sa_sin6; - if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) - sin6->sin6_scope_id = bs->ifp != NULL - ? bs->ifp->ifindex - : IFINDEX_INTERNAL; - - sin6 = (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) - ? &bs->mhop.local.sa_sin6 - : &bs->local_address.sa_sin6; - if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) - sin6->sin6_scope_id = bs->ifp != NULL - ? bs->ifp->ifindex - : IFINDEX_INTERNAL; - - bs->local_ip.sa_sin6 = *sin6; - bs->local_address.sa_sin6 = *sin6; - } - /* * Get socket for transmitting control packets. Note that if we * could use the destination port (3784) for the source @@ -251,25 +185,6 @@ int bfd_session_enable(struct bfd_session *bs) bfd_recvtimer_update(bs); ptm_bfd_start_xmt_timer(bs, false); - /* Registrate session into data structures. */ - bs->discrs.my_discr = ptm_bfd_gen_ID(); - bfd_id_insert(bs); - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { - if (vrf != NULL) - bs->mhop.vrfid = vrf->vrf_id; - else - bs->mhop.vrfid = VRF_DEFAULT; - - bfd_mhop_insert(bs); - } else { - if (ifp != NULL) - bs->shop.ifindex = ifp->ifindex; - else - bs->shop.ifindex = IFINDEX_INTERNAL; - - bfd_shop_insert(bs); - } - return 0; } @@ -292,13 +207,6 @@ void bfd_session_disable(struct bfd_session *bs) bfd_echo_recvtimer_delete(bs); bfd_xmttimer_delete(bs); bfd_echo_xmttimer_delete(bs); - - /* Unregister session from hashes to avoid unwanted activation. */ - bfd_id_delete(bs->discrs.my_discr); - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) - bfd_mhop_delete(bs->mhop); - else - bfd_shop_delete(bs->shop); } static uint32_t ptm_bfd_gen_ID(void) @@ -442,21 +350,20 @@ static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, if (bs == NULL) return NULL; - /* Remove unused fields. */ - switch (sa->sa_sin.sin_family) { + switch (bs->key.family) { case AF_INET: - sa->sa_sin.sin_port = 0; - if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin)) == 0) - return bs; + if (memcmp(&sa->sa_sin.sin_addr, &bs->key.peer, + sizeof(sa->sa_sin.sin_addr))) + return NULL; break; case AF_INET6: - sa->sa_sin6.sin6_port = 0; - if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin6)) == 0) - return bs; + if (memcmp(&sa->sa_sin6.sin6_addr, &bs->key.peer, + sizeof(sa->sa_sin6.sin6_addr))) + return NULL; break; } - return NULL; + return bs; } struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, @@ -465,32 +372,30 @@ struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, ifindex_t ifindex, vrf_id_t vrfid, bool is_mhop) { - struct bfd_session *l_bfd = NULL; - struct bfd_mhop_key mhop; - struct bfd_shop_key shop; + struct interface *ifp; + struct vrf *vrf; + struct bfd_key key; /* Find our session using the ID signaled by the remote end. */ if (cp->discrs.remote_discr) return bfd_find_disc(peer, ntohl(cp->discrs.remote_discr)); /* Search for session without using discriminator. */ - if (is_mhop) { - memset(&mhop, 0, sizeof(mhop)); - mhop.peer = *peer; - mhop.local = *local; - mhop.vrfid = vrfid; + ifp = if_lookup_by_index(ifindex, vrfid); + if (vrfid == VRF_DEFAULT) { + /* + * Don't use the default vrf, otherwise we won't find + * sessions that doesn't specify it. + */ + vrf = NULL; + } else + vrf = vrf_lookup_by_id(vrfid); - l_bfd = bfd_mhop_lookup(mhop); - } else { - memset(&shop, 0, sizeof(shop)); - shop.peer = *peer; - shop.ifindex = ifindex; - - l_bfd = bfd_shop_lookup(shop); - } + gen_bfd_key(&key, peer, local, is_mhop, ifp ? ifp->name : NULL, + vrf ? vrf->name : NULL); /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */ - return l_bfd; + return bfd_key_lookup(key); } int bfd_xmt_cb(struct thread *t) @@ -705,6 +610,9 @@ static void bfd_session_free(struct bfd_session *bs) bfd_session_disable(bs); + bfd_key_delete(bs->key); + bfd_id_delete(bs->discrs.my_discr); + /* Remove observer if any. */ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { if (bso->bso_bs != bs) @@ -747,29 +655,51 @@ struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) * start. See `bfd_session_enable` for more information. */ if (bpc->bpc_has_localif) - strlcpy(bfd->ifname, bpc->bpc_localif, sizeof(bfd->ifname)); + strlcpy(bfd->key.ifname, bpc->bpc_localif, + sizeof(bfd->key.ifname)); if (bpc->bpc_has_vrfname) - strlcpy(bfd->vrfname, bpc->bpc_vrfname, sizeof(bfd->vrfname)); + strlcpy(bfd->key.vrfname, bpc->bpc_vrfname, + sizeof(bfd->key.vrfname)); /* Add observer if we have moving parts. */ - if (bfd->ifname[0] || bfd->vrfname[0]) + if (bfd->key.ifname[0] || bfd->key.vrfname[0]) bs_observer_add(bfd); /* Copy remaining data. */ if (bpc->bpc_ipv4 == false) BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); - if (bpc->bpc_mhop) { - BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); - bfd->mhop.peer = bpc->bpc_peer; - bfd->mhop.local = bpc->bpc_local; - } else { - bfd->shop.peer = bpc->bpc_peer; + bfd->key.family = (bpc->bpc_ipv4) ? AF_INET : AF_INET6; + switch (bfd->key.family) { + case AF_INET: + memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin.sin_addr, + sizeof(bpc->bpc_peer.sa_sin.sin_addr)); + memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin.sin_addr, + sizeof(bpc->bpc_local.sa_sin.sin_addr)); + break; + + case AF_INET6: + memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin6.sin6_addr, + sizeof(bpc->bpc_peer.sa_sin6.sin6_addr)); + memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin6.sin6_addr, + sizeof(bpc->bpc_local.sa_sin6.sin6_addr)); + break; + + default: + assert(1); + break; } - bfd->local_ip = bpc->bpc_local; - bfd->local_address = bpc->bpc_local; + if (bpc->bpc_mhop) + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); + + bfd->key.mhop = bpc->bpc_mhop; + + /* Registrate session into data structures. */ + bfd_key_insert(bfd); + bfd->discrs.my_discr = ptm_bfd_gen_ID(); + bfd_id_insert(bfd); /* Try to enable session and schedule for packet receive/send. */ if (bfd_session_enable(bfd) == -1) { @@ -1223,35 +1153,26 @@ void integer2timestr(uint64_t time, char *buf, size_t buflen) snprintf(buf, buflen, "%u second(s)", second); } -const char *bs_to_string(struct bfd_session *bs) +const char *bs_to_string(const struct bfd_session *bs) { static char buf[256]; + char addr_buf[INET6_ADDRSTRLEN]; int pos; bool is_mhop = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH); pos = snprintf(buf, sizeof(buf), "mhop:%s", is_mhop ? "yes" : "no"); - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { - pos += snprintf(buf + pos, sizeof(buf) - pos, - " peer:%s local:%s", satostr(&bs->mhop.peer), - satostr(&bs->mhop.local)); - - if (bs->mhop.vrfid != VRF_DEFAULT) - snprintf(buf + pos, sizeof(buf) - pos, " vrf:%u", - bs->mhop.vrfid); - } else { - pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s", - satostr(&bs->shop.peer)); - - if (bs->local_address.sa_sin.sin_family) - pos += snprintf(buf + pos, sizeof(buf) - pos, - " local:%s", - satostr(&bs->local_address)); - - if (bs->shop.ifindex) - snprintf(buf + pos, sizeof(buf) - pos, " ifindex:%u", - bs->shop.ifindex); - } - + pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s", + inet_ntop(bs->key.family, &bs->key.peer, addr_buf, + sizeof(addr_buf))); + pos += snprintf(buf + pos, sizeof(buf) - pos, " local:%s", + inet_ntop(bs->key.family, &bs->key.local, addr_buf, + sizeof(addr_buf))); + if (bs->key.vrfname[0]) + pos += snprintf(buf + pos, sizeof(buf) - pos, " vrf:%s", + bs->key.vrfname); + if (bs->key.ifname[0]) + pos += snprintf(buf + pos, sizeof(buf) - pos, " ifname:%s", + bs->key.ifname); return buf; } @@ -1263,10 +1184,10 @@ int bs_observer_add(struct bfd_session *bs) bso->bso_bs = bs; bso->bso_isinterface = !BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH); if (bso->bso_isinterface) - strlcpy(bso->bso_entryname, bs->ifname, + strlcpy(bso->bso_entryname, bs->key.ifname, sizeof(bso->bso_entryname)); else - strlcpy(bso->bso_entryname, bs->vrfname, + strlcpy(bso->bso_entryname, bs->key.vrfname, sizeof(bso->bso_entryname)); TAILQ_INSERT_TAIL(&bglobal.bg_obslist, bso, bso_entry); @@ -1280,21 +1201,59 @@ void bs_observer_del(struct bfd_session_observer *bso) XFREE(MTYPE_BFDD_SESSION_OBSERVER, bso); } +void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc) +{ + memset(bpc, 0, sizeof(*bpc)); + + bpc->bpc_ipv4 = (bs->key.family == AF_INET); + bpc->bpc_mhop = bs->key.mhop; + + switch (bs->key.family) { + case AF_INET: + bpc->bpc_peer.sa_sin.sin_family = AF_INET; + memcpy(&bpc->bpc_peer.sa_sin.sin_addr, &bs->key.peer, + sizeof(bpc->bpc_peer.sa_sin.sin_addr)); + + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) { + bpc->bpc_local.sa_sin.sin_family = AF_INET6; + memcpy(&bpc->bpc_local.sa_sin.sin_addr, &bs->key.peer, + sizeof(bpc->bpc_local.sa_sin.sin_addr)); + } + break; + + case AF_INET6: + bpc->bpc_peer.sa_sin.sin_family = AF_INET6; + memcpy(&bpc->bpc_peer.sa_sin6.sin6_addr, &bs->key.peer, + sizeof(bpc->bpc_peer.sa_sin6.sin6_addr)); + + bpc->bpc_local.sa_sin6.sin6_family = AF_INET6; + memcpy(&bpc->bpc_local.sa_sin6.sin6_addr, &bs->key.peer, + sizeof(bpc->bpc_local.sa_sin6.sin6_addr)); + break; + } + + if (bs->key.ifname[0]) { + bpc->bpc_has_localif = true; + strlcpy(bpc->bpc_localif, bs->key.ifname, + sizeof(bpc->bpc_localif)); + } + + if (bs->key.vrfname[0]) { + bpc->bpc_has_vrfname = true; + strlcpy(bpc->bpc_vrfname, bs->key.vrfname, + sizeof(bpc->bpc_vrfname)); + } +} + /* * BFD hash data structures to find sessions. */ static struct hash *bfd_id_hash; -static struct hash *bfd_shop_hash; -static struct hash *bfd_mhop_hash; +static struct hash *bfd_key_hash; static unsigned int bfd_id_hash_do(void *p); -static unsigned int bfd_shop_hash_do(void *p); -static unsigned int bfd_mhop_hash_do(void *p); - -static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop); -static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop); -static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop); +static unsigned int bfd_key_hash_do(void *p); static void _bfd_free(struct hash_bucket *hb, void *arg __attribute__((__unused__))); @@ -1315,73 +1274,20 @@ static bool bfd_id_hash_cmp(const void *n1, const void *n2) } /* BFD hash for single hop. */ -static unsigned int bfd_shop_hash_do(void *p) +static unsigned int bfd_key_hash_do(void *p) { struct bfd_session *bs = p; - return jhash(&bs->shop, sizeof(bs->shop), 0); + return jhash(&bs->key, sizeof(bs->key), 0); } -static bool bfd_shop_hash_cmp(const void *n1, const void *n2) +static bool bfd_key_hash_cmp(const void *n1, const void *n2) { const struct bfd_session *bs1 = n1, *bs2 = n2; - return memcmp(&bs1->shop, &bs2->shop, sizeof(bs1->shop)) == 0; + return memcmp(&bs1->key, &bs2->key, sizeof(bs1->key)) == 0; } -/* BFD hash for multi hop. */ -static unsigned int bfd_mhop_hash_do(void *p) -{ - struct bfd_session *bs = p; - - return jhash(&bs->mhop, sizeof(bs->mhop), 0); -} - -static bool bfd_mhop_hash_cmp(const void *n1, const void *n2) -{ - const struct bfd_session *bs1 = n1, *bs2 = n2; - - return memcmp(&bs1->mhop, &bs2->mhop, sizeof(bs1->mhop)) == 0; -} - -/* Helper functions */ -static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop) -{ - bs->shop = *shop; - - /* Remove unused fields. */ - switch (bs->shop.peer.sa_sin.sin_family) { - case AF_INET: - bs->shop.peer.sa_sin.sin_port = 0; - break; - case AF_INET6: - bs->shop.peer.sa_sin6.sin6_port = 0; - break; - } -} - -static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop) -{ - _shop_key(bs, shop); - bs->shop.ifindex = IFINDEX_INTERNAL; -} - -static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop) -{ - bs->mhop = *mhop; - - /* Remove unused fields. */ - switch (bs->mhop.peer.sa_sin.sin_family) { - case AF_INET: - bs->mhop.peer.sa_sin.sin_port = 0; - bs->mhop.local.sa_sin.sin_port = 0; - break; - case AF_INET6: - bs->mhop.peer.sa_sin6.sin6_port = 0; - bs->mhop.local.sa_sin6.sin6_port = 0; - break; - } -} /* * Hash public interface / exported functions. @@ -1397,34 +1303,35 @@ struct bfd_session *bfd_id_lookup(uint32_t id) return hash_lookup(bfd_id_hash, &bs); } -struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop) +struct bfd_session *bfd_key_lookup(struct bfd_key key) { struct bfd_session bs, *bsp; - _shop_key(&bs, &shop); + bs.key = key; + bsp = hash_lookup(bfd_key_hash, &bs); - bsp = hash_lookup(bfd_shop_hash, &bs); - if (bsp == NULL && bs.shop.ifindex != 0) { - /* - * Since the local interface spec is optional, try - * searching the key without it as well. - */ - _shop_key2(&bs, &shop); - bsp = hash_lookup(bfd_shop_hash, &bs); + /* Handle cases where local-address is optional. */ + if (bsp == NULL && bs.key.family == AF_INET) { + memset(&bs.key.local, 0, sizeof(bs.key.local)); + bsp = hash_lookup(bfd_key_hash, &bs); + } + + /* Handle cases where ifname is optional. */ + bs.key = key; + if (bsp == NULL && bs.key.ifname[0]) { + memset(bs.key.ifname, 0, sizeof(bs.key.ifname)); + bsp = hash_lookup(bfd_key_hash, &bs); + + /* Handle cases where local-address and ifname are optional. */ + if (bsp == NULL && bs.key.family == AF_INET) { + memset(&bs.key.local, 0, sizeof(bs.key.local)); + bsp = hash_lookup(bfd_key_hash, &bs); + } } return bsp; } -struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop) -{ - struct bfd_session bs; - - _mhop_key(&bs, &mhop); - - return hash_lookup(bfd_mhop_hash, &bs); -} - /* * Delete functions. * @@ -1444,31 +1351,18 @@ struct bfd_session *bfd_id_delete(uint32_t id) return hash_release(bfd_id_hash, &bs); } -struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop) +struct bfd_session *bfd_key_delete(struct bfd_key key) { struct bfd_session bs, *bsp; - _shop_key(&bs, &shop); - bsp = hash_release(bfd_shop_hash, &bs); - if (bsp == NULL && shop.ifindex != 0) { - /* - * Since the local interface spec is optional, try - * searching the key without it as well. - */ - _shop_key2(&bs, &shop); - bsp = hash_release(bfd_shop_hash, &bs); + bs.key = key; + bsp = hash_lookup(bfd_key_hash, &bs); + if (bsp == NULL && key.ifname[0]) { + memset(bs.key.ifname, 0, sizeof(bs.key.ifname)); + bsp = hash_lookup(bfd_key_hash, &bs); } - return bsp; -} - -struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop) -{ - struct bfd_session bs; - - _mhop_key(&bs, &mhop); - - return hash_release(bfd_mhop_hash, &bs); + return hash_release(bfd_key_hash, bsp); } /* Iteration functions. */ @@ -1477,14 +1371,9 @@ void bfd_id_iterate(hash_iter_func hif, void *arg) hash_iterate(bfd_id_hash, hif, arg); } -void bfd_shop_iterate(hash_iter_func hif, void *arg) +void bfd_key_iterate(hash_iter_func hif, void *arg) { - hash_iterate(bfd_shop_hash, hif, arg); -} - -void bfd_mhop_iterate(hash_iter_func hif, void *arg) -{ - hash_iterate(bfd_mhop_hash, hif, arg); + hash_iterate(bfd_key_hash, hif, arg); } /* @@ -1498,24 +1387,17 @@ bool bfd_id_insert(struct bfd_session *bs) return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs); } -bool bfd_shop_insert(struct bfd_session *bs) +bool bfd_key_insert(struct bfd_session *bs) { - return (hash_get(bfd_shop_hash, bs, hash_alloc_intern) == bs); -} - -bool bfd_mhop_insert(struct bfd_session *bs) -{ - return (hash_get(bfd_mhop_hash, bs, hash_alloc_intern) == bs); + return (hash_get(bfd_key_hash, bs, hash_alloc_intern) == bs); } void bfd_initialize(void) { bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp, - "BFD discriminator hash"); - bfd_shop_hash = hash_create(bfd_shop_hash_do, bfd_shop_hash_cmp, - "BFD single hop hash"); - bfd_mhop_hash = hash_create(bfd_mhop_hash_do, bfd_mhop_hash_cmp, - "BFD multihop hop hash"); + "BFD session discriminator hash"); + bfd_key_hash = hash_create(bfd_key_hash_do, bfd_key_hash_cmp, + "BFD session hash"); } static void _bfd_free(struct hash_bucket *hb, @@ -1536,11 +1418,9 @@ void bfd_shutdown(void) * assert() here to make sure it really happened. */ bfd_id_iterate(_bfd_free, NULL); - assert(bfd_shop_hash->count == 0); - assert(bfd_mhop_hash->count == 0); + assert(bfd_key_hash->count == 0); /* Now free the hashes themselves. */ hash_free(bfd_id_hash); - hash_free(bfd_shop_hash); - hash_free(bfd_mhop_hash); + hash_free(bfd_key_hash); } diff --git a/bfdd/bfd.h b/bfdd/bfd.h index 7451ca82a3..1556012296 100644 --- a/bfdd/bfd.h +++ b/bfdd/bfd.h @@ -173,15 +173,13 @@ enum bfd_session_flags { #define BFD_CHECK_FLAG(field, flag) (field & flag) /* BFD session hash keys */ -struct bfd_shop_key { - struct sockaddr_any peer; - ifindex_t ifindex; -}; - -struct bfd_mhop_key { - struct sockaddr_any peer; - struct sockaddr_any local; - vrf_id_t vrfid; +struct bfd_key { + uint16_t family; + uint8_t mhop; + struct in6_addr peer; + struct in6_addr local; + char ifname[MAXNAMELEN]; + char vrfname[MAXNAMELEN]; }; struct bfd_session_stats { @@ -227,19 +225,14 @@ struct bfd_session { uint8_t polling; /* This and the localDiscr are the keys to state info */ + struct bfd_key key; struct peer_label *pl; - union { - struct bfd_shop_key shop; - struct bfd_mhop_key mhop; - }; - int sock; struct sockaddr_any local_address; - struct sockaddr_any local_ip; struct interface *ifp; struct vrf *vrf; - char ifname[MAXNAMELEN]; - char vrfname[MAXNAMELEN]; + + int sock; /* BFD session flags */ enum bfd_session_flags flags; @@ -531,38 +524,28 @@ const char *satostr(struct sockaddr_any *sa); const char *diag2str(uint8_t diag); int strtosa(const char *addr, struct sockaddr_any *sa); void integer2timestr(uint64_t time, char *buf, size_t buflen); -const char *bs_to_string(struct bfd_session *bs); +const char *bs_to_string(const struct bfd_session *bs); int bs_observer_add(struct bfd_session *bs); void bs_observer_del(struct bfd_session_observer *bso); +void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc); + /* BFD hash data structures interface */ void bfd_initialize(void); void bfd_shutdown(void); struct bfd_session *bfd_id_lookup(uint32_t id); -struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop); -struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop); -struct bfd_vrf *bfd_vrf_lookup(int vrf_id); -struct bfd_iface *bfd_iface_lookup(const char *ifname); +struct bfd_session *bfd_key_lookup(struct bfd_key key); struct bfd_session *bfd_id_delete(uint32_t id); -struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop); -struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop); -struct bfd_vrf *bfd_vrf_delete(int vrf_id); -struct bfd_iface *bfd_iface_delete(const char *ifname); +struct bfd_session *bfd_key_delete(struct bfd_key key); bool bfd_id_insert(struct bfd_session *bs); -bool bfd_shop_insert(struct bfd_session *bs); -bool bfd_mhop_insert(struct bfd_session *bs); -bool bfd_vrf_insert(struct bfd_vrf *vrf); -bool bfd_iface_insert(struct bfd_iface *iface); +bool bfd_key_insert(struct bfd_session *bs); typedef void (*hash_iter_func)(struct hash_bucket *hb, void *arg); void bfd_id_iterate(hash_iter_func hif, void *arg); -void bfd_shop_iterate(hash_iter_func hif, void *arg); -void bfd_mhop_iterate(hash_iter_func hif, void *arg); -void bfd_vrf_iterate(hash_iter_func hif, void *arg); -void bfd_iface_iterate(hash_iter_func hif, void *arg); +void bfd_key_iterate(hash_iter_func hif, void *arg); /* Export callback functions for `event.c`. */ extern struct thread_master *master; @@ -572,6 +555,8 @@ int bfd_echo_recvtimer_cb(struct thread *t); int bfd_xmt_cb(struct thread *t); int bfd_echo_xmt_cb(struct thread *t); +extern struct in6_addr zero_addr; + /* * bfdd_vty.c diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c index 8601bd2e40..69d27ed698 100644 --- a/bfdd/bfd_packet.c +++ b/bfdd/bfd_packet.c @@ -79,7 +79,10 @@ int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; - sin6.sin6_addr = bs->shop.peer.sa_sin6.sin6_addr; + memcpy(&sin6.sin6_addr, &bs->key.peer, sizeof(sin6.sin6_addr)); + if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = bs->ifp->ifindex; + sin6.sin6_port = (port) ? *port : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) @@ -92,7 +95,7 @@ int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, } else { memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; - sin.sin_addr = bs->shop.peer.sa_sin.sin_addr; + memcpy(&sin.sin_addr, &bs->key.peer, sizeof(sin.sin_addr)); sin.sin_port = (port) ? *port : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) @@ -120,7 +123,7 @@ int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, void ptm_bfd_echo_snd(struct bfd_session *bfd) { - struct sockaddr_any *sa; + struct sockaddr *sa; socklen_t salen; int sd; struct bfd_echo_pkt bep; @@ -135,31 +138,34 @@ void ptm_bfd_echo_snd(struct bfd_session *bfd) bep.len = BFD_ECHO_PKT_LEN; bep.my_discr = htonl(bfd->discrs.my_discr); - sa = BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MH) ? &bfd->mhop.peer - : &bfd->shop.peer; if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6)) { sd = bglobal.bg_echov6; - sin6 = sa->sa_sin6; + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6.sin6_addr, &bfd->key.peer, sizeof(sin6.sin6_addr)); + if (bfd->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = bfd->ifp->ifindex; + sin6.sin6_port = htons(BFD_DEF_ECHO_PORT); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin6.sin6_len = sizeof(sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ - sa = (struct sockaddr_any *)&sin6; + sa = (struct sockaddr *)&sin6; salen = sizeof(sin6); } else { sd = bglobal.bg_echo; - sin = sa->sa_sin; + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin.sin_addr, &bfd->key.peer, sizeof(sin.sin_addr)); sin.sin_port = htons(BFD_DEF_ECHO_PORT); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin.sin_len = sizeof(sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ - sa = (struct sockaddr_any *)&sin; + sa = (struct sockaddr *)&sin; salen = sizeof(sin); } - if (bp_udp_send(sd, BFD_TTL_VAL, (uint8_t *)&bep, sizeof(bep), - (struct sockaddr *)sa, salen) + if (bp_udp_send(sd, BFD_TTL_VAL, (uint8_t *)&bep, sizeof(bep), sa, + salen) == -1) return; @@ -602,8 +608,8 @@ int bfd_recv_cb(struct thread *t) bfd->mh_ttl, BFD_TTL_VAL); return 0; } - } else if (bfd->local_ip.sa_sin.sin_family == AF_UNSPEC) { - bfd->local_ip = local; + } else if (bfd->local_address.sa_sin.sin_family == AF_UNSPEC) { + bfd->local_address = local; } /* @@ -917,25 +923,26 @@ int bp_peer_socket(const struct bfd_session *bs) return -1; } - if (bs->shop.ifindex != IFINDEX_INTERNAL) { - if (bp_bind_dev(sd, bs->ifp->name) != 0) { + if (bs->key.ifname[0]) { + if (bp_bind_dev(sd, bs->key.ifname) != 0) { close(sd); return -1; } - } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) && - bs->mhop.vrfid != VRF_DEFAULT) { - if (bp_bind_dev(sd, bs->vrf->name) != 0) { + } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + && bs->key.vrfname[0]) { + if (bp_bind_dev(sd, bs->key.vrfname) != 0) { close(sd); return -1; } } /* Find an available source port in the proper range */ - sin = bs->local_ip.sa_sin; + memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin.sin_len = sizeof(sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + memcpy(&sin.sin_addr, &bs->key.local, sizeof(sin.sin_addr)); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0) sin.sin_addr.s_addr = INADDR_ANY; @@ -987,20 +994,23 @@ int bp_peer_socketv6(const struct bfd_session *bs) } /* Find an available source port in the proper range */ - sin6 = bs->local_ip.sa_sin6; + memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin6.sin6_len = sizeof(sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + memcpy(&sin6.sin6_addr, &bs->key.local, sizeof(sin6.sin6_addr)); + if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = bs->ifp->ifindex; - if (bs->shop.ifindex != IFINDEX_INTERNAL) { - if (bp_bind_dev(sd, bs->ifp->name) != 0) { + if (bs->key.ifname[0]) { + if (bp_bind_dev(sd, bs->key.ifname) != 0) { close(sd); return -1; } - } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) && - bs->mhop.vrfid != VRF_DEFAULT) { - if (bp_bind_dev(sd, bs->vrf->name) != 0) { + } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + && bs->key.vrfname[0]) { + if (bp_bind_dev(sd, bs->key.vrfname) != 0) { close(sd); return -1; } diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c index c77cd08be8..f4f38c7f1d 100644 --- a/bfdd/bfdd_vty.c +++ b/bfdd/bfdd_vty.c @@ -79,7 +79,6 @@ _find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv, const char *local_str, const char *ifname, const char *vrfname); - /* * Commands definition. */ @@ -369,22 +368,25 @@ DEFPY(bfd_no_peer, bfd_no_peer_cmd, */ static void _display_peer_header(struct vty *vty, struct bfd_session *bs) { - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { - vty_out(vty, "\tpeer %s", satostr(&bs->mhop.peer)); + char addr_buf[INET6_ADDRSTRLEN]; + + vty_out(vty, "\tpeer %s", + inet_ntop(bs->key.family, &bs->key.peer, addr_buf, + sizeof(addr_buf))); + + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) vty_out(vty, " multihop"); - vty_out(vty, " local-address %s", satostr(&bs->mhop.local)); - if (bs->vrfname[0]) - vty_out(vty, " vrf %s", bs->vrfname); - vty_out(vty, "\n"); - } else { - vty_out(vty, "\tpeer %s", satostr(&bs->shop.peer)); - if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) - vty_out(vty, " local-address %s", - satostr(&bs->local_address)); - if (bs->ifname[0]) - vty_out(vty, " interface %s", bs->ifname); - vty_out(vty, "\n"); - } + + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + vty_out(vty, " local-address %s", + inet_ntop(bs->key.family, &bs->key.local, addr_buf, + sizeof(addr_buf))); + + if (bs->key.vrfname[0]) + vty_out(vty, " vrf %s", bs->key.vrfname); + if (bs->key.ifname[0]) + vty_out(vty, " interface %s", bs->key.ifname); + vty_out(vty, "\n"); if (bs->pl) vty_out(vty, "\t\tlabel: %s\n", bs->pl->pl_label); @@ -453,22 +455,25 @@ static void _display_peer(struct vty *vty, struct bfd_session *bs) static struct json_object *_peer_json_header(struct bfd_session *bs) { struct json_object *jo = json_object_new_object(); + char addr_buf[INET6_ADDRSTRLEN]; - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + if (bs->key.mhop) json_object_boolean_true_add(jo, "multihop"); - json_object_string_add(jo, "peer", satostr(&bs->mhop.peer)); - json_object_string_add(jo, "local", satostr(&bs->mhop.local)); - if (bs->vrfname[0]) - json_object_string_add(jo, "vrf", bs->vrfname); - } else { + else json_object_boolean_false_add(jo, "multihop"); - json_object_string_add(jo, "peer", satostr(&bs->shop.peer)); - if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) - json_object_string_add(jo, "local", - satostr(&bs->local_address)); - if (bs->ifname[0]) - json_object_string_add(jo, "interface", bs->ifname); - } + + json_object_string_add(jo, "peer", + inet_ntop(bs->key.family, &bs->key.peer, + addr_buf, sizeof(addr_buf))); + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + json_object_string_add(jo, "local", + inet_ntop(bs->key.family, &bs->key.local, + addr_buf, sizeof(addr_buf))); + + if (bs->key.vrfname[0]) + json_object_string_add(jo, "vrf", bs->key.vrfname); + if (bs->key.ifname[0]) + json_object_string_add(jo, "interface", bs->key.ifname); if (bs->pl) json_object_string_add(jo, "label", bs->pl->pl_label); @@ -916,22 +921,25 @@ static int bfdd_write_config(struct vty *vty) static void _bfdd_peer_write_config(struct vty *vty, struct bfd_session *bs) { - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { - vty_out(vty, " peer %s", satostr(&bs->mhop.peer)); + char addr_buf[INET6_ADDRSTRLEN]; + + vty_out(vty, " peer %s", + inet_ntop(bs->key.family, &bs->key.peer, addr_buf, + sizeof(addr_buf))); + + if (bs->key.mhop) vty_out(vty, " multihop"); - vty_out(vty, " local-address %s", satostr(&bs->mhop.local)); - if (bs->vrfname[0]) - vty_out(vty, " vrf %s", bs->vrfname); - vty_out(vty, "\n"); - } else { - vty_out(vty, " peer %s", satostr(&bs->shop.peer)); - if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) - vty_out(vty, " local-address %s", - satostr(&bs->local_address)); - if (bs->ifname[0]) - vty_out(vty, " interface %s", bs->ifname); - vty_out(vty, "\n"); - } + + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + vty_out(vty, " local-address %s", + inet_ntop(bs->key.family, &bs->key.local, addr_buf, + sizeof(addr_buf))); + + if (bs->key.vrfname[0]) + vty_out(vty, " vrf %s", bs->key.vrfname); + if (bs->key.ifname[0]) + vty_out(vty, " interface %s", bs->key.ifname); + vty_out(vty, "\n"); if (bs->sock == -1) vty_out(vty, " ! vrf or interface doesn't exist\n"); @@ -980,16 +988,7 @@ static void _bfdd_peer_write_config_iter(struct hash_bucket *hb, void *arg) static int bfdd_peer_write_config(struct vty *vty) { - struct bfd_session_observer *bso; - bfd_id_iterate(_bfdd_peer_write_config_iter, vty); - TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { - /* Only print disabled sessions here. */ - if (bso->bso_bs->sock != -1) - continue; - - _bfdd_peer_write_config(vty, bso->bso_bs); - } return 1; } diff --git a/bfdd/config.c b/bfdd/config.c index 17e155e41d..cd57ea9fe3 100644 --- a/bfdd/config.c +++ b/bfdd/config.c @@ -309,24 +309,7 @@ static int parse_peer_label_config(struct json_object *jo, log_debug("\tpeer-label: %s", sval); /* Translate the label into BFD address keys. */ - bpc->bpc_ipv4 = !BFD_CHECK_FLAG(pl->pl_bs->flags, BFD_SESS_FLAG_IPV6); - bpc->bpc_mhop = BFD_CHECK_FLAG(pl->pl_bs->flags, BFD_SESS_FLAG_MH); - if (bpc->bpc_mhop) { - bpc->bpc_peer = pl->pl_bs->mhop.peer; - bpc->bpc_local = pl->pl_bs->mhop.local; - if (pl->pl_bs->mhop.vrfid != VRF_DEFAULT) { - bpc->bpc_has_vrfname = true; - strlcpy(bpc->bpc_vrfname, pl->pl_bs->vrf->name, - sizeof(bpc->bpc_vrfname)); - } - } else { - bpc->bpc_peer = pl->pl_bs->shop.peer; - if (pl->pl_bs->ifname[0]) { - bpc->bpc_has_localif = true; - strlcpy(bpc->bpc_localif, pl->pl_bs->ifname, - sizeof(bpc->bpc_localif)); - } - } + bs_to_bpc(pl->pl_bs, bpc); return 0; } @@ -519,6 +502,8 @@ int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs) { + char addr_buf[INET6_ADDRSTRLEN]; + /* Add peer 'key' information. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) json_object_boolean_true_add(jo, "ipv6"); @@ -528,21 +513,26 @@ static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs) if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { json_object_boolean_true_add(jo, "multihop"); json_object_string_add(jo, "peer-address", - satostr(&bs->mhop.peer)); + inet_ntop(bs->key.family, &bs->key.peer, + addr_buf, sizeof(addr_buf))); json_object_string_add(jo, "local-address", - satostr(&bs->mhop.local)); - if (bs->vrfname[0]) - json_object_string_add(jo, "vrf-name", bs->vrfname); + inet_ntop(bs->key.family, &bs->key.local, + addr_buf, sizeof(addr_buf))); + if (bs->key.vrfname[0]) + json_object_string_add(jo, "vrf-name", bs->key.vrfname); } else { json_object_boolean_false_add(jo, "multihop"); json_object_string_add(jo, "peer-address", - satostr(&bs->shop.peer)); - if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) - json_object_string_add(jo, "local-address", - satostr(&bs->local_address)); - if (bs->ifname[0]) + inet_ntop(bs->key.family, &bs->key.peer, + addr_buf, sizeof(addr_buf))); + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + json_object_string_add( + jo, "local-address", + inet_ntop(bs->key.family, &bs->key.local, + addr_buf, sizeof(addr_buf))); + if (bs->key.ifname[0]) json_object_string_add(jo, "local-interface", - bs->ifname); + bs->key.ifname); } if (bs->pl) diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c index 5610c352fb..d85bd26705 100644 --- a/bfdd/ptm_adapter.c +++ b/bfdd/ptm_adapter.c @@ -55,7 +55,7 @@ static struct zclient *zclient; /* * Prototypes */ -static int _ptm_msg_address(struct stream *msg, struct sockaddr_any *sa); +static int _ptm_msg_address(struct stream *msg, int family, const void *addr); static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa); static int _ptm_msg_read(struct stream *msg, int command, @@ -127,24 +127,24 @@ static void debug_printbpc(const char *func, unsigned int line, #define DEBUG_PRINTBPC(bpc) #endif /* BFD_DEBUG */ -static int _ptm_msg_address(struct stream *msg, struct sockaddr_any *sa) +static int _ptm_msg_address(struct stream *msg, int family, const void *addr) { - switch (sa->sa_sin.sin_family) { + stream_putc(msg, family); + + switch (family) { case AF_INET: - stream_putc(msg, sa->sa_sin.sin_family); - stream_put_in_addr(msg, &sa->sa_sin.sin_addr); + stream_put(msg, addr, sizeof(struct in_addr)); stream_putc(msg, 32); break; case AF_INET6: - stream_putc(msg, sa->sa_sin6.sin6_family); - stream_put(msg, &sa->sa_sin6.sin6_addr, - sizeof(sa->sa_sin6.sin6_addr)); + stream_put(msg, addr, sizeof(struct in6_addr)); stream_putc(msg, 128); break; default: - return -1; + assert(0); + break; } return 0; @@ -153,7 +153,6 @@ static int _ptm_msg_address(struct stream *msg, struct sockaddr_any *sa) int ptm_bfd_notify(struct bfd_session *bs) { struct stream *msg; - struct sockaddr_any sac; bs->stats.znotification++; @@ -195,10 +194,7 @@ int ptm_bfd_notify(struct bfd_session *bs) stream_putl(msg, IFINDEX_INTERNAL); /* BFD destination prefix information. */ - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) - _ptm_msg_address(msg, &bs->mhop.peer); - else - _ptm_msg_address(msg, &bs->shop.peer); + _ptm_msg_address(msg, bs->key.family, &bs->key.peer); /* BFD status */ switch (bs->ses_state) { @@ -218,34 +214,7 @@ int ptm_bfd_notify(struct bfd_session *bs) } /* BFD source prefix information. */ - if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { - _ptm_msg_address(msg, &bs->mhop.local); - } else { - if (bs->local_address.sa_sin.sin_family) - _ptm_msg_address(msg, &bs->local_address); - else if (bs->local_address.sa_sin.sin_family) - _ptm_msg_address(msg, &bs->local_ip); - else { - sac = bs->shop.peer; - switch (sac.sa_sin.sin_family) { - case AF_INET: - memset(&sac.sa_sin.sin_addr, 0, - sizeof(sac.sa_sin.sin_family)); - break; - case AF_INET6: - memset(&sac.sa_sin6.sin6_addr, 0, - sizeof(sac.sa_sin6.sin6_family)); - break; - - default: - assert(false); - break; - } - - /* No local address found yet, so send zeroes. */ - _ptm_msg_address(msg, &sac); - } - } + _ptm_msg_address(msg, bs->key.family, &bs->key.local); /* Write packet size. */ stream_putw_at(msg, 0, stream_get_endp(msg)); @@ -290,7 +259,6 @@ stream_failure: static int _ptm_msg_read(struct stream *msg, int command, struct bfd_peer_cfg *bpc, struct ptm_client **pc) { - struct interface *ifp; uint32_t pid; uint8_t ttl __attribute__((unused)); size_t ifnamelen; @@ -385,31 +353,6 @@ static int _ptm_msg_read(struct stream *msg, int command, if (bpc->bpc_has_localif) { STREAM_GET(bpc->bpc_localif, msg, ifnamelen); bpc->bpc_localif[ifnamelen] = 0; - - /* - * IPv6 link-local addresses must use scope id, - * otherwise the session lookup will always fail - * and we'll have multiple sessions showing up. - * - * This problem only happens with single hop - * since it is not possible to have link-local - * address for multi hop sessions. - */ - if (bpc->bpc_ipv4 == false - && IN6_IS_ADDR_LINKLOCAL( - &bpc->bpc_peer.sa_sin6.sin6_addr)) { - ifp = if_lookup_by_name_all_vrf( - bpc->bpc_localif); - if (ifp == NULL) { - log_error( - "ptm-read: interface %s doesn't exists", - bpc->bpc_localif); - return -1; - } - - bpc->bpc_peer.sa_sin6.sin6_scope_id = - ifp->ifindex; - } } } @@ -608,7 +551,7 @@ static void bfdd_sessions_enable_interface(struct interface *ifp) /* Interface name mismatch. */ bs = bso->bso_bs; - if (strcmp(ifp->name, bs->ifname)) + if (strcmp(ifp->name, bs->key.ifname)) continue; /* Skip enabled sessions. */ if (bs->sock != -1) @@ -630,7 +573,7 @@ static void bfdd_sessions_disable_interface(struct interface *ifp) /* Interface name mismatch. */ bs = bso->bso_bs; - if (strcmp(ifp->name, bs->ifname)) + if (strcmp(ifp->name, bs->key.ifname)) continue; /* Skip disabled sessions. */ if (bs->sock == -1) From 261e0ba94d24cc28462a12eda23d9ed8ce747765 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Mon, 11 Mar 2019 21:26:13 -0300 Subject: [PATCH 3/4] bfdd: don't enable sessions without local-address When the local-address configured by the peer doesn't exist, then we must observe the session until the mentioned address comes up. Signed-off-by: Rafael Zalamena --- bfdd/bfd.c | 27 +++++++++++++++++++++------ bfdd/bfd.h | 6 +++++- bfdd/bfdd_vty.c | 3 ++- bfdd/ptm_adapter.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/bfdd/bfd.c b/bfdd/bfd.c index ea059cc1c2..c8adf82a83 100644 --- a/bfdd/bfd.c +++ b/bfdd/bfd.c @@ -162,6 +162,13 @@ int bfd_session_enable(struct bfd_session *bs) && BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0) bs->ifp = ifp; + /* Sanity check: don't leak open sockets. */ + if (bs->sock != -1) { + zlog_debug("session-enable: previous socket open"); + close(bs->sock); + bs->sock = -1; + } + /* * Get socket for transmitting control packets. Note that if we * could use the destination port (3784) for the source @@ -170,11 +177,11 @@ int bfd_session_enable(struct bfd_session *bs) if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6) == 0) { psock = bp_peer_socket(bs); if (psock == -1) - return -1; + return 0; } else { psock = bp_peer_socketv6(bs); if (psock == -1) - return -1; + return 0; } /* @@ -662,10 +669,6 @@ struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) strlcpy(bfd->key.vrfname, bpc->bpc_vrfname, sizeof(bfd->key.vrfname)); - /* Add observer if we have moving parts. */ - if (bfd->key.ifname[0] || bfd->key.vrfname[0]) - bs_observer_add(bfd); - /* Copy remaining data. */ if (bpc->bpc_ipv4 == false) BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); @@ -708,6 +711,10 @@ struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) return NULL; } + /* Add observer if we have moving parts. */ + if (bfd->key.ifname[0] || bfd->key.vrfname[0] || bfd->sock == -1) + bs_observer_add(bfd); + /* Apply other configurations. */ _bfd_session_update(bfd, bpc); @@ -1190,6 +1197,14 @@ int bs_observer_add(struct bfd_session *bs) strlcpy(bso->bso_entryname, bs->key.vrfname, sizeof(bso->bso_entryname)); + /* Handle socket binding failures caused by missing local addresses. */ + if (bs->sock == -1) { + bso->bso_isaddress = true; + bso->bso_addr.family = bs->key.family; + memcpy(&bso->bso_addr.u.prefix, &bs->key.local, + sizeof(bs->key.local)); + } + TAILQ_INSERT_TAIL(&bglobal.bg_obslist, bso, bso_entry); return 0; diff --git a/bfdd/bfd.h b/bfdd/bfd.h index 1556012296..a69ff9a1a7 100644 --- a/bfdd/bfd.h +++ b/bfdd/bfd.h @@ -274,7 +274,11 @@ struct bfd_state_str_list { struct bfd_session_observer { struct bfd_session *bso_bs; bool bso_isinterface; - char bso_entryname[MAXNAMELEN]; + bool bso_isaddress; + union { + char bso_entryname[MAXNAMELEN]; + struct prefix bso_addr; + }; TAILQ_ENTRY(bfd_session_observer) bso_entry; }; diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c index f4f38c7f1d..c139492076 100644 --- a/bfdd/bfdd_vty.c +++ b/bfdd/bfdd_vty.c @@ -942,7 +942,8 @@ static void _bfdd_peer_write_config(struct vty *vty, struct bfd_session *bs) vty_out(vty, "\n"); if (bs->sock == -1) - vty_out(vty, " ! vrf or interface doesn't exist\n"); + vty_out(vty, + " ! vrf, interface or local-address doesn't exist\n"); if (bs->detect_mult != BPC_DEF_DETECTMULTIPLIER) vty_out(vty, " detect-multiplier %d\n", bs->detect_mult); diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c index d85bd26705..b44d13f132 100644 --- a/bfdd/ptm_adapter.c +++ b/bfdd/ptm_adapter.c @@ -634,6 +634,48 @@ static int bfdd_interface_vrf_update(int command __attribute__((__unused__)), return 0; } +static void bfdd_sessions_enable_address(struct connected *ifc) +{ + struct bfd_session_observer *bso; + struct bfd_session *bs; + struct prefix prefix; + + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + if (bso->bso_isaddress == false) + continue; + + /* Skip enabled sessions. */ + bs = bso->bso_bs; + if (bs->sock != -1) + continue; + + /* Check address. */ + prefix = bso->bso_addr; + prefix.prefixlen = ifc->address->prefixlen; + if (prefix_cmp(&prefix, ifc->address)) + continue; + + /* Try to enable it. */ + bfd_session_enable(bs); + } +} + +static int bfdd_interface_address_update(int cmd, struct zclient *zc, + zebra_size_t len + __attribute__((__unused__)), + vrf_id_t vrfid) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zc->ibuf, vrfid); + if (ifc == NULL) + return 0; + + bfdd_sessions_enable_address(ifc); + + return 0; +} + void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv) { zclient = zclient_new(master, &zclient_options_default); @@ -656,6 +698,10 @@ void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv) /* Learn about interface VRF. */ zclient->interface_vrf_update = bfdd_interface_vrf_update; + + /* Learn about new addresses being registered. */ + zclient->interface_address_add = bfdd_interface_address_update; + zclient->interface_address_delete = bfdd_interface_address_update; } void bfdd_zclient_stop(void) From 812f5a3d3b42617326ab62ae95b5a9c283da2e7f Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Thu, 7 Mar 2019 16:44:08 -0300 Subject: [PATCH 4/4] topotests: add new bfd topology with IPv6 New BFD topology using IPv6 and multi hop peer to cover more daemon features. This topology also tests BFD integration with BGP, OSPF and OSPF6. Signed-off-by: Rafael Zalamena --- tests/topotests/bfd-topo2/__init__.py | 0 tests/topotests/bfd-topo2/r1/bfdd.conf | 5 + tests/topotests/bfd-topo2/r1/bgpd.conf | 13 ++ tests/topotests/bfd-topo2/r1/ipv4_routes.json | 68 +++++++ tests/topotests/bfd-topo2/r1/ipv6_routes.json | 63 ++++++ tests/topotests/bfd-topo2/r1/peers.json | 29 +++ tests/topotests/bfd-topo2/r1/zebra.conf | 6 + tests/topotests/bfd-topo2/r2/bgpd.conf | 16 ++ tests/topotests/bfd-topo2/r2/ipv4_routes.json | 108 ++++++++++ tests/topotests/bfd-topo2/r2/ipv6_routes.json | 63 ++++++ tests/topotests/bfd-topo2/r2/ospf6d.conf | 9 + tests/topotests/bfd-topo2/r2/ospfd.conf | 9 + tests/topotests/bfd-topo2/r2/peers.json | 42 ++++ tests/topotests/bfd-topo2/r2/zebra.conf | 15 ++ tests/topotests/bfd-topo2/r3/ipv4_routes.json | 109 ++++++++++ tests/topotests/bfd-topo2/r3/ipv6_routes.json | 2 + tests/topotests/bfd-topo2/r3/ospfd.conf | 8 + tests/topotests/bfd-topo2/r3/peers.json | 16 ++ tests/topotests/bfd-topo2/r3/zebra.conf | 6 + tests/topotests/bfd-topo2/r4/bfdd.conf | 5 + tests/topotests/bfd-topo2/r4/ipv4_routes.json | 24 +++ tests/topotests/bfd-topo2/r4/ipv6_routes.json | 63 ++++++ tests/topotests/bfd-topo2/r4/ospf6d.conf | 8 + tests/topotests/bfd-topo2/r4/peers.json | 29 +++ tests/topotests/bfd-topo2/r4/zebra.conf | 6 + tests/topotests/bfd-topo2/test_bfd_topo2.dot | 73 +++++++ tests/topotests/bfd-topo2/test_bfd_topo2.jpg | Bin 0 -> 24206 bytes tests/topotests/bfd-topo2/test_bfd_topo2.py | 191 ++++++++++++++++++ 28 files changed, 986 insertions(+) create mode 100644 tests/topotests/bfd-topo2/__init__.py create mode 100644 tests/topotests/bfd-topo2/r1/bfdd.conf create mode 100644 tests/topotests/bfd-topo2/r1/bgpd.conf create mode 100644 tests/topotests/bfd-topo2/r1/ipv4_routes.json create mode 100644 tests/topotests/bfd-topo2/r1/ipv6_routes.json create mode 100644 tests/topotests/bfd-topo2/r1/peers.json create mode 100644 tests/topotests/bfd-topo2/r1/zebra.conf create mode 100644 tests/topotests/bfd-topo2/r2/bgpd.conf create mode 100644 tests/topotests/bfd-topo2/r2/ipv4_routes.json create mode 100644 tests/topotests/bfd-topo2/r2/ipv6_routes.json create mode 100644 tests/topotests/bfd-topo2/r2/ospf6d.conf create mode 100644 tests/topotests/bfd-topo2/r2/ospfd.conf create mode 100644 tests/topotests/bfd-topo2/r2/peers.json create mode 100644 tests/topotests/bfd-topo2/r2/zebra.conf create mode 100644 tests/topotests/bfd-topo2/r3/ipv4_routes.json create mode 100644 tests/topotests/bfd-topo2/r3/ipv6_routes.json create mode 100644 tests/topotests/bfd-topo2/r3/ospfd.conf create mode 100644 tests/topotests/bfd-topo2/r3/peers.json create mode 100644 tests/topotests/bfd-topo2/r3/zebra.conf create mode 100644 tests/topotests/bfd-topo2/r4/bfdd.conf create mode 100644 tests/topotests/bfd-topo2/r4/ipv4_routes.json create mode 100644 tests/topotests/bfd-topo2/r4/ipv6_routes.json create mode 100644 tests/topotests/bfd-topo2/r4/ospf6d.conf create mode 100644 tests/topotests/bfd-topo2/r4/peers.json create mode 100644 tests/topotests/bfd-topo2/r4/zebra.conf create mode 100644 tests/topotests/bfd-topo2/test_bfd_topo2.dot create mode 100644 tests/topotests/bfd-topo2/test_bfd_topo2.jpg create mode 100644 tests/topotests/bfd-topo2/test_bfd_topo2.py diff --git a/tests/topotests/bfd-topo2/__init__.py b/tests/topotests/bfd-topo2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bfd-topo2/r1/bfdd.conf b/tests/topotests/bfd-topo2/r1/bfdd.conf new file mode 100644 index 0000000000..5c2571bdbd --- /dev/null +++ b/tests/topotests/bfd-topo2/r1/bfdd.conf @@ -0,0 +1,5 @@ +bfd + peer 2001:db8:4::1 multihop local-address 2001:db8:1::1 + no shutdown + ! +! diff --git a/tests/topotests/bfd-topo2/r1/bgpd.conf b/tests/topotests/bfd-topo2/r1/bgpd.conf new file mode 100644 index 0000000000..1623b4578b --- /dev/null +++ b/tests/topotests/bfd-topo2/r1/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 101 + bgp router-id 10.254.254.1 + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r1-eth0 interface peer-group r2g + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bfd-topo2/r1/ipv4_routes.json b/tests/topotests/bfd-topo2/r1/ipv4_routes.json new file mode 100644 index 0000000000..8a2ec25baa --- /dev/null +++ b/tests/topotests/bfd-topo2/r1/ipv4_routes.json @@ -0,0 +1,68 @@ +{ + "10.0.3.0/24": [ + { + "distance": 20, + "protocol": "bgp", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.0.3.0/24", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r1-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "10.254.254.2/32": [ + { + "distance": 20, + "protocol": "bgp", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.2/32", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r1-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.1/32", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "interfaceIndex": 1, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r1/ipv6_routes.json b/tests/topotests/bfd-topo2/r1/ipv6_routes.json new file mode 100644 index 0000000000..618853bd42 --- /dev/null +++ b/tests/topotests/bfd-topo2/r1/ipv6_routes.json @@ -0,0 +1,63 @@ +{ + "2001:db8:4::/64": [ + { + "distance": 20, + "protocol": "bgp", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:4::/64", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r1-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "2001:db8:1::/64": [ + { + "distance": 20, + "protocol": "bgp", + "internalFlags": 0, + "metric": 0, + "internalStatus": 2, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "interfaceIndex": 2, + "flags": 1, + "active": true, + "afi": "ipv6" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:1::/64", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r1-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r1/peers.json b/tests/topotests/bfd-topo2/r1/peers.json new file mode 100644 index 0000000000..b14351cd81 --- /dev/null +++ b/tests/topotests/bfd-topo2/r1/peers.json @@ -0,0 +1,29 @@ +[ + { + "multihop":true, + "peer":"2001:db8:4::1", + "local":"2001:db8:1::1", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-interval":50 + }, + { + "multihop":false, + "interface":"r1-eth0", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-interval":50 + } +] diff --git a/tests/topotests/bfd-topo2/r1/zebra.conf b/tests/topotests/bfd-topo2/r1/zebra.conf new file mode 100644 index 0000000000..7fe5eb218f --- /dev/null +++ b/tests/topotests/bfd-topo2/r1/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ipv6 address 2001:db8:1::1/64 +! diff --git a/tests/topotests/bfd-topo2/r2/bgpd.conf b/tests/topotests/bfd-topo2/r2/bgpd.conf new file mode 100644 index 0000000000..bf42d21812 --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/bgpd.conf @@ -0,0 +1,16 @@ +router bgp 102 + bgp router-id 10.254.254.2 + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r2-eth0 interface peer-group r2g + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bfd-topo2/r2/ipv4_routes.json b/tests/topotests/bfd-topo2/r2/ipv4_routes.json new file mode 100644 index 0000000000..b9d8afb430 --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/ipv4_routes.json @@ -0,0 +1,108 @@ +{ + "10.0.3.0/24": [ + { + "distance": 110, + "protocol": "ospf", + "internalFlags": 0, + "metric": 10, + "internalStatus": 2, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "flags": 1, + "interfaceIndex": 3, + "interfaceName": "r2-eth1" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.0.3.0/24", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth1", + "interfaceIndex": 3, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.3/32": [ + { + "distance": 110, + "protocol": "ospf", + "internalFlags": 8, + "metric": 20, + "selected": true, + "installed": true, + "prefix": "10.254.254.3/32", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r2-eth1", + "ip": "10.0.3.1", + "interfaceIndex": 3, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.2/32": [ + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.2/32", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "interfaceIndex": 1, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 20, + "protocol": "bgp", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.1/32", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r2-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r2/ipv6_routes.json b/tests/topotests/bfd-topo2/r2/ipv6_routes.json new file mode 100644 index 0000000000..004e7588aa --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/ipv6_routes.json @@ -0,0 +1,63 @@ +{ + "2001:db8:4::/64": [ + { + "distance": 110, + "protocol": "ospf6", + "internalFlags": 0, + "metric": 10, + "internalStatus": 2, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "flags": 1, + "interfaceIndex": 4, + "interfaceName": "r2-eth2" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:4::/64", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth2", + "interfaceIndex": 4, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "2001:db8:1::/64": [ + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:1::/64", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r2/ospf6d.conf b/tests/topotests/bfd-topo2/r2/ospf6d.conf new file mode 100644 index 0000000000..f1cdb50285 --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/ospf6d.conf @@ -0,0 +1,9 @@ +interface r2-eth2 + ipv6 ospf6 bfd +! +router ospf6 + ospf6 router-id 10.254.254.2 + redistribute connected + redistribute bgp + interface r2-eth2 area 0.0.0.1 +! diff --git a/tests/topotests/bfd-topo2/r2/ospfd.conf b/tests/topotests/bfd-topo2/r2/ospfd.conf new file mode 100644 index 0000000000..8e0c45980d --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/ospfd.conf @@ -0,0 +1,9 @@ +interface r2-eth1 + ip ospf area 0.0.0.1 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.2 + redistribute connected + redistribute bgp +! diff --git a/tests/topotests/bfd-topo2/r2/peers.json b/tests/topotests/bfd-topo2/r2/peers.json new file mode 100644 index 0000000000..29075fcc80 --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/peers.json @@ -0,0 +1,42 @@ +[ + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r2-eth0", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-interval": 50, + "remote-diagnostic": "ok" + }, + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r2-eth2", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-interval": 50, + "remote-diagnostic": "ok" + }, + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r2-eth1", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-interval": 50, + "remote-diagnostic": "ok", + "peer": "10.0.3.1" + } +] diff --git a/tests/topotests/bfd-topo2/r2/zebra.conf b/tests/topotests/bfd-topo2/r2/zebra.conf new file mode 100644 index 0000000000..cccbf6574a --- /dev/null +++ b/tests/topotests/bfd-topo2/r2/zebra.conf @@ -0,0 +1,15 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ipv6 address 2001:db8:1::2/64 +! +interface r2-eth1 + ip address 10.0.3.2/24 +! +interface r2-eth2 + ipv6 address 2001:db8:4::2/64 +! diff --git a/tests/topotests/bfd-topo2/r3/ipv4_routes.json b/tests/topotests/bfd-topo2/r3/ipv4_routes.json new file mode 100644 index 0000000000..14dfc692fe --- /dev/null +++ b/tests/topotests/bfd-topo2/r3/ipv4_routes.json @@ -0,0 +1,109 @@ +{ + "10.0.3.0/24": [ + { + "distance": 110, + "protocol": "ospf", + "internalFlags": 0, + "metric": 10, + "internalStatus": 0, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "flags": 1, + "interfaceIndex": 2, + "interfaceName": "r3-eth0" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.0.3.0/24", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r3-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.3/32": [ + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.3/32", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "interfaceIndex": 1, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.2/32": [ + { + "distance": 110, + "protocol": "ospf", + "internalFlags": 8, + "metric": 20, + "selected": true, + "installed": true, + "prefix": "10.254.254.2/32", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r3-eth0", + "ip": "10.0.3.2", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 110, + "protocol": "ospf", + "internalFlags": 8, + "metric": 20, + "selected": true, + "installed": true, + "prefix": "10.254.254.1/32", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r3-eth0", + "ip": "10.0.3.2", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv4" + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r3/ipv6_routes.json b/tests/topotests/bfd-topo2/r3/ipv6_routes.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/tests/topotests/bfd-topo2/r3/ipv6_routes.json @@ -0,0 +1,2 @@ +{ +} diff --git a/tests/topotests/bfd-topo2/r3/ospfd.conf b/tests/topotests/bfd-topo2/r3/ospfd.conf new file mode 100644 index 0000000000..cf2a1bdf76 --- /dev/null +++ b/tests/topotests/bfd-topo2/r3/ospfd.conf @@ -0,0 +1,8 @@ +interface r3-eth0 + ip ospf area 0.0.0.1 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.3 + redistribute connected +! diff --git a/tests/topotests/bfd-topo2/r3/peers.json b/tests/topotests/bfd-topo2/r3/peers.json new file mode 100644 index 0000000000..6698bff201 --- /dev/null +++ b/tests/topotests/bfd-topo2/r3/peers.json @@ -0,0 +1,16 @@ +[ + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r3-eth0", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-interval": 50, + "remote-diagnostic": "ok", + "peer": "10.0.3.2" + } +] diff --git a/tests/topotests/bfd-topo2/r3/zebra.conf b/tests/topotests/bfd-topo2/r3/zebra.conf new file mode 100644 index 0000000000..96fd08c729 --- /dev/null +++ b/tests/topotests/bfd-topo2/r3/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.3/32 +! +interface r3-eth0 + ip address 10.0.3.1/24 +! diff --git a/tests/topotests/bfd-topo2/r4/bfdd.conf b/tests/topotests/bfd-topo2/r4/bfdd.conf new file mode 100644 index 0000000000..fdb4412446 --- /dev/null +++ b/tests/topotests/bfd-topo2/r4/bfdd.conf @@ -0,0 +1,5 @@ +bfd + peer 2001:db8:1::1 multihop local-address 2001:db8:4::1 + no shutdown + ! +! diff --git a/tests/topotests/bfd-topo2/r4/ipv4_routes.json b/tests/topotests/bfd-topo2/r4/ipv4_routes.json new file mode 100644 index 0000000000..ae1e97b017 --- /dev/null +++ b/tests/topotests/bfd-topo2/r4/ipv4_routes.json @@ -0,0 +1,24 @@ +{ + "10.254.254.4/32": [ + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.4/32", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "interfaceIndex": 1, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r4/ipv6_routes.json b/tests/topotests/bfd-topo2/r4/ipv6_routes.json new file mode 100644 index 0000000000..33608b45aa --- /dev/null +++ b/tests/topotests/bfd-topo2/r4/ipv6_routes.json @@ -0,0 +1,63 @@ +{ + "2001:db8:4::/64": [ + { + "distance": 110, + "protocol": "ospf6", + "internalFlags": 0, + "metric": 10, + "internalStatus": 2, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "flags": 1, + "interfaceIndex": 2, + "interfaceName": "r4-eth0" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "internalFlags": 8, + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:4::/64", + "internalStatus": 32, + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r4-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "2001:db8:1::/64": [ + { + "distance": 110, + "protocol": "ospf6", + "internalFlags": 8, + "metric": 10, + "selected": true, + "installed": true, + "prefix": "2001:db8:1::/64", + "internalStatus": 34, + "nexthops": [ + { + "interfaceName": "r4-eth0", + "interfaceIndex": 2, + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ] +} diff --git a/tests/topotests/bfd-topo2/r4/ospf6d.conf b/tests/topotests/bfd-topo2/r4/ospf6d.conf new file mode 100644 index 0000000000..756597d6f8 --- /dev/null +++ b/tests/topotests/bfd-topo2/r4/ospf6d.conf @@ -0,0 +1,8 @@ +interface r4-eth0 + ipv6 ospf6 bfd +! +router ospf6 + ospf6 router-id 10.254.254.4 + redistribute connected + interface r4-eth0 area 0.0.0.1 +! diff --git a/tests/topotests/bfd-topo2/r4/peers.json b/tests/topotests/bfd-topo2/r4/peers.json new file mode 100644 index 0000000000..83101eb47f --- /dev/null +++ b/tests/topotests/bfd-topo2/r4/peers.json @@ -0,0 +1,29 @@ +[ + { + "multihop":true, + "peer":"2001:db8:1::1", + "local":"2001:db8:4::1", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-interval":50 + }, + { + "multihop":false, + "interface":"r4-eth0", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-interval":50 + } +] diff --git a/tests/topotests/bfd-topo2/r4/zebra.conf b/tests/topotests/bfd-topo2/r4/zebra.conf new file mode 100644 index 0000000000..e4f8fd8514 --- /dev/null +++ b/tests/topotests/bfd-topo2/r4/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.4/32 +! +interface r4-eth0 + ipv6 address 2001:db8:4::1/64 +! diff --git a/tests/topotests/bfd-topo2/test_bfd_topo2.dot b/tests/topotests/bfd-topo2/test_bfd_topo2.dot new file mode 100644 index 0000000000..6b68fb398f --- /dev/null +++ b/tests/topotests/bfd-topo2/test_bfd_topo2.dot @@ -0,0 +1,73 @@ +## 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-topo2"; + + # 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, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n10.0.3.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n2001:db8:4::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + + r2 -- sw2 [label="eth1"]; + r3 -- sw2 [label="eth0"]; + + r2 -- sw3 [label="eth2"]; + r4 -- sw3 [label="eth0"]; +} diff --git a/tests/topotests/bfd-topo2/test_bfd_topo2.jpg b/tests/topotests/bfd-topo2/test_bfd_topo2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..35fe562a80b74baad7a922e7a1485326019149f8 GIT binary patch literal 24206 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

r