blob: 8195e47f4ab1e57f1d43493274ba62b6e508e9e1 [file] [log] [blame]
/* $QuaggaId: Format:%an, %ai, %h$ $
*
* BGP Multipath
* Copyright (C) 2010 Google Inc.
*
* This file is part of Quagga
*
* Quagga 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.
*
* Quagga 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 Quagga; see the file COPYING. If not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <zebra.h>
#include "command.h"
#include "prefix.h"
#include "linklist.h"
#include "sockunion.h"
#include "memory.h"
#include "filter.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_aspath.h"
#include "bgpd/bgp_community.h"
#include "bgpd/bgp_ecommunity.h"
#include "bgpd/bgp_lcommunity.h"
#include "bgpd/bgp_mpath.h"
bool
bgp_mpath_is_configured_sort (struct bgp *bgp, bgp_peer_sort_t sort,
afi_t afi, safi_t safi)
{
struct bgp_maxpaths_cfg *cfg = &bgp->maxpaths[afi][safi];
/* XXX: BGP_DEFAULT_MAXPATHS is 1, and this test only seems to make sense
* if if it stays 1, so not sure the DEFAULT define is that useful.
*/
switch (sort)
{
case BGP_PEER_IBGP:
return cfg->maxpaths_ibgp != BGP_DEFAULT_MAXPATHS;
case BGP_PEER_EBGP:
return cfg->maxpaths_ebgp != BGP_DEFAULT_MAXPATHS;
default:
return false;
}
}
bool
bgp_mpath_is_configured (struct bgp *bgp, afi_t afi, safi_t safi)
{
return bgp_mpath_is_configured_sort (bgp, BGP_PEER_IBGP, afi, safi)
|| bgp_mpath_is_configured_sort (bgp, BGP_PEER_EBGP, afi, safi);
}
/*
* bgp_maximum_paths_set
*
* Record maximum-paths configuration for BGP instance
*/
int
bgp_maximum_paths_set (struct bgp *bgp, afi_t afi, safi_t safi,
int peertype, u_int16_t maxpaths)
{
if (!bgp || (afi >= AFI_MAX) || (safi >= SAFI_MAX))
return -1;
switch (peertype)
{
case BGP_PEER_IBGP:
bgp->maxpaths[afi][safi].maxpaths_ibgp = maxpaths;
break;
case BGP_PEER_EBGP:
bgp->maxpaths[afi][safi].maxpaths_ebgp = maxpaths;
break;
default:
return -1;
}
return 0;
}
/*
* bgp_maximum_paths_unset
*
* Remove maximum-paths configuration from BGP instance
*/
int
bgp_maximum_paths_unset (struct bgp *bgp, afi_t afi, safi_t safi,
int peertype)
{
if (!bgp || (afi >= AFI_MAX) || (safi >= SAFI_MAX))
return -1;
switch (peertype)
{
case BGP_PEER_IBGP:
bgp->maxpaths[afi][safi].maxpaths_ibgp = BGP_DEFAULT_MAXPATHS;
break;
case BGP_PEER_EBGP:
bgp->maxpaths[afi][safi].maxpaths_ebgp = BGP_DEFAULT_MAXPATHS;
break;
default:
return -1;
}
return 0;
}
/*
* bgp_info_nexthop_cmp
*
* Compare the nexthops of two paths. Return value is less than, equal to,
* or greater than zero if bi1 is respectively less than, equal to,
* or greater than bi2.
*/
static int
bgp_info_nexthop_cmp (struct bgp_info *bi1, struct bgp_info *bi2)
{
struct attr_extra *ae1, *ae2;
int compare;
ae1 = bi1->attr->extra;
ae2 = bi2->attr->extra;
compare = IPV4_ADDR_CMP (&bi1->attr->nexthop, &bi2->attr->nexthop);
if (!compare && ae1 && ae2)
{
if (ae1->mp_nexthop_len == ae2->mp_nexthop_len)
{
switch (ae1->mp_nexthop_len)
{
case 4:
case 12:
compare = IPV4_ADDR_CMP (&ae1->mp_nexthop_global_in,
&ae2->mp_nexthop_global_in);
break;
case 16:
compare = IPV6_ADDR_CMP (&ae1->mp_nexthop_global,
&ae2->mp_nexthop_global);
break;
case 32:
compare = IPV6_ADDR_CMP (&ae1->mp_nexthop_global,
&ae2->mp_nexthop_global);
if (!compare)
compare = IPV6_ADDR_CMP (&ae1->mp_nexthop_local,
&ae2->mp_nexthop_local);
break;
}
}
/* This can happen if one IPv6 peer sends you global and link-local
* nexthops but another IPv6 peer only sends you global
*/
else if (ae1->mp_nexthop_len == 16 || ae1->mp_nexthop_len == 32)
{
compare = IPV6_ADDR_CMP (&ae1->mp_nexthop_global,
&ae2->mp_nexthop_global);
if (!compare)
{
if (ae1->mp_nexthop_len < ae2->mp_nexthop_len)
compare = -1;
else
compare = 1;
}
}
}
return compare;
}
/*
* bgp_info_mpath_cmp
*
* This function determines our multipath list ordering. By ordering
* the list we can deterministically select which paths are included
* in the multipath set. The ordering also helps in detecting changes
* in the multipath selection so we can detect whether to send an
* update to zebra.
*
* The order of paths is determined first by received nexthop, and then
* by peer address if the nexthops are the same.
*/
static int
bgp_info_mpath_cmp (void *val1, void *val2)
{
struct bgp_info *bi1, *bi2;
int compare;
bi1 = val1;
bi2 = val2;
compare = bgp_info_nexthop_cmp (bi1, bi2);
if (!compare)
compare = sockunion_cmp (bi1->peer->su_remote, bi2->peer->su_remote);
return compare;
}
/*
* bgp_mp_list_init
*
* Initialize the mp_list, which holds the list of multipaths
* selected by bgp_best_selection
*/
void
bgp_mp_list_init (struct list *mp_list)
{
assert (mp_list);
memset (mp_list, 0, sizeof (struct list));
mp_list->cmp = bgp_info_mpath_cmp;
}
/*
* bgp_mp_list_clear
*
* Clears all entries out of the mp_list
*/
void
bgp_mp_list_clear (struct list *mp_list)
{
assert (mp_list);
list_delete_all_node (mp_list);
}
/*
* bgp_mp_list_add
*
* Adds a multipath entry to the mp_list
*/
void
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)
{
if ((*mpath)->mp_attr)
bgp_attr_unintern (&(*mpath)->mp_attr);
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_attr
*
* Given bestpath bgp_info, return aggregated attribute set used
* for advertising the multipath route
*/
struct attr *
bgp_info_mpath_attr (struct bgp_info *binfo)
{
if (!binfo->mpath)
return NULL;
return binfo->mpath->mp_attr;
}
/*
* bgp_info_mpath_attr_set
*
* Sets the aggregated attribute into bestpath's mpath element
*/
static void
bgp_info_mpath_attr_set (struct bgp_info *binfo, struct attr *attr)
{
struct bgp_info_mpath *mpath;
if (!attr && !binfo->mpath)
return;
mpath = bgp_info_mpath_get (binfo);
if (!mpath)
return;
mpath->mp_attr = attr;
}
/*
* 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,
afi_t afi, safi_t safi)
{
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[INET6_ADDRSTRLEN], nh_buf[2][INET6_ADDRSTRLEN];
struct bgp_maxpaths_cfg *mpath_cfg = NULL;
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_cfg = &new_best->peer->bgp->maxpaths[afi][safi];
mpath_count++;
if (new_best != old_best)
bgp_info_mpath_dequeue (new_best);
maxpaths = (new_best->peer->sort == 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 ((mpath_count < maxpaths) && (new_mpath != new_best) &&
bgp_info_nexthop_cmp (prev_mpath, new_mpath))
{
if (new_mpath == next_mpath)
next_mpath = bgp_info_mpath_next (new_mpath);
bgp_info_mpath_dequeue (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);
}
}
/*
* bgp_mp_dmed_deselect
*
* Clean up multipath information for BGP_INFO_DMED_SELECTED path that
* is not selected as best path
*/
void
bgp_mp_dmed_deselect (struct bgp_info *dmed_best)
{
struct bgp_info *mpinfo, *mpnext;
if (!dmed_best)
return;
for (mpinfo = bgp_info_mpath_first (dmed_best); mpinfo; mpinfo = mpnext)
{
mpnext = bgp_info_mpath_next (mpinfo);
bgp_info_mpath_dequeue (mpinfo);
}
bgp_info_mpath_count_set (dmed_best, 0);
UNSET_FLAG (dmed_best->flags, BGP_INFO_MULTIPATH_CHG);
assert (bgp_info_mpath_first (dmed_best) == 0);
}
/*
* bgp_info_mpath_aggregate_update
*
* Set the multipath aggregate attribute. We need to see if the
* aggregate has changed and then set the ATTR_CHANGED flag on the
* bestpath info so that a peer update will be generated. The
* change is detected by generating the current attribute,
* interning it, and then comparing the interned pointer with the
* current value. We can skip this generate/compare step if there
* is no change in multipath selection and no attribute change in
* any multipath.
*/
void
bgp_info_mpath_aggregate_update (struct bgp_info *new_best,
struct bgp_info *old_best)
{
struct bgp_info *mpinfo;
struct aspath *aspath;
struct aspath *asmerge;
struct attr *new_attr, *old_attr;
u_char origin, attr_chg;
struct community *community, *commerge;
struct ecommunity *ecomm, *ecommerge;
struct lcommunity *lcomm, *lcommerge;
struct attr_extra *ae;
struct attr attr = { 0 };
if (old_best && (old_best != new_best) &&
(old_attr = bgp_info_mpath_attr (old_best)))
{
bgp_attr_unintern (&old_attr);
bgp_info_mpath_attr_set (old_best, NULL);
}
if (!new_best)
return;
if (!bgp_info_mpath_count (new_best))
{
if ((new_attr = bgp_info_mpath_attr (new_best)))
{
bgp_attr_unintern (&new_attr);
bgp_info_mpath_attr_set (new_best, NULL);
SET_FLAG (new_best->flags, BGP_INFO_ATTR_CHANGED);
}
return;
}
/*
* Bail out here if the following is true:
* - MULTIPATH_CHG bit is not set on new_best, and
* - No change in bestpath, and
* - ATTR_CHANGED bit is not set on new_best or any of the multipaths
*/
if (!CHECK_FLAG (new_best->flags, BGP_INFO_MULTIPATH_CHG) &&
(old_best == new_best))
{
attr_chg = 0;
if (CHECK_FLAG (new_best->flags, BGP_INFO_ATTR_CHANGED))
attr_chg = 1;
else
for (mpinfo = bgp_info_mpath_first (new_best); mpinfo;
mpinfo = bgp_info_mpath_next (mpinfo))
{
if (CHECK_FLAG (mpinfo->flags, BGP_INFO_ATTR_CHANGED))
{
attr_chg = 1;
break;
}
}
if (!attr_chg)
{
assert (bgp_info_mpath_attr (new_best));
return;
}
}
bgp_attr_dup (&attr, new_best->attr);
/* aggregate attribute from multipath constituents */
aspath = aspath_dup (attr.aspath);
origin = attr.origin;
community = attr.community ? community_dup (attr.community) : NULL;
ae = attr.extra;
ecomm = (ae && ae->ecommunity) ? ecommunity_dup (ae->ecommunity) : NULL;
lcomm = (ae && ae->lcommunity) ? lcommunity_dup (ae->lcommunity) : NULL;
for (mpinfo = bgp_info_mpath_first (new_best); mpinfo;
mpinfo = bgp_info_mpath_next (mpinfo))
{
asmerge = aspath_aggregate_mpath (aspath, mpinfo->attr->aspath);
aspath_free (aspath);
aspath = asmerge;
if (origin < mpinfo->attr->origin)
origin = mpinfo->attr->origin;
if (mpinfo->attr->community)
{
if (community)
{
commerge = community_merge (community, mpinfo->attr->community);
community = community_uniq_sort (commerge);
community_free (commerge);
}
else
community = community_dup (mpinfo->attr->community);
}
ae = mpinfo->attr->extra;
if (ae && ae->ecommunity)
{
if (ecomm)
{
ecommerge = ecommunity_merge (ecomm, ae->ecommunity);
ecomm = ecommunity_uniq_sort (ecommerge);
ecommunity_free (&ecommerge);
}
else
ecomm = ecommunity_dup (ae->ecommunity);
}
if (ae && ae->lcommunity)
{
if (lcomm)
{
lcommerge = lcommunity_merge (lcomm, ae->lcommunity);
lcomm = lcommunity_uniq_sort (lcommerge);
lcommunity_free (&lcommerge);
}
else
lcomm = lcommunity_dup (ae->lcommunity);
}
}
attr.aspath = aspath;
attr.origin = origin;
if (community)
{
attr.community = community;
attr.flag |= ATTR_FLAG_BIT (BGP_ATTR_COMMUNITIES);
}
if (ecomm)
{
ae = bgp_attr_extra_get (&attr);
ae->ecommunity = ecomm;
attr.flag |= ATTR_FLAG_BIT (BGP_ATTR_EXT_COMMUNITIES);
}
/* Zap multipath attr nexthop so we set nexthop to self */
attr.nexthop.s_addr = 0;
if (attr.extra)
memset (&attr.extra->mp_nexthop_global, 0, sizeof (struct in6_addr));
/* TODO: should we set ATOMIC_AGGREGATE and AGGREGATOR? */
new_attr = bgp_attr_intern (&attr);
bgp_attr_extra_free (&attr);
if (new_attr != bgp_info_mpath_attr (new_best))
{
if ((old_attr = bgp_info_mpath_attr (new_best)))
bgp_attr_unintern (&old_attr);
bgp_info_mpath_attr_set (new_best, new_attr);
SET_FLAG (new_best->flags, BGP_INFO_ATTR_CHANGED);
}
else
bgp_attr_unintern (&new_attr);
}