Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e771bc7
add capability check for sampled port mirror
Janetxxx Apr 21, 2026
a9d7e01
extend mock tests
Janetxxx Apr 24, 2026
38fdbc4
[orchagent] Add sampled port mirroring support to MirrorOrch
Janetxxx Apr 16, 2026
648e5d1
Add updateEntry implementationAdd updateEntry implementation
Janetxxx Apr 23, 2026
72c5233
Implement hybrid session update: in-place for mutable fields, delete…
Janetxxx Apr 23, 2026
b0f2246
Write sample_rate and truncate_size to STATE_DB in setSessionState
Janetxxx Apr 24, 2026
0473ba4
Add capability check with fallback for sampled mirroring and truncation
Janetxxx Apr 24, 2026
bb1eeef
[mirrororch] Fix SPAN session regression: only route ERSPAN updates t…
Janetxxx May 5, 2026
3dd93d0
Add VS tests for sampled ERSPAN port mirroring
Janetxxx Apr 28, 2026
70ad543
[VS test] Add update, invalid entry, cleanup and multi-port tests for…
Janetxxx May 5, 2026
df9e5b9
[mirrororch] Fix updateEntry: compare field values instead of presenc…
Janetxxx May 6, 2026
870724c
Add mock tests for updateEntry coverage
Janetxxx May 12, 2026
a7bbf90
Add samplepacket conflict check before binding to port
Janetxxx May 12, 2026
9656e42
Add samplepacket conflict check to sFlowOrch
Janetxxx May 13, 2026
207a2c8
Add mock test for updateEntry immutable field change
Janetxxx May 14, 2026
8f5ea1c
Add mock test for sFlowOrch samplepacket conflict check
Janetxxx May 14, 2026
008aa3c
reject early if sample rate or truncation is not supported rather tha…
Janetxxx May 29, 2026
70d408b
sFlow: pre-check both directions, rollback ingress on egress failure
Janetxxx May 29, 2026
dc97dd8
mirrororch: expand LAG source ports for sampled mirror sessions
Janetxxx May 29, 2026
8ff5cc5
mirrororch: treat sampled <-> full mirror transition as immutable up…
Janetxxx May 29, 2026
3449a82
mirrororch: treat sampled <-> full mirror transition as immutable up…
Janetxxx May 29, 2026
4956515
mirrororch: fix three updateEntry / STATE_DB consistency bugs
Janetxxx May 29, 2026
8df24f3
sFlow: harden samplepacket binding against owner conflicts
Janetxxx May 29, 2026
de574e4
tests: tighten mirror sampled VS tests
Janetxxx May 29, 2026
0686d77
tests/mock: cover updateEntry truncate/recreate paths and STATE_DB hdel
Janetxxx May 30, 2026
9dafb4b
tests/mock: cover sFlow port-binding conflict pre-checks
Janetxxx May 30, 2026
0bf5d7a
mirrororch: add mock_test for samplepacket removal path
Janetxxx Jun 1, 2026
5065c4f
add mock tests to sampled mirror phy port set, conflict and clear
Janetxxx Jun 2, 2026
8afcbaf
Mock INGRESS samplepacket port attrs in mirrororch_ut“
Janetxxx Jun 2, 2026
4705e00
[sfloworch][test] Cover ingress samplepacket conflict pre-check branches
Janetxxx Jun 2, 2026
efbb2df
[mirrororch] Drop LCOV_EXCL on gtest-covered capability-reject paths
Janetxxx Jun 2, 2026
4e5acb6
[mirrororch] Remove in-place mirror-session update; reject duplicates
Janetxxx Jun 4, 2026
91c9849
[mirrororch] createEntry validates the sample-mirror capability per d…
Janetxxx Jun 5, 2026
cf82ea2
[mirrororch] Default sample rate to 1 for truncation without sample rate
Janetxxx Jun 5, 2026
8654efb
[mirrororch]: remove the LCOV exclusions on the sample_rate/truncate_…
Janetxxx Jun 5, 2026
955b56f
[mirrororch]: Add unit tests covering sampled mirror SAI set/clear fa…
Janetxxx Jun 5, 2026
078faa7
[mirrororch]: Add unit tests covering samplepacket SAI create/remove …
Janetxxx Jun 5, 2026
83ed4d5
[VS test]: align sampled-mirror VS tests with current behavior and e…
Janetxxx Jun 5, 2026
0a1fd39
[sfloworch] drop LCOV lines
Janetxxx Jun 5, 2026
8e2332f
[VS test] Add an autouse isolation fixture that force-cleans the shar…
Janetxxx Jun 6, 2026
2e0d34a
[sfloworch]: Refactor sflowAddPort to remove duplicated logic and dro…
Janetxxx Jun 17, 2026
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
359 changes: 350 additions & 9 deletions orchagent/mirrororch.cpp

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion orchagent/mirrororch.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
#define MIRROR_SESSION_SPAN "SPAN"
#define MIRROR_SESSION_ERSPAN "ERSPAN"

enum class MirrorBindDirection
{
Ingress,
Egress
};

/*
* Contains session data specified by user in config file
* and data required for MAC address and port resolution
Expand Down Expand Up @@ -57,6 +63,11 @@ struct MirrorEntry
sai_object_id_t portId;
} neighborInfo;

// Sampled mirroring fields
uint32_t sample_rate; // 0 = full mirror (default)
uint32_t truncate_size; // 0 = no truncation (default)
sai_object_id_t samplepacketId; // SAI_NULL_OBJECT_ID if not sampled

sai_object_id_t sessionId;

int64_t refCount;
Expand Down Expand Up @@ -136,9 +147,21 @@ class MirrorOrch : public Orch, public Observer, public Subject
bool validateSrcPortList(const string& srcPort);
bool validateDstPort(const string& dstPort);
bool setUnsetPortMirror(Port port, bool ingress, bool set,
sai_object_id_t sessionId);
sai_object_id_t sessionId,
sai_object_id_t samplepacketId = SAI_NULL_OBJECT_ID,
uint32_t sample_rate = 0);
bool configurePortMirrorSession(const string&, MirrorEntry&, bool enable);

// Sampled mirroring helpers
bool createSamplePacket(const string& name, MirrorEntry& session);
bool removeSamplePacket(const string& name, MirrorEntry& session);
bool setUnsetSampledMirrorOnPhyPort(sai_object_id_t phy_port_id,
const std::string& phy_port_alias,
bool set,
MirrorBindDirection direction,
sai_object_id_t sessionId,
sai_object_id_t samplepacketId);

void doTask(Consumer& consumer);
};

Expand Down
61 changes: 59 additions & 2 deletions orchagent/sfloworch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,36 @@ bool SflowOrch::sflowUpdateRate(sai_object_id_t port_id, uint32_t rate)
return true;
}

bool SflowOrch::isSflowSamplePacket(sai_object_id_t oid)
{
for (auto& it : m_sflowRateSampleMap)
{
if (it.second.m_sample_id == oid)
return true;
}
return false;
}

// Check-before-set: refuse to bind sFlow if this port's samplepacket attr is
// already owned by another (non-sFlow) feature
bool SflowOrch::isSamplepacketFreeForSflow(sai_object_id_t port_id, sai_port_attr_t attr_id,
sai_object_id_t sample_id, const char* dir_name)
{
sai_attribute_t check_attr;
check_attr.id = attr_id;
if (sai_port_api->get_port_attribute(port_id, 1, &check_attr) == SAI_STATUS_SUCCESS
&& check_attr.value.oid != SAI_NULL_OBJECT_ID
&& check_attr.value.oid != sample_id
&& !isSflowSamplePacket(check_attr.value.oid))
{
SWSS_LOG_ERROR("Port %" PRIx64 " %s_SAMPLEPACKET_ENABLE already bound to "
"OID 0x%" PRIx64 ", cannot bind sFlow",
port_id, dir_name, check_attr.value.oid);
return false;
}
return true;
}

bool SflowOrch::sflowAddPort(sai_object_id_t sample_id, sai_object_id_t port_id, string direction)
{
sai_attribute_t attr;
Expand All @@ -115,7 +145,22 @@ bool SflowOrch::sflowAddPort(sai_object_id_t sample_id, sai_object_id_t port_id,
SWSS_LOG_DEBUG("sflowAddPort %" PRIx64 " portOid %" PRIx64 " dir %s",
sample_id, port_id, direction.c_str());

if (direction == "both" || direction == "rx")
bool need_ingress = (direction == "both" || direction == "rx");
Comment thread
Janetxxx marked this conversation as resolved.
bool need_egress = (direction == "both" || direction == "tx");

// If the port's samplepacket is already owned by another (non-sFlow) feature, refuse to bind.
if (need_ingress &&
!isSamplepacketFreeForSflow(port_id, SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE, sample_id, "INGRESS"))
{
return false;
}
Comment thread
Janetxxx marked this conversation as resolved.
if (need_egress &&
!isSamplepacketFreeForSflow(port_id, SAI_PORT_ATTR_EGRESS_SAMPLEPACKET_ENABLE, sample_id, "EGRESS"))
{
return false;
}

if (need_ingress)
{
attr.id = SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE;
attr.value.oid = sample_id;
Expand All @@ -132,7 +177,7 @@ bool SflowOrch::sflowAddPort(sai_object_id_t sample_id, sai_object_id_t port_id,
}
}

if (direction == "both" || direction == "tx")
if (need_egress)
{
attr.id = SAI_PORT_ATTR_EGRESS_SAMPLEPACKET_ENABLE;
attr.value.oid = sample_id;
Expand Down Expand Up @@ -224,6 +269,18 @@ bool SflowOrch::sflowUpdateSampleDirection(sai_object_id_t port_id, string old_d
egr_sample_oid = port_info->second.m_sample_id;
}

// Check-before-set: on a direction change
if (ing_sample_oid != SAI_NULL_OBJECT_ID &&
!isSamplepacketFreeForSflow(port_id, SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE, ing_sample_oid, "INGRESS"))
{
return false;
}
if (egr_sample_oid != SAI_NULL_OBJECT_ID &&
!isSamplepacketFreeForSflow(port_id, SAI_PORT_ATTR_EGRESS_SAMPLEPACKET_ENABLE, egr_sample_oid, "EGRESS"))
{
return false;
}

attr.id = SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE;
attr.value.oid = ing_sample_oid;
sai_rc = sai_port_api->set_port_attribute(port_id, &attr);
Expand Down
3 changes: 3 additions & 0 deletions orchagent/sfloworch.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,7 @@ class SflowOrch : public Orch
uint32_t sflowSessionGetRate(sai_object_id_t sample_id);
bool handleSflowSessionDel(sai_object_id_t port_id);
void sflowExtractInfo(std::vector<FieldValueTuple> &fvs, bool &admin, uint32_t &rate, string &dir);
bool isSflowSamplePacket(sai_object_id_t oid);
bool isSamplepacketFreeForSflow(sai_object_id_t port_id, sai_port_attr_t attr_id,
sai_object_id_t sample_id, const char* dir_name);
};
82 changes: 82 additions & 0 deletions orchagent/switchorch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ SwitchOrch::SwitchOrch(DBConnector *db, vector<TableConnector>& connectors, Tabl
querySwitchTpidCapability();
querySwitchPortEgressSampleCapability();
querySwitchPortMirrorCapability();
querySwitchSamplePacketCapability();
querySwitchHashDefaults();
setSwitchIcmpOffloadCapability();
setFastLinkupCapability();
Expand Down Expand Up @@ -1957,6 +1958,87 @@ void SwitchOrch::querySwitchPortMirrorCapability()
set_switch_capability(fvVector);
}

void SwitchOrch::querySwitchSamplePacketCapability()
{
vector<FieldValueTuple> fvVector;
sai_status_t status = SAI_STATUS_SUCCESS;
sai_attr_capability_t capability;

// Check if SAI is capable of handling Port ingress sample mirror session
status = sai_query_attribute_capability(gSwitchId, SAI_OBJECT_TYPE_PORT,
SAI_PORT_ATTR_INGRESS_SAMPLE_MIRROR_SESSION, &capability);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_WARN("Could not query port ingress sample mirror capability %d", status);
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_PORT_INGRESS_SAMPLE_MIRROR_CAPABLE, "false");
m_portIngressSampleMirrorSupported = false;
}
else
{
if (capability.set_implemented)
{
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_PORT_INGRESS_SAMPLE_MIRROR_CAPABLE, "true");
m_portIngressSampleMirrorSupported = true;
}
else
{
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_PORT_INGRESS_SAMPLE_MIRROR_CAPABLE, "false");
m_portIngressSampleMirrorSupported = false;
}
SWSS_LOG_NOTICE("port ingress sample mirror capability %d", capability.set_implemented);
}

// Check if SAI is capable of handling Port egress sample mirror session
status = sai_query_attribute_capability(gSwitchId, SAI_OBJECT_TYPE_PORT,
Comment thread
Janetxxx marked this conversation as resolved.
SAI_PORT_ATTR_EGRESS_SAMPLE_MIRROR_SESSION, &capability);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_WARN("Could not query port egress sample mirror capability %d", status);
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_PORT_EGRESS_SAMPLE_MIRROR_CAPABLE, "false");
m_portEgressSampleMirrorSupported = false;
}
else
{
if (capability.set_implemented)
{
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_PORT_EGRESS_SAMPLE_MIRROR_CAPABLE, "true");
m_portEgressSampleMirrorSupported = true;
}
else
{
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_PORT_EGRESS_SAMPLE_MIRROR_CAPABLE, "false");
m_portEgressSampleMirrorSupported = false;
}
SWSS_LOG_NOTICE("port egress sample mirror capability %d", capability.set_implemented);
}

// Check if SAI is capable of handling samplepacket truncation
status = sai_query_attribute_capability(gSwitchId, SAI_OBJECT_TYPE_SAMPLEPACKET,
SAI_SAMPLEPACKET_ATTR_TRUNCATE_ENABLE, &capability);
if (status != SAI_STATUS_SUCCESS)
{
SWSS_LOG_WARN("Could not query samplepacket truncation capability %d", status);
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_SAMPLEPACKET_TRUNCATION_CAPABLE, "false");
m_samplepacketTruncationSupported = false;
}
else
{
if (capability.set_implemented)
{
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_SAMPLEPACKET_TRUNCATION_CAPABLE, "true");
m_samplepacketTruncationSupported = true;
}
else
{
fvVector.emplace_back(SWITCH_CAPABILITY_TABLE_SAMPLEPACKET_TRUNCATION_CAPABLE, "false");
m_samplepacketTruncationSupported = false;
}
SWSS_LOG_NOTICE("samplepacket truncation capability %d", capability.set_implemented);
}

set_switch_capability(fvVector);
}
Comment thread
Janetxxx marked this conversation as resolved.

void SwitchOrch::querySwitchTpidCapability()
{
SWSS_LOG_ENTER();
Expand Down
10 changes: 10 additions & 0 deletions orchagent/switchorch.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
#define SWITCH_CAPABILITY_TABLE_REG_NOTICE_ASIC_SDK_HEALTH_CATEGORY "REG_NOTICE_ASIC_SDK_HEALTH_CATEGORY"
#define SWITCH_CAPABILITY_TABLE_PORT_INGRESS_MIRROR_CAPABLE "PORT_INGRESS_MIRROR_CAPABLE"
#define SWITCH_CAPABILITY_TABLE_PORT_EGRESS_MIRROR_CAPABLE "PORT_EGRESS_MIRROR_CAPABLE"
#define SWITCH_CAPABILITY_TABLE_PORT_INGRESS_SAMPLE_MIRROR_CAPABLE "PORT_INGRESS_SAMPLE_MIRROR_CAPABLE"
#define SWITCH_CAPABILITY_TABLE_PORT_EGRESS_SAMPLE_MIRROR_CAPABLE "PORT_EGRESS_SAMPLE_MIRROR_CAPABLE"
#define SWITCH_CAPABILITY_TABLE_SAMPLEPACKET_TRUNCATION_CAPABLE "SAMPLEPACKET_TRUNCATION_CAPABLE"

#define SWITCH_STAT_COUNTER_FLEX_COUNTER_GROUP "SWITCH_STAT_COUNTER"

Expand Down Expand Up @@ -88,6 +91,9 @@ class SwitchOrch : public Orch
// Mirror capability interface for MirrorOrch
bool isPortIngressMirrorSupported() const { return m_portIngressMirrorSupported; }
bool isPortEgressMirrorSupported() const { return m_portEgressMirrorSupported; }
bool isPortIngressSampleMirrorSupported() const { return m_portIngressSampleMirrorSupported; }
bool isPortEgressSampleMirrorSupported() const { return m_portEgressSampleMirrorSupported; }
bool isSamplepacketTruncationSupported() const { return m_samplepacketTruncationSupported; }

private:
void doTask(Consumer &consumer);
Expand All @@ -102,6 +108,7 @@ class SwitchOrch : public Orch
void querySwitchTpidCapability();
void querySwitchPortEgressSampleCapability();
void querySwitchPortMirrorCapability();
void querySwitchSamplePacketCapability();

// Statistics
void generateSwitchCounterNameMap() const;
Expand Down Expand Up @@ -184,6 +191,9 @@ class SwitchOrch : public Orch
// Port mirror capabilities
bool m_portIngressMirrorSupported = false;
bool m_portEgressMirrorSupported = false;
bool m_portIngressSampleMirrorSupported = false;
bool m_portEgressSampleMirrorSupported = false;
bool m_samplepacketTruncationSupported = false;

// ASIC SDK health event
std::shared_ptr<swss::DBConnector> m_stateDbForNotification = nullptr;
Expand Down
27 changes: 27 additions & 0 deletions tests/dvslib/dvs_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def create_span_session(self, name, dst_port, src_ports=None, direction="BOTH",

def create_erspan_session(self, name, src, dst, gre, dscp, ttl, queue, policer=None, src_ports=None, direction="BOTH"):
mirror_entry = {
"type": "ERSPAN",
"src_ip": src,
"dst_ip": dst,
"gre_type": gre,
Expand All @@ -41,6 +42,32 @@ def create_erspan_session(self, name, src, dst, gre, dscp, ttl, queue, policer=N

self.config_db.create_entry("MIRROR_SESSION", name, mirror_entry)


def create_erspan_session_sampled(self, name, src, dst, gre, dscp, ttl, queue,
policer=None, src_ports=None, direction="RX",
sample_rate=None, truncate_size=None):
mirror_entry = {
"type": "ERSPAN",
"src_ip": src,
"dst_ip": dst,
"gre_type": gre,
"dscp": dscp,
"ttl": ttl,
"queue": queue,
"direction": direction
}

if policer:
mirror_entry["policer"] = policer
if src_ports:
mirror_entry["src_port"] = src_ports
if sample_rate:
mirror_entry["sample_rate"] = sample_rate
if truncate_size:
mirror_entry["truncate_size"] = truncate_size

self.config_db.create_entry("MIRROR_SESSION", name, mirror_entry)

def remove_mirror_session(self, name):
self.config_db.delete_entry("MIRROR_SESSION", name)

Expand Down
1 change: 1 addition & 0 deletions tests/mock_tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ tests_SOURCES = aclorch_ut.cpp \
saihelper_ut.cpp \
mock_saihelper.cpp \
mirrororch_ut.cpp \
mirrororch_sample_port_sai_wrap.cpp \
hftelorch_ut.cpp \
hftelorch_notify_ut.cpp \
hftelorch_is_supported_sai_wrap.cpp \
Expand Down
Loading
Loading