Skip to content

[mcast] remove multicast feature + keep routing table sizes - #371

Open
zeeshanlakhani wants to merge 4 commits into
mainfrom
zl/mcast-remove-feature
Open

zeeshanlakhani wants to merge 4 commits into
mainfrom
zl/mcast-remove-feature

Conversation

@zeeshanlakhani

@zeeshanlakhani zeeshanlakhani commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

This work is split up into two pieces in turning multicast "on" by default:

[mcast, feature] Feature removal

This removes the multicast feature gating altogether, making multicast inclusive to the work in dendrite/dpd/sidecar.

[p4, sidecar] keep (IPV*_LPM_SIZE - 1) entries for routing tables w/ multicast

Removing the multicast feature gate got us over Tofino's 20-stage pipeline limit. The table resizing work in #268 matched the v6 LPM table size to what we had in v4, putting us at 20 stages without multicast.

In #208, I explored a few different approaches torefactor our pipeline, across ingress and egress, to give us more breathing room (stage-wise). However, we want to be efficient and targeted when making changes to the sidecar.

Reducing mcast table sizes was my first attempt. Route and replication tables fit in SRAM; SSM is TCAM. The result, sadly, was fewer TCAM blocks, but the same stage count.

Instead, I went the direction of fulfilling a Nils'ism:

XXX: this control could be moved to the Egress pipeline if we need more space
in the Ingress pipeline. Currently unicast packets are able to bypass that
pipeline, which is why we've tacked it on here. We could probably also merge
it with the MacRewrite control, as they are both per-port settings, but that
would present some weird semantics to the control plane daemon.

I moved the EgressFilter control to the egress pipeline to save a stage, carrying (or bridging) the NAT egress bit in the bridge header. I did not move the MacRewrite control over to handle both uni+multi(cast) in egress, but that was demonstrated in #208 for a follow-up (post-testing).

With the move, 20 stages is achieved! However, the v4/v6 route tests still stopped below 8191 entries with a deficit. The exact-match route-target tables were losing capacity under four-way cuckoo placement (default). @ways(8) was the trick I found that gives each key in the table eight placement choices, which now makes table tests fill all 8191 entries.

Includes:

  • Removal of the #ifdef MULTICAST codegen guards.
  • Counter updates to match our move of the NAT egress filter

This removes the multicast feature gating altogether, making multicast
inclusive to the work in dendrite/dpd/sidecar.
…multicast

Removing the multicast feature gate got us over Tofino's 20-stage
pipeline limit. The table resizing work in #268 matched the v6 LPM
table size to what we had in v4, putting us at 20 stages without
multicast.

In #208, I explored a few different approaches to refactor our pipeline,
across ingress and egress, to give us more breathing room (stage-wise).
However, we want to be efficient and targeted when making changes to
the sidecar.

Reducing mcast table sizes was my first attempt. Route and replication tables
fit in SRAM; SSM is TCAM. The result, sadly, was fewer TCAM blocks, but
the same stage count.

Instead, I went the direction of fulfilling a Nils'ism:

> XXX: this control could be moved to the Egress pipeline if we need more space
> in the Ingress pipeline.  Currently unicast packets are able to bypass that
> pipeline, which is why we've tacked it on here.  We could probably also merge
> it with the MacRewrite control, as they are both per-port settings, but that
> would present some weird semantics to the control plane daemon.

I moved the `EgressFilter` control to the egress pipeline to save a
stage, carrying (or bridging) the NAT egress bit in the bridge header.
I *did not* move the `MacRewrite` control over to handle both
uni+multi(cast) in egress, but that was demonstrated in #208 for a
follow-up (post-testing).

With the move, 20 stages is achieved! However, the v4/v6 route tests
still stopped below 8191 entries with a deficit. The exact-match
route-target tables were losing capacity under four-way
cuckoo placement (default). [`@ways(8)`][ways] was the trick I found
that gives each key in the table eight placement choices, which now makes
table tests fill all 8191 entries.

Includes:
  - Removal of the `#ifdef MULTICAST` codegen guards.
  - Counter updates to match our move of the NAT egress filter
  - Ordering of the NAT egress filter before the CPU-copy
   (`egress_rid`) check, making sure NAT unicast is not treated as
   a multicast CPU copy.

[ways]: https://github.com/p4lang/p4c/blob/a19f1c3d85a867a6288fd983f7bad505ac47d728/backends/tofino/bf-p4c/common/pragma/pragmas.cpp#L1172-L1183

@cfzimmerman cfzimmerman left a comment

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.

  1. From an aesthetic perspective, this PR is magnificent. Tysm for doing this. Very excited to see it land.
  2. What's the plan for validation? All good if E2E testing needs to come at the top of the stack, but do our current CI tests cover these changes well? Both for new functionality and to prevent regressions. If not, I'd appreciate whatever is needed to fill the gap.
  3. I'm still pretty new to our P4 program, so I'm unlikely to give a great review on that. Hopefully we can get at least one more reviewer. I'm also experimenting with Claude PR reviews. Imo it's not a replacement for human review, but they are a nice supplement. I'm going to have Claude take a look at this, and I'll comb through some of its comments to see how useful they are.

Lmk once you feel confident about validation, and I'm happy to stamp.

Comment thread dpd-client/tests/integration_tests/table_tests.rs
Comment thread dpd/p4/constants.p4
Comment thread dpd/src/counters.rs Outdated
cfzimmerman

This comment was marked as resolved.

@cfzimmerman

This comment was marked as resolved.

@cfzimmerman cfzimmerman left a comment

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.

Recent changes lgtm. Although a second reviewer warranted before merge, particularly to review the p4 diffs.

@FelixMcFelix FelixMcFelix left a comment

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.

Thanks Zeeshan.

Comment thread dpd/p4/constants.p4
Comment on lines -9 to -10
// TODO: these all need to be bigger. Early experimentation is showing that this
// is going to need to come either through ATCAM/ALPM or code restructuring.

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.

I think this comment still holds true. 1024 entries for, e.g., NAT is quite paltry compared to how many VMs you can feasibly run on a single rack (particularly with each OPTE port consuming several such entries).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can return/extend it.

Comment thread asic/src/tofino_asic/mod.rs Outdated
Comment thread dpd/src/link.rs Outdated
Comment thread dpd/src/macaddrs.rs Outdated
Comment thread dpd/p4/sidecar.p4
*
* See https://github.com/p4lang/p4c/blob/a19f1c3d85a867a6288fd983f7bad505ac47d728/backends/tofino/bf-p4c/common/pragma/pragmas.cpp#L1172-L1183.
*/
@ways(8)

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.

Out of curiosity, do we have any idea what this defaults to?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  1. I'll add that to the comment.

Comment thread dpd/p4/sidecar.p4
Comment on lines 2132 to 2141
if (ig_tm_md.ucast_egress_port != USER_SPACE_SERVICE_PORT) {
mac_rewrite.apply(hdr, ig_tm_md.ucast_egress_port);
}
meta.bridge_hdr.setInvalid();
ig_tm_md.bypass_egress = 1w1;
if (meta.nat_egress_hit && !meta.service_routed) {
meta.bridge_hdr.nat_egress_hit = true;
} else {
meta.bridge_hdr.setInvalid();
ig_tm_md.bypass_egress = 1w1;
}
}

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.

I think the change in meanings of counters is confusing enough that it should be documented or worked around.

My current read is that to get the true egress count on a port, you need to compute pipe.Ingress.egress_ctr[port_idx] - pipe.Egress.drop_port_ctr[port_idx]. Not unreasonable, but it requires decent knowledge of the dataplane to make sense. Thinking on alternate approaches:

  • Could we move the egress counter hit into the else block? This way the sum of packets allowed to leave on a port is pipe.Ingress.egress_ctr[port_idx] + pipe.Egress.unicast_ctr[port_idx] (plus + pipe.Egress.mcast_ctr[port_idx], if you're so inclined). The counter names are still a little misleading this way, in that Ingress.egress_ctr would be underlay plus inbound nat'd traffic, whereas Egress.unicast_ctr is outbound traffic being decapsulated successfully.
  • Does bypass_egress give us meaningful latency improvement? Does it make enough difference to prevent us pushing all packets through the egress pipeline, and then we only need pipe.Egress.unicast_ctr[port_idx]? This would also mesh well with making MacRewrite an unconditional part of the egress pipeline in future.

@zeeshanlakhani

Copy link
Copy Markdown
Contributor Author

On #371 (comment),

I was being a bit conservative here, but the second alternative approach is what I already explored in #208 (with the MAC rewriting, however). I'll make that update, as it's cleaner anyway and sets us up for that move.

This work follows up on Kyle's review, including adding documentation
on `@ways` defaults (and now refers to a more informational link)
and removing leftover, unnecessary `{}` blocks.

The main change is that every packet copy now runs through to the egress
pipeline, no longer relying on `bypass_egress` in the ingress pipeline.
This follows from #208 and makes the counters more explicit:

- `forwarded_ctr` records all packets leaving a port;
- `unicast_ctr` and `mcast_ctr` now partition what gets forwarded by type;
- drops are recorded separately with port and reason counters.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants