vslib/SwitchStateBaseFdb.cpp (410 lines of code) (raw):
#include "SwitchStateBase.h"
#include "EventPayloadNotification.h"
#include "swss/logger.h"
#include "swss/select.h"
#include "meta/sai_serialize.h"
#include "meta/NotificationFdbEvent.h"
#include <linux/if_ether.h>
#include <arpa/inet.h>
using namespace saivs;
void SwitchStateBase::updateLocalDB(
_In_ const sai_fdb_event_notification_data_t &data,
_In_ sai_fdb_event_t fdb_event)
{
SWSS_LOG_ENTER();
sai_status_t status;
switch (fdb_event)
{
case SAI_FDB_EVENT_LEARNED:
{
auto sid = sai_serialize_fdb_entry(data.fdb_entry);
status = create(SAI_OBJECT_TYPE_FDB_ENTRY, sid, m_switch_id, data.attr_count, data.attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to create fdb entry: %s",
sai_serialize_fdb_entry(data.fdb_entry).c_str());
}
}
break;
case SAI_FDB_EVENT_AGED:
{
auto sid = sai_serialize_fdb_entry(data.fdb_entry);
status = remove(SAI_OBJECT_TYPE_FDB_ENTRY, sid);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to remove fdb entry %s",
sai_serialize_fdb_entry(data.fdb_entry).c_str());
}
}
break;
default:
SWSS_LOG_ERROR("unsupported fdb event: %d", fdb_event);
break;
}
}
void SwitchStateBase::processFdbInfo(
_In_ const FdbInfo &fi,
_In_ sai_fdb_event_t fdb_event)
{
SWSS_LOG_ENTER();
sai_attribute_t attrs[2];
attrs[0].id = SAI_FDB_ENTRY_ATTR_TYPE;
attrs[0].value.s32 = SAI_FDB_ENTRY_TYPE_DYNAMIC;
attrs[1].id = SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID;
attrs[1].value.oid = fi.getBridgePortId();
sai_fdb_event_notification_data_t data;
data.event_type = fdb_event;
data.fdb_entry = fi.getFdbEntry();
data.attr_count = 2;
data.attr = attrs;
// update local DB
updateLocalDB(data, fdb_event); // TODO we could move to send_fdb_event_notification and support flush
send_fdb_event_notification(data);
}
void SwitchStateBase::findBridgeVlanForPortVlan(
_In_ sai_object_id_t port_id,
_In_ sai_vlan_id_t vlan_id,
_Inout_ sai_object_id_t &bv_id,
_Inout_ sai_object_id_t &bridge_port_id)
{
SWSS_LOG_ENTER();
bv_id = SAI_NULL_OBJECT_ID;
bridge_port_id = SAI_NULL_OBJECT_ID;
sai_object_id_t bridge_id;
/*
* The bridge port lookup process is two steps:
*
* - use (vlan_id, physical port_id) to match any .1D bridge port created.
* If there is match, then quit, found=true
*
* - use (physical port_id) to match any .1Q bridge created. if there is a
* match, the quite, found=true.
*
* If found==true, generate fdb learn event on the .1D or .1Q bridge port.
* If not found, then do not generate fdb event. It means the packet is not
* received on the bridge port.
*
* XXX this is not whats happening here, we are just looking for any
* bridge id (as in our case this is shortcut, we will remove all bridge ports
* when we will use router interface based port/lag and no bridge
* will be found.
*/
auto &objectHash = m_objectHash.at(SAI_OBJECT_TYPE_BRIDGE_PORT);
// iterate via all bridge ports to find match on port id
sai_object_id_t lag_id = SAI_NULL_OBJECT_ID;
if (getLagFromPort(port_id,lag_id))
{
SWSS_LOG_INFO("got lag %s for port %s",
sai_serialize_object_id(lag_id).c_str(),
sai_serialize_object_id(port_id).c_str());
}
bool bv_id_set = false;
for (auto it = objectHash.begin(); it != objectHash.end(); ++it)
{
sai_object_id_t bpid;
sai_deserialize_object_id(it->first, bpid);
sai_attribute_t attrs[2];
attrs[0].id = SAI_BRIDGE_PORT_ATTR_PORT_ID;
attrs[1].id = SAI_BRIDGE_PORT_ATTR_TYPE;
sai_status_t status = get(SAI_OBJECT_TYPE_BRIDGE_PORT, bpid, (uint32_t)(sizeof(attrs)/sizeof(attrs[0])), attrs);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_WARN("failed to get attr PORT_ID and TYPE for bridge port %s",
sai_serialize_object_id(bpid).c_str());
continue;
}
if (lag_id != SAI_NULL_OBJECT_ID)
{
// if port is member of lag, we should check if port_id is that LAG
if (port_id == attrs[0].value.oid)
{
// there should be no case that the same port is lag member and has bridge port object on it
SWSS_LOG_ERROR("port %s is member of lag %s, and also has bridge port created: %s",
sai_serialize_object_id(port_id).c_str(),
sai_serialize_object_id(lag_id).c_str(),
sai_serialize_object_id(attrs[0].value.oid).c_str());
continue;
}
if (lag_id != attrs[0].value.oid)
{
// this is not expected port
continue;
}
}
else if (port_id != attrs[0].value.oid)
{
// this is not expected port
continue;
}
bridge_port_id = bpid;
// get the 1D bridge id if the bridge port type is subport
auto bp_type = attrs[1].value.s32;
SWSS_LOG_DEBUG("found bridge port %s of type %d",
sai_serialize_object_id(bridge_port_id).c_str(),
bp_type);
if (bp_type == SAI_BRIDGE_PORT_TYPE_SUB_PORT)
{
sai_attribute_t attr;
attr.id = SAI_BRIDGE_PORT_ATTR_BRIDGE_ID;
status = get(SAI_OBJECT_TYPE_BRIDGE_PORT, bridge_port_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
break;
}
bridge_id = attr.value.oid;
SWSS_LOG_DEBUG("found bridge %s for port %s",
sai_serialize_object_id(bridge_id).c_str(),
sai_serialize_object_id(port_id).c_str());
attr.id = SAI_BRIDGE_ATTR_TYPE;
status = get(SAI_OBJECT_TYPE_BRIDGE, bridge_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
break;
}
SWSS_LOG_DEBUG("bridge %s type is %d",
sai_serialize_object_id(bridge_id).c_str(),
attr.value.s32);
bv_id = bridge_id;
bv_id_set = true;
}
else
{
auto &objectHash2 = m_objectHash.at(SAI_OBJECT_TYPE_VLAN);
// iterate via all vlans to find match on vlan id
for (auto it2 = objectHash2.begin(); it2 != objectHash2.end(); ++it2)
{
sai_object_id_t vlan_oid;
sai_deserialize_object_id(it2->first, vlan_oid);
sai_attribute_t attr;
attr.id = SAI_VLAN_ATTR_VLAN_ID;
status = get(SAI_OBJECT_TYPE_VLAN, vlan_oid, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
continue;
}
if (vlan_id == attr.value.u16)
{
bv_id = vlan_oid;
bv_id_set = true;
break;
}
}
}
break;
}
if (!bv_id_set)
{
// if port is lag member, then we didn't found bridge_port for that lag (expected for rif lag)
SWSS_LOG_WARN("failed to find bv_id for vlan %d and port_id %s",
vlan_id,
sai_serialize_object_id(port_id).c_str());
}
}
bool SwitchStateBase::getLagFromPort(
_In_ sai_object_id_t port_id,
_Inout_ sai_object_id_t& lag_id)
{
SWSS_LOG_ENTER();
lag_id = SAI_NULL_OBJECT_ID;
auto &objectHash = m_objectHash.at(SAI_OBJECT_TYPE_LAG_MEMBER);
// iterate via all lag members to find match on port id
for (auto it = objectHash.begin(); it != objectHash.end(); ++it)
{
sai_object_id_t lag_member_id;
sai_deserialize_object_id(it->first, lag_member_id);
sai_attribute_t attr;
attr.id = SAI_LAG_MEMBER_ATTR_PORT_ID;
sai_status_t status = get(SAI_OBJECT_TYPE_LAG_MEMBER, lag_member_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to get port id from leg member %s",
sai_serialize_object_id(lag_member_id).c_str());
continue;
}
if (port_id != attr.value.oid)
{
// this is not the port we are looking for
continue;
}
attr.id = SAI_LAG_MEMBER_ATTR_LAG_ID;
status = get(SAI_OBJECT_TYPE_LAG_MEMBER, lag_member_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to get lag id from lag member %s",
sai_serialize_object_id(lag_member_id).c_str());
continue;
}
lag_id = attr.value.oid;
return true;
}
// this port does not belong to any lag
return false;
}
bool SwitchStateBase::isLagOrPortRifBased(
_In_ sai_object_id_t lag_or_port_id)
{
SWSS_LOG_ENTER();
auto &objectHash = m_objectHash.at(SAI_OBJECT_TYPE_ROUTER_INTERFACE);
// iterate via all lag members to find match on port id
for (auto it = objectHash.begin(); it != objectHash.end(); ++it)
{
sai_object_id_t rif_id;
sai_deserialize_object_id(it->first, rif_id);
sai_attribute_t attr;
attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE;
sai_status_t status = get(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to get rif type from rif %s",
sai_serialize_object_id(rif_id).c_str());
continue;
}
switch (attr.value.s32)
{
case SAI_ROUTER_INTERFACE_TYPE_PORT:
case SAI_ROUTER_INTERFACE_TYPE_SUB_PORT:
break;
default:
continue;
}
attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID;
status = get(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("failed to get rif port id from rif %s",
sai_serialize_object_id(rif_id).c_str());
continue;
}
if (attr.value.oid == lag_or_port_id)
{
return true;
}
}
return false;
}
void SwitchStateBase::process_packet_for_fdb_event(
_In_ sai_object_id_t portId,
_In_ const std::string& name,
_In_ const uint8_t *buffer,
_In_ size_t size)
{
SWSS_LOG_ENTER();
// we would need hostif info here and maybe interface index, then we can
// find host info from index
uint32_t frametime = (uint32_t)time(NULL);
/*
* We add +2 in case if frame contains 1Q VLAN tag.
*/
if (size < (sizeof(ethhdr) + 2))
{
SWSS_LOG_WARN("ethernet frame is too small: %zu", size);
return;
}
const ethhdr *eh = (const ethhdr*)buffer;
uint16_t proto = htons(eh->h_proto);
uint16_t vlan_id = DEFAULT_VLAN_NUMBER;
bool tagged = (proto == ETH_P_8021Q);
if (tagged)
{
// this is tagged frame, get vlan id from frame
uint16_t tci = htons(((const uint16_t*)&eh->h_proto)[1]); // tag is after h_proto field
vlan_id = tci & 0xfff;
if (vlan_id == 0xfff)
{
SWSS_LOG_WARN("invalid vlan id %u in ethernet frame on %s", vlan_id, name.c_str());
return;
}
if (vlan_id == 0)
{
// priority packet, frame should be treated as non tagged
tagged = false;
}
}
if (tagged == false)
{
// untagged ethernet frame
sai_attribute_t attr;
#ifdef SAI_LAG_ATTR_PORT_VLAN_ID
sai_object_id_t lag_id;
if (getLagFromPort(portid, lag_id))
{
// if port belongs to lag we need to get SAI_LAG_ATTR_PORT_VLAN_ID
attr.id = SAI_LAG_ATTR_PORT_VLAN_ID
sai_status_t status = get(SAI_OBJECT_TYPE_LAG, lag_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_WARN("failed to get lag vlan id from lag %s",
sai_serialize_object_id(lag_id).c_str());
return;
}
vlan_id = attr.value.u16;
if (isLagOrPortRifBased(lag_id))
{
// this lag is router interface based, skip mac learning
return;
}
}
else
#endif
{
attr.id = SAI_PORT_ATTR_PORT_VLAN_ID;
sai_status_t status = get(SAI_OBJECT_TYPE_PORT, portId, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_WARN("failed to get port vlan id from port %s",
sai_serialize_object_id(portId).c_str());
return;
}
// untagged port vlan (default is 1, but may change setting port attr)
vlan_id = attr.value.u16;
}
}
sai_object_id_t lag_id;
if (getLagFromPort(portId, lag_id) && isLagOrPortRifBased(lag_id))
{
SWSS_LOG_DEBUG("lag %s is rif based, skip mac learning for port %s",
sai_serialize_object_id(lag_id).c_str(),
sai_serialize_object_id(portId).c_str());
return;
}
if (isLagOrPortRifBased(portId))
{
SWSS_LOG_DEBUG("port %s is rif based, skip mac learning",
sai_serialize_object_id(portId).c_str());
return;
}
// we have vlan and mac address which is KEY, so just see if that is already defined
FdbInfo fi;
fi.setPortId((lag_id != SAI_NULL_OBJECT_ID) ? lag_id : portId);
fi.setVlanId(vlan_id);
memcpy(fi.m_fdbEntry.mac_address, eh->h_source, sizeof(sai_mac_t));
std::set<FdbInfo>::iterator it = m_fdb_info_set.find(fi);
if (it != m_fdb_info_set.end())
{
// this key was found, update timestamp
// and since iterator is const we need to reinsert
fi = *it;
fi.setTimestamp(frametime);
m_fdb_info_set.insert(fi);
return;
}
// key was not found, get additional information
fi.setTimestamp(frametime);
fi.m_fdbEntry.switch_id = m_switch_id;
findBridgeVlanForPortVlan(portId, vlan_id, fi.m_fdbEntry.bv_id, fi.m_bridgePortId);
if (fi.getFdbEntry().bv_id == SAI_NULL_OBJECT_ID)
{
SWSS_LOG_WARN("skipping mac learn for %s, since BV_ID was not found for mac",
sai_serialize_fdb_entry(fi.getFdbEntry()).c_str());
// bridge was not found, skip mac learning
return;
}
sai_attribute_t attr;
attr.id = SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE;
sai_status_t status = get(SAI_OBJECT_TYPE_BRIDGE_PORT, fi.getBridgePortId(), 1, &attr);
if (status == SAI_STATUS_SUCCESS)
{
if (attr.value.s32 == SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW)
{
SWSS_LOG_INFO("inserting to fdb_info set: %s, vlan id: %d",
sai_serialize_fdb_entry(fi.getFdbEntry()).c_str(),
fi.getVlanId());
m_fdb_info_set.insert(fi);
processFdbInfo(fi, SAI_FDB_EVENT_LEARNED);
}
else if (attr.value.s32 == SAI_BRIDGE_PORT_FDB_LEARNING_MODE_DISABLE)
{
// do not learn, actually linux kernel will learn that MAC
}
else
{
SWSS_LOG_WARN("not supported SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE: %d, for %s",
attr.value.s32,
sai_serialize_fdb_entry(fi.getFdbEntry()).c_str());
}
}
else
{
SWSS_LOG_ERROR("failed to get SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE for %s: %s",
sai_serialize_object_id(fi.getBridgePortId()).c_str(),
sai_serialize_status(status).c_str());
}
}
void SwitchStateBase::send_fdb_event_notification(
_In_ const sai_fdb_event_notification_data_t& data)
{
SWSS_LOG_ENTER();
auto meta = getMeta();
if (meta)
{
meta->meta_sai_on_fdb_event(1, &data);
}
sai_attribute_t attr;
attr.id = SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY;
sai_status_t status = get(SAI_OBJECT_TYPE_SWITCH, m_switch_id, 1, &attr);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_ERROR("unable to get SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY attribute for %s",
sai_serialize_object_id(m_switch_id).c_str());
return;
}
auto str = sai_serialize_fdb_event_ntf(1, &data);
sai_switch_notifications_t sn = { };
sn.on_fdb_event = (sai_fdb_event_notification_fn)attr.value.ptr;
SWSS_LOG_INFO("send event SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY %s", str.c_str());
auto ntf = std::make_shared<sairedis::NotificationFdbEvent>(str);
auto payload = std::make_shared<EventPayloadNotification>(ntf, sn);
m_switchConfig->m_eventQueue->enqueue(std::make_shared<Event>(EVENT_TYPE_NOTIFICATION, payload));
}