blob: 2eaa15f9b302cbee50643aa7065f7a9eebdaa2f8 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2024, The University of Memphis,
* Regents of the University of California,
* Arizona Board of Regents.
*
* This file is part of NLSR (Named-data Link State Routing).
* See AUTHORS.md for complete list of NLSR authors and contributors.
*
* NLSR 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 3 of the License, or (at your option) any later version.
*
* NLSR 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
* NLSR, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
*/
#include "routing-calculator.hpp"
#include "name-map.hpp"
#include "nexthop.hpp"
#include "adjacent.hpp"
#include "logger.hpp"
#include "nlsr.hpp"
#include <boost/multi_array.hpp>
namespace nlsr {
namespace {
INIT_LOGGER(route.RoutingCalculatorLinkState);
constexpr int EMPTY_PARENT = -12345;
constexpr double INF_DISTANCE = 2147483647;
constexpr int NO_NEXT_HOP = -12345;
/**
* @brief Adjacency matrix.
*
* The matrix shall be a 2D array with N rows and N columns, where N is the number of routers.
* Element i,j is the cost from router i to router j.
*/
using AdjMatrix = boost::multi_array<double, 2>;
struct PrintAdjMatrix
{
const AdjMatrix& matrix;
const NameMap& map;
};
/**
* @brief Print adjacency matrix.
*/
std::ostream&
operator<<(std::ostream& os, const PrintAdjMatrix& p)
{
size_t nRouters = p.map.size();
os << "-----------Legend (routerName -> index)------\n";
for (size_t i = 0; i < nRouters; ++i) {
os << "Router:" << *p.map.getRouterNameByMappingNo(i)
<< " Index:" << i << "\n";
}
os << " |";
for (size_t i = 0; i < nRouters; ++i) {
os << i << " ";
}
os << "\n";
os << "--";
for (size_t i = 0; i < nRouters; ++i) {
os << "--";
}
os << "\n";
for (size_t i = 0; i < nRouters; i++) {
os << i << "|";
for (size_t j = 0; j < nRouters; j++) {
double cost = p.matrix[i][j];
if (cost == NO_NEXT_HOP) {
os << "0 ";
}
else {
os << cost << " ";
}
}
os << "\n";
}
return os;
}
/**
* @brief Allocate and populate adjacency matrix.
*
* The adjacency matrix is resized to match the number of routers in @p map .
* Costs from Adjacency LSAs are filled into the matrix; in case of a mismatch in bidirectional
* costs, the higher cost is assigned for both directions.
* All other elements are set to @c NON_ADJACENT_COST .
*/
AdjMatrix
makeAdjMatrix(const Lsdb& lsdb, NameMap& map)
{
// Create the matrix to have N rows and N columns, where N is number of routers.
size_t nRouters = map.size();
AdjMatrix matrix(boost::extents[nRouters][nRouters]);
// Initialize all elements to NON_ADJACENT_COST.
std::fill_n(matrix.origin(), matrix.num_elements(), Adjacent::NON_ADJACENT_COST);
// For each LSA represented in the map
auto lsaRange = lsdb.getLsdbIterator<AdjLsa>();
for (auto lsaIt = lsaRange.first; lsaIt != lsaRange.second; ++lsaIt) {
auto adjLsa = std::static_pointer_cast<AdjLsa>(*lsaIt);
auto row = map.getMappingNoByRouterName(adjLsa->getOriginRouter());
std::list<Adjacent> adl = adjLsa->getAdl().getAdjList();
// For each adjacency represented in the LSA
for (const auto& adjacent : adl) {
auto col = map.getMappingNoByRouterName(adjacent.getName());
double cost = adjacent.getLinkCost();
if (row && col && *row < static_cast<int32_t>(nRouters)
&& *col < static_cast<int32_t>(nRouters)) {
matrix[*row][*col] = cost;
}
}
}
// Links that do not have the same cost for both directions should
// have their costs corrected:
//
// If the cost of one side of the link is NON_ADJACENT_COST (i.e. broken) or negative,
// both direction of the link should have their cost corrected to NON_ADJACENT_COST.
//
// Otherwise, both sides of the link should use the larger of the two costs.
//
// Additionally, this means that we can halve the amount of space
// that the matrix uses by only maintaining a triangle.
// - But that is not yet implemented.
for (size_t row = 0; row < nRouters; ++row) {
for (size_t col = 0; col < nRouters; ++col) {
double& toCost = matrix[row][col];
double& fromCost = matrix[col][row];
if (fromCost == toCost) {
continue;
}
// If both sides of the link are up, use the larger cost else break the link
double correctedCost = Adjacent::NON_ADJACENT_COST;
if (toCost >= 0 && fromCost >= 0) {
correctedCost = std::max(toCost, fromCost);
}
NLSR_LOG_WARN("Cost between [" << row << "][" << col << "] and [" << col << "][" << row <<
"] are not the same (" << toCost << " != " << fromCost << "). " <<
"Correcting to cost: " << correctedCost);
toCost = correctedCost;
fromCost = correctedCost;
}
}
return matrix;
}
void
sortQueueByDistance(std::vector<int>& q, const std::vector<double>& dist, size_t start)
{
for (size_t i = start; i < q.size(); ++i) {
for (size_t j = i + 1; j < q.size(); ++j) {
if (dist[q[j]] < dist[q[i]]) {
std::swap(q[i], q[j]);
}
}
}
}
bool
isNotExplored(std::vector<int>& q, int u, size_t start)
{
for (size_t i = start; i < q.size(); i++) {
if (q[i] == u) {
return true;
}
}
return false;
}
struct Link
{
size_t index;
double cost;
};
/**
* @brief List adjacencies and link costs from a source router.
*/
std::vector<Link>
gatherLinks(const AdjMatrix& matrix, int sourceRouter)
{
size_t nRouters = matrix.size();
std::vector<Link> result;
result.reserve(nRouters);
for (size_t i = 0; i < nRouters; ++i) {
if (i == static_cast<size_t>(sourceRouter)) {
continue;
}
double cost = matrix[sourceRouter][i];
if (cost >= 0.0) {
result.emplace_back(Link{i, cost});
}
}
return result;
}
/**
* @brief Adjust link costs to simulate having only one accessible neighbor.
*/
void
simulateOneNeighbor(AdjMatrix& matrix, int sourceRouter, const Link& accessibleNeighbor)
{
size_t nRouters = matrix.size();
for (size_t i = 0; i < nRouters; ++i) {
if (i == accessibleNeighbor.index) {
matrix[sourceRouter][i] = accessibleNeighbor.cost;
}
else {
// if "i" is not a link to the source, set its cost to a non adjacent value.
matrix[sourceRouter][i] = Adjacent::NON_ADJACENT_COST;
}
}
}
class DijkstraResult
{
public:
int
getNextHop(int dest, int source) const
{
int nextHop = NO_NEXT_HOP;
while (parent[dest] != EMPTY_PARENT) {
nextHop = dest;
dest = parent[dest];
}
if (dest != source) {
nextHop = NO_NEXT_HOP;
}
return nextHop;
}
public:
std::vector<int> parent;
std::vector<double> distance;
};
/**
* @brief Compute the shortest path from a source router to every other router.
*/
DijkstraResult
calculateDijkstraPath(const AdjMatrix& matrix, int sourceRouter)
{
size_t nRouters = matrix.size();
std::vector<int> parent(nRouters, EMPTY_PARENT);
// Array where the ith element is the distance to the router with mapping no i.
std::vector<double> distance(nRouters, INF_DISTANCE);
// Each cell represents the router with that mapping no.
std::vector<int> q(nRouters);
for (size_t i = 0 ; i < nRouters; ++i) {
q[i] = static_cast<int>(i);
}
size_t head = 0;
// Distance to source from source is always 0.
distance[sourceRouter] = 0;
sortQueueByDistance(q, distance, head);
// While we haven't visited every node.
while (head < nRouters) {
int u = q[head]; // Set u to be the current node pointed to by head.
if (distance[u] == INF_DISTANCE) {
break; // This can only happen when there are no accessible nodes.
}
// Iterate over the adjacent nodes to u.
for (size_t v = 0; v < nRouters; ++v) {
// If the current node is accessible and we haven't visited it yet.
if (matrix[u][v] >= 0 && isNotExplored(q, v, head + 1)) {
// And if the distance to this node + from this node to v
// is less than the distance from our source node to v
// that we got when we built the adj LSAs
double newDistance = distance[u] + matrix[u][v];
if (newDistance < distance[v]) {
// Set the new distance
distance[v] = newDistance;
// Set how we get there.
parent[v] = u;
}
}
}
// Increment the head position, resort the list by distance from where we are.
++head;
sortQueueByDistance(q, distance, head);
}
return DijkstraResult{std::move(parent), std::move(distance)};
}
/**
* @brief Insert shortest paths into the routing table.
*/
void
addNextHopsToRoutingTable(RoutingTable& rt, const NameMap& map, int sourceRouter,
const AdjacencyList& adjacencies, const DijkstraResult& dr)
{
NLSR_LOG_DEBUG("addNextHopsToRoutingTable Called");
int nRouters = static_cast<int>(map.size());
// For each router we have
for (int i = 0; i < nRouters; ++i) {
if (i == sourceRouter) {
continue;
}
// Obtain the next hop that was determined by the algorithm
int nextHopRouter = dr.getNextHop(i, sourceRouter);
if (nextHopRouter == NO_NEXT_HOP) {
continue;
}
// If this router is accessible at all
// Fetch its distance
double routeCost = dr.distance[i];
// Fetch its actual name
auto nextHopRouterName = map.getRouterNameByMappingNo(nextHopRouter);
BOOST_ASSERT(nextHopRouterName.has_value());
auto nextHopFace = adjacencies.getAdjacent(*nextHopRouterName).getFaceUri();
// Add next hop to routing table
NextHop nh(nextHopFace, routeCost);
rt.addNextHop(*map.getRouterNameByMappingNo(i), nh);
}
}
} // anonymous namespace
void
calculateLinkStateRoutingPath(NameMap& map, RoutingTable& rt, ConfParameter& confParam,
const Lsdb& lsdb)
{
NLSR_LOG_DEBUG("calculateLinkStateRoutingPath called");
auto sourceRouter = map.getMappingNoByRouterName(confParam.getRouterPrefix());
if (!sourceRouter) {
NLSR_LOG_DEBUG("Source router is absent, nothing to do");
return;
}
AdjMatrix matrix = makeAdjMatrix(lsdb, map);
NLSR_LOG_DEBUG((PrintAdjMatrix{matrix, map}));
if (confParam.getMaxFacesPerPrefix() == 1) {
// In the single path case we can simply run Dijkstra's algorithm.
auto dr = calculateDijkstraPath(matrix, *sourceRouter);
// Inform the routing table of the new next hops.
addNextHopsToRoutingTable(rt, map, *sourceRouter, confParam.getAdjacencyList(), dr);
}
else {
// Multi Path
// Gets a sparse listing of adjacencies for path calculation
auto links = gatherLinks(matrix, *sourceRouter);
for (const auto& link : links) {
// Simulate that only the current neighbor is accessible
simulateOneNeighbor(matrix, *sourceRouter, link);
NLSR_LOG_DEBUG((PrintAdjMatrix{matrix, map}));
// Do Dijkstra's algorithm using the current neighbor as your start.
auto dr = calculateDijkstraPath(matrix, *sourceRouter);
// Update the routing table with the calculations.
addNextHopsToRoutingTable(rt, map, *sourceRouter, confParam.getAdjacencyList(), dr);
}
}
}
} // namespace nlsr