summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_mpath.c313
-rw-r--r--bgpd/bgp_mpath.h32
-rw-r--r--bgpd/bgp_route.c37
-rw-r--r--bgpd/bgp_route.h6
-rw-r--r--lib/memtypes.c1
-rw-r--r--tests/bgp_mpath_test.c84
6 files changed, 443 insertions, 30 deletions
diff --git a/bgpd/bgp_mpath.c b/bgpd/bgp_mpath.c
index 09b46951..af1c342c 100644
--- a/bgpd/bgp_mpath.c
+++ b/bgpd/bgp_mpath.c
@@ -27,11 +27,13 @@
#include "prefix.h"
#include "linklist.h"
#include "sockunion.h"
+#include "memory.h"
#include "bgpd/bgpd.h"
#include "bgpd/bgp_table.h"
#include "bgpd/bgp_route.h"
#include "bgpd/bgp_attr.h"
+#include "bgpd/bgp_debug.h"
#include "bgpd/bgp_mpath.h"
/*
@@ -200,3 +202,314 @@ bgp_mp_list_add (struct list *mp_list, struct bgp_info *mpinfo)
assert (mp_list && mpinfo);
listnode_add_sort (mp_list, mpinfo);
}
+
+/*
+ * bgp_info_mpath_new
+ *
+ * Allocate and zero memory for a new bgp_info_mpath element
+ */
+static struct bgp_info_mpath *
+bgp_info_mpath_new (void)
+{
+ struct bgp_info_mpath *new_mpath;
+ new_mpath = XCALLOC (MTYPE_BGP_MPATH_INFO, sizeof (struct bgp_info_mpath));
+ return new_mpath;
+}
+
+/*
+ * bgp_info_mpath_free
+ *
+ * Release resources for a bgp_info_mpath element and zero out pointer
+ */
+void
+bgp_info_mpath_free (struct bgp_info_mpath **mpath)
+{
+ if (mpath && *mpath)
+ {
+ XFREE (MTYPE_BGP_MPATH_INFO, *mpath);
+ *mpath = NULL;
+ }
+}
+
+/*
+ * bgp_info_mpath_get
+ *
+ * Fetch the mpath element for the given bgp_info. Used for
+ * doing lazy allocation.
+ */
+static struct bgp_info_mpath *
+bgp_info_mpath_get (struct bgp_info *binfo)
+{
+ struct bgp_info_mpath *mpath;
+ if (!binfo->mpath)
+ {
+ mpath = bgp_info_mpath_new();
+ if (!mpath)
+ return NULL;
+ binfo->mpath = mpath;
+ mpath->mp_info = binfo;
+ }
+ return binfo->mpath;
+}
+
+/*
+ * bgp_info_mpath_enqueue
+ *
+ * Enqueue a path onto the multipath list given the previous multipath
+ * list entry
+ */
+static void
+bgp_info_mpath_enqueue (struct bgp_info *prev_info, struct bgp_info *binfo)
+{
+ struct bgp_info_mpath *prev, *mpath;
+
+ prev = bgp_info_mpath_get (prev_info);
+ mpath = bgp_info_mpath_get (binfo);
+ if (!prev || !mpath)
+ return;
+
+ mpath->mp_next = prev->mp_next;
+ mpath->mp_prev = prev;
+ if (prev->mp_next)
+ prev->mp_next->mp_prev = mpath;
+ prev->mp_next = mpath;
+
+ SET_FLAG (binfo->flags, BGP_INFO_MULTIPATH);
+}
+
+/*
+ * bgp_info_mpath_dequeue
+ *
+ * Remove a path from the multipath list
+ */
+void
+bgp_info_mpath_dequeue (struct bgp_info *binfo)
+{
+ struct bgp_info_mpath *mpath = binfo->mpath;
+ if (!mpath)
+ return;
+ if (mpath->mp_prev)
+ mpath->mp_prev->mp_next = mpath->mp_next;
+ if (mpath->mp_next)
+ mpath->mp_next->mp_prev = mpath->mp_prev;
+ mpath->mp_next = mpath->mp_prev = NULL;
+ UNSET_FLAG (binfo->flags, BGP_INFO_MULTIPATH);
+}
+
+/*
+ * bgp_info_mpath_next
+ *
+ * Given a bgp_info, return the next multipath entry
+ */
+struct bgp_info *
+bgp_info_mpath_next (struct bgp_info *binfo)
+{
+ if (!binfo->mpath || !binfo->mpath->mp_next)
+ return NULL;
+ return binfo->mpath->mp_next->mp_info;
+}
+
+/*
+ * bgp_info_mpath_first
+ *
+ * Given bestpath bgp_info, return the first multipath entry.
+ */
+struct bgp_info *
+bgp_info_mpath_first (struct bgp_info *binfo)
+{
+ return bgp_info_mpath_next (binfo);
+}
+
+/*
+ * bgp_info_mpath_count
+ *
+ * Given the bestpath bgp_info, return the number of multipath entries
+ */
+u_int32_t
+bgp_info_mpath_count (struct bgp_info *binfo)
+{
+ if (!binfo->mpath)
+ return 0;
+ return binfo->mpath->mp_count;
+}
+
+/*
+ * bgp_info_mpath_count_set
+ *
+ * Sets the count of multipaths into bestpath's mpath element
+ */
+static void
+bgp_info_mpath_count_set (struct bgp_info *binfo, u_int32_t count)
+{
+ struct bgp_info_mpath *mpath;
+ if (!count && !binfo->mpath)
+ return;
+ mpath = bgp_info_mpath_get (binfo);
+ if (!mpath)
+ return;
+ mpath->mp_count = count;
+}
+
+/*
+ * bgp_info_mpath_update
+ *
+ * Compare and sync up the multipath list with the mp_list generated by
+ * bgp_best_selection
+ */
+void
+bgp_info_mpath_update (struct bgp_node *rn, struct bgp_info *new_best,
+ struct bgp_info *old_best, struct list *mp_list,
+ struct bgp_maxpaths_cfg *mpath_cfg)
+{
+ u_int16_t maxpaths, mpath_count, old_mpath_count;
+ struct listnode *mp_node, *mp_next_node;
+ struct bgp_info *cur_mpath, *new_mpath, *next_mpath, *prev_mpath;
+ int mpath_changed, debug;
+ char pfx_buf[INET_ADDRSTRLEN], nh_buf[2][INET_ADDRSTRLEN];
+
+ mpath_changed = 0;
+ maxpaths = BGP_DEFAULT_MAXPATHS;
+ mpath_count = 0;
+ cur_mpath = NULL;
+ old_mpath_count = 0;
+ prev_mpath = new_best;
+ mp_node = listhead (mp_list);
+ debug = BGP_DEBUG (events, EVENTS);
+
+ if (debug)
+ prefix2str (&rn->p, pfx_buf, sizeof (pfx_buf));
+
+ if (new_best)
+ {
+ mpath_count++;
+ if (new_best != old_best)
+ bgp_info_mpath_dequeue (new_best);
+ maxpaths = (peer_sort (new_best->peer) == BGP_PEER_IBGP) ?
+ mpath_cfg->maxpaths_ibgp : mpath_cfg->maxpaths_ebgp;
+ }
+
+ if (old_best)
+ {
+ cur_mpath = bgp_info_mpath_first (old_best);
+ old_mpath_count = bgp_info_mpath_count (old_best);
+ bgp_info_mpath_count_set (old_best, 0);
+ bgp_info_mpath_dequeue (old_best);
+ }
+
+ /*
+ * We perform an ordered walk through both lists in parallel.
+ * The reason for the ordered walk is that if there are paths
+ * that were previously multipaths and are still multipaths, the walk
+ * should encounter them in both lists at the same time. Otherwise
+ * there will be paths that are in one list or another, and we
+ * will deal with these separately.
+ *
+ * Note that new_best might be somewhere in the mp_list, so we need
+ * to skip over it
+ */
+ while (mp_node || cur_mpath)
+ {
+ /*
+ * We can bail out of this loop if all existing paths on the
+ * multipath list have been visited (for cleanup purposes) and
+ * the maxpath requirement is fulfulled
+ */
+ if (!cur_mpath && (mpath_count >= maxpaths))
+ break;
+
+ mp_next_node = mp_node ? listnextnode (mp_node) : NULL;
+ next_mpath = cur_mpath ? bgp_info_mpath_next (cur_mpath) : NULL;
+
+ /*
+ * If equal, the path was a multipath and is still a multipath.
+ * Insert onto new multipath list if maxpaths allows.
+ */
+ if (mp_node && (listgetdata (mp_node) == cur_mpath))
+ {
+ list_delete_node (mp_list, mp_node);
+ bgp_info_mpath_dequeue (cur_mpath);
+ if ((mpath_count < maxpaths) &&
+ bgp_info_nexthop_cmp (prev_mpath, cur_mpath))
+ {
+ bgp_info_mpath_enqueue (prev_mpath, cur_mpath);
+ prev_mpath = cur_mpath;
+ mpath_count++;
+ }
+ else
+ {
+ mpath_changed = 1;
+ if (debug)
+ zlog_debug ("%s remove mpath nexthop %s peer %s", pfx_buf,
+ inet_ntop (AF_INET, &cur_mpath->attr->nexthop,
+ nh_buf[0], sizeof (nh_buf[0])),
+ sockunion2str (cur_mpath->peer->su_remote,
+ nh_buf[1], sizeof (nh_buf[1])));
+ }
+ mp_node = mp_next_node;
+ cur_mpath = next_mpath;
+ continue;
+ }
+
+ if (cur_mpath && (!mp_node ||
+ (bgp_info_mpath_cmp (cur_mpath,
+ listgetdata (mp_node)) < 0)))
+ {
+ /*
+ * If here, we have an old multipath and either the mp_list
+ * is finished or the next mp_node points to a later
+ * multipath, so we need to purge this path from the
+ * multipath list
+ */
+ bgp_info_mpath_dequeue (cur_mpath);
+ mpath_changed = 1;
+ if (debug)
+ zlog_debug ("%s remove mpath nexthop %s peer %s", pfx_buf,
+ inet_ntop (AF_INET, &cur_mpath->attr->nexthop,
+ nh_buf[0], sizeof (nh_buf[0])),
+ sockunion2str (cur_mpath->peer->su_remote,
+ nh_buf[1], sizeof (nh_buf[1])));
+ cur_mpath = next_mpath;
+ }
+ else
+ {
+ /*
+ * If here, we have a path on the mp_list that was not previously
+ * a multipath (due to non-equivalance or maxpaths exceeded),
+ * or the matching multipath is sorted later in the multipath
+ * list. Before we enqueue the path on the new multipath list,
+ * make sure its not on the old_best multipath list or referenced
+ * via next_mpath:
+ * - If next_mpath points to this new path, update next_mpath to
+ * point to the multipath after this one
+ * - Dequeue the path from the multipath list just to make sure
+ */
+ new_mpath = listgetdata (mp_node);
+ list_delete_node (mp_list, mp_node);
+ if (new_mpath == next_mpath)
+ next_mpath = bgp_info_mpath_next (new_mpath);
+ bgp_info_mpath_dequeue (new_mpath);
+ if ((mpath_count < maxpaths) && (new_mpath != new_best) &&
+ bgp_info_nexthop_cmp (prev_mpath, new_mpath))
+ {
+ bgp_info_mpath_enqueue (prev_mpath, new_mpath);
+ prev_mpath = new_mpath;
+ mpath_changed = 1;
+ mpath_count++;
+ if (debug)
+ zlog_debug ("%s add mpath nexthop %s peer %s", pfx_buf,
+ inet_ntop (AF_INET, &new_mpath->attr->nexthop,
+ nh_buf[0], sizeof (nh_buf[0])),
+ sockunion2str (new_mpath->peer->su_remote,
+ nh_buf[1], sizeof (nh_buf[1])));
+ }
+ mp_node = mp_next_node;
+ }
+ }
+
+ if (new_best)
+ {
+ bgp_info_mpath_count_set (new_best, mpath_count-1);
+ if (mpath_changed || (bgp_info_mpath_count (new_best) != old_mpath_count))
+ SET_FLAG (new_best->flags, BGP_INFO_MULTIPATH_CHG);
+ }
+}
diff --git a/bgpd/bgp_mpath.h b/bgpd/bgp_mpath.h
index de6461fd..1dc2687e 100644
--- a/bgpd/bgp_mpath.h
+++ b/bgpd/bgp_mpath.h
@@ -27,6 +27,24 @@
/* BGP default maximum-paths */
#define BGP_DEFAULT_MAXPATHS 1
+/* Supplemental information linked to bgp_info for keeping track of
+ * multipath selections, lazily allocated to save memory
+ */
+struct bgp_info_mpath
+{
+ /* Points to the first multipath (on bestpath) or the next multipath */
+ struct bgp_info_mpath *mp_next;
+
+ /* Points to the previous multipath or NULL on bestpath */
+ struct bgp_info_mpath *mp_prev;
+
+ /* Points to bgp_info associated with this multipath info */
+ struct bgp_info *mp_info;
+
+ /* When attached to best path, the number of selected multipaths */
+ u_int32_t mp_count;
+};
+
/* Functions to support maximum-paths configuration */
extern int bgp_maximum_paths_set (struct bgp *, afi_t, safi_t, int, u_int16_t);
extern int bgp_maximum_paths_unset (struct bgp *, afi_t, safi_t, int);
@@ -37,5 +55,19 @@ extern int bgp_maximum_paths_unset (struct bgp *, afi_t, safi_t, int);
extern void bgp_mp_list_init (struct list *);
extern void bgp_mp_list_clear (struct list *);
extern void bgp_mp_list_add (struct list *, struct bgp_info *);
+extern void bgp_info_mpath_update (struct bgp_node *, struct bgp_info *,
+ struct bgp_info *, struct list *,
+ struct bgp_maxpaths_cfg *);
+
+/* Unlink and free multipath information associated with a bgp_info */
+extern void bgp_info_mpath_dequeue (struct bgp_info *);
+extern void bgp_info_mpath_free (struct bgp_info_mpath **);
+
+/* Walk list of multipaths associated with a best path */
+extern struct bgp_info *bgp_info_mpath_first (struct bgp_info *);
+extern struct bgp_info *bgp_info_mpath_next (struct bgp_info *);
+
+/* Accessors for multipath information */
+extern u_int32_t bgp_info_mpath_count (struct bgp_info *);
#endif /* _QUAGGA_BGP_MPATH_H */
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 718e25ff..ec17dc61 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -141,6 +141,7 @@ bgp_info_free (struct bgp_info *binfo)
bgp_attr_unintern (binfo->attr);
bgp_info_extra_free (&binfo->extra);
+ bgp_info_mpath_free (&binfo->mpath);
peer_unlock (binfo->peer); /* bgp_info peer reference */
@@ -211,6 +212,7 @@ bgp_info_reap (struct bgp_node *rn, struct bgp_info *ri)
else
rn->info = ri->next;
+ bgp_info_mpath_dequeue (ri);
bgp_info_unlock (ri);
bgp_unlock_node (rn);
}
@@ -1384,35 +1386,7 @@ bgp_best_selection (struct bgp *bgp, struct bgp_node *rn,
}
- /*
- * TODO: Will add some additional filtering later to only
- * output debugs when multipath state for the route changes
- */
- if (BGP_DEBUG (events, EVENTS) && do_mpath)
- {
- struct listnode *mp_node;
- struct bgp_info *mp_info;
- char buf[2][INET_ADDRSTRLEN];
-
- prefix2str (&rn->p, buf[0], sizeof (buf[0]));
- zlog_debug ("%s bestpath nexthop %s, %d mpath candidates",
- buf[0],
- (new_select ?
- inet_ntop(AF_INET, &new_select->attr->nexthop,
- buf[1], sizeof (buf[1])) :
- "None"),
- listcount (&mp_list));
- for (mp_node = listhead (&mp_list); mp_node;
- mp_node = listnextnode (mp_node))
- {
- mp_info = listgetdata (mp_node);
- if (mp_info == new_select)
- continue;
- zlog_debug (" candidate mpath nexthop %s",
- inet_ntop(AF_INET, &mp_info->attr->nexthop, buf[0],
- sizeof (buf[0])));
- }
- }
+ bgp_info_mpath_update (rn, new_select, old_select, &mp_list, mpath_cfg);
bgp_mp_list_clear (&mp_list);
@@ -5995,6 +5969,11 @@ route_vty_out_detail (struct vty *vty, struct bgp *bgp, struct prefix *p,
if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE))
vty_out (vty, ", atomic-aggregate");
+ if (CHECK_FLAG (binfo->flags, BGP_INFO_MULTIPATH) ||
+ (CHECK_FLAG (binfo->flags, BGP_INFO_SELECTED) &&
+ bgp_info_mpath_count (binfo)))
+ vty_out (vty, ", multipath");
+
if (CHECK_FLAG (binfo->flags, BGP_INFO_SELECTED))
vty_out (vty, ", best");
diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h
index 3e528596..45e8d2e8 100644
--- a/bgpd/bgp_route.h
+++ b/bgpd/bgp_route.h
@@ -57,6 +57,10 @@ struct bgp_info
/* Extra information */
struct bgp_info_extra *extra;
+
+ /* Multipath information */
+ struct bgp_info_mpath *mpath;
+
/* Uptime. */
time_t uptime;
@@ -76,6 +80,8 @@ struct bgp_info
#define BGP_INFO_STALE (1 << 8)
#define BGP_INFO_REMOVED (1 << 9)
#define BGP_INFO_COUNTED (1 << 10)
+#define BGP_INFO_MULTIPATH (1 << 11)
+#define BGP_INFO_MULTIPATH_CHG (1 << 12)
/* BGP route type. This can be static, RIP, OSPF, BGP etc. */
u_char type;
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 59020671..dca32caa 100644
--- a/lib/memtypes.c
+++ b/lib/memtypes.c
@@ -115,6 +115,7 @@ struct memory_list memory_list_bgp[] =
{ MTYPE_BGP_SYNCHRONISE, "BGP synchronise" },
{ MTYPE_BGP_ADJ_IN, "BGP adj in" },
{ MTYPE_BGP_ADJ_OUT, "BGP adj out" },
+ { MTYPE_BGP_MPATH_INFO, "BGP multipath info" },
{ 0, NULL },
{ MTYPE_AS_LIST, "BGP AS list" },
{ MTYPE_AS_FILTER, "BGP AS filter" },
diff --git a/tests/bgp_mpath_test.c b/tests/bgp_mpath_test.c
index 4d51ddb8..15e450a2 100644
--- a/tests/bgp_mpath_test.c
+++ b/tests/bgp_mpath_test.c
@@ -194,7 +194,13 @@ testcase_t test_bgp_cfg_maximum_paths = {
/*=========================================================
* Testcase for bgp_mp_list
*/
-struct peer test_mp_list_peer[5];
+struct peer test_mp_list_peer[] = {
+ { .local_as = 1, .as = 2 },
+ { .local_as = 1, .as = 2 },
+ { .local_as = 1, .as = 2 },
+ { .local_as = 1, .as = 2 },
+ { .local_as = 1, .as = 2 },
+};
int test_mp_list_peer_count = sizeof (test_mp_list_peer)/ sizeof (struct peer);
struct attr test_mp_list_attr[4];
struct bgp_info test_mp_list_info[] = {
@@ -278,11 +284,87 @@ testcase_t test_bgp_mp_list = {
};
/*=========================================================
+ * Testcase for bgp_info_mpath_update
+ */
+
+struct bgp_node test_rn;
+
+static int
+setup_bgp_info_mpath_update (testcase_t *t)
+{
+ int i;
+ str2prefix ("42.1.1.0/24", &test_rn.p);
+ setup_bgp_mp_list (t);
+ for (i = 0; i < test_mp_list_info_count; i++)
+ bgp_info_add (&test_rn, &test_mp_list_info[i]);
+ return 0;
+}
+
+static int
+run_bgp_info_mpath_update (testcase_t *t)
+{
+ struct bgp_info *new_best, *old_best, *mpath;
+ struct list mp_list;
+ struct bgp_maxpaths_cfg mp_cfg = { 3, 3 };
+ int test_result = TEST_PASSED;
+ bgp_mp_list_init (&mp_list);
+ bgp_mp_list_add (&mp_list, &test_mp_list_info[4]);
+ bgp_mp_list_add (&mp_list, &test_mp_list_info[3]);
+ bgp_mp_list_add (&mp_list, &test_mp_list_info[0]);
+ bgp_mp_list_add (&mp_list, &test_mp_list_info[1]);
+ new_best = &test_mp_list_info[3];
+ old_best = NULL;
+ bgp_info_mpath_update (&test_rn, new_best, old_best, &mp_list, &mp_cfg);
+ bgp_mp_list_clear (&mp_list);
+ EXPECT_TRUE (bgp_info_mpath_count (new_best) == 2, test_result);
+ mpath = bgp_info_mpath_first (new_best);
+ EXPECT_TRUE (mpath == &test_mp_list_info[0], test_result);
+ EXPECT_TRUE (CHECK_FLAG (mpath->flags, BGP_INFO_MULTIPATH), test_result);
+ mpath = bgp_info_mpath_next (mpath);
+ EXPECT_TRUE (mpath == &test_mp_list_info[1], test_result);
+ EXPECT_TRUE (CHECK_FLAG (mpath->flags, BGP_INFO_MULTIPATH), test_result);
+
+ bgp_mp_list_add (&mp_list, &test_mp_list_info[0]);
+ bgp_mp_list_add (&mp_list, &test_mp_list_info[1]);
+ new_best = &test_mp_list_info[0];
+ old_best = &test_mp_list_info[3];
+ bgp_info_mpath_update (&test_rn, new_best, old_best, &mp_list, &mp_cfg);
+ bgp_mp_list_clear (&mp_list);
+ EXPECT_TRUE (bgp_info_mpath_count (new_best) == 1, test_result);
+ mpath = bgp_info_mpath_first (new_best);
+ EXPECT_TRUE (mpath == &test_mp_list_info[1], test_result);
+ EXPECT_TRUE (CHECK_FLAG (mpath->flags, BGP_INFO_MULTIPATH), test_result);
+ EXPECT_TRUE (!CHECK_FLAG (test_mp_list_info[0].flags, BGP_INFO_MULTIPATH),
+ test_result);
+
+ return test_result;
+}
+
+static int
+cleanup_bgp_info_mpath_update (testcase_t *t)
+{
+ int i;
+
+ for (i = 0; i < test_mp_list_peer_count; i++)
+ sockunion_free (test_mp_list_peer[i].su_remote);
+
+ return 0;
+}
+
+testcase_t test_bgp_info_mpath_update = {
+ .desc = "Test bgp_info_mpath_update",
+ .setup = setup_bgp_info_mpath_update,
+ .run = run_bgp_info_mpath_update,
+ .cleanup = cleanup_bgp_info_mpath_update,
+};
+
+/*=========================================================
* Set up testcase vector
*/
testcase_t *all_tests[] = {
&test_bgp_cfg_maximum_paths,
&test_bgp_mp_list,
+ &test_bgp_info_mpath_update,
};
int all_tests_count = (sizeof(all_tests)/sizeof(testcase_t *));