fboss/agent/LookupClassRouteUpdater.cpp (1,066 lines of code) (raw):
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <fb303/ServiceData.h>
#include "fboss/agent/LookupClassRouteUpdater.h"
#include "fboss/agent/FibHelpers.h"
#include "fboss/agent/SwSwitchRouteUpdateWrapper.h"
#include "fboss/agent/SwitchStats.h"
#include "fboss/agent/VlanTableDeltaCallbackGenerator.h"
#include "fboss/agent/state/Interface.h"
#include "fboss/agent/state/Port.h"
#include "fboss/agent/state/SwitchState.h"
/*
* TODO get rid of this flag once configs have been updated to
* not pass in this cmdline flag
*/
DEFINE_bool(
queue_per_host_route_fix,
true,
"Enable route entry (ip-per-task/VIP) portion of Queue-per-host fix");
namespace {
constexpr auto kQphMultiNextHopCounter = "qph.multinexthop.route";
} // namespace
namespace facebook::fboss {
// Helper methods
void LookupClassRouteUpdater::reAddAllRoutes(const StateDelta& stateDelta) {
auto addRoute = [&stateDelta, this](RouterID rid, const auto& route) {
if (!route->getClassID().has_value()) {
processRouteAdded(stateDelta, rid, route);
}
};
if (!vlan2SubnetsCache_.empty()) {
forAllRoutes(stateDelta.newState(), addRoute);
}
}
bool LookupClassRouteUpdater::vlanHasOtherPortsWithClassIDs(
const std::shared_ptr<SwitchState>& switchState,
const std::shared_ptr<Vlan>& vlan,
const std::shared_ptr<Port>& removedPort) {
for (auto& [portID, portInfo] : vlan->getPorts()) {
std::ignore = portInfo;
auto port = switchState->getPorts()->getPortIf(portID);
if (portID != removedPort->getID() &&
port->getLookupClassesToDistributeTrafficOn().size() != 0) {
return true;
}
}
return false;
}
void LookupClassRouteUpdater::removeNextHopsForSubnet(
const StateDelta& stateDelta,
const folly::CIDRNetwork& subnet,
const std::shared_ptr<Vlan>& vlan) {
auto& [ipAddress, mask] = subnet;
auto it = nextHopAndVlan2Prefixes_.begin();
while (it != nextHopAndVlan2Prefixes_.end()) {
const auto& [nextHop, vlanID] = it->first;
// Element pointed by the iterator would be deleted from
// nextHopAndVlan2Prefixes_ as part of processNeighborRemoved processing.
// Thus, advance the iterator to next element.
++it;
if (vlanID == vlan->getID() && nextHop.inSubnet(ipAddress, mask)) {
if (nextHop.isV6()) {
auto ndpEntry = vlan->getNdpTable()->getEntryIf(nextHop.asV6());
if (ndpEntry) {
processNeighborRemoved(stateDelta, vlan->getID(), ndpEntry);
}
} else if (nextHop.isV4()) {
auto arpEntry = vlan->getArpTable()->getEntryIf(nextHop.asV4());
if (arpEntry) {
processNeighborRemoved(stateDelta, vlan->getID(), arpEntry);
}
}
}
}
}
std::optional<cfg::AclLookupClass>
LookupClassRouteUpdater::getClassIDForLinkLocal(
const std::shared_ptr<SwitchState>& switchState,
VlanID vlanID,
const folly::IPAddressV6& ipAddressV6) {
CHECK(ipAddressV6.isLinkLocal());
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return std::nullopt;
}
/*
* Link local neighbors don't have classID, thus classID for a route whose
* nexthop is link local is derived from MAC address corresponding to the
* link local address.
* Try extracting MAC for LL from IP itself. If it fails, fallback to
* looking up MAC from corresponding NDP entry
*/
auto mac = ipAddressV6.getMacAddressFromLinkLocal();
if (!mac) {
auto ndpEntry = vlan->getNdpTable()->getNodeIf(ipAddressV6);
if (ndpEntry) {
mac = ndpEntry->getMac();
}
}
if (mac) {
auto macEntry = vlan->getMacTable()->getNodeIf(*mac);
return macEntry ? macEntry->getClassID() : std::nullopt;
}
return std::nullopt;
}
std::optional<cfg::AclLookupClass>
LookupClassRouteUpdater::getClassIDForNeighbor(
const std::shared_ptr<SwitchState>& switchState,
VlanID vlanID,
const folly::IPAddress& ipAddress) {
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return std::nullopt;
}
if (ipAddress.isV6() && ipAddress.asV6().isLinkLocal()) {
return getClassIDForLinkLocal(switchState, vlanID, ipAddress.asV6());
}
if (ipAddress.isV6()) {
auto ndpEntry = vlan->getNdpTable()->getEntryIf(ipAddress.asV6());
return ndpEntry ? ndpEntry->getClassID() : std::nullopt;
} else if (ipAddress.isV4()) {
auto arpEntry = vlan->getArpTable()->getEntryIf(ipAddress.asV4());
return arpEntry ? arpEntry->getClassID() : std::nullopt;
}
return std::nullopt;
}
// Methods for dealing with vlan2SubnetsCache_
bool LookupClassRouteUpdater::belongsToSubnetInCache(
VlanID vlanID,
const folly::IPAddress& ipToSearch) {
auto it = vlan2SubnetsCache_.find(vlanID);
if (it != vlan2SubnetsCache_.end()) {
auto subnetsCache = it->second;
for (const auto& [ipAddress, mask] : subnetsCache) {
if (ipToSearch.inSubnet(ipAddress, mask)) {
return true;
}
}
}
return false;
}
void LookupClassRouteUpdater::updateSubnetsCache(
const StateDelta& stateDelta,
std::shared_ptr<Port> port,
bool reAddAllRoutesEnabled) {
auto& newState = stateDelta.newState();
bool subnetCacheUpdated = false;
for (const auto& [vlanID, vlanInfo] : port->getVlans()) {
std::ignore = vlanInfo;
auto vlan = newState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
continue;
}
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
auto interface =
newState->getInterfaces()->getInterfaceIf(vlan->getInterfaceID());
if (interface) {
for (auto address : interface->getAddresses()) {
subnetCacheUpdated = subnetsCache.insert(address).second;
}
}
}
if (subnetCacheUpdated && reAddAllRoutesEnabled) {
/*
* When a new subnet is added to the cache, the nextHops of existing
* routes may become eligible for caching in
* nextHopAndVlan2Prefixes_. Furthermore, such a nextHop may have
* classID associated with it, and in that case, the corresponding
* route could inherit that classID. Thus, re-add all the routes.
*/
reAddAllRoutes(stateDelta);
}
}
// Methods for handling port updates
void LookupClassRouteUpdater::processPortAdded(
const StateDelta& stateDelta,
const std::shared_ptr<Port>& addedPort,
bool reAddAllRoutesEnabled) {
CHECK(addedPort);
if (addedPort->getLookupClassesToDistributeTrafficOn().size() == 0) {
/*
* Only downlink ports connecting to MH-NIC have lookupClasses
* configured. For all the other ports, no-op.
*/
return;
}
updateSubnetsCache(stateDelta, addedPort, reAddAllRoutesEnabled);
}
void LookupClassRouteUpdater::processPortRemovedForVlan(
const StateDelta& stateDelta,
const std::shared_ptr<Port>& removedPort,
VlanID vlanID) {
auto& newState = stateDelta.newState();
auto vlanIter = vlan2SubnetsCache_.find(vlanID);
if (vlanIter == vlan2SubnetsCache_.end()) {
return;
}
auto vlan = newState->getVlans()->getVlanIf(vlanID);
if (!vlan || vlanHasOtherPortsWithClassIDs(newState, vlan, removedPort)) {
return;
}
auto interface =
newState->getInterfaces()->getInterfaceIf(vlan->getInterfaceID());
if (!interface) {
return;
}
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
for (auto address : interface->getAddresses()) {
removeNextHopsForSubnet(stateDelta, address, vlan);
subnetsCache.erase(address);
}
}
void LookupClassRouteUpdater::processPortRemoved(
const StateDelta& stateDelta,
const std::shared_ptr<Port>& removedPort) {
CHECK(removedPort);
if (removedPort->getLookupClassesToDistributeTrafficOn().size() == 0) {
/*
* Only downlink ports connecting to MH-NIC have lookupClasses
* configured. For all the other ports, no-op.
*/
return;
}
for (const auto& [vlanID, vlanInfo] : removedPort->getVlans()) {
std::ignore = vlanInfo;
processPortRemovedForVlan(stateDelta, removedPort, vlanID);
}
}
void LookupClassRouteUpdater::processPortChanged(
const StateDelta& stateDelta,
const std::shared_ptr<Port>& oldPort,
const std::shared_ptr<Port>& newPort) {
CHECK(oldPort && newPort);
CHECK_EQ(oldPort->getID(), newPort->getID());
if (oldPort->getLookupClassesToDistributeTrafficOn().size() == 0 &&
newPort->getLookupClassesToDistributeTrafficOn().size() != 0) {
// enable queue-per-host for this port
processPortAdded(stateDelta, newPort, true /* re-all all routes */);
} else if (
oldPort->getLookupClassesToDistributeTrafficOn().size() != 0 &&
newPort->getLookupClassesToDistributeTrafficOn().size() == 0) {
// disable queue-per-host for this port
processPortRemoved(stateDelta, oldPort);
} else if (
oldPort->getLookupClassesToDistributeTrafficOn().size() != 0 &&
newPort->getLookupClassesToDistributeTrafficOn().size() != 0) {
// queue-per-host remains enabled, but port's VLAN membership changed, readd
if (oldPort->getVlans() != newPort->getVlans()) {
processPortRemoved(stateDelta, oldPort);
processPortAdded(stateDelta, newPort, true /* re-all all routes */);
}
}
}
void LookupClassRouteUpdater::processPortUpdates(const StateDelta& stateDelta) {
for (const auto& delta : stateDelta.getPortsDelta()) {
auto oldPort = delta.getOld();
auto newPort = delta.getNew();
if (!oldPort && newPort) {
// processRouteUpdates is invoked after processPortAdd,
// thus, we don't need to re-add all the routes.
processPortAdded(
stateDelta, newPort, false /* don't re-add all routes */);
} else if (oldPort && !newPort) {
processPortRemoved(stateDelta, oldPort);
} else {
processPortChanged(stateDelta, oldPort, newPort);
}
}
}
// Methods for handling interface updates
void LookupClassRouteUpdater::processInterfaceAdded(
const StateDelta& stateDelta,
const std::shared_ptr<Interface>& addedInterface) {
CHECK(addedInterface);
auto switchState = stateDelta.newState();
auto vlanID = addedInterface->getVlanID();
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return;
}
for (auto& [portID, portInfo] : vlan->getPorts()) {
std::ignore = portInfo;
auto port = switchState->getPorts()->getPortIf(portID);
// routes are re-added once outside the for loop
processPortAdded(stateDelta, port, false /* don't re-add all routes */);
}
// TODO(zecheng): Remove this logic once block neighbor support is removed.
/*
* processBlockNeighborUpdates would not cache subnet corresponding to newly
* added blocked neighbor if there is no interface for that subnet.
* Thus, when an interface is added, process blocked neighbor list again.
*/
for (const auto& [blockedVlanID, blockedNeighborIP] :
switchState->getSwitchSettings()->getBlockNeighbors()) {
if (blockedVlanID != vlanID) {
continue;
}
for (const auto& address : addedInterface->getAddresses()) {
if (blockedNeighborIP.inSubnet(address.first, address.second)) {
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
subnetsCache.insert(address);
}
}
}
/*
* processMacAddrsToBlockUpdates would not cache subnet corresponding to newly
* added blocked mac address if there is no interface for that subnet.
* Thus, when an interface is added, process blocked mac address list again.
*/
for (const auto& [blockedVlanID, blockedNeighborMac] :
switchState->getSwitchSettings()->getMacAddrsToBlock()) {
if (blockedVlanID != vlanID) {
continue;
}
std::vector<folly::IPAddress> neighborIPAddr;
for (const auto& neighborEntry :
*VlanTableDeltaCallbackGenerator::getTable<folly::IPAddressV4>(vlan)) {
if (neighborEntry->getMac() == blockedNeighborMac) {
neighborIPAddr.push_back(neighborEntry->getIP());
}
}
for (const auto& neighborEntry :
*VlanTableDeltaCallbackGenerator::getTable<folly::IPAddressV6>(vlan)) {
if (neighborEntry->getMac() == blockedNeighborMac &&
!isNoHostRoute(neighborEntry)) {
neighborIPAddr.push_back(neighborEntry->getIP());
}
}
for (const auto& address : addedInterface->getAddresses()) {
for (auto& neighborIP : neighborIPAddr) {
if (neighborIP.inSubnet(address.first, address.second)) {
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
subnetsCache.insert(address);
break;
}
}
}
}
reAddAllRoutes(stateDelta);
}
void LookupClassRouteUpdater::processInterfaceRemoved(
const StateDelta& stateDelta,
const std::shared_ptr<Interface>& removedInterface) {
CHECK(removedInterface);
auto switchState = stateDelta.newState();
auto vlanID = removedInterface->getVlanID();
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return;
}
for (auto& [portID, portInfo] : vlan->getPorts()) {
std::ignore = portInfo;
auto port = switchState->getPorts()->getPortIf(portID);
/*
* Subnets for an interface could be cached in following cases:
* - port has non-empty lookup class list,
* - blocked neighbor IP
*
* However, when an interface is removed, subnets cached due to the
* interface are removed regardless.
*
* When an interface is added, processInterfaceAdded processes the
* switchState for ports with non-empty lookup class list as well as
* blocked neighbor IP to reopulate the subnet cache.
*/
processPortRemovedForVlan(stateDelta, port, vlanID);
}
}
void LookupClassRouteUpdater::processInterfaceChanged(
const StateDelta& stateDelta,
const std::shared_ptr<Interface>& oldInterface,
const std::shared_ptr<Interface>& newInterface) {
CHECK(oldInterface && newInterface);
CHECK_EQ(oldInterface->getID(), newInterface->getID());
processInterfaceRemoved(stateDelta, oldInterface);
processInterfaceAdded(stateDelta, newInterface);
}
void LookupClassRouteUpdater::processInterfaceUpdates(
const StateDelta& stateDelta) {
for (const auto& delta : stateDelta.getIntfsDelta()) {
auto oldInterface = delta.getOld();
auto newInterface = delta.getNew();
if (!oldInterface && newInterface) {
processInterfaceAdded(stateDelta, newInterface);
} else if (oldInterface && !newInterface) {
processInterfaceRemoved(stateDelta, oldInterface);
} else {
processInterfaceChanged(stateDelta, oldInterface, newInterface);
}
}
}
// Methods for handling neighbor updates
bool LookupClassRouteUpdater::isNeighborReachable(
const std::shared_ptr<SwitchState>& switchState,
VlanID vlanID,
const folly::IPAddressV6& neighborIP) {
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return false;
}
auto linkLocalEntry =
VlanTableDeltaCallbackGenerator::getTable<folly::IPAddressV6>(vlan)
->getEntryIf(neighborIP);
return linkLocalEntry && linkLocalEntry->isReachable();
}
/*
* Link local neighbors don't have classID, thus classID for a route whose
* nexthop is link local is derived from MAC address corresponding to the
* link local address.
*
* There are two cases here:
*
* 1. LinkLocal is resolved, then MAC gets classID.
* processNeighborAdded<folly::MacAddress> assigns classID to Route(s)
*
* 2. MAC gets classID, then LinkLocal is resolved.
* processNeighborAdded<folly::IPAddressV6> assigns classID to Route(s)
*/
template <typename AddedNeighborT>
void LookupClassRouteUpdater::processNeighborAdded(
const StateDelta& stateDelta,
VlanID vlanID,
const std::shared_ptr<AddedNeighborT>& addedNeighbor) {
CHECK(addedNeighbor);
auto newState = stateDelta.newState();
folly::IPAddress addedNeighborIP;
std::optional<cfg::AclLookupClass> neighborClassID;
if constexpr (std::is_same_v<AddedNeighborT, MacEntry>) {
addedNeighborIP = folly::IPAddressV6(
folly::IPAddressV6::LinkLocalTag::LINK_LOCAL, addedNeighbor->getMac());
if (!isNeighborReachable(newState, vlanID, addedNeighborIP.asV6())) {
return;
}
// LinkLocal is resolved, then MAC gets classID
neighborClassID = addedNeighbor->getClassID();
} else {
addedNeighborIP = addedNeighbor->getIP();
if (addedNeighborIP.isV6() && addedNeighborIP.isLinkLocal()) {
// MAC gets classID, then LinkLocal is resolved
neighborClassID = getClassIDForLinkLocal(
stateDelta.newState(), vlanID, addedNeighborIP.asV6());
} else {
neighborClassID = addedNeighbor->getClassID();
}
}
if (!belongsToSubnetInCache(vlanID, addedNeighborIP)) {
return;
}
// If the neighbor is nextHop for a route that is already processed, the
// neighbor would be present in the cache.
auto& [withClassIDPrefixes, withoutClassIDPrefixes] =
nextHopAndVlan2Prefixes_[std::make_pair(addedNeighborIP, vlanID)];
if (!neighborClassID.has_value()) {
return;
}
/*
* Check if the added neighbor would make a route become QPH multiple next
* hop, since neighbor is already associated with a class ID.
*/
addPrefixesWithMultiNextHops(
withoutClassIDPrefixes, addedNeighborIP, newState, vlanID);
/*
* For every route with *this* nexthop, if the route does not have a
* classID associated with it, then assign *this* nexthop's classID.
*/
std::vector<RidAndCidr> toBeUpdatedPrefixes;
std::set_difference(
withoutClassIDPrefixes.begin(),
withoutClassIDPrefixes.end(),
allPrefixesWithClassID_.begin(),
allPrefixesWithClassID_.end(),
std::inserter(toBeUpdatedPrefixes, toBeUpdatedPrefixes.end()));
auto routeClassID = neighborClassID.value();
for (const auto& ridAndCidr : toBeUpdatedPrefixes) {
withoutClassIDPrefixes.erase(ridAndCidr);
withClassIDPrefixes.insert(ridAndCidr);
allPrefixesWithClassID_.insert(ridAndCidr);
toUpdateRoutesAndClassIDs_.emplace_back(
std::make_pair(ridAndCidr, routeClassID));
}
}
template <typename RemovedNeighborT>
void LookupClassRouteUpdater::processNeighborRemoved(
const StateDelta& stateDelta,
VlanID vlanID,
const std::shared_ptr<RemovedNeighborT>& removedNeighbor) {
CHECK(removedNeighbor);
folly::IPAddress removedNeighborIP;
if constexpr (std::is_same_v<RemovedNeighborT, MacEntry>) {
/*
* Link local neighbors don't have classID, thus classID for a route whose
* nexthop is link local is derived from MAC address corresponding to the
* link local address.
* Thus, when a MAC is removed, process classID change for route whose
* nexthop is link local corresponding to the MAC being removed.
*/
removedNeighborIP = folly::IPAddressV6(
folly::IPAddressV6::LinkLocalTag::LINK_LOCAL,
removedNeighbor->getMac());
} else {
removedNeighborIP = removedNeighbor->getIP();
}
if (!belongsToSubnetInCache(vlanID, removedNeighborIP)) {
return;
}
auto it =
nextHopAndVlan2Prefixes_.find(std::make_pair(removedNeighborIP, vlanID));
if (it == nextHopAndVlan2Prefixes_.end()) {
return;
}
auto& [withClassIDPrefixes, withoutClassIDPrefixes] = it->second;
if (withClassIDPrefixes.empty() && withoutClassIDPrefixes.empty()) {
// neighbor being removed is not a nexthop for any route
nextHopAndVlan2Prefixes_.erase(it);
return;
}
auto& newState = stateDelta.newState();
// Updating prefixesWithMultiNextHops_ to see if there's any route with
// multiple next hop becomes single next hop. Routes with classId will be
// handled in addRouteAndFindClassID.
removePrefixesWithMultiNextHops(withoutClassIDPrefixes, removedNeighborIP);
auto iter = withClassIDPrefixes.begin();
while (iter != withClassIDPrefixes.end()) {
auto ridAndCidr = *iter;
// erase the current iterator, and advance
iter = withClassIDPrefixes.erase(iter);
withoutClassIDPrefixes.insert(ridAndCidr);
allPrefixesWithClassID_.erase(ridAndCidr);
auto& [rid, cidr] = ridAndCidr;
std::optional<cfg::AclLookupClass> routeClassID{std::nullopt};
/*
* Reuse addRouteAndFindClassID to find another nexthop (if any) that has
* classID. Note that stateDelta.newState() may still contain the neighbor
* with classID - if LookupClassUpdater hasn't processed it yet. So the
* neighbor being removed is omitted explicitly from the computatation by
* passing to addRouteAndFindClassID.
*/
if (cidr.first.isV6()) {
auto route = findRoute<folly::IPAddressV6>(rid, cidr, newState);
if (route) {
routeClassID = addRouteAndFindClassID(
stateDelta, rid, route, std::make_pair(removedNeighborIP, vlanID));
}
} else {
auto route = findRoute<folly::IPAddressV4>(rid, cidr, newState);
if (route) {
routeClassID = addRouteAndFindClassID(
stateDelta, rid, route, std::make_pair(removedNeighborIP, vlanID));
}
}
toUpdateRoutesAndClassIDs_.push_back(
std::make_pair(ridAndCidr, routeClassID));
}
}
template <typename ChangedNeighborT>
void LookupClassRouteUpdater::processNeighborChanged(
const StateDelta& stateDelta,
VlanID vlanID,
const std::shared_ptr<ChangedNeighborT>& oldNeighbor,
const std::shared_ptr<ChangedNeighborT>& newNeighbor) {
CHECK(oldNeighbor && newNeighbor);
if (!oldNeighbor->getClassID().has_value() &&
!newNeighbor->getClassID().has_value()) {
return;
} else if (
!oldNeighbor->getClassID().has_value() &&
newNeighbor->getClassID().has_value()) {
processNeighborAdded(stateDelta, vlanID, newNeighbor);
} else if (
oldNeighbor->getClassID().has_value() &&
!newNeighbor->getClassID().has_value()) {
processNeighborRemoved(stateDelta, vlanID, oldNeighbor);
} else if (
oldNeighbor->getClassID().has_value() &&
newNeighbor->getClassID().has_value()) {
if (oldNeighbor->getClassID().value() !=
newNeighbor->getClassID().value()) {
processNeighborRemoved(stateDelta, vlanID, oldNeighbor);
processNeighborAdded(stateDelta, vlanID, newNeighbor);
}
}
}
template <typename AddrT>
void LookupClassRouteUpdater::processNeighborUpdates(
const StateDelta& stateDelta) {
for (const auto& vlanDelta : stateDelta.getVlansDelta()) {
auto newVlan = vlanDelta.getNew();
if (!newVlan) {
auto oldVlan = vlanDelta.getOld();
for (const auto& entry :
*VlanTableDeltaCallbackGenerator::getTable<AddrT>(oldVlan)) {
processNeighborRemoved(stateDelta, oldVlan->getID(), entry);
}
continue;
}
auto vlan = newVlan->getID();
for (const auto& delta :
VlanTableDeltaCallbackGenerator::getTableDelta<AddrT>(vlanDelta)) {
auto oldNeighbor = delta.getOld();
auto newNeighbor = delta.getNew();
/*
* At this point in time, queue-per-host fix is needed (and thus
* supported) for physical link only.
*/
if ((oldNeighbor && !oldNeighbor->getPort().isPhysicalPort()) ||
(newNeighbor && !newNeighbor->getPort().isPhysicalPort())) {
continue;
}
if (!oldNeighbor) {
processNeighborAdded(stateDelta, vlan, newNeighbor);
} else if (!newNeighbor) {
processNeighborRemoved(stateDelta, vlan, oldNeighbor);
} else {
processNeighborChanged(stateDelta, vlan, oldNeighbor, newNeighbor);
}
}
}
}
template <typename RouteT>
bool LookupClassRouteUpdater::addRouteToMultiNextHopMap(
const std::shared_ptr<SwitchState>& newState,
const std::shared_ptr<RouteT>& route,
std::optional<std::pair<folly::IPAddress, VlanID>> addedNeighborIPandVlan,
const RidAndCidr& ridAndCidr) {
for (const auto& nextHop : route->getForwardInfo().getNextHopSet()) {
auto vlanID =
newState->getInterfaces()->getInterfaceIf(nextHop.intf())->getVlanID();
if (!belongsToSubnetInCache(vlanID, nextHop.addr())) {
continue;
}
const auto& [addedNeighborIP, addedNeighborVlan] =
addedNeighborIPandVlan.value();
if (nextHop.addr() == addedNeighborIP && vlanID == addedNeighborVlan) {
continue;
}
auto neighborClassID =
getClassIDForNeighbor(newState, vlanID, nextHop.addr());
if (neighborClassID.has_value() && neighborClassID == route->getClassID()) {
// Insert this route to the multi-nexthop map.
prefixesWithMultiNextHops_[ridAndCidr] = {
addedNeighborIP, nextHop.addr()};
return true;
}
}
return false;
}
// Methods for handling route updates
template <typename RouteT>
std::optional<cfg::AclLookupClass>
LookupClassRouteUpdater::addRouteAndFindClassID(
const StateDelta& stateDelta,
RouterID rid,
const std::shared_ptr<RouteT>& addedRoute,
std::optional<std::pair<folly::IPAddress, VlanID>> nextHopAndVlanToOmit) {
auto ridAndCidr = std::make_pair(
rid,
folly::CIDRNetwork{
addedRoute->prefix().network, addedRoute->prefix().mask});
auto& newState = stateDelta.newState();
std::optional<cfg::AclLookupClass> routeClassID{std::nullopt};
std::set<folly::IPAddress> neighborsWithClassId;
for (const auto& nextHop : addedRoute->getForwardInfo().getNextHopSet()) {
auto vlanID =
newState->getInterfaces()->getInterfaceIf(nextHop.intf())->getVlanID();
if (!belongsToSubnetInCache(vlanID, nextHop.addr())) {
continue;
}
if (nextHopAndVlanToOmit.has_value()) {
const auto& [nextHopToOmit, vlanIDToOmit] = nextHopAndVlanToOmit.value();
if (nextHop.addr() == nextHopToOmit && vlanID == vlanIDToOmit) {
continue;
}
}
auto neighborClassID =
getClassIDForNeighbor(newState, vlanID, nextHop.addr());
if (neighborClassID.has_value()) {
neighborsWithClassId.insert(nextHop.addr());
}
/*
* The nextHopAndVlan may already be cached if:
* - it is also nextHop for some other route that was previously added.
* - during processNeighborAdded
* retrieve previously cached entry, and if absent, create new entry.
*/
auto& [withClassIDPrefixes, withoutClassIDPrefixes] =
nextHopAndVlan2Prefixes_[std::make_pair(nextHop.addr(), vlanID)];
/*
* In the current implementation, route inherits classID of the 'first'
* nexthop that has classID. This could be revised in future if necessary.
*/
if (!routeClassID.has_value() && neighborClassID.has_value()) {
routeClassID = neighborClassID;
withClassIDPrefixes.insert(ridAndCidr);
withoutClassIDPrefixes.erase(ridAndCidr);
} else {
/*
* Refer to detailed comment in agent/LookupClassRouteUpdater.h
* Route inherits classID of one of its reachable next hops...
*/
if (neighborClassID.has_value()) {
XLOG(DBG2) << "Queue-per-host can break for Route " << addedRoute->str()
<< " when traffic chooses vlan: " << vlanID
<< " nexthop: " << nextHop.addr();
}
withoutClassIDPrefixes.insert(ridAndCidr);
withClassIDPrefixes.erase(ridAndCidr);
}
}
if (routeClassID.has_value()) {
allPrefixesWithClassID_.insert(ridAndCidr);
} else {
allPrefixesWithClassID_.erase(ridAndCidr);
}
/*
* Update prefixesWithMultiNextHops_ to keep track of routes with multiple
* nexthop.
*/
updatePrefixesWithMultiNextHops(neighborsWithClassId, ridAndCidr);
return routeClassID;
}
void LookupClassRouteUpdater::updatePrefixesWithMultiNextHops(
const std::set<folly::IPAddress>& neighborsWithClassId,
const RidAndCidr& ridAndCidr) {
if (neighborsWithClassId.size() > 1) {
// The route has multiple nexthops pointing to different class IDs - add
// into the map if not already.
auto ret = prefixesWithMultiNextHops_.insert(
std::pair<RidAndCidr, std::set<folly::IPAddress>>(
ridAndCidr, neighborsWithClassId));
if (ret.second) {
fb303::fbData->setCounter(
SwitchStats::kCounterPrefix + kQphMultiNextHopCounter,
prefixesWithMultiNextHops_.size());
XLOG(DBG2) << "Number of routes with QPH multiple next hops: "
<< prefixesWithMultiNextHops_.size();
}
} else {
// Remove route from the set if the route no longer has multiple nexthops.
auto ret = prefixesWithMultiNextHops_.erase(ridAndCidr);
if (ret) {
fb303::fbData->setCounter(
SwitchStats::kCounterPrefix + kQphMultiNextHopCounter,
prefixesWithMultiNextHops_.size());
XLOG(DBG2) << "Number of routes with QPH multiple next hops: "
<< prefixesWithMultiNextHops_.size();
}
}
}
void LookupClassRouteUpdater::removePrefixesWithMultiNextHops(
const std::set<RidAndCidr>& withoutClassIDPrefixes,
const folly::IPAddress& removedNeighborIP) {
for (const auto& ridAndCidr : withoutClassIDPrefixes) {
if (prefixesWithMultiNextHops_.find(ridAndCidr) !=
prefixesWithMultiNextHops_.end()) {
auto ret =
prefixesWithMultiNextHops_[ridAndCidr].erase(removedNeighborIP);
if (ret && prefixesWithMultiNextHops_[ridAndCidr].size() <= 1) {
prefixesWithMultiNextHops_.erase(ridAndCidr);
fb303::fbData->setCounter(
SwitchStats::kCounterPrefix + kQphMultiNextHopCounter,
prefixesWithMultiNextHops_.size());
XLOG(DBG2) << "Number of routes with QPH multiple next hops: "
<< prefixesWithMultiNextHops_.size();
}
}
}
}
void LookupClassRouteUpdater::addPrefixesWithMultiNextHops(
const std::set<RidAndCidr>& withoutClassIDPrefixes,
const folly::IPAddress& addedNeighborIP,
const std::shared_ptr<SwitchState>& newState,
VlanID vlanID) {
bool prefixesWithMultiNextHopsAdded = false;
for (const auto& ridAndCidr : withoutClassIDPrefixes) {
// Route already has multiple next hops - add neighbor to the set.
if (prefixesWithMultiNextHops_.find(ridAndCidr) !=
prefixesWithMultiNextHops_.end()) {
prefixesWithMultiNextHops_[ridAndCidr].insert(addedNeighborIP);
} else {
// If route has classId, with the addition of neighbor class ID, it will
// have multiple next hops with classID.
auto& [rid, cidr] = ridAndCidr;
std::set<folly::IPAddress> nextHopsWithClassId{addedNeighborIP};
if (cidr.first.isV6()) {
auto route = findRoute<folly::IPAddressV6>(rid, cidr, newState);
if (route->getClassID().has_value()) {
prefixesWithMultiNextHopsAdded |= addRouteToMultiNextHopMap(
newState,
route,
std::make_pair(addedNeighborIP, vlanID),
ridAndCidr);
}
} else {
auto route = findRoute<folly::IPAddressV4>(rid, cidr, newState);
if (route->getClassID().has_value()) {
prefixesWithMultiNextHopsAdded |= addRouteToMultiNextHopMap(
newState,
route,
std::make_pair(addedNeighborIP, vlanID),
ridAndCidr);
}
}
}
}
if (prefixesWithMultiNextHopsAdded) {
fb303::fbData->setCounter(
SwitchStats::kCounterPrefix + kQphMultiNextHopCounter,
prefixesWithMultiNextHops_.size());
XLOG(DBG2) << "Number of routes with QPH multiple next hops: "
<< prefixesWithMultiNextHops_.size();
}
}
template <typename RouteT>
void LookupClassRouteUpdater::processRouteAdded(
const StateDelta& stateDelta,
RouterID rid,
const std::shared_ptr<RouteT>& addedRoute) {
CHECK(addedRoute);
/*
* Non-resolved routes are not programmed in HW
* routes to CPU have no nextHops, and can't get classID.
*/
if (!addedRoute->isResolved() || addedRoute->isToCPU()) {
return;
}
auto ridAndCidr = std::make_pair(
rid,
folly::CIDRNetwork{
addedRoute->prefix().network, addedRoute->prefix().mask});
auto routeClassID =
addRouteAndFindClassID(stateDelta, rid, addedRoute, std::nullopt);
if (routeClassID.has_value()) {
toUpdateRoutesAndClassIDs_.push_back(
std::make_pair(ridAndCidr, routeClassID));
}
}
template <typename RouteT>
void LookupClassRouteUpdater::processRouteRemoved(
const StateDelta& stateDelta,
RouterID rid,
const std::shared_ptr<RouteT>& removedRoute) {
CHECK(removedRoute);
/*
* Non-resolved routes are not programmed in HW
* routes to CPU (have no nextHops), and can't get classID.
*/
if (!removedRoute->isResolved() || removedRoute->isToCPU()) {
return;
}
// ClassID is associated with (and refCnt'ed for) MAC and ARP/NDP neighbor.
// Route simply inherits classID of its nexthop, so we need not release
// classID here. Furthermore, the route is already removed, so we don't need
// to schedule a state update either. Just remove the route from local data
// structures.
auto ridAndCidr = std::make_pair(
rid,
folly::CIDRNetwork{
removedRoute->prefix().network, removedRoute->prefix().mask});
auto routeClassID = removedRoute->getClassID();
auto& newState = stateDelta.newState();
for (const auto& nextHop : removedRoute->getForwardInfo().getNextHopSet()) {
auto vlanID =
newState->getInterfaces()->getInterfaceIf(nextHop.intf())->getVlanID();
if (!belongsToSubnetInCache(vlanID, nextHop.addr())) {
continue;
}
auto it =
nextHopAndVlan2Prefixes_.find(std::make_pair(nextHop.addr(), vlanID));
CHECK(it != nextHopAndVlan2Prefixes_.end());
auto& [withClassIDPrefixes, withoutClassIDPrefixes] = it->second;
// The prefix has to be in either of the sets.
auto numErased = withClassIDPrefixes.erase(ridAndCidr) +
withoutClassIDPrefixes.erase(ridAndCidr);
CHECK_EQ(numErased, 1);
if (withClassIDPrefixes.empty() && withoutClassIDPrefixes.empty()) {
// if this was the only route this entry was NextHop for, and there is no
// neighbor corresponding to this NextHop, erase it.
auto vlan = newState->getVlans()->getVlanIf(vlanID);
if (vlan) {
if (nextHop.addr().isV6()) {
auto ndpEntry =
vlan->getNdpTable()->getEntryIf(nextHop.addr().asV6());
if (!ndpEntry) {
nextHopAndVlan2Prefixes_.erase(it);
}
} else if (nextHop.addr().isV4()) {
auto arpEntry =
vlan->getArpTable()->getEntryIf(nextHop.addr().asV4());
if (!arpEntry) {
nextHopAndVlan2Prefixes_.erase(it);
}
}
}
}
}
if (routeClassID.has_value()) {
auto numErasedFromAllPrefixes = allPrefixesWithClassID_.erase(ridAndCidr);
// LE because this maybe and an update due to neighbor going away. In which
// case we would clear inherited class ID on route due to neighbor and prune
// from allPrefixesWithClassID_ during neighbor removal. Then we would get
// another update due to change route (some classId->none). Which should be
// a noop.
CHECK_LE(numErasedFromAllPrefixes, 1);
}
}
template <typename RouteT>
void LookupClassRouteUpdater::processRouteChanged(
const StateDelta& stateDelta,
RouterID rid,
const std::shared_ptr<RouteT>& oldRoute,
const std::shared_ptr<RouteT>& newRoute) {
CHECK(oldRoute);
CHECK(newRoute);
if (!oldRoute->isResolved() && !newRoute->isResolved()) {
return;
} else if (!oldRoute->isResolved() && newRoute->isResolved()) {
processRouteAdded(stateDelta, rid, newRoute);
} else if (oldRoute->isResolved() && !newRoute->isResolved()) {
processRouteRemoved(stateDelta, rid, oldRoute);
} else if (oldRoute->isResolved() && newRoute->isResolved()) {
/*
* If the list of nexthops changes, a route may lose the nexthop it
* inherited classID from. In that case, we need to find another reachable
* nexthop for the route.
*
* This could be implemented by std::set_difference of getNextHopSet().
* However, it is easier to remove the route and add it again.
* processRouteRemoved does not schedule state update, so the only
* additional overhead of this approach is some local computation.
*/
if ((oldRoute->getForwardInfo().getNextHopSet() !=
newRoute->getForwardInfo().getNextHopSet()) ||
(oldRoute->getClassID() != newRoute->getClassID())) {
processRouteRemoved(stateDelta, rid, oldRoute);
processRouteAdded(stateDelta, rid, newRoute);
}
}
}
template <typename AddrT>
void LookupClassRouteUpdater::processRouteUpdates(
const StateDelta& stateDelta) {
auto changedFn =
[&stateDelta, this](
RouterID rid, const auto& oldRoute, const auto& newRoute) {
processRouteChanged(stateDelta, rid, oldRoute, newRoute);
};
auto addedFn = [&stateDelta, this](RouterID rid, const auto& newRoute) {
processRouteAdded(stateDelta, rid, newRoute);
};
auto removedFn = [&stateDelta, this](RouterID rid, const auto& oldRoute) {
processRouteRemoved(stateDelta, rid, oldRoute);
};
forEachChangedRoute<AddrT>(stateDelta, changedFn, addedFn, removedFn);
}
// Methods for scheduling state updates
void LookupClassRouteUpdater::updateClassIDsForRoutes(
const std::vector<RouteAndClassID>& routesAndClassIDs) const {
std::unordered_map<
std::pair<RouterID, std::optional<cfg::AclLookupClass>>,
std::vector<folly::CIDRNetwork>>
ridClassId2Prefixes;
for (const auto& [ridAndCidr, classID] : routesAndClassIDs) {
auto& [rid, cidr] = ridAndCidr;
ridClassId2Prefixes[std::make_pair(rid, classID)].emplace_back(cidr);
}
auto updater = sw_->getRouteUpdater();
for (const auto& [ridAndClassId, prefixes] : ridClassId2Prefixes) {
updater.programClassID(
ridAndClassId.first, prefixes, ridAndClassId.second, true /*async*/);
}
}
// Methods for blocked neighbor processing
std::optional<folly::CIDRNetwork>
LookupClassRouteUpdater::getInterfaceSubnetForIPIf(
const std::shared_ptr<SwitchState>& switchState,
VlanID vlanID,
const folly::IPAddress& ipAddress) const {
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return std::nullopt;
}
auto interface =
switchState->getInterfaces()->getInterfaceIf(vlan->getInterfaceID());
if (interface) {
for (const auto& address : interface->getAddresses()) {
if (ipAddress.inSubnet(address.first, address.second)) {
return std::make_pair(address.first, address.second);
}
}
}
return std::nullopt;
}
bool LookupClassRouteUpdater::isSubnetCachedByBlockedNeighborIP(
const std::shared_ptr<SwitchState>& switchState,
VlanID vlanID,
const folly::CIDRNetwork& addressToSearch) const {
for (const auto& [blockedVlanID, blockedNeighborIP] :
switchState->getSwitchSettings()->getBlockNeighbors()) {
if (blockedVlanID != vlanID) {
continue;
}
auto address =
getInterfaceSubnetForIPIf(switchState, vlanID, blockedNeighborIP);
if (address.has_value() && address.value() == addressToSearch) {
return true;
}
}
return false;
}
bool LookupClassRouteUpdater::isSubnetCachedByLookupClasses(
const std::shared_ptr<SwitchState>& switchState,
VlanID vlanID,
const folly::CIDRNetwork& addressToSearch) const {
auto vlan = switchState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
return false;
}
auto interface =
switchState->getInterfaces()->getInterfaceIf(vlan->getInterfaceID());
if (!interface) {
return false;
}
bool searchInterfaceAddresses = false;
for (const auto& port : *switchState->getPorts()) {
if (port->getLookupClassesToDistributeTrafficOn().size() == 0) {
continue;
}
// port is member of vlan for addressToSearch i.e. blocked IP
auto it = port->getVlans().find(vlanID);
if (it == port->getVlans().end()) {
continue;
}
/*
* There is a port with non-empty lookupClasses && that port is member of
* vlan for addressToearch i.e. blocked IP.
*/
searchInterfaceAddresses = true;
break;
}
if (searchInterfaceAddresses) {
for (const auto& address : interface->getAddresses()) {
if (address == addressToSearch) {
return true;
}
}
}
return false;
}
void LookupClassRouteUpdater::processBlockNeighborAdded(
const StateDelta& stateDelta,
std::vector<std::pair<VlanID, folly::IPAddress>> toBeAddedBlockNeighbors) {
auto newState = stateDelta.newState();
bool subnetCacheUpdated = false;
for (const auto& [vlanID, blockedNeighborIP] : toBeAddedBlockNeighbors) {
auto address =
getInterfaceSubnetForIPIf(newState, vlanID, blockedNeighborIP);
if (address.has_value()) {
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
subnetCacheUpdated |= subnetsCache.insert(address.value()).second;
}
}
if (subnetCacheUpdated) {
/*
* When a new subnet is added to the cache, the nextHops of existing
* routes may become eligible for caching in
* nextHopAndVlan2Prefixes_. Furthermore, such a nextHop may have
* classID associated with it, and in that case, the corresponding
* route could inherit that classID. Thus, re-add all the routes.
*/
reAddAllRoutes(stateDelta);
}
}
void LookupClassRouteUpdater::processBlockNeighborRemoved(
const StateDelta& stateDelta,
std::vector<std::pair<VlanID, folly::IPAddress>>
toBeRemovedBlockNeighbors) {
auto newState = stateDelta.newState();
for (const auto& [vlanID, blockedNeighborIP] : toBeRemovedBlockNeighbors) {
auto address =
getInterfaceSubnetForIPIf(newState, vlanID, blockedNeighborIP);
/*
* Remove subnet corresponding to blockedNeighborIP being removed if and
* only if it would not be cached by neither lookup class caching nor
* by any other blocked neighbor ip caching.
*/
if (address.has_value() &&
!isSubnetCachedByLookupClasses(newState, vlanID, address.value()) &&
!isSubnetCachedByBlockedNeighborIP(newState, vlanID, address.value())) {
auto vlan = newState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
continue;
}
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
removeNextHopsForSubnet(stateDelta, address.value(), vlan);
subnetsCache.erase(address.value());
}
}
}
void LookupClassRouteUpdater::processBlockNeighborUpdates(
const StateDelta& stateDelta) {
auto oldState = stateDelta.oldState();
auto newState = stateDelta.newState();
auto oldBlockedNeighbors{oldState->getSwitchSettings()->getBlockNeighbors()};
auto newBlockedNeighbors{newState->getSwitchSettings()->getBlockNeighbors()};
sort(oldBlockedNeighbors.begin(), oldBlockedNeighbors.end());
sort(newBlockedNeighbors.begin(), newBlockedNeighbors.end());
if (oldBlockedNeighbors == newBlockedNeighbors) {
return;
}
std::vector<std::pair<VlanID, folly::IPAddress>> toBeRemovedBlockNeighbors;
std::set_difference(
oldBlockedNeighbors.begin(),
oldBlockedNeighbors.end(),
newBlockedNeighbors.begin(),
newBlockedNeighbors.end(),
std::inserter(
toBeRemovedBlockNeighbors, toBeRemovedBlockNeighbors.end()));
processBlockNeighborRemoved(stateDelta, toBeRemovedBlockNeighbors);
std::vector<std::pair<VlanID, folly::IPAddress>> toBeAddedBlockNeighbors;
std::set_difference(
newBlockedNeighbors.begin(),
newBlockedNeighbors.end(),
oldBlockedNeighbors.begin(),
oldBlockedNeighbors.end(),
std::inserter(toBeAddedBlockNeighbors, toBeAddedBlockNeighbors.end()));
processBlockNeighborAdded(stateDelta, toBeAddedBlockNeighbors);
}
template <typename AddrT>
bool LookupClassRouteUpdater::addBlockedNeighborIPtoSubnetCache(
VlanID vlanID,
const folly::MacAddress& blockedNeighborMac,
const std::shared_ptr<SwitchState>& newState) {
bool subnetCacheUpdated = false;
auto vlan = newState->getVlans()->getVlanIf(vlanID);
for (const auto& neighborEntry :
*VlanTableDeltaCallbackGenerator::getTable<AddrT>(vlan)) {
if (neighborEntry->getMac() != blockedNeighborMac ||
isNoHostRoute(neighborEntry)) {
continue;
}
auto neighborIPToBlock = neighborEntry->getIP();
auto address =
getInterfaceSubnetForIPIf(newState, vlanID, neighborIPToBlock);
if (address.has_value()) {
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
subnetCacheUpdated |= subnetsCache.insert(address.value()).second;
}
}
return subnetCacheUpdated;
}
template <typename AddrT>
void LookupClassRouteUpdater::removeBlockedNeighborIPfromSubnetCache(
VlanID vlanID,
const folly::MacAddress& blockedNeighborMac,
const StateDelta& stateDelta) {
auto newState = stateDelta.newState();
auto vlan = newState->getVlans()->getVlanIf(vlanID);
for (const auto& neighborEntry :
*VlanTableDeltaCallbackGenerator::getTable<AddrT>(vlan)) {
if (neighborEntry->getMac() != blockedNeighborMac ||
isNoHostRoute(neighborEntry)) {
continue;
}
auto neighborIPToBlock = neighborEntry->getIP();
auto address =
getInterfaceSubnetForIPIf(newState, vlanID, neighborIPToBlock);
/*
* Remove subnet corresponding to IPs resovled to the unblocked MAC
* if and only if it would not be cached by other lookup class.
*/
if (address.has_value() &&
!isSubnetCachedByLookupClasses(newState, vlanID, address.value())) {
auto& subnetsCache = vlan2SubnetsCache_[vlanID];
removeNextHopsForSubnet(stateDelta, address.value(), vlan);
subnetsCache.erase(address.value());
}
}
}
void LookupClassRouteUpdater::processMacAddrsToBlockAdded(
const StateDelta& stateDelta,
const std::vector<std::pair<VlanID, folly::MacAddress>>&
toBeAddedMacAddrsToBlock) {
auto newState = stateDelta.newState();
bool subnetCacheUpdated = false;
for (const auto& [vlanID, blockedNeighborMac] : toBeAddedMacAddrsToBlock) {
auto vlan = newState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
continue;
}
subnetCacheUpdated |=
(addBlockedNeighborIPtoSubnetCache<folly::IPAddressV4>(
vlanID, blockedNeighborMac, newState) |
addBlockedNeighborIPtoSubnetCache<folly::IPAddressV6>(
vlanID, blockedNeighborMac, newState));
}
if (subnetCacheUpdated) {
/*
* When a new subnet is added to the cache, the nextHops of existing
* routes may become eligible for caching in
* nextHopAndVlan2Prefixes_. Furthermore, such a nextHop may have
* classID associated with it, and in that case, the corresponding
* route could inherit that classID. Thus, re-add all the routes.
*/
reAddAllRoutes(stateDelta);
}
}
void LookupClassRouteUpdater::processMacAddrsToBlockRemoved(
const StateDelta& stateDelta,
const std::vector<std::pair<VlanID, folly::MacAddress>>&
toBeRemovedMacAddrsToBlock) {
auto newState = stateDelta.newState();
for (const auto& [vlanID, blockedNeighborMac] : toBeRemovedMacAddrsToBlock) {
auto vlan = newState->getVlans()->getVlanIf(vlanID);
if (!vlan) {
continue;
}
removeBlockedNeighborIPfromSubnetCache<folly::IPAddressV4>(
vlanID, blockedNeighborMac, stateDelta);
removeBlockedNeighborIPfromSubnetCache<folly::IPAddressV6>(
vlanID, blockedNeighborMac, stateDelta);
}
}
void LookupClassRouteUpdater::processMacAddrsToBlockUpdates(
const StateDelta& stateDelta) {
auto oldState = stateDelta.oldState();
auto newState = stateDelta.newState();
std::vector<std::pair<VlanID, folly::MacAddress>> oldMacAddrsToBlock(
oldState->getSwitchSettings()->getMacAddrsToBlock());
std::vector<std::pair<VlanID, folly::MacAddress>> newMacAddrsToBlock(
newState->getSwitchSettings()->getMacAddrsToBlock());
sort(oldMacAddrsToBlock.begin(), oldMacAddrsToBlock.end());
sort(newMacAddrsToBlock.begin(), newMacAddrsToBlock.end());
if (newMacAddrsToBlock.empty() && oldMacAddrsToBlock.empty()) {
return;
}
std::vector<std::pair<VlanID, folly::MacAddress>> toBeRemovedMacAddrsToBlock;
std::set_difference(
oldMacAddrsToBlock.begin(),
oldMacAddrsToBlock.end(),
newMacAddrsToBlock.begin(),
newMacAddrsToBlock.end(),
std::inserter(
toBeRemovedMacAddrsToBlock, toBeRemovedMacAddrsToBlock.end()));
processMacAddrsToBlockRemoved(stateDelta, toBeRemovedMacAddrsToBlock);
/*
* It could be insufficient to only process the newly added MAC address.
* Consider the scenario:
* 1. MAC is added to the block list
* 2. Neighbor corresponding to blocked MAC is resolved after MAC address
* is added to the block list.
* Route/neighbor entry would not exist at the time when MAC is added to the
* block list. To gurantee correctness, always process all currently blocked
* MAC address.
*/
processMacAddrsToBlockAdded(stateDelta, newMacAddrsToBlock);
}
void LookupClassRouteUpdater::stateUpdated(const StateDelta& stateDelta) {
/*
* If FLAGS_queue_per_host_route_fix is false:
* - if inited_ is false i.e. first call to this state observer, disable
* queue-per-host route fix (clear classIDs associated with routes).
* - if inited_ is true i.e. subsequent calls to state observer, do nothing.
*/
if (!inited_) {
inited_ = true;
}
/*
* If vlan2SubnetsCache_ is updated after routes are added, every update to
* vlan2SubnetsCache_ must check if the nextHops of previously processed
* routes now become eligible for addition to nextHopAndVlan2Prefixes_.
* This would require processing ALL the routes from the switchState, which
* is expensive. We avoid that by processing port additions before processing
* route additions (i.e. by calling processPortUpdates before
* processRouteUpdates).
*/
processPortUpdates(stateDelta);
processInterfaceUpdates(stateDelta);
processBlockNeighborUpdates(stateDelta);
processMacAddrsToBlockUpdates(stateDelta);
/*
* Only RSWs connected to MH-NIC (e.g. Yosemite) need queue-per-host fix, and
* thus have non-empty vlan2SubnetsCache_ (populated by processPortUpdates).
* Skip the processing on other setups.
*/
if (vlan2SubnetsCache_.empty()) {
return;
}
processNeighborUpdates<folly::MacAddress>(stateDelta);
processNeighborUpdates<folly::IPAddressV6>(stateDelta);
processNeighborUpdates<folly::IPAddressV4>(stateDelta);
processRouteUpdates<folly::IPAddressV6>(stateDelta);
processRouteUpdates<folly::IPAddressV4>(stateDelta);
updateClassIDsForRoutes(toUpdateRoutesAndClassIDs_);
toUpdateRoutesAndClassIDs_.clear();
}
} // namespace facebook::fboss