summaryrefslogtreecommitdiffstats
path: root/zebra/label_manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'zebra/label_manager.c')
-rw-r--r--zebra/label_manager.c489
1 files changed, 489 insertions, 0 deletions
diff --git a/zebra/label_manager.c b/zebra/label_manager.c
new file mode 100644
index 0000000..2634a33
--- /dev/null
+++ b/zebra/label_manager.c
@@ -0,0 +1,489 @@
+/*
+ * Label Manager for FRR
+ *
+ * Copyright (C) 2017 by Bingen Eguzkitza,
+ * Volta Networks Inc.
+ *
+ * This file is part of FRRouting (FRR)
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/log.h"
+#include "lib/memory.h"
+#include "lib/mpls.h"
+#include "lib/network.h"
+#include "lib/stream.h"
+#include "lib/zclient.h"
+#include "lib/libfrr.h"
+
+//#include "zebra/zserv.h"
+#include "zebra/zebra_router.h"
+#include "zebra/label_manager.h"
+#include "zebra/zebra_errors.h"
+#include "zebra/zapi_msg.h"
+#include "zebra/debug.h"
+
+#define CONNECTION_DELAY 5
+
+struct label_manager lbl_mgr;
+
+DEFINE_MGROUP(LBL_MGR, "Label Manager");
+DEFINE_MTYPE_STATIC(LBL_MGR, LM_CHUNK, "Label Manager Chunk");
+
+/* define hooks for the basic API, so that it can be specialized or served
+ * externally
+ */
+
+DEFINE_HOOK(lm_client_connect, (struct zserv *client, vrf_id_t vrf_id),
+ (client, vrf_id));
+DEFINE_HOOK(lm_client_disconnect, (struct zserv *client), (client));
+DEFINE_HOOK(lm_get_chunk,
+ (struct label_manager_chunk * *lmc, struct zserv *client,
+ uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id),
+ (lmc, client, keep, size, base, vrf_id));
+DEFINE_HOOK(lm_release_chunk,
+ (struct zserv *client, uint32_t start, uint32_t end),
+ (client, start, end));
+DEFINE_HOOK(lm_cbs_inited, (), ());
+
+/* define wrappers to be called in zapi_msg.c (as hooks must be called in
+ * source file where they were defined)
+ */
+void lm_client_connect_call(struct zserv *client, vrf_id_t vrf_id)
+{
+ hook_call(lm_client_connect, client, vrf_id);
+}
+void lm_get_chunk_call(struct label_manager_chunk **lmc, struct zserv *client,
+ uint8_t keep, uint32_t size, uint32_t base,
+ vrf_id_t vrf_id)
+{
+ hook_call(lm_get_chunk, lmc, client, keep, size, base, vrf_id);
+}
+void lm_release_chunk_call(struct zserv *client, uint32_t start, uint32_t end)
+{
+ hook_call(lm_release_chunk, client, start, end);
+}
+
+/* forward declarations of the static functions to be used for some hooks */
+static int label_manager_connect(struct zserv *client, vrf_id_t vrf_id);
+static int label_manager_disconnect(struct zserv *client);
+static int label_manager_get_chunk(struct label_manager_chunk **lmc,
+ struct zserv *client, uint8_t keep,
+ uint32_t size, uint32_t base,
+ vrf_id_t vrf_id);
+static int label_manager_release_label_chunk(struct zserv *client,
+ uint32_t start, uint32_t end);
+
+void delete_label_chunk(void *val)
+{
+ XFREE(MTYPE_LM_CHUNK, val);
+}
+
+/**
+ * Release label chunks from a client.
+ *
+ * Called on client disconnection or reconnection. It only releases chunks
+ * with empty keep value.
+ *
+ * @param proto Daemon protocol of client, to identify the owner
+ * @param instance Instance, to identify the owner
+ * @return Number of chunks released
+ */
+int release_daemon_label_chunks(struct zserv *client)
+{
+ struct listnode *node;
+ struct label_manager_chunk *lmc;
+ int count = 0;
+ int ret;
+
+ if (IS_ZEBRA_DEBUG_PACKET)
+ zlog_debug("%s: Releasing chunks for client proto %s, instance %d, session %u",
+ __func__, zebra_route_string(client->proto),
+ client->instance, client->session_id);
+
+ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) {
+ if (lmc->proto == client->proto &&
+ lmc->instance == client->instance &&
+ lmc->session_id == client->session_id && lmc->keep == 0) {
+ ret = release_label_chunk(lmc->proto, lmc->instance,
+ lmc->session_id,
+ lmc->start, lmc->end);
+ if (ret == 0)
+ count++;
+ }
+ }
+
+ if (IS_ZEBRA_DEBUG_PACKET)
+ zlog_debug("%s: Released %d label chunks", __func__, count);
+
+ return count;
+}
+
+int lm_client_disconnect_cb(struct zserv *client)
+{
+ hook_call(lm_client_disconnect, client);
+ return 0;
+}
+
+void lm_hooks_register(void)
+{
+ hook_register(lm_client_connect, label_manager_connect);
+ hook_register(lm_client_disconnect, label_manager_disconnect);
+ hook_register(lm_get_chunk, label_manager_get_chunk);
+ hook_register(lm_release_chunk, label_manager_release_label_chunk);
+}
+void lm_hooks_unregister(void)
+{
+ hook_unregister(lm_client_connect, label_manager_connect);
+ hook_unregister(lm_client_disconnect, label_manager_disconnect);
+ hook_unregister(lm_get_chunk, label_manager_get_chunk);
+ hook_unregister(lm_release_chunk, label_manager_release_label_chunk);
+}
+
+/**
+ * Init label manager (or proxy to an external one)
+ */
+void label_manager_init(void)
+{
+ lbl_mgr.lc_list = list_new();
+ lbl_mgr.lc_list->del = delete_label_chunk;
+ hook_register(zserv_client_close, lm_client_disconnect_cb);
+
+ /* register default hooks for the label manager actions */
+ lm_hooks_register();
+
+ /* notify any external module that we are done */
+ hook_call(lm_cbs_inited);
+}
+
+/* alloc and fill a label chunk */
+struct label_manager_chunk *
+create_label_chunk(uint8_t proto, unsigned short instance, uint32_t session_id,
+ uint8_t keep, uint32_t start, uint32_t end)
+{
+ /* alloc chunk, fill it and return it */
+ struct label_manager_chunk *lmc =
+ XCALLOC(MTYPE_LM_CHUNK, sizeof(struct label_manager_chunk));
+
+ lmc->start = start;
+ lmc->end = end;
+ lmc->proto = proto;
+ lmc->instance = instance;
+ lmc->session_id = session_id;
+ lmc->keep = keep;
+
+ return lmc;
+}
+
+/* attempt to get a specific label chunk */
+static struct label_manager_chunk *
+assign_specific_label_chunk(uint8_t proto, unsigned short instance,
+ uint32_t session_id, uint8_t keep, uint32_t size,
+ uint32_t base)
+{
+ struct label_manager_chunk *lmc;
+ struct listnode *node, *next = NULL;
+ struct listnode *first_node = NULL;
+ struct listnode *last_node = NULL;
+ struct listnode *insert_node = NULL;
+
+ /* precompute last label from base and size */
+ uint32_t end = base + size - 1;
+
+ /* sanities */
+ if ((base < MPLS_LABEL_UNRESERVED_MIN)
+ || (end > MPLS_LABEL_UNRESERVED_MAX)) {
+ zlog_err("Invalid LM request arguments: base: %u, size: %u",
+ base, size);
+ return NULL;
+ }
+
+ /* Scan the existing chunks to see if the requested range of labels
+ * falls inside any of such chunks */
+ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) {
+
+ /* skip chunks for labels < base */
+ if (base > lmc->end)
+ continue;
+
+ /* requested range is not covered by any existing, free chunk.
+ * Therefore, need to insert a chunk */
+ if ((end < lmc->start) && !first_node) {
+ insert_node = node;
+ break;
+ }
+
+ if (!first_node)
+ first_node = node;
+
+ /* if chunk is used, cannot honor request */
+ if (lmc->proto != NO_PROTO)
+ return NULL;
+
+ if (end <= lmc->end) {
+ last_node = node;
+ break;
+ }
+ }
+
+ /* insert chunk between existing chunks */
+ if (insert_node) {
+ lmc = create_label_chunk(proto, instance, session_id, keep,
+ base, end);
+ listnode_add_before(lbl_mgr.lc_list, insert_node, lmc);
+ return lmc;
+ }
+
+ if (first_node) {
+ /* get node past the last one, if there */
+ if (last_node)
+ last_node = listnextnode(last_node);
+
+ /* delete node coming after the above chunk whose labels are
+ * included in the previous one */
+ for (node = first_node; node && (node != last_node);
+ node = next) {
+ struct label_manager_chunk *death;
+
+ next = listnextnode(node);
+ death = listgetdata(node);
+ list_delete_node(lbl_mgr.lc_list, node);
+ delete_label_chunk(death);
+ }
+
+ lmc = create_label_chunk(proto, instance, session_id, keep,
+ base, end);
+ if (last_node)
+ listnode_add_before(lbl_mgr.lc_list, last_node, lmc);
+ else
+ listnode_add(lbl_mgr.lc_list, lmc);
+
+ return lmc;
+ } else {
+ /* create a new chunk past all the existing ones and link at
+ * tail */
+ lmc = create_label_chunk(proto, instance, session_id, keep,
+ base, end);
+ listnode_add(lbl_mgr.lc_list, lmc);
+ return lmc;
+ }
+}
+
+/**
+ * Core function, assigns label chunks
+ *
+ * It first searches through the list to check if there's one available
+ * (previously released). Otherwise it creates and assigns a new one
+ *
+ * @param proto Daemon protocol of client, to identify the owner
+ * @param instance Instance, to identify the owner
+ * @param keep If set, avoid garbage collection
+ * @param size Size of the label chunk
+ * @param base Desired starting label of the chunk; if MPLS_LABEL_BASE_ANY it does not apply
+ * @return Pointer to the assigned label chunk, or NULL if the request could not be satisfied
+ */
+struct label_manager_chunk *
+assign_label_chunk(uint8_t proto, unsigned short instance, uint32_t session_id,
+ uint8_t keep, uint32_t size, uint32_t base)
+{
+ struct label_manager_chunk *lmc;
+ struct listnode *node;
+ uint32_t prev_end = MPLS_LABEL_UNRESERVED_MIN;
+
+ /* handle chunks request with a specific base label */
+ if (base != MPLS_LABEL_BASE_ANY)
+ return assign_specific_label_chunk(proto, instance, session_id,
+ keep, size, base);
+
+ /* appease scan-build, who gets confused by the use of macros */
+ assert(lbl_mgr.lc_list);
+
+ /* first check if there's one available */
+ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) {
+ if (lmc->proto == NO_PROTO
+ && lmc->end - lmc->start + 1 == size) {
+ lmc->proto = proto;
+ lmc->instance = instance;
+ lmc->session_id = session_id;
+ lmc->keep = keep;
+ return lmc;
+ }
+ /* check if we hadve a "hole" behind us that we can squeeze into
+ */
+ if ((lmc->start > prev_end) && (lmc->start - prev_end > size)) {
+ lmc = create_label_chunk(proto, instance, session_id,
+ keep, prev_end + 1,
+ prev_end + size);
+ listnode_add_before(lbl_mgr.lc_list, node, lmc);
+ return lmc;
+ }
+ prev_end = lmc->end;
+ }
+ /* otherwise create a new one */
+ uint32_t start_free;
+
+ if (list_isempty(lbl_mgr.lc_list))
+ start_free = MPLS_LABEL_UNRESERVED_MIN;
+ else
+ start_free = ((struct label_manager_chunk *)listgetdata(
+ listtail(lbl_mgr.lc_list)))
+ ->end
+ + 1;
+
+ if (start_free > MPLS_LABEL_UNRESERVED_MAX - size + 1) {
+ flog_err(EC_ZEBRA_LM_EXHAUSTED_LABELS,
+ "Reached max labels. Start: %u, size: %u", start_free,
+ size);
+ return NULL;
+ }
+
+ /* create chunk and link at tail */
+ lmc = create_label_chunk(proto, instance, session_id, keep, start_free,
+ start_free + size - 1);
+ listnode_add(lbl_mgr.lc_list, lmc);
+ return lmc;
+}
+
+/**
+ * Release label chunks from a client.
+ *
+ * Called on client disconnection or reconnection. It only releases chunks
+ * with empty keep value.
+ *
+ * @param client Client zapi session
+ * @param start First label of the chunk
+ * @param end Last label of the chunk
+ * @return 0 on success
+ */
+static int label_manager_release_label_chunk(struct zserv *client,
+ uint32_t start, uint32_t end)
+{
+ return release_label_chunk(client->proto, client->instance,
+ client->session_id, start, end);
+}
+
+/**
+ * Core function, release no longer used label chunks
+ *
+ * @param proto Daemon protocol of client, to identify the owner
+ * @param instance Instance, to identify the owner
+ * @param session_id Zclient session ID, to identify the zclient session
+ * @param start First label of the chunk
+ * @param end Last label of the chunk
+ * @return 0 on success, -1 otherwise
+ */
+int release_label_chunk(uint8_t proto, unsigned short instance,
+ uint32_t session_id, uint32_t start, uint32_t end)
+{
+ struct listnode *node;
+ struct label_manager_chunk *lmc;
+ int ret = -1;
+
+ /* check that size matches */
+ if (IS_ZEBRA_DEBUG_PACKET)
+ zlog_debug("Releasing label chunk: %u - %u", start, end);
+ /* find chunk and disown */
+ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) {
+ if (lmc->start != start)
+ continue;
+ if (lmc->end != end)
+ continue;
+ if (lmc->proto != proto || lmc->instance != instance ||
+ lmc->session_id != session_id) {
+ flog_err(EC_ZEBRA_LM_DAEMON_MISMATCH,
+ "%s: Daemon mismatch!!", __func__);
+ continue;
+ }
+ lmc->proto = NO_PROTO;
+ lmc->instance = 0;
+ lmc->session_id = 0;
+ lmc->keep = 0;
+ ret = 0;
+ break;
+ }
+ if (ret != 0)
+ flog_err(EC_ZEBRA_LM_UNRELEASED_CHUNK,
+ "%s: Label chunk not released!!", __func__);
+
+ return ret;
+}
+
+/* default functions to be called on hooks */
+static int label_manager_connect(struct zserv *client, vrf_id_t vrf_id)
+{
+ /*
+ * Release previous labels of same protocol and instance.
+ * This is done in case it restarted from an unexpected shutdown.
+ */
+ release_daemon_label_chunks(client);
+ return zsend_label_manager_connect_response(client, vrf_id, 0);
+}
+static int label_manager_disconnect(struct zserv *client)
+{
+ release_daemon_label_chunks(client);
+ return 0;
+}
+static int label_manager_get_chunk(struct label_manager_chunk **lmc,
+ struct zserv *client, uint8_t keep,
+ uint32_t size, uint32_t base,
+ vrf_id_t vrf_id)
+{
+ *lmc = assign_label_chunk(client->proto, client->instance,
+ client->session_id, keep, size, base);
+ return lm_get_chunk_response(*lmc, client, vrf_id);
+}
+
+/* Respond to a connect request */
+int lm_client_connect_response(uint8_t proto, uint16_t instance,
+ uint32_t session_id, vrf_id_t vrf_id,
+ uint8_t result)
+{
+ struct zserv *client = zserv_find_client_session(proto, instance,
+ session_id);
+ if (!client) {
+ zlog_err("%s: could not find client for daemon %s instance %u session %u",
+ __func__, zebra_route_string(proto), instance,
+ session_id);
+ return 1;
+ }
+ return zsend_label_manager_connect_response(client, vrf_id, result);
+}
+
+/* Respond to a get_chunk request */
+int lm_get_chunk_response(struct label_manager_chunk *lmc, struct zserv *client,
+ vrf_id_t vrf_id)
+{
+ if (!lmc)
+ flog_err(EC_ZEBRA_LM_CANNOT_ASSIGN_CHUNK,
+ "Unable to assign Label Chunk to %s instance %u",
+ zebra_route_string(client->proto), client->instance);
+ else if (IS_ZEBRA_DEBUG_PACKET)
+ zlog_debug("Assigned Label Chunk %u - %u to %s instance %u",
+ lmc->start, lmc->end,
+ zebra_route_string(client->proto), client->instance);
+
+ return zsend_assign_label_chunk_response(client, vrf_id, lmc);
+}
+
+void label_manager_close(void)
+{
+ list_delete(&lbl_mgr.lc_list);
+}