Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions orchagent/macsecorch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2379,6 +2379,21 @@ task_process_status MACsecOrch::createMACsecSA(
SWSS_LOG_NOTICE("MACsec SA %s is created.", port_sci_an.c_str());

recover.clear();

// If the port is a LAG member, clear the MACsec-down suppression only once
// the MACsec data plane is up in both directions. MKA installs the egress
// SA before the ingress SA, so gating on both prevents lifting suppression
// while inbound traffic would still fail ICV validation. teamsyncd drives
// the actual SAI re-enable once LACP selects the member.
if (bothDirectionsUp(*ctx.get_macsec_port()))
{
Port port;
if (m_port_orch->getPort(port_name, port))
{
m_port_orch->setLagMemberMacsecSaActive(port, true);
}
}

return task_success;
}

Expand Down Expand Up @@ -2425,6 +2440,20 @@ task_process_status MACsecOrch::deleteMACsecSA(
SWSS_LOG_WARN("Cannot change the ACL entry action from MACsec flow to packet action");
result = task_failed;
}

// Disable the LAG member only once the MACsec data plane is fully down in
// both directions. Deleting the last SA on one SC while the other
// direction still has active SAs (asymmetric teardown / rekey) must not
// drop the member prematurely -- symmetric with createMACsecSA gating on
// bothDirectionsUp().
if (bothDirectionsDown(*ctx.get_macsec_port()))
{
Port port;
if (m_port_orch->getPort(port_name, port))
{
m_port_orch->setLagMemberMacsecSaActive(port, false);
}
}
}


Expand All @@ -2441,6 +2470,30 @@ task_process_status MACsecOrch::deleteMACsecSA(
return result;
}

bool MACsecOrch::bothDirectionsUp(const MACsecPort &macsec_port) const
{
return hasActiveSaInDirection(macsec_port.m_egress_scs) &&
hasActiveSaInDirection(macsec_port.m_ingress_scs);
}

bool MACsecOrch::bothDirectionsDown(const MACsecPort &macsec_port) const
{
return !hasActiveSaInDirection(macsec_port.m_egress_scs) &&
!hasActiveSaInDirection(macsec_port.m_ingress_scs);
}

bool MACsecOrch::hasActiveSaInDirection(const std::map<sai_uint64_t, MACsecSC> &scs) const
{
for (const auto &sc : scs)
{
if (!sc.second.m_sa_ids.empty())
{
return true;
}
}
return false;
}

bool MACsecOrch::createMACsecSA(
sai_object_id_t &sa_id,
sai_object_id_t switch_id,
Expand Down
3 changes: 3 additions & 0 deletions orchagent/macsecorch.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ class MACsecOrch : public Orch
sai_object_id_t entry_id,
sai_object_id_t flow_id,
bool active);
bool bothDirectionsUp(const MACsecPort &macsec_port) const;
bool bothDirectionsDown(const MACsecPort &macsec_port) const;
bool hasActiveSaInDirection(const std::map<sai_uint64_t, MACsecSC> &scs) const;
bool deleteMACsecACLEntry(sai_object_id_t entry_id);
bool getAclPriority(
sai_object_id_t switch_id,
Expand Down
12 changes: 12 additions & 0 deletions orchagent/port.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,18 @@ class Port
sai_object_id_t m_hif_id = 0;
sai_object_id_t m_lag_id = 0;
sai_object_id_t m_lag_member_id = 0;
/* MACsec data-plane state for a LAG member. Set false when the last
* MACsec SA on the port is torn down (session timeout) so a teamsyncd
* refresh of APP_LAG_MEMBER_TABLE does not silently re-enable the member
* while MACsec is down.
*
* Known limitation: this intent is in-memory only and defaults true. Any
* orchagent/swss restart loses the prior value, so until macsecorch
* rebuilds MACsec state (createMACsecPort -> setMACsecEnabledState(true)
* -> setLagMemberMacsecSaActive(false)), a teamsyncd status=enabled
* refresh is not suppressed. It is not reconciled from STATE_DB MACsec SA
* presence on init. */
bool m_macsec_sa_active = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the behavior after orchagent restart and not specific to warmboot. This can impact security, please check.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not specific to warmboot. m_macsec_sa_active is in-memory only and defaults to true, so any orchagent restart loses the prior intent. I’ll correct the comment.

sai_object_id_t m_tunnel_id = 0;
sai_object_id_t m_nexthop_group_id = 0;
sai_object_id_t m_ingress_acl_table_group_id = 0;
Expand Down
93 changes: 93 additions & 0 deletions orchagent/portsorch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6519,6 +6519,16 @@ void PortsOrch::doLagMemberTask(Consumer &consumer)
/* Sync an enabled member */
if (status == "enabled")
{
/* If the MACsec data plane on this member is down, suppress the
* teamsyncd-driven re-enable. MACsec controls collection and
* distribution directly until its SAs are re-established. */
if (!port.m_macsec_sa_active)
{
SWSS_LOG_NOTICE("Skip enabling LAG member %s: MACsec SA inactive",
port.m_alias.c_str());
it = consumer.m_toSync.erase(it);
continue;
}
/* enable collection first, distribution-only mode
* is not supported on Mellanox platform
*/
Expand Down Expand Up @@ -11560,6 +11570,89 @@ void PortsOrch::setMACsecEnabledState(sai_object_id_t port_id, bool enabled)
{
setPortMtu(p, p.m_mtu);
}

/*
* When MACsec is enabled on a port, the MACsec hardware will drop traffic
* until the SAs are established. Thus, the MACsec data plane is considered
* down (false). When MACsec is disabled on the port, the port returns to
* normal cleartext forwarding, so the MACsec data plane constraint is lifted (true).
*/
setLagMemberMacsecSaActive(p, !enabled);
}

void PortsOrch::setLagMemberMacsecSaActive(Port &port, bool enabled)
{
SWSS_LOG_ENTER();

/* Nothing to do if the intent is unchanged. Both MACsec SCs going empty on
* a session timeout would otherwise drive a redundant disable (and a
* duplicate SAI write + log notice) per direction. */
if (port.m_macsec_sa_active == enabled)
{
return;
}

/* Persist the MACsec data-plane intent so that a later teamsyncd refresh
* of APP_LAG_MEMBER_TABLE (handled in doLagMemberTask) does not silently
* re-enable the member while MACsec is down. Always update this, including
* for ports that are not yet (or no longer) LAG members. setMACsecEnabledState
* is shared for all MACsec ports; only hostif/SAI side effects below are
* LAG-member-specific. */
port.m_macsec_sa_active = enabled;
auto it = m_portList.find(port.m_alias);
if (it != m_portList.end())
{
it->second.m_macsec_sa_active = enabled;
}

/* Non-LAG ports: intent is recorded above; skip hostif flap and SAI LAG
* member attribute writes (flapping a standalone hostif would risk dropping
* routing adjacencies). */
if (port.m_lag_member_id == SAI_NULL_OBJECT_ID)
{
return;
}

if (!enabled)
{
/* Flap the host interface oper status to force teamd to instantly drop
* the LAG member (bypassing the 90s LACP timeout) without permanently
* holding carrier down (which would block wpa_supplicant EAPOL). */
if (port.m_oper_status == SAI_PORT_OPER_STATUS_UP)
{
SWSS_LOG_NOTICE("Flapping host interface %s to force teamd LACP reset due to MACsec down",
port.m_alias.c_str());
setHostIntfsOperStatus(port, false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setHostIntfsOperStatus has a single production caller today — updatePortOperStatus in the SAI port-oper-status notification path — where it mirrors a real hardware link event to the kernel. Using it here to synthesize a fake link flap has a few side effects:

  • Spurious telemetry: each call publishes an if-state event via event_publish, so monitoring systems will see two false link-flap events.
  • State divergence: updatePortOperStatus also updates m_oper_status, DB oper status, flap counters, nexthop reachability, gearbox state, and VoQ sync. Calling setHostIntfsOperStatus directly skips all of that, so orchagent's internal state won't reflect the kernel-visible flap.
  • Timing: back-to-back down/up with no delay risks the kernel coalescing the two netlink carrier events, in which case teamd never sees the down and never deselects the member — breaking the recovery path.
    It would be probably cleaner to drive teamd deselection through a teamd-native mechanism (e.g. teamdctl runner port disable, or writing to the teamd control socket) rather than faking a link event through the SAI hostif layer?

setHostIntfsOperStatus(port, true);
}

/* Disable collection/distribution directly via SAI rather than writing
* APP_LAG_MEMBER_TABLE, to avoid a write race with teamsyncd. */
bool distribution_ok = setDistributionOnLagMember(port, false);
bool collection_ok = setCollectionOnLagMember(port, false);

if (!collection_ok || !distribution_ok)
{
Comment thread
karthik-nexthop marked this conversation as resolved.
SWSS_LOG_ERROR("Failed to disable collection/distribution on LAG member %s",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we reset port.m_macsec_sa_active for any SAI call failures?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We set m_macsec_sa_active = false before the SAI calls specifically so doLagMemberTask will suppress a later teamsyncd status=enabled refresh.

If the SAI disable fails, the hardware may still be forwarding on that member — we log that as an error, and I agree that risk remains. But reverting the flag on failure would let orchagent re-enable collection/distribution from APP_DB while MACsec is still down, which makes the failure mode worse. Leaving the suppress flag set is the more conservative choice; a retry of the SAI disable would be a separate improvement if we want one.

port.m_alias.c_str());
return;
}

SWSS_LOG_NOTICE("MACsec disabled LAG member %s", port.m_alias.c_str());
}
else
{
/* MACsec data plane is up again. This path only clears m_macsec_sa_active
* above; it does not call setCollectionOnLagMember /
* setDistributionOnLagMember. Re-enable is driven by teamsyncd
* (TeamPortSync::onChange writes APP_LAG_MEMBER_TABLE status=enabled
* when teamd selects the member). doLagMemberTask then calls
* setCollectionOnLagMember(true) and setDistributionOnLagMember(true)
* once LACP has completed, avoiding hashing to a member before teamd
* selects it. */
SWSS_LOG_NOTICE("MACsec SA active on %s; awaiting teamsyncd to re-enable LAG member",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which module sets setDistributionOnLagMember and setCollectionOnLagMember to true? Will be good to update in this section.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setLagMemberState(true) itself does not re-enable SAI collection/distribution — it only clears m_macsec_sa_active.

Re-enable is driven by teamsyncd. Will update the comment.

port.m_alias.c_str());
}
}

bool PortsOrch::isMACsecPort(sai_object_id_t port_id) const
Expand Down
1 change: 1 addition & 0 deletions orchagent/portsorch.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ class PortsOrch : public Orch, public Subject
bool decrFdbCount(const string& alias, int count);

void setMACsecEnabledState(sai_object_id_t port_id, bool enabled);
void setLagMemberMacsecSaActive(Port &port, bool enabled);
bool isMACsecPort(sai_object_id_t port_id) const;
vector<sai_object_id_t> getPortVoQIds(Port& port);
bool isFrontPanelPort(Port& port);
Expand Down
Loading
Loading