DEFINE_MTYPE_STATIC(BFDD, BFDD_CONFIG, "long-lived configuration memory")
DEFINE_MTYPE_STATIC(BFDD, BFDD_SESSION_OBSERVER, "Session observer")
DEFINE_MTYPE_STATIC(BFDD, BFDD_VRF, "BFD VRF")
-DEFINE_QOBJ_TYPE(bfd_session)
/*
* Prototypes
*/
-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);
-static void bfd_session_free(struct bfd_session *bs);
-static struct bfd_session *bfd_session_new(void);
static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa,
uint32_t ldisc);
static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc);
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);
+static void bs_neighbour_admin_down_handler(struct bfd_session *bfd,
+ uint8_t diag);
/* Zeroed array with the size of an IPv6 address. */
struct in6_addr zero_addr;
strlcpy(key->ifname, ifname, sizeof(key->ifname));
if (vrfname && vrfname[0])
strlcpy(key->vrfname, vrfname, sizeof(key->vrfname));
+ else
+ strlcpy(key->vrfname, VRF_DEFAULT_NAME, sizeof(key->vrfname));
}
struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc)
bs->vrf = vrf;
if (bs->vrf == NULL)
bs->vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ assert(bs->vrf);
if (bs->key.ifname[0]
&& BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0)
/* Disable all timers. */
bfd_recvtimer_delete(bs);
- bfd_echo_recvtimer_delete(bs);
bfd_xmttimer_delete(bs);
- bfd_echo_xmttimer_delete(bs);
+ ptm_bfd_echo_stop(bs);
+ bs->vrf = NULL;
+ bs->ifp = NULL;
+
+ /* Set session down so it doesn't report UP and disabled. */
+ ptm_bfd_sess_dn(bs, BD_PATH_DOWN);
}
static uint32_t ptm_bfd_gen_ID(void)
/* Start sending control packets with poll bit immediately. */
ptm_bfd_snd(bfd, 0);
- control_notify(bfd);
+ control_notify(bfd, bfd->ses_state);
if (old_state != bfd->ses_state) {
bfd->stats.session_up++;
bfd->demand_mode = 0;
monotime(&bfd->downtime);
- ptm_bfd_snd(bfd, 0);
+ /*
+ * Only attempt to send if we have a valid socket:
+ * this function might be called by session disablers and in
+ * this case we won't have a valid socket (i.e. interface was
+ * removed or VRF doesn't exist anymore).
+ */
+ if (bfd->sock != -1)
+ ptm_bfd_snd(bfd, 0);
/* Slow down the control packets, the connection is down. */
bs_set_slow_timers(bfd);
/* only signal clients when going from up->down state */
if (old_state == PTM_BFD_UP)
- control_notify(bfd);
+ control_notify(bfd, PTM_BFD_DOWN);
/* Stop echo packet transmission if they are active */
if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
/* Search for session without using discriminator. */
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);
+
+ vrf = vrf_lookup_by_id(vrfid);
gen_bfd_key(&key, peer, local, is_mhop, ifp ? ifp->name : NULL,
- vrf ? vrf->name : NULL);
+ vrf ? vrf->name : VRF_DEFAULT_NAME);
/* XXX maybe remoteDiscr should be checked for remoteHeard cases. */
return bfd_key_lookup(key);
return 0;
}
-static struct bfd_session *bfd_session_new(void)
+struct bfd_session *bfd_session_new(void)
{
struct bfd_session *bs;
bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs));
- QOBJ_REG(bs, bfd_session);
-
bs->timers.desired_min_tx = BFD_DEFDESIREDMINTX;
bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX;
bs->timers.required_min_echo = BFD_DEF_REQ_MIN_ECHO;
/* Change and notify state change. */
bs->ses_state = PTM_BFD_ADM_DOWN;
- control_notify(bs);
+ control_notify(bs, bs->ses_state);
/* Don't try to send packets with a disabled session. */
if (bs->sock != -1)
/* Change and notify state change. */
bs->ses_state = PTM_BFD_DOWN;
- control_notify(bs);
+ control_notify(bs, bs->ses_state);
/* Enable all timers. */
bfd_recvtimer_update(bs);
return 0;
}
-static void bfd_session_free(struct bfd_session *bs)
+void bfd_session_free(struct bfd_session *bs)
{
struct bfd_session_observer *bso;
pl_free(bs->pl);
- QOBJ_UNREG(bs);
XFREE(MTYPE_BFDD_CONFIG, bs);
}
if (bpc->bpc_has_vrfname)
strlcpy(bfd->key.vrfname, bpc->bpc_vrfname,
sizeof(bfd->key.vrfname));
+ else
+ strlcpy(bfd->key.vrfname, VRF_DEFAULT_NAME,
+ sizeof(bfd->key.vrfname));
/* Copy remaining data. */
if (bpc->bpc_ipv4 == false)
bfd->key.mhop = bpc->bpc_mhop;
+ if (bs_registrate(bfd) == NULL)
+ return NULL;
+
+ /* Apply other configurations. */
+ _bfd_session_update(bfd, bpc);
+
+ return bfd;
+}
+
+struct bfd_session *bs_registrate(struct bfd_session *bfd)
+{
/* Registrate session into data structures. */
bfd_key_insert(bfd);
bfd->discrs.my_discr = ptm_bfd_gen_ID();
if (bfd->key.ifname[0] || bfd->key.vrfname[0] || bfd->sock == -1)
bs_observer_add(bfd);
- /* Apply other configurations. */
- _bfd_session_update(bfd, bpc);
-
log_info("session-new: %s", bs_to_string(bfd));
control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd);
}
}
+static void bs_neighbour_admin_down_handler(struct bfd_session *bfd,
+ uint8_t diag)
+{
+ int old_state = bfd->ses_state;
+
+ bfd->local_diag = diag;
+ bfd->discrs.remote_discr = 0;
+ bfd->ses_state = PTM_BFD_DOWN;
+ bfd->polling = 0;
+ bfd->demand_mode = 0;
+ monotime(&bfd->downtime);
+
+ /* Slow down the control packets, the connection is down. */
+ bs_set_slow_timers(bfd);
+
+ /* only signal clients when going from up->down state */
+ if (old_state == PTM_BFD_UP)
+ control_notify(bfd, PTM_BFD_ADM_DOWN);
+
+ /* Stop echo packet transmission if they are active */
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
+ ptm_bfd_echo_stop(bfd);
+
+ if (old_state != bfd->ses_state) {
+ bfd->stats.session_down++;
+
+ log_info("state-change: [%s] %s -> %s reason:%s",
+ bs_to_string(bfd), state_list[old_state].str,
+ state_list[bfd->ses_state].str,
+ get_diag_str(bfd->local_diag));
+ }
+}
+
static void bs_up_handler(struct bfd_session *bs, int nstate)
{
switch (nstate) {
case PTM_BFD_ADM_DOWN:
+ bs_neighbour_admin_down_handler(bs, BD_ADMIN_DOWN);
+ break;
+
case PTM_BFD_DOWN:
/* Peer lost or asked to shutdown connection. */
ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN);
struct bfd_session_observer *bso;
bso = XCALLOC(MTYPE_BFDD_SESSION_OBSERVER, sizeof(*bso));
- bso->bso_isaddress = false;
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->key.ifname,
- 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));
- }
+ 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);
};
/* ignore some parameters */
-static int bfd_key_lookup_ignore_partial_walker(struct hash_bucket *b, void *data)
+static int bfd_key_lookup_ignore_partial_walker(struct hash_bucket *b,
+ void *data)
{
- struct bfd_key_walk_partial_lookup *ctx =
+ struct bfd_key_walk_partial_lookup *ctx =
(struct bfd_key_walk_partial_lookup *)data;
struct bfd_session *given = ctx->given;
struct bfd_session *parsed = b->data;
return HASHWALK_CONTINUE;
if (given->key.mhop != parsed->key.mhop)
return HASHWALK_CONTINUE;
- if (memcmp(&given->key.peer, &parsed->key.peer, sizeof(struct in6_addr)))
+ if (memcmp(&given->key.peer, &parsed->key.peer,
+ sizeof(struct in6_addr)))
return HASHWALK_CONTINUE;
if (memcmp(given->key.vrfname, parsed->key.vrfname, MAXNAMELEN))
return HASHWALK_CONTINUE;
if (ctx.result) {
bsp = ctx.result;
log_debug(" peer %s found, but ifp"
- " and/or loc-addr params ignored");
+ " and/or loc-addr params ignored", peer_buf);
}
return bsp;
}
hash_free(bfd_key_hash);
}
+struct bfd_session_iterator {
+ int bsi_stop;
+ bool bsi_mhop;
+ const struct bfd_session *bsi_bs;
+};
+
+static int _bfd_session_next(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_session_iterator *bsi = arg;
+ struct bfd_session *bs = hb->data;
+
+ /* Previous entry signaled stop. */
+ if (bsi->bsi_stop == 1) {
+ /* Match the single/multi hop sessions. */
+ if (bs->key.mhop != bsi->bsi_mhop)
+ return HASHWALK_CONTINUE;
+
+ bsi->bsi_bs = bs;
+ return HASHWALK_ABORT;
+ }
+
+ /* We found the current item, stop in the next one. */
+ if (bsi->bsi_bs == hb->data) {
+ bsi->bsi_stop = 1;
+ /* Set entry to NULL to signal end of list. */
+ bsi->bsi_bs = NULL;
+ } else if (bsi->bsi_bs == NULL && bsi->bsi_mhop == bs->key.mhop) {
+ /* We want the first list item. */
+ bsi->bsi_stop = 1;
+ bsi->bsi_bs = hb->data;
+ return HASHWALK_ABORT;
+ }
+
+ return HASHWALK_CONTINUE;
+}
+
+/*
+ * bfd_session_next: uses the current session to find the next.
+ *
+ * `bs` might point to NULL to get the first item of the data structure.
+ */
+const struct bfd_session *bfd_session_next(const struct bfd_session *bs,
+ bool mhop)
+{
+ struct bfd_session_iterator bsi;
+
+ bsi.bsi_stop = 0;
+ bsi.bsi_bs = bs;
+ bsi.bsi_mhop = mhop;
+ hash_walk(bfd_key_hash, _bfd_session_next, &bsi);
+ if (bsi.bsi_stop == 0)
+ return NULL;
+
+ return bsi.bsi_bs;
+}
+
+static void _bfd_session_remove_manual(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct bfd_session *bs = hb->data;
+
+ /* Delete only manually configured sessions. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0)
+ return;
+
+ bs->refcount--;
+ BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG);
+
+ /* Don't delete sessions still in use. */
+ if (bs->refcount != 0)
+ return;
+
+ bfd_session_free(bs);
+}
+
+/*
+ * bfd_sessions_remove_manual: remove all manually configured sessions.
+ *
+ * NOTE: this function doesn't remove automatically created sessions.
+ */
+void bfd_sessions_remove_manual(void)
+{
+ hash_iterate(bfd_key_hash, _bfd_session_remove_manual, NULL);
+}
+
+/*
+ * VRF related functions.
+ */
static int bfd_vrf_new(struct vrf *vrf)
{
log_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id);
return 0;
}
+static int bfd_vrf_update(struct vrf *vrf)
+{
+ if (!vrf_is_enabled(vrf))
+ return 0;
+ log_debug("VRF update: %s(%u)", vrf->name, vrf->vrf_id);
+ /* a different name is given; update bfd list */
+ bfdd_sessions_enable_vrf(vrf);
+ return 0;
+}
+
static int bfd_vrf_enable(struct vrf *vrf)
{
struct bfd_vrf_global *bvrf;
if (vrf->vrf_id == VRF_DEFAULT ||
vrf_get_backend() == VRF_BACKEND_NETNS) {
if (!bvrf->bg_shop)
- bvrf->bg_shop = bp_udp_shop(vrf->vrf_id);
+ bvrf->bg_shop = bp_udp_shop(vrf);
if (!bvrf->bg_mhop)
- bvrf->bg_mhop = bp_udp_mhop(vrf->vrf_id);
+ bvrf->bg_mhop = bp_udp_mhop(vrf);
if (!bvrf->bg_shop6)
- bvrf->bg_shop6 = bp_udp6_shop(vrf->vrf_id);
+ bvrf->bg_shop6 = bp_udp6_shop(vrf);
if (!bvrf->bg_mhop6)
- bvrf->bg_mhop6 = bp_udp6_mhop(vrf->vrf_id);
+ bvrf->bg_mhop6 = bp_udp6_mhop(vrf);
if (!bvrf->bg_echo)
- bvrf->bg_echo = bp_echo_socket(vrf->vrf_id);
+ bvrf->bg_echo = bp_echo_socket(vrf);
if (!bvrf->bg_echov6)
- bvrf->bg_echov6 = bp_echov6_socket(vrf->vrf_id);
+ bvrf->bg_echov6 = bp_echov6_socket(vrf);
/* Add descriptors to the event loop. */
if (!bvrf->bg_ev[0])
}
log_debug("VRF disable %s id %d", vrf->name, vrf->vrf_id);
+
+ /* Disable read/write poll triggering. */
+ THREAD_OFF(bvrf->bg_ev[0]);
+ THREAD_OFF(bvrf->bg_ev[1]);
+ THREAD_OFF(bvrf->bg_ev[2]);
+ THREAD_OFF(bvrf->bg_ev[3]);
+ THREAD_OFF(bvrf->bg_ev[4]);
+ THREAD_OFF(bvrf->bg_ev[5]);
+
/* Close all descriptors. */
socket_close(&bvrf->bg_echo);
socket_close(&bvrf->bg_shop);
socket_close(&bvrf->bg_mhop);
socket_close(&bvrf->bg_shop6);
socket_close(&bvrf->bg_mhop6);
+ socket_close(&bvrf->bg_echo);
+ socket_close(&bvrf->bg_echov6);
/* free context */
XFREE(MTYPE_BFDD_VRF, bvrf);
void bfd_vrf_init(void)
{
vrf_init(bfd_vrf_new, bfd_vrf_enable, bfd_vrf_disable,
- bfd_vrf_delete, NULL);
+ bfd_vrf_delete, bfd_vrf_update);
}
void bfd_vrf_terminate(void)
return NULL;
return bfd->vrf->info;
}
+
+void bfd_session_update_vrf_name(struct bfd_session *bs, struct vrf *vrf)
+{
+ if (!vrf || !bs)
+ return;
+ /* update key */
+ hash_release(bfd_key_hash, bs);
+ /*
+ * HACK: Change the BFD VRF in the running configuration directly,
+ * bypassing the northbound layer. This is necessary to avoid deleting
+ * the BFD and readding it in the new VRF, which would have
+ * several implications.
+ */
+ if (yang_module_find("frr-bfdd") && bs->key.vrfname[0]) {
+ struct lyd_node *bfd_dnode;
+ char xpath[XPATH_MAXLEN], xpath_srcaddr[XPATH_MAXLEN + 32];
+ char addr_buf[INET6_ADDRSTRLEN];
+ int slen;
+
+ /* build xpath */
+ if (bs->key.mhop) {
+ inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf));
+ snprintf(xpath_srcaddr, sizeof(xpath_srcaddr), "[source-addr='%s']",
+ addr_buf);
+ } else
+ xpath_srcaddr[0] = 0;
+ inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf));
+ slen = snprintf(xpath, sizeof(xpath),
+ "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']",
+ bs->key.mhop ? "multi-hop" : "single-hop", xpath_srcaddr,
+ addr_buf);
+ if (bs->key.ifname[0])
+ slen += snprintf(xpath + slen, sizeof(xpath) - slen,
+ "[interface='%s']", bs->key.ifname);
+ else
+ slen += snprintf(xpath + slen, sizeof(xpath) - slen,
+ "[interface='']");
+ snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']/vrf",
+ bs->key.vrfname);
+
+ bfd_dnode = yang_dnode_get(running_config->dnode, xpath,
+ bs->key.vrfname);
+ if (bfd_dnode) {
+ yang_dnode_change_leaf(bfd_dnode, vrf->name);
+ running_config->version++;
+ }
+ }
+ memset(bs->key.vrfname, 0, sizeof(bs->key.vrfname));
+ strlcpy(bs->key.vrfname, vrf->name, sizeof(bs->key.vrfname));
+ hash_get(bfd_key_hash, bs, hash_alloc_intern);
+}