]> git.puffer.fish Git - matthieu/frr.git/commitdiff
lib: Add CSPF Path Computation algorithm
authorOlivier Dugeon <olivier.dugeon@orange.com>
Thu, 13 Jan 2022 09:15:51 +0000 (10:15 +0100)
committerOlivier Dugeon <olivier.dugeon@orange.com>
Wed, 2 Feb 2022 16:04:12 +0000 (17:04 +0100)
As helper function of Segment Routing Flex Algo or RSVP-TE
add Constrained Shortest Path First algorithm able to compute
path with constraints. Supported constraints are as follow:
 - Standard IGP metric
 - TE IGP metric
 - Delay metric
 - Bandwidth for given Class of Service for bandwidth reservation (RSVP-TE)

Usage of CSPF algorithms is detailed in the doc/developer/cspf.rst file

Signed-off-by: Olivier Dugeon <olivier.dugeon@orange.com>
doc/developer/cspf.rst [new file with mode: 0644]
lib/cspf.c [new file with mode: 0644]
lib/cspf.h [new file with mode: 0644]
lib/subdir.am

diff --git a/doc/developer/cspf.rst b/doc/developer/cspf.rst
new file mode 100644 (file)
index 0000000..426553f
--- /dev/null
@@ -0,0 +1,196 @@
+Path Computation Algorithms
+===========================
+
+Introduction
+------------
+
+Both RSVP-TE and Segment Routing Flex Algo need to compute end to end path
+with other constraints as the standard IGP metric. Based on Shortest Path First
+(SPF) algorithms, a new class of Constrained SPF (CSPF) is provided by the FRR
+library.
+
+Supported constraints are as follow:
+- Standard IGP metric (here, CSPF provides the same result as a normal SPF)
+- Traffic Engineering (TE) IGP metric
+- Delay from the IGP Extended Metrics
+- Bandwidth for a given Class of Service (CoS) for bandwidth reservation
+
+Algorithm
+---------
+
+The CSPF algorithm is based on a Priority Queue which store the on-going
+possible path sorted by their respective weights. This weight corresponds
+to the cost of the cuurent path from the source up to the current node.
+
+The algorithm is as followed:
+
+```
+   cost = MAX_COST;
+       Priority_Queue.empty();
+       Visited_Node.empty();
+       Processed_Path.empty();
+   src = new_path(source_address);
+       src.cost = 0;
+   dst = new_destinatio(destination_address);
+   dst.cost = MAX_COST;
+       Processed_Path.add(src);
+       Processed_Path.add(dst);
+       while (Priority_Queue.count != 0) {
+               current_path = Priority_Queue.pop();
+               current_node = next_path.destination;
+      Visited_Node.add(current_node);
+               for (current_node.edges: edge) {
+                       if (prune_edge(current_path, edge)
+                               continue;
+                       if (relax(current_path) && cost > current_path.cost) {
+                               optim_path = current_path;
+                               cost = current_path.cost;
+                       }
+               }
+       }
+
+       prune_edge(path, edge) {
+               // check that path + edge meet constraints  e.g.
+               if (current_path.cost + edge.cost > constrained_cost)
+                       return false;
+               else
+                       return true;
+       }
+
+       relax_edge(current_path, edge) {
+               next_node = edge.destination;
+               if (Visited_Node.get(next_node))
+                       return false;
+               next_path = Processed_Path.get(edge.destination);
+               if (!next_path) {
+                       next_path = new path(edge.destination);
+                       Processed_Path.add(next_path);
+               }
+               total_cost = current_path.cost + edge.cost;
+               if (total_cost < next_path.cost) {
+                       next_path = current_path;
+                       next_path.add_edge(edge);
+                       next_path.cost = total_cost;
+                       Priority_Queue.add(next_path);
+               }
+               return (next_path.destination == destination);
+       }
+
+```
+
+Definition
+----------
+
+.. c:struct:: constraints
+
+This is the constraints structure that contains:
+
+- cost: the total cost that the path must respect
+- ctype: type of constraints:
+
+  - CSPF_METRIC for standard metric
+  - CSPF_TE_METRIC for TE metric
+  - CSPF_DELAY for delay metric
+
+- bw: bandwidth that the path must respect
+- cos: Class of Service (COS) for the bandwidth
+- family: AF_INET or AF_INET6
+- type: RSVP_TE, SR_TE or SRV6_TE
+
+.. c:struct:: c_path
+
+This is the Constraint Path structure that contains:
+
+- edges: List of Edges that compose the path
+- status: FAILED, IN_PROGRESS, SUCCESS, NO_SOURCE, NO_DESTINATION, SAME_SRC_DST
+- weight: the cost from source to the destination of the path
+- dst: key of the destination vertex
+
+.. c:struct:: cspf
+
+This is the main structure for path computation. Even if it is public, you
+don't need to set manually the internal field of the structure. Instead, use
+the following functions:
+
+.. c:function:: struct cspf *cspf_new(void);
+
+Function to create an empty cspf for future call of path computation
+
+.. c:function:: struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src, const struct ls_vertex *dst, struct constraints *csts);
+
+This function initialize the cspf with source and destination vertex and
+constraints and return pointer to the cspf structure. If input cspf structure
+is NULL, a new cspf structure is allocated and initialize.
+
+.. c:function:: struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted, const struct in_addr src, const struct in_addr dst, struct constraints *csts);
+
+Same as cspf_init, but here, source and destination vertex are extract from
+the TED data base based on respective IPv4 source and destination addresses.
+
+.. c:function:: struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted, const struct in6_addr src, const struct in6_addr dst, struct constraints *csts);
+
+Same as cspf_init_v4 but with IPv6 source and destination addresses.
+
+.. c:function:: void cspf_clean(struct cspf *algo);
+
+Clean internal structure of cspf in order to reuse it for another path
+computation.
+
+.. c:function:: void cspf_del(struct cspf *algo);
+
+Delete cspf structure. A call to cspf_clean() function is perform prior to
+free allocated memeory.
+
+.. c:function:: struct c_path *compute_p2p_path(struct ls_ted *ted, struct cspf *algo);
+
+Compute point to point path from the ted and cspf.
+The function always return a constraints path. The status of the path gives
+indication about the success or failure of the algorithm. If cspf structure has
+not been initialize with a call to `cspf_init() or cspf_init_XX()`, the
+algorithm returns a constraints path with status set to FAILED.
+Note that a call to `cspf_clean()` is performed at the end of this function,
+thus it is mandatory to initialize the cspf structure again prior to call again
+the path computation algorithm.
+
+
+Usage
+-----
+
+Of course, CSPF algorithm needs a network topology that contains the
+various metrics. Link State provides such Traffic Engineering Database.
+
+To perform a Path Computation with given constraints, proceed as follow:
+
+.. code-block:: c
+       struct cspf *algo;
+       struct ls_ted *ted;
+       struct in_addr src;
+       struct in_addr dst;
+       struct constraints csts;
+       struct c_path *path;
+
+       // Create a new CSPF structure
+       algo = cspf_new();
+
+       // Initialize constraints
+       csts.cost = 100;
+       csts.ctype = CSPF_TE_METRIC;
+       csts.family = AF_INET;
+       csts.type = SR_TE;
+       csts.bw = 1000000;
+       csts.cos = 3;
+
+       // Then, initialise th CSPF with source, destination and constraints
+       cspf_init_v4(algo, ted, src, dst, &csts);
+
+       // Finally, got the Computed Path;
+       path = compute_p2p_path(ted, algo);
+
+       if (path.status == SUCCESS)
+               zlog_info("Got a valid constraints path");
+       else
+               zlog_info("Unable to compute constraints path. Got %d status", path->status);
+
+
+If you would compute another path, you must call `cspf_init()` prior to
+`compute_p2p_path()` to change source, destination and/or constraints.
diff --git a/lib/cspf.c b/lib/cspf.c
new file mode 100644 (file)
index 0000000..ef3e7c2
--- /dev/null
@@ -0,0 +1,646 @@
+/*
+ * Constraints Shortest Path First algorithms - cspf.c
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2022 Orange http://www.orange.com
+ *
+ * This file is part of Free Range Routing (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 "if.h"
+#include "linklist.h"
+#include "log.h"
+#include "hash.h"
+#include "memory.h"
+#include "prefix.h"
+#include "table.h"
+#include "stream.h"
+#include "printfrr.h"
+#include "link_state.h"
+#include "cspf.h"
+
+/* Link State Memory allocation */
+DEFINE_MTYPE_STATIC(LIB, PCA, "Path Computation Algorithms");
+
+/**
+ * Create new Constrained Path. Memory is dynamically allocated.
+ *
+ * @param key  Vertex key of the destination of this path
+ *
+ * @return     Pointer to a new Constrained Path structure
+ */
+static struct c_path *cpath_new(uint64_t key)
+{
+       struct c_path *path;
+
+       /* Sanity Check */
+       if (key == 0)
+               return NULL;
+
+       path = XCALLOC(MTYPE_PCA, sizeof(struct c_path));
+       path->dst = key;
+       path->status = IN_PROGRESS;
+       path->edges = list_new();
+       path->weight = MAX_COST;
+
+       return path;
+}
+
+/**
+ * Copy src Constrained Path into dst Constrained Path. A new Constrained Path
+ * structure is dynamically allocated if dst is NULL. If src is NULL, the
+ * function return the dst disregarding if it is NULL or not.
+ *
+ * @param dest Destination Constrained Path structure
+ * @param src  Source Constrained Path structure
+ *
+ * @return     Pointer to the destination Constrained Path structure
+ */
+static struct c_path *cpath_copy(struct c_path *dest, const struct c_path *src)
+{
+       struct c_path *new_path;
+
+       if (!src)
+               return dest;
+
+       if (!dest) {
+               new_path = XCALLOC(MTYPE_PCA, sizeof(struct c_path));
+       } else {
+               new_path = dest;
+               if (dest->edges)
+                       list_delete(&new_path->edges);
+       }
+
+       new_path->dst = src->dst;
+       new_path->weight = src->weight;
+       new_path->edges = list_dup(src->edges);
+       new_path->status = src->status;
+
+       return new_path;
+}
+
+/**
+ * Delete Constrained Path structure. Previous allocated memory is freed.
+ *
+ * @param path Constrained Path structure to be deleted
+ */
+static void cpath_del(struct c_path *path)
+{
+       if (!path)
+               return;
+
+       if (path->edges)
+               list_delete(&path->edges);
+
+       XFREE(MTYPE_PCA, path);
+       path = NULL;
+}
+
+/**
+ * Replace the list of edges in the next Constrained Path by the list of edges
+ * in the current Constrained Path.
+ *
+ * @param next_path    next Constrained Path structure
+ * @param cur_path     current Constrained Path structure
+ */
+static void cpath_replace(struct c_path *next_path, struct c_path *cur_path)
+{
+
+       if (next_path->edges)
+               list_delete(&next_path->edges);
+
+       next_path->edges = list_dup(cur_path->edges);
+}
+
+/**
+ * Create a new Visited Node structure from the provided Vertex. Structure is
+ * dynamically allocated.
+ *
+ * @param vertex       Vertex structure
+ *
+ * @return             Pointer to the new Visited Node structure
+ */
+static struct v_node *vnode_new(struct ls_vertex *vertex)
+{
+       struct v_node *vnode;
+
+       if (!vertex)
+               return NULL;
+
+       vnode = XCALLOC(MTYPE_PCA, sizeof(struct v_node));
+       vnode->vertex = vertex;
+       vnode->key = vertex->key;
+
+       return vnode;
+}
+
+/**
+ * Delete Visited Node structure. Previous allocated memory is freed.
+ *
+ * @param vnode                Visited Node structure to be deleted
+ */
+static void vnode_del(struct v_node *vnode)
+{
+       if (!vnode)
+               return;
+
+       XFREE(MTYPE_PCA, vnode);
+       vnode = NULL;
+}
+
+/**
+ * Search Vertex in TED by IPv4 address. The function search vertex by browsing
+ * the subnets table. It allows to find not only vertex by router ID, but also
+ * vertex by interface IPv4 address.
+ *
+ * @param ted  Traffic Engineering Database
+ * @param ipv4 IPv4 address
+ *
+ * @return     Vertex if found, NULL otherwise
+ */
+static struct ls_vertex *get_vertex_by_ipv4(struct ls_ted *ted,
+                                           struct in_addr ipv4)
+{
+       struct ls_subnet *subnet;
+       struct prefix p;
+
+       p.family = AF_INET;
+       p.u.prefix4 = ipv4;
+
+       frr_each (subnets, &ted->subnets, subnet) {
+               if (subnet->key.family != AF_INET)
+                       continue;
+               p.prefixlen = subnet->key.prefixlen;
+               if (prefix_same(&subnet->key, &p))
+                       return subnet->vertex;
+       }
+
+       return NULL;
+}
+
+/**
+ * Search Vertex in TED by IPv6 address. The function search vertex by browsing
+ * the subnets table. It allows to find not only vertex by router ID, but also
+ * vertex by interface IPv6 address.
+ *
+ * @param ted  Traffic Engineering Database
+ * @param ipv6 IPv6 address
+ *
+ * @return     Vertex if found, NULL otherwise
+ */
+static struct ls_vertex *get_vertex_by_ipv6(struct ls_ted *ted,
+                                           struct in6_addr ipv6)
+{
+       struct ls_subnet *subnet;
+       struct prefix p;
+
+       p.family = AF_INET6;
+       p.u.prefix6 = ipv6;
+
+       frr_each (subnets, &ted->subnets, subnet) {
+               if (subnet->key.family != AF_INET6)
+                       continue;
+               p.prefixlen = subnet->key.prefixlen;
+               if (prefix_cmp(&subnet->key, &p) == 0)
+                       return subnet->vertex;
+       }
+
+       return NULL;
+}
+
+struct cspf *cspf_new(void)
+{
+       struct cspf *algo;
+
+       /* Allocate New CSPF structure */
+       algo = XCALLOC(MTYPE_PCA, sizeof(struct cspf));
+
+       /* Initialize RB-Trees */
+       processed_init(&algo->processed);
+       visited_init(&algo->visited);
+       pqueue_init(&algo->pqueue);
+
+       algo->path = NULL;
+       algo->pdst = NULL;
+
+       return algo;
+}
+
+struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src,
+                      const struct ls_vertex *dst, struct constraints *csts)
+{
+       struct cspf *new_algo;
+       struct c_path *psrc;
+
+       if (!csts)
+               return NULL;
+
+       if (!algo)
+               new_algo = cspf_new();
+       else
+               new_algo = algo;
+
+       /* Initialize Processed Path and Priority Queue with Src & Dst */
+       if (src) {
+               psrc = cpath_new(src->key);
+               psrc->weight = 0;
+               processed_add(&new_algo->processed, psrc);
+               pqueue_add(&new_algo->pqueue, psrc);
+               new_algo->path = psrc;
+       }
+       if (dst) {
+               new_algo->pdst = cpath_new(dst->key);
+               processed_add(&new_algo->processed, new_algo->pdst);
+       }
+
+       memcpy(&new_algo->csts, csts, sizeof(struct constraints));
+
+       return new_algo;
+}
+
+struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted,
+                         const struct in_addr src, const struct in_addr dst,
+                         struct constraints *csts)
+{
+       struct ls_vertex *vsrc;
+       struct ls_vertex *vdst;
+       struct cspf *new_algo;
+
+       /* Sanity Check */
+       if (!ted)
+               return algo;
+
+       if (!algo)
+               new_algo = cspf_new();
+       else
+               new_algo = algo;
+
+       /* Got Source and Destination Vertex from TED */
+       vsrc = get_vertex_by_ipv4(ted, src);
+       vdst = get_vertex_by_ipv4(ted, dst);
+       csts->family = AF_INET;
+
+       return cspf_init(new_algo, vsrc, vdst, csts);
+}
+
+struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted,
+                         const struct in6_addr src, const struct in6_addr dst,
+                         struct constraints *csts)
+{
+       struct ls_vertex *vsrc;
+       struct ls_vertex *vdst;
+       struct cspf *new_algo;
+
+       /* Sanity Check */
+       if (!ted)
+               return algo;
+
+       if (!algo)
+               new_algo = cspf_new();
+       else
+               new_algo = algo;
+
+       /* Got Source and Destination Vertex from TED */
+       vsrc = get_vertex_by_ipv6(ted, src);
+       vdst = get_vertex_by_ipv6(ted, dst);
+       csts->family = AF_INET6;
+
+       return cspf_init(new_algo, vsrc, vdst, csts);
+}
+
+void cspf_clean(struct cspf *algo)
+{
+       struct c_path *path;
+       struct v_node *vnode;
+
+       if (!algo)
+               return;
+
+       /* Normally, Priority Queue is empty. Clean it in case of. */
+       if (pqueue_count(&algo->pqueue)) {
+               frr_each_safe (pqueue, &algo->pqueue, path) {
+                       pqueue_del(&algo->pqueue, path);
+               }
+       }
+
+       /* Empty Processed Path tree and associated Path */
+       if (processed_count(&algo->processed)) {
+               frr_each_safe (processed, &algo->processed, path) {
+                       processed_del(&algo->processed, path);
+                       cpath_del(path);
+               }
+       }
+
+       /* Empty visited Vertex tree and associated Node */
+       if (visited_count(&algo->visited)) {
+               frr_each_safe (visited, &algo->visited, vnode) {
+                       visited_del(&algo->visited, vnode);
+                       vnode_del(vnode);
+               }
+       }
+
+       memset(&algo->csts, 0, sizeof(struct constraints));
+       algo->path = NULL;
+       algo->pdst = NULL;
+}
+
+void cspf_del(struct cspf *algo)
+{
+       if (!algo)
+               return;
+
+       /* Empty Priority Queue and Processes Path */
+       cspf_clean(algo);
+
+       /* Then, reset Priority Queue, Processed Path and Visited RB-Tree */
+       pqueue_fini(&algo->pqueue);
+       processed_fini(&algo->processed);
+       visited_fini(&algo->visited);
+
+       XFREE(MTYPE_PCA, algo);
+       algo = NULL;
+}
+
+/**
+ * Prune Edge if constraints are not met by testing Edge Attributes against
+ * given constraints and cumulative cost of the given constrained path.
+ *
+ * @param path On-going Computed Path with cumulative cost constraints
+ * @param edge Edge to be validate against Constraints
+ * @param csts Constraints for this path
+ *
+ * @return     True if Edge should be prune, false if Edge is valid
+ */
+static bool prune_edge(const struct c_path *path, const struct ls_edge *edge,
+                      const struct constraints *csts)
+{
+       struct ls_vertex *dst;
+       struct ls_attributes *attr;
+
+       /* Check that Path, Edge and Constraints are valid */
+       if (!path || !edge || !csts)
+               return true;
+
+       /* Check that Edge has a valid destination */
+       if (!edge->destination)
+               return true;
+       dst = edge->destination;
+
+       /* Check that Edge has valid attributes */
+       if (!edge->attributes)
+               return true;
+       attr = edge->attributes;
+
+       /* Check that Edge belongs to the requested Address Family and type */
+       if (csts->family == AF_INET) {
+               if (IPV4_NET0(attr->standard.local.s_addr))
+                       return true;
+               if (csts->type == SR_TE)
+                       if (!CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID) ||
+                           !CHECK_FLAG(dst->node->flags, LS_NODE_SR))
+                               return true;
+       }
+       if (csts->family == AF_INET6) {
+               if (IN6_IS_ADDR_UNSPECIFIED(&attr->standard.local6))
+                       return true;
+               if (csts->type == SR_TE)
+                       if (!CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6) ||
+                           !CHECK_FLAG(dst->node->flags, LS_NODE_SR))
+                               return true;
+       }
+
+       /*
+        * Check that total cost, up to this edge, respects the initial
+        * constraints
+        */
+       switch (csts->ctype) {
+       case CSPF_METRIC:
+               if (!CHECK_FLAG(attr->flags, LS_ATTR_METRIC))
+                       return true;
+               if ((attr->metric + path->weight) > csts->cost)
+                       return true;
+               break;
+
+       case CSPF_TE_METRIC:
+               if (!CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC))
+                       return true;
+               if ((attr->standard.te_metric + path->weight) > csts->cost)
+                       return true;
+               break;
+
+       case CSPF_DELAY:
+               if (!CHECK_FLAG(attr->flags, LS_ATTR_DELAY))
+                       return true;
+               if ((attr->extended.delay + path->weight) > csts->cost)
+                       return true;
+               break;
+       }
+
+       /* If specified, check that Edge meet Bandwidth constraint */
+       if (csts->bw > 0.0) {
+               if (attr->standard.max_bw < csts->bw ||
+                   attr->standard.max_rsv_bw < csts->bw ||
+                   attr->standard.unrsv_bw[csts->cos] < csts->bw)
+                       return true;
+       }
+
+       /* All is fine. We can consider this Edge valid, so not to be prune */
+       return false;
+}
+
+/**
+ * Relax constraints of the current path up to the destination vertex of the
+ * provided Edge. This function progress in the network topology by validating
+ * the next vertex on the computed path. If Vertex has not already been visited,
+ * list of edges of the current path is augmented with this edge if the new cost
+ * is lower than prior path up to this vertex. Current path is re-inserted in
+ * the Priority Queue with its new cost i.e. current cost + edge cost.
+ *
+ * @param algo CSPF structure
+ * @param edge Next Edge to be added to the current computed path
+ *
+ * @return     True if current path reach destination, false otherwise
+ */
+static bool relax_constraints(struct cspf *algo, struct ls_edge *edge)
+{
+
+       struct c_path pkey = {};
+       struct c_path *next_path;
+       struct v_node vnode = {};
+       uint32_t total_cost = MAX_COST;
+
+       /* Verify that we have a current computed path */
+       if (!algo->path)
+               return false;
+
+       /* Verify if we have not visited the next Vertex to avoid loop */
+       vnode.key = edge->destination->key;
+       if (visited_member(&algo->visited, &vnode)) {
+               return false;
+       }
+
+       /*
+        * Get Next Computed Path from next vertex key
+        * or create a new one if it has not yet computed.
+        */
+       pkey.dst = edge->destination->key;
+       next_path = processed_find(&algo->processed, &pkey);
+       if (!next_path) {
+               next_path = cpath_new(pkey.dst);
+               processed_add(&algo->processed, next_path);
+       }
+
+       /*
+        * Add or update the Computed Path in the Priority Queue if total cost
+        * is lower than cost associated to this next Vertex. This could occurs
+        * if we process a Vertex that as not yet been visited in the Graph
+        * or if we found a shortest path up to this Vertex.
+        */
+       switch (algo->csts.ctype) {
+       case CSPF_METRIC:
+               total_cost = edge->attributes->metric + algo->path->weight;
+               break;
+       case CSPF_TE_METRIC:
+               total_cost = edge->attributes->standard.te_metric +
+                            algo->path->weight;
+               break;
+       case CSPF_DELAY:
+               total_cost =
+                       edge->attributes->extended.delay + algo->path->weight;
+               break;
+       default:
+               break;
+       }
+       if (total_cost < next_path->weight) {
+               /*
+                * It is not possible to directly update the q_path in the
+                * Priority Queue. Indeed, if we modify the path weight, the
+                * Priority Queue must be re-ordered. So, we need fist to remove
+                * the q_path if it is present in the Priority Queue, then,
+                * update the Path, in particular the Weight, and finally
+                * (re-)insert it in the Priority Queue.
+                */
+               struct c_path *path;
+               frr_each_safe (pqueue, &algo->pqueue, path) {
+                       if (path->dst == pkey.dst) {
+                               pqueue_del(&algo->pqueue, path);
+                               break;
+                       }
+               }
+               next_path->weight = total_cost;
+               cpath_replace(next_path, algo->path);
+               listnode_add(next_path->edges, edge);
+               pqueue_add(&algo->pqueue, next_path);
+       }
+
+       /* Return True if we reach the destination */
+       return (next_path->dst == algo->pdst->dst);
+}
+
+struct c_path *compute_p2p_path(struct cspf *algo, struct ls_ted *ted)
+{
+       struct listnode *node;
+       struct ls_vertex *vertex;
+       struct ls_edge *edge;
+       struct c_path *optim_path;
+       struct v_node *vnode;
+       uint32_t cur_cost;
+
+       optim_path = cpath_new(0xFFFFFFFFFFFFFFFF);
+       optim_path->status = FAILED;
+
+       /* Check that all is correctly initialized */
+       if (!algo)
+               return optim_path;
+
+       if (!algo->csts.ctype)
+               return optim_path;
+
+       if (!algo->pdst) {
+               optim_path->status = NO_DESTINATION;
+               return optim_path;
+       }
+
+       if (!algo->path) {
+               optim_path->status = NO_SOURCE;
+               return optim_path;
+       }
+
+       if (algo->pdst->dst == algo->path->dst) {
+               optim_path->status = SAME_SRC_DST;
+               return optim_path;
+       }
+
+       optim_path->dst = algo->pdst->dst;
+       optim_path->status = IN_PROGRESS;
+
+       /*
+        * Process all Connected Vertex until priority queue becomes empty.
+        * Connected Vertices are added into the priority queue when
+        * processing the next Connected Vertex: see relax_constraints()
+        */
+       cur_cost = MAX_COST;
+       while (pqueue_count(&algo->pqueue) != 0) {
+               /* Got shortest current Path from the Priority Queue */
+               algo->path = pqueue_pop(&algo->pqueue);
+
+               /* Add destination Vertex of this path to the visited RB Tree */
+               vertex = ls_find_vertex_by_key(ted, algo->path->dst);
+               if (!vertex)
+                       continue;
+               vnode = vnode_new(vertex);
+               visited_add(&algo->visited, vnode);
+
+               /* Process all outgoing links from this Vertex */
+               for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
+                       /*
+                        * Skip Connected Edges that must be prune i.e.
+                        * Edges that not satisfy the given constraints,
+                        * in particular the Bandwidth, TE Metric and Delay.
+                        */
+                       if (prune_edge(algo->path, edge, &algo->csts))
+                               continue;
+
+                       /*
+                        * Relax constraints and check if we got a shorter
+                        * candidate path
+                        */
+                       if (relax_constraints(algo, edge) &&
+                           algo->pdst->weight < cur_cost) {
+                               cur_cost = algo->pdst->weight;
+                               cpath_copy(optim_path, algo->pdst);
+                               optim_path->status = SUCCESS;
+                       }
+               }
+       }
+
+       /*
+        * The priority queue is empty => all the possible (vertex, path)
+        * elements have been explored. The optim_path contains the optimal
+        * path if it exists. Otherwise an empty path with status failed is
+        * returned.
+        */
+       if (optim_path->status == IN_PROGRESS ||
+           listcount(optim_path->edges) == 0)
+               optim_path->status = FAILED;
+       cspf_clean(algo);
+
+       return optim_path;
+}
diff --git a/lib/cspf.h b/lib/cspf.h
new file mode 100644 (file)
index 0000000..6466ddb
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Constraints Shortest Path First algorithms definition - cspf.h
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2022 Orange http://www.orange.com
+ *
+ * This file is part of Free Range Routing (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
+ */
+
+#ifndef _FRR_CSPF_H_
+#define _FRR_CSPF_H_
+
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This file defines the different structure used for Path Computation with
+ * various constrained. Up to now, standard metric, TE metric, delay and
+ * bandwidth constraints are supported.
+ * All proposed algorithms used the same principle:
+ *  - A pruning function that keeps only links that meet constraints
+ *  - A priority Queue that keeps the shortest on-going computed path
+ *  - A main loop over all vertices to find the shortest path
+ */
+
+#define MAX_COST       0xFFFFFFFF
+
+/* Status of the path */
+enum path_status {
+       FAILED = 0,
+       NO_SOURCE,
+       NO_DESTINATION,
+       SAME_SRC_DST,
+       IN_PROGRESS,
+       SUCCESS
+};
+enum path_type {RSVP_TE = 1, SR_TE, SRV6_TE};
+enum metric_type {CSPF_METRIC = 1, CSPF_TE_METRIC, CSPF_DELAY};
+
+/* Constrained metrics structure */
+struct constraints {
+       uint32_t cost;          /* total cost (metric) of the path */
+       enum metric_type ctype; /* Metric Type: standard, TE or Delay */
+       float bw;               /* bandwidth of the path */
+       uint8_t cos;            /* Class of Service of the path */
+       enum path_type type;    /* RSVP-TE or SR-TE path */
+       uint8_t family;         /* AF_INET or AF_INET6 address family */
+};
+
+/* Priority Queue for Constrained Path Computation */
+PREDECL_RBTREE_NONUNIQ(pqueue);
+
+/* Processed Path for Constrained Path Computation */
+PREDECL_RBTREE_UNIQ(processed);
+
+/* Constrained Path structure */
+struct c_path {
+       struct pqueue_item q_itm;    /* entry in the Priority Queue */
+       uint32_t weight;             /* Weight to sort path in Priority Queue */
+       struct processed_item p_itm; /* entry in the Processed RB Tree */
+       uint64_t dst;                /* Destination vertex key of this path */
+       struct list *edges;          /* List of Edges that compose this path */
+       enum path_status status;     /* status of the computed path */
+};
+
+macro_inline int q_cmp(const struct c_path *p1, const struct c_path *p2)
+{
+       return numcmp(p1->weight, p2->weight);
+}
+DECLARE_RBTREE_NONUNIQ(pqueue, struct c_path, q_itm, q_cmp);
+
+macro_inline int p_cmp(const struct c_path *p1, const struct c_path *p2)
+{
+       return numcmp(p1->dst, p2->dst);
+}
+DECLARE_RBTREE_UNIQ(processed, struct c_path, p_itm, p_cmp);
+
+/* List of visited node */
+PREDECL_RBTREE_UNIQ(visited);
+struct v_node {
+       struct visited_item item; /* entry in the Processed RB Tree */
+       uint64_t key;
+       struct ls_vertex *vertex;
+};
+
+macro_inline int v_cmp(const struct v_node *p1, const struct v_node *p2)
+{
+       return numcmp(p1->key, p2->key);
+}
+DECLARE_RBTREE_UNIQ(visited, struct v_node, item, v_cmp);
+
+/* Path Computation algorithms structure */
+struct cspf {
+       struct pqueue_head pqueue;       /* Priority Queue */
+       struct processed_head processed; /* Paths that have been processed */
+       struct visited_head visited;     /* Vertices that have been visited */
+       struct constraints csts;         /* Constraints of the path */
+       struct c_path *path;             /* Current Computed Path */
+       struct c_path *pdst;             /* Computed Path to the destination */
+};
+
+/**
+ * Create a new CSPF structure. Memory is dynamically allocated.
+ *
+ * @return     pointer to the new cspf structure
+ */
+extern struct cspf *cspf_new(void);
+
+/**
+ * Initialize CSPF structure prior to compute a constrained path. If CSPF
+ * structure is NULL, a new CSPF is dynamically allocated prior to the
+ * configuration itself.
+ *
+ * @param algo CSPF structure, may be null if a new CSPF must be created
+ * @param src  Source vertex of the requested path
+ * @param dst  Destination vertex of the requested path
+ * @param csts Constraints of the requested path
+ *
+ * @return     pointer to the initialized CSPF structure
+ */
+extern struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src,
+                             const struct ls_vertex *dst,
+                             struct constraints *csts);
+
+/**
+ * Initialize CSPF structure prior to compute a constrained path. If CSPF
+ * structure is NULL, a new CSPF is dynamically allocated prior to the
+ * configuration itself. This function starts by searching source and
+ * destination vertices from the IPv4 addresses in the provided TED.
+ *
+ * @param algo CSPF structure, may be null if a new CSPF must be created
+ * @param ted  Traffic Engineering Database
+ * @param src  Source IPv4 address of the requested path
+ * @param dst  Destination IPv4 address of the requested path
+ * @param csts Constraints of the requested path
+ *
+ * @return     pointer to the initialized CSPF structure
+ */
+extern struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted,
+                                const struct in_addr src,
+                                const struct in_addr dst,
+                                struct constraints *csts);
+
+/**
+ * Initialize CSPF structure prior to compute a constrained path. If CSPF
+ * structure is NULL, a new CSPF is dynamically allocated prior to the
+ * configuration itself. This function starts by searching source and
+ * destination vertices from the IPv6 addresses in the provided TED.
+ *
+ * @param algo CSPF structure, may be null if a new CSPF must be created
+ * @param ted  Traffic Engineering Database
+ * @param src  Source IPv6 address of the requested path
+ * @param dst  Destination IPv6 address of the requested path
+ * @param csts Constraints of the requested path
+ *
+ * @return     pointer to the initialized CSPF structure
+ */
+extern struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted,
+                                const struct in6_addr src,
+                                const struct in6_addr dst,
+                                struct constraints *csts);
+
+/**
+ * Clean CSPF structure. Reset all internal list and priority queue for latter
+ * initialization of the CSPF structure and new path computation.
+ *
+ * @param algo CSPF structure
+ */
+extern void cspf_clean(struct cspf *algo);
+
+/**
+ * Delete CSPF structure, internal list and priority queue.
+ *
+ * @param algo CSPF structure
+ */
+extern void cspf_del(struct cspf *algo);
+
+/**
+ * Compute point-to-point constrained path. cspf_init() function must be call
+ * prior to call this function.
+ *
+ * @param algo CSPF structure
+ * @param ted  Traffic Engineering Database
+ *
+ * @return     Constrained Path with status to indicate computation success
+ */
+extern struct c_path *compute_p2p_path(struct cspf *algo, struct ls_ted *ted);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_CSPF_H_ */
index bb10d71ed1a116f4a80a4662c43a82d0a2562032..2cd329c8ad74f55668f80b5d3d938590a75520c8 100644 (file)
@@ -16,6 +16,7 @@ lib_libfrr_la_SOURCES = \
        lib/command_lex.l \
        lib/command_match.c \
        lib/command_parse.y \
+       lib/cspf.c \
        lib/csv.c \
        lib/debug.c \
        lib/defaults.c \
@@ -182,6 +183,7 @@ pkginclude_HEADERS += \
        lib/command_graph.h \
        lib/command_match.h \
        lib/compiler.h \
+       lib/cspf.h \
        lib/csv.h \
        lib/db.h \
        lib/debug.h \