[vpp][acl] match on ingress interface to support SAI ACL IN_PORTS - #2064
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
This PR prototypes support for SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS in the VPP-backed VS pipeline by scoping generated VPP ACL rules to a specific ingress interface, while keeping the higher SAI layer working in interface names and resolving to VPP sw_if_index at programming time.
Changes:
- Extend
vpp_acl_rule_twith anin_hwif_namefield to carry an optional ingress interface name for rule scoping. - In
SwitchVpp::fill_acl_rules(), fan out generated rules per IN_PORTS port and stamp each copy with the resolved VPP interface name. - In
vpp_acl_add_replace(), resolvein_hwif_nametoin_sw_if_index(or 0 for “any”) when programming rules into the VPP ACL plugin.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| vslib/vpp/vppxlate/SaiVppXlate.h | Adds in_hwif_name to the VPP ACL rule representation for ingress scoping. |
| vslib/vpp/vppxlate/SaiVppXlate.c | Resolves in_hwif_name to in_sw_if_index when sending rules to VPP. |
| vslib/vpp/SwitchVppAcl.cpp | Implements IN_PORTS-driven rule fan-out and adds helper to re-read the IN_PORTS list. |
| vslib/vpp/SwitchVpp.h | Declares the new IN_PORTS helper and adds needed STL includes. |
Suppressed comments (3)
vslib/vpp/SwitchVppAcl.cpp:1052
acl_entry_in_ports_get()is documented to return true only when the entry is scoped to named ports, but thecount == 0path returns true with an emptyhwifsset. That can trigger the fan-out path and drop rules. Return false here so the caller treats the entry as unscoped (or handle empty-set scoping explicitly).
if (count == 0) {
SWSS_LOG_WARN("ACL entry %s has an enabled but empty IN_PORTS list",
sai_serialize_object_id(ace_oid).c_str());
return true;
}
vslib/vpp/SwitchVppAcl.cpp:1082
- The function contract says it returns true only when the entry is scoped to named ports, but this unconditional
return true;reports scoped even if all ports failed to resolve to VPP interface names (leavinghwifsempty). Returning!hwifs.empty()keeps the return value consistent with the output set.
return true;
vslib/vpp/SwitchVppAcl.cpp:1066
- On failure to re-read the IN_PORTS list,
acl_entry_in_ports_get()currently returns true, which makes the caller think the entry should be scoped even though no interfaces were produced. Return false on read failure so rules are not accidentally dropped.
if (get(SAI_OBJECT_TYPE_ACL_ENTRY, ace_oid, 1, &attr) != SAI_STATUS_SUCCESS) {
SWSS_LOG_ERROR("Failed to read IN_PORTS list of ACL entry %s",
sai_serialize_object_id(ace_oid).c_str());
return true;
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
43a2a03 to
42594a4
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
…RTS branches
Build a test image carrying the SAI ACL IN_PORTS support so it can be
deployed to a dual-ToR VPP testbed.
The two changes are a matched pair and cannot be tested apart. Adding
in_sw_if_index to the VPP acl_rule type changes the layout of the
acl_add_replace message and therefore its CRC, so a syncd built against
one side cannot program ACLs on the other.
platform/vpp -> lolyu/acl_match_in_port
sonic-net/sonic-platform-vpp#278
src/sonic-sairedis -> lolyu/vpp_acl_in_port_match
sonic-net/sonic-sairedis#2064
vpp.mk carries VPP_VERSION 2606-0.6 for the new patch series. That deb
is not published, so the download step fails and vppbld/Makefile falls
back to build_locally, which applies the patch series and builds VPP
from source. The image build therefore also verifies that the patch
applies and compiles under the real build flags.
Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
…RTS branches
Build a test image carrying the SAI ACL IN_PORTS support so it can be
deployed to a dual-ToR VPP testbed.
The two changes are a matched pair and cannot be tested apart. Adding
in_sw_if_index to the VPP acl_rule type changes the layout of the
acl_add_replace message and therefore its CRC, so a syncd built against
one side cannot program ACLs on the other.
platform/vpp -> lolyu/acl_match_in_port
sonic-net/sonic-platform-vpp#278
src/sonic-sairedis -> lolyu/vpp_acl_in_port_match
sonic-net/sonic-sairedis#2064
vpp.mk carries VPP_VERSION 2606-0.6 for the new patch series. That deb
is not published, so the download step fails and vppbld/Makefile falls
back to build_locally, which applies the patch series and builds VPP
from source. The image build therefore also verifies that the patch
applies and compiles under the real build flags.
Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
|
/azp run |
|
Pushed @yue-fred-gao asked what happens if if (is_input)
out_sw_if_index[ii] = vnet_buffer (b[ii])->sw_if_index[VLIB_RX];
else
out_sw_if_index[ii] = vnet_buffer (b[ii])->sw_if_index[VLIB_TX];SAI puts no stage constraint on
No in-tree consumer creates this configuration (every SONiC user of Verified with a full |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
6864c79 to
3b1cdbe
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
3b1cdbe to
e0de9e7
Compare
|
/azp run |
|
For anyone looking at the red X: the two Azure failures are expected at this point, and they are the matched-pair dependency this PR describes, showing up as a compile error rather than a runtime one. Both failing jobs (
The header comes from the VPP artifact the build environment installs for us: That This is the same CRC coupling called out in the PR description and in the HLD: the field has to exist in the What unblocks it: merge sonic-platform-vpp#278 first. Once its master build publishes a new There is no way to make this PR green beforehand. Functionally the pair has been validated together: the combined patch series builds, and the resulting image was exercised on a dual-ToR VPP testbed. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Thanks — this is a genuinely useful review. All four line references check out against Pushed as 1. Unresolved
|
| ports {A, B}, B unresolved | A | B |
|---|---|---|
| today | denied | not denied |
| proposed | not denied | not denied |
Same direction for permit-in-default-deny. Partial resolution is closer to intent, and a port absent from VPP can't carry traffic anyway.
That said, the inconsistency you identified was real — and fixing item 1 dissolves it without a separate change. Both paths now follow one rule: emit rules for the ports that resolved, with all-unresolved as the degenerate zero case. That's the consistent policy you asked for, arrived at from the other end.
3. MAX_ACL_ATTRS truncation — finding accepted, remedy declined, and it's bigger than IN_PORTS
Confirmed the mechanism: AttrHash is std::map<std::string, ...> (SwitchState.h:29) keyed by attridname (SwitchStateBase.cpp:639), so the cut is alphabetical as you said. IN_PORTS ranks 107/170 in the full namespace — 62 ACTION_*, 43 FIELD_* and ADMIN_STATE sort ahead of it. (Also worth noting every FIELD_INNER_* sorts before FIELD_IN_PORTS, since 'N' 0x4e < '_' 0x5f.)
Modeling real rule shapes:
| shape | IN_PORTS position |
|---|---|
| mux standby drop | 4/12 |
| L3 5-tuple | 5/12 |
| L3V4V6 v4+v6 + icmp + range | 11/12 |
+ ACL_IP_TYPE |
12/12 |
+ ACL_USER_META |
13/12 — truncated |
So your "reachable entries are safe" holds, but by two attributes.
Here's why I don't want the targeted get(): at 21 attributes the same cut also drops PRIORITY, TABLE_ID, SRC_IP, IP_PROTOCOL, L4_* and TCP_FLAGS. And acl_priority_attr_get() returns FAILURE when PRIORITY is missing with its return value ignored at :768, leaving priority 0 and silently misordering the rule.
That's a pre-existing systemic bug, considerably worse than the IN_PORTS symptom. Fixing only IN_PORTS would make it harder to find, since scoping would start working while match fields kept vanishing. I'd rather raise the cap and warn when attrs_count hits it — fixing all of it — in a separate PR, since none of it is caused by this change. Happy to open that; say the word.
4. Egress guard fallback direction — kept, but I'd flip it if you feel strongly
Reasoning: the branch is near-unreachable, so the tiebreaker is which error costs more. On VPP mux_tunnel_egress_acl defaults to disabled (below), so an ingress table is overwhelmingly the likely case, and flipping means an unreadable stage silently stops emitting rules on ordinary ingress tables — making the guard's failure mode worse than not having the guard. My rule of thumb is that a guard which can't make its determination shouldn't change behaviour. Low stakes either way; genuinely happy to flip on your call.
The thing your review led me to
Chasing item 4's reachability turned up something that corrects a claim in my own HLD. I had written that no in-tree consumer creates an egress table carrying IN_PORTS. That's wrong — muxorch.cpp:
1485: string table_name = is_ingress_acl_ ? MUX_ACL_TABLE_NAME : EGRESS_TABLE_DROP;
1551: acl_table.stage = is_ingress_acl_ ? ACL_STAGE_INGRESS : ACL_STAGE_EGRESS;
...
1566: // Add MATCH_IN_PORTS as match criteria for ingress table
1567: attr_name = MATCH_IN_PORTS; // NOT conditioned on is_ingress_acl_With SYSTEM_DEFAULTS|mux_tunnel_egress_acl enabled, MuxAclHandler creates an egress DROP table and adds MATCH_IN_PORTS unconditionally — the comment says "for ingress table" but the code doesn't branch. init_cfg.json.j2:211 defaults that key to enabled on mellanox and disabled elsewhere, so VPP is unaffected as shipped, but it's one CONFIG_DB key away, not a hypothetical.
So the egress guard is protecting a real in-tree path. HLD updated (84073d13) with this, the corrected failure-handling semantics, and a new restriction that mux_tunnel_egress_acl must stay disabled on VPP.
The red build is unrelated — cross-repo ordering with sonic-platform-vpp#278, as you noted and as detailed in the comment above.
There was a problem hiding this comment.
🔵 Needs a closer look
acl_entry_in_ports_get() currently returns success on IN_PORTS resolution failures (read failure / no ports resolve), making fill_acl_rules()’s error handling ineffective and potentially leading to silently omitted rules.
Review details
Suppressed comments (2)
vslib/vpp/SwitchVppAcl.cpp:1184
- When IN_PORTS is enabled and non-empty but none of the ports resolve to a VPP interface, this function only logs and still returns success;
fill_acl_rules()will then treat the entry as scoped with an empty set and emit no rule, which is an implicit behavioral change. Return a failure status here so the caller can handle the error path consistently with the doxygen contract.
if (hwifs.empty()) {
SWSS_LOG_ERROR("None of the %u port(s) named by IN_PORTS of ACL entry %s resolve "
"to a VPP interface; no rule is emitted for this entry",
count, sai_serialize_object_id(ace_oid).c_str());
}
vslib/vpp/SwitchVppAcl.cpp:1146
acl_entry_in_ports_get()currently returnsSAI_STATUS_SUCCESSeven when re-reading the IN_PORTS list fails, which makes thestatus != SAI_STATUS_SUCCESScheck infill_acl_rules()ineffective and contradicts the function contract inSwitchVpp.h. Return the failing status (orSAI_STATUS_FAILURE) here so ACL programming can abort rather than silently proceeding with an unknown scope.
if (get(SAI_OBJECT_TYPE_ACL_ENTRY, ace_oid, 1, &attr) != SAI_STATUS_SUCCESS) {
/*
* The scope cannot be determined, so leave hwifs empty and let the
* caller emit no rule for this entry. See the note below on why that
* is preferred over failing the table.
*/
SWSS_LOG_ERROR("Failed to read IN_PORTS list of ACL entry %s; no rule is emitted "
"for this entry",
sai_serialize_object_id(ace_oid).c_str());
return SAI_STATUS_SUCCESS;
}
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
a6f0a40 to
0c92e8b
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
CI update — the
|
| step | before | now |
|---|---|---|
Compile sonic sairedis with coverage enabled (Build amd64) |
fail | pass |
Compile sonic sairedis with coverage enabled (BuildAsan amd64) |
fail | pass |
BuildAsan amd64 overall |
fail | pass |
The one remaining red is FlexCounter.bulkChunksize in unittest/syncd/, which is a known-flaky test tracked by #1978, #1888, #1986 and #1818. It's unrelated to this PR — the change here is confined to vslib/vpp/ — and it passed in the ASan job of this same run.
Also rebased onto master (1205916d, the l2-vlan-filter FDB change) so the branch is current. SwitchVppAcl.cpp is byte-identical across that rebase, so the review threads above are unaffected.
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Thanks, this is a thorough pass and the egress + IN_PORTS catch in MuxOrch is a good one. The item 1 fix looks right: both the read failure and the all unresolved case now cost the entry its rules instead of the table, and partial and total collapse into one policy. Agreed on keeping item 4 as ingress too; that branch is a single attr get of a mandatory on create stage so it is effectively unreachable, and I would rather not silence rules on ordinary ingress tables to harden a path that cannot really be hit. The one thing I want to land, and I think it is the same follow up you already offered: nothing reprograms an entry once a port it named becomes resolvable. The only caller of AclTblConfig is an ACL add or remove on that table, so an entry that resolves to zero ports or a subset is created and reported done, and the missing scope stays missing until some later unrelated add or remove on the same table happens to rewalk it. That is strictly better than the old freeze, so I am not blocking on it, but it turns a transient resolution miss into a durable one. That is also why I would not lean on "a port absent from VPP cannot carry traffic" to justify the partial case. Unresolved here is a name to hwif resolution miss, not proof the interface is down. Warm boot restores the object hash but does not repopulate m_hostif_info_map, so a live forwarding port can miss resolution during bring up and, with no reprogram on resolve, stay unscoped afterward. For a standby DROP that is a real fail open window on exactly the port that matters. So my one ask: fold a reprogram on resolve (or the createAclEntry rollback you mentioned) into the follow up, so a port that appears after the entry gets the scope applied rather than waiting on an unrelated table mutation. Two smaller notes, neither blocking:
Reviewed by AI agent on behalf of aaronber0614. |
0c92e8b to
b51bb24
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
All three land. Two of them correct things I had written, so thank you — I've fixed the artifacts rather than just the conversation. Pushed The retry mechanism — you're right, my write-up was wrongI traced it and confirmed your version. // lib/RedisRemoteSaiInterface.cpp:219
*objectId = m_virtualObjectIdManager->allocateNewObjectId(objectType, switchId);So every retry arrives at So it's a leak per retry, not a rule falsely reported healthy. The freeze and no-rollback conclusions are unchanged, but the mechanism was wrong and it was wrong in the commit message and the HLD, not just in a comment. Both corrected: commit message amended in place (message-only — verified the tree hash is byte-identical), HLD updated in The partial-resolution justification — also right to reject itI had written "a port genuinely absent from VPP cannot carry traffic". That's not what an unresolved port means. One refinement on the warm-boot specifics: // vslib/SwitchStateBase.cpp:57
// TODO populate m_hostif_info_map - need to be able to remove port after warm boot
// should be auto populated vs_recreate_hostif_tap_interfaces on create_switchbut the map the Reprogram on resolve — agreed, and confirmedVerified: Taking it as follow-up, and I've recorded it in the HLD as a limitation with both candidate fixes (reprogram on resolve, or roll back in
|
| entry shape | attrs_count |
warns | IN_PORTS lost |
PRIORITY lost |
|---|---|---|---|---|
| mux standby drop | 6 | no | no | no |
| L3 five-tuple | 11 | no | no | no |
L3V4V6 + ICMP + range + ACL_IP_TYPE + ACL_USER_META |
12 (capped from 21) | yes | yes | yes |
And agreed on acl_priority_attr_get — its SAI_STATUS_FAILURE return is discarded at :768, so a truncated PRIORITY silently leaves the rule at 0 and misorders it. That's the worse half of the bug and I'll fold it into the cap-raise PR alongside the assert/warn, not leave it to a follow-up of a follow-up. Also recorded as a limitation in the HLD in the meantime.
Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
The ACL plugin fills the 5-tuple slot this scope matches on from sw_if_index[VLIB_RX] only when the ACL is bound inbound; bound outbound the same slot holds sw_if_index[VLIB_TX] (get_sw_if_index_xN in dataplane_node.c). Direction is a property of the node, not of the ACL: is_input is a compile-time argument to acl_fa_node_fn(), and none of single_acl_match_5tuple(), single_rule_match_5tuple() or multi_acl_match_get_applied_ace_index() receives it, so the matcher cannot tell the two apart even in principle. Nothing stopped an egress table from reaching the fan-out. SAI puts no stage constraint on SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS, aclorch applies none either, and both TABLE_TYPE_MIRROR and TABLE_TYPE_L3V4V6 declare it as a match while being creatable at egress on this platform. Such an entry would have been programmed normally and then matched against the egress interface - turning "drop what arrives on EthernetX" into "drop what leaves on EthernetX" while the dump still reported the interface the operator named. Emit no rule for such an entry, and let the rest of the table program. Failing the entry instead is not survivable here: createAclEntry() registers the entry in m_objectHash and m_acl_tbl_rules_map before AclAddRemoveCheck() runs and never rolls back, orchagent does not remove a rule whose create failed, and on its retry create_internal() returns ITEM_ALREADY_EXISTS, which AclRule::create() treats as success. The table would be left permanently unprogrammable while orchagent reported the rule ACTIVE - and, since IN_PORTS was previously ignored outright, that would be a regression for a configuration that programs today. Skipping matches how an enabled-but-empty IN_PORTS list is already handled: log, emit nothing, keep the table programmable. The stage is resolved lazily, only once an entry carrying IN_PORTS is seen, and an unreadable stage is treated as ingress, so a table with no scoped entry is untouched by this path. Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
An IN_PORTS list that resolved to no VPP interface failed fill_acl_rules, which aborts AclTblConfig for the whole table. That is too broad a blast radius for one bad entry. ACL tables are shared. MuxOrch's drop rule and the PFC watchdog's rules both live in IngressTableDrop, so a mux entry naming a port that has not appeared in VPP yet would stop the watchdog's rules from being programmed as well. The failure is also sticky. createAclEntry commits the entry with create_internal and registers it in m_acl_tbl_rules_map before AclAddRemoveCheck runs, and rolls back neither, so the entry stays in the table's list and every later add or remove on that table walks it and fails here again. The table keeps its last good rule set but can never be updated, and each orchagent retry allocates a fresh VID and leaks the previous object rather than being recognised as a duplicate, so the entry accumulates in m_objectHash while the rule sits in PENDING_CREATION. Leave the interface set empty instead and let the caller emit no rule for that entry, which is what the egress guard and the empty-list case already do. The damage is then limited to the entry that could not be resolved, and it is still reported. This also makes the partial and total cases one policy rather than two: rules are emitted for the ports that resolved, and resolving none is the degenerate case of that rather than a separate error path. A failure to read the list at all is treated the same way, since the scope is equally unknown and the alternatives are the same. Signed-off-by: Longxiang Lyu <lolv@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
get_sorted_aces reads each entry with get_max capped at MAX_ACL_ATTRS, and the attribute store is a std::map keyed by the attribute name, so an entry that exceeds the cap loses whichever attributes sort last by name rather than whichever matter least. That was survivable while the cached attributes were only scanned for a few qualifiers, but resolving IN_PORTS now depends on the qualifier being present in that dump: an entry that loses it is not reported as scoped and is programmed as though it named no port, which is the table-wide behaviour this series removes. Warn when the cap is reached. This does not raise it - the truncation predates this series and costs more than IN_PORTS, since PRIORITY sorts past the cut on a rich entry and acl_priority_attr_get's failure return is discarded, silently leaving the rule at priority 0 - so the fix belongs in its own change. Until then this is the only signal that an entry was read incompletely. Modelling the cut over the SAI attribute names shows the margin is thin rather than theoretical: a mux drop entry sits at 4 of 12 and an L3 five-tuple at 5, but a full L3V4V6 entry with ICMP, a port range, ACL_IP_TYPE and ACL_USER_META reaches 13 and drops both IN_PORTS and PRIORITY. Signed-off-by: Longxiang Lyu <lolv@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b51bb24 to
fba649b
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
yue-fred-gao
left a comment
There was a problem hiding this comment.
The MAX_ACL_ATTRS could potentially cause problem. A quick fix is increasing it to 20. It is fine if you want to use a separate PR.
…ority (#2073) Description of PR Summary: raises MAX_ACL_ATTRS from 12 to 20 and stops the ACL entry attribute read from discarding a truncated PRIORITY silently. Split out of #2064 at the request of @yue-fred-gao and @aaronber0614. Neither problem is caused by that change — both predate it — so they are fixed here on their own. No dependency between the two PRs; they touch adjacent lines and merge in either order. Fixes # (issue) — n/a, review follow-up. Type of change Bug fix New feature Refactor / cleanup Documentation update Test improvement Approach What is the motivation for this PR? get_sorted_aces() reads each ACL entry back with get_max(), capped at MAX_ACL_ATTRS. The store behind it is a std::map keyed by attribute name (AttrHash in SwitchState.h, keyed by attridname in SwitchStateBase.cpp), and get_max() walks it in iteration order and breaks at the cap. An entry with more attributes than the cap therefore does not lose the ones it was given last — it loses the ones that sort last alphabetically. For names of the form SAI_ACL_ENTRY_ATTR_* that order is: every ACTION_*, then ADMIN_STATE, then every FIELD_*, then PRIORITY, then TABLE_ID. So the first two casualties of any truncation are PRIORITY and TABLE_ID, and it then eats match fields from the tail of the FIELD_* range — SRC_IP, TCP_FLAGS, L4_* among them, all of which this code goes on to read. Nothing about the cut follows what the entry means. At 12 that was close enough to real configurations to matter: entry shape attributes mux standby drop (IN_PORTS + drop + counter) 6 L3 five-tuple 11 L3V4V6 + ICMP + port range + ACL_IP_TYPE + ACL_USER_META past the cap A truncated entry loses its priority first, and the priority read then swallowed the failure: acl_priority_attr_get() returns SAI_STATUS_FAILURE when PRIORITY is absent, the return value was discarded, and the entry kept the 0 initialised on the line above. The rule sorts to the bottom of the table with nothing in the log to say why. How did you do it? Two changes, both in vslib/vpp/: MAX_ACL_ATTRS 12 → 20, with a comment in the header recording that the cut is by name and that PRIORITY/TABLE_ID go first, so the number is not free to trim back later. 20 clears every entry shape orchagent builds in the configurations shipped today. acl_priority_attr_get()'s return value is checked, and a missing PRIORITY is logged with the entry id and its attribute count. Priority still falls back to 0 — failing a whole table over one entry costs more than misordering it, and that lesson is from the review of [vpp][acl] match on ingress interface to support SAI ACL IN_PORTS #2064 — but it is no longer invisible. The second change doubles as the general truncation detector: since PRIORITY is the first attribute the cut discards, this log fires on any future overflow of the raised cap, whatever else was lost alongside it. Work item tracking Microsoft ADO (number only): 39625316 How did you verify/test it? Reasoned from the code rather than from a run, since neither path is reachable with the configurations currently shipped — that is the point of the cap raise: The ordering claim is mechanical: AttrHash is a std::map<std::string, ...> keyed by attridname, get_max() (SwitchVpp.cpp) iterates it and breaks at idx == max_attr_count, so the surviving set is the alphabetical prefix. PRIORITY and TABLE_ID sort after every ACTION_*, ADMIN_STATE and FIELD_* name in sai_acl_entry_attr_t. The attribute counts above were taken from aclMatchLookup / aclL3ActionLookup in sonic-swss/orchagent/aclorch.cpp against the rule shapes SONiC actually writes. The change is confined to a #define and one added if, with no behavioural change on the path where PRIORITY is present. Any platform specific information? VPP only — vslib/vpp/. The extra 8 sai_attribute_t slots are per ACL entry in the calloc in get_sorted_aces(), freed at the end of the same call. Documentation No. Behaviour of an internal read-back limit; nothing user-facing changes.
Description of PR
Summary:
Implements
SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTSfor the VPP platform by fanning each ACL entry out into one VPP rule per named ingress port.A VPP ACL rule has no notion of an ingress interface, so until now the field had nowhere to go: it fell through to the
default:case ofacl_rule_field_update(), which logsUnhandled ACL entry attribute IDand carries on. The entry was still programmed, just without the restriction, so it applied to every port its table was bound to.That is the wrong direction to fail in. On a dual ToR the standby DENY entries name only the standby mux ports, so dropping the restriction turns them into a table wide deny and blackholes traffic on the active ports as well.
The matching VPP side is sonic-net/sonic-platform-vpp#278, which adds an
in_sw_if_indexfield to the ACL rule. Design writeup: sonic-net/SONiC#2506.Type of change
Approach
What is the motivation for this PR?
Fixes: sonic-net/sonic-buildimage#29077
IN_PORTSis how orchagent scopes an ACL entry to a subset of the ports its table is bound to.aclorchrestricts it to physical interfaces, and the dual ToR standby DENY entries rely on it.Silently widening those entries to the whole table is worse than not supporting the field at all, because nothing in the logs distinguishes a rule that was scoped from one that was not.
Work item tracking
How did you do it?
A VPP ACL rule matches one interface, so a SAI entry naming N ports becomes N VPP rules:
acl_entry_in_ports_get()finds the attribute and resolves it. The cached attribute only carries the list count, its list pointer is still null, so the entry is read again with a buffer of the right size. Each port OID is then resolved to a VPP interface name viavpp_get_hwif_name().fill_acl_rules()pops the rules it just generated for the entry and re-adds one copy per resolved interface. This is the same shape as the existing fan-out where a port based entry with no protocol becomes a UDP rule and a TCP rule.vpp_acl_rule_tgainsin_hwif_name. The SAI layer keeps working in interface names, as it already does for binding, andvpp_acl_add_replace()resolves the name to asw_if_indexwhen it builds the API message. Empty means any.SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTSbecomes an explicit no-op inacl_rule_field_update(), since it is not a per rule field.The fanned out rules stay contiguous from
ace.vpp_rule_base_index, sonum_rulesstill describes the entry and the counter aggregation ingetAclEntryStats()sums over them unchanged.Failure handling is deliberate, since three different things can leave the interface set empty and they do not mean the same thing:
The last row matters. On failure the scope of the entry is simply unknown, and installing it unscoped would re-introduce exactly the blackhole this PR is fixing, so
fill_acl_rules()fails andCHECK_STATUS_ACLTBLCONFIGleaves the previous ACL in place.The empty list case is left as "matches nothing" on purpose: no ingress interface can be a member of an empty list, so emitting no rule is the faithful translation.
How did you verify/test it?
Built into
docker-syncd-vppand deployed to avms-kvm-dual-vpp-t0-1dual ToR testbed, then checked the programmed rules against the mux state:vlab-vpp-03: 18 standby ports, 18 matching ingress scoped deny rules.vlab-vpp-04: 6 standby ports, 6 matching ingress scoped deny rules.Rule counts tracking the standby set on two ToRs with different mux distributions is the behaviour this PR is after, and is what regressed before.
Still outstanding: live downstream forwarding under mux toggles (standby → active → standby) has not been run yet, so the dataplane effect is verified by rule inspection rather than by packets.
Any platform specific information?
VPP only.
This depends on sonic-net/sonic-platform-vpp#278 and the two have to merge together. Adding the field changes the
acl_add_replacemessage, and therefore its CRC, so a syncd built against one VPP version cannot program ACLs on the other. That PR also bumpsVPP_VERSIONto2606-0.6.A scale note for reviewers: an entry naming N ports costs N rules, so a table bound to many mux ports grows proportionally. VPP's tuplemerge can additionally fold the fanned out copies into one less specific mask type, and
split_partition()has no interface dimension to split them back apart on, so they can end up on a single collision chain. Correctness is unaffected,single_rule_match_5tuple()in the platform PR re-checks the interface on the collision path, but it is worth knowing before scaling the port count up.Documentation
Design is written up in sonic-net/SONiC#2506.