Repository navigation
Checkpoint 3 l2 fdb - #722
thineshvs-google wants to merge 7 commits into
Conversation
…d entrypoint stubs (checkpoint 1)
…ng and aging events
…d SAI Bookworm build (checkpoint 2) - dataplane/saiserver/routing.go: - Dynamically set packet output decap pipeline on VLAN member ports based on VlanTaggingMode (TAGGED keeps 802.1Q header on wire; UNTAGGED decaps 802.1Q header). - Store BridgePortID in vlanMember struct for fast, reliable bridge port OID resolution. - dataplane/saiserver/switch.go: - Fixed onBridgeLearn bridge port resolution for all physical ports (including high port IDs) via member.BridgePortID and PopulateAttributes. - dataplane/saiserver/fdb.go & dataplane/forwarding/: - Fixed FlushFdbEntries and RemoveFdbEntry to synchronize state with internal fwdbridge.Table. - dataplane/standalone/sai/fdb.cc: - Added conversion from sai_fdb_entry_t to protobuf in SAI FDB entry API functions and bulk operations. - dataplane/standalone/BUILD & Dockerfile.saibuilder: - Added Debian 12 Bookworm container build definition with static libstdc++/libgcc linking for SONiC runtime.
mohankarthik
left a comment
There was a problem hiding this comment.
Leaving inline comments on the sections of #722 corresponding to Chunk 2 (#759), Chunk 3 (l2-chunk3-vlan-flood-tagging), and Chunk 7 (l2-chunk7-switch-bridge-learn) so these edge cases (hybrid untagged+tagged ports, LAG VLAN members, and vlan.mu synchronization) can be resolved directly as the remaining chunks are split out.
For reference, the main-compatible portions are already split out into small standalone PRs:
- #762 (
vlan.musync.RWMutexacross all map accesses +RemoveVlanMemberrollback on dataplane error) - #763 (
RemoveVlanMemberVLAN_ATTR_MEMBER_LISTcleanup) - #764 (composite
(PACKET_PORT_INPUT, VLAN_TAG)key inVlanTablefor hybrid untagged+tagged ports) - #765 (
lag.musynchronization +lag.Reset()) - #766 (
OBJECT_TYPE_LAGVLAN membership programming across physical member ports)
| func (vlan *vlan) oidByVid(vid uint32) (uint64, bool) { | ||
| vlan.mu.Lock() | ||
| defer vlan.mu.Unlock() | ||
| oid, ok := vlan.oidByVId[vid] | ||
| return oid, ok | ||
| } |
There was a problem hiding this comment.
Incomplete vlan.mu synchronization and lock held across dataplane RPCs:
memberByPortId(line 1254) andoidByVid(line 1267) acquirevlan.mu, butvidByOid(line 1234),memberByOid(line 1242),CreateVlan(lines 1274, 1302–1303),CreateVlanMember(line 1476vlan.oidByVId[member.Vid]), andReset()(lines 1562–1563) read and writevlan.oidByVIdandvlan.vlanswithout holdingvlan.mu, causing concurrent map read/write panics under-race.- In
RemoveVlanMember(lines 1499–1500),vlan.mu.Lock(); defer vlan.mu.Unlock()is held across synchronousObjectNID,TableEntryRemove, andPortUpdatedataplane RPCs, andVLAN_ATTR_MEMBER_LISTon the parent VLAN attribute is never updated to remover.GetOid().
We split out the complete sync.RWMutex fix (releasing the lock before dataplane RPCs and rolling back if ObjectNID/TableEntryRemove fails) in #762 and the VLAN_ATTR_MEMBER_LIST cleanup in #763 so Chunk 2 / Chunk 3 can rebase directly on top of them.
| outputActions := []*fwdpb.ActionDesc{} | ||
| if r.GetVlanTaggingMode() == saipb.VlanTaggingMode_VLAN_TAGGING_MODE_UNTAGGED || r.GetVlanTaggingMode() == saipb.VlanTaggingMode_VLAN_TAGGING_MODE_PRIORITY_TAGGED { | ||
| outputActions = []*fwdpb.ActionDesc{ | ||
| fwdconfig.Action(fwdconfig.DecapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET_VLAN)).Build(), | ||
| } | ||
| } | ||
| portUpd := &fwdpb.PortUpdateRequest{ | ||
| ContextId: &fwdpb.ContextId{Id: vlan.dataplane.ID()}, | ||
| PortId: &fwdpb.PortId{ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(portID)}}, | ||
| Update: &fwdpb.PortUpdateDesc{ | ||
| Port: &fwdpb.PortUpdateDesc_Kernel{ | ||
| Kernel: &fwdpb.KernelPortUpdateDesc{ | ||
| Inputs: getPreIngressPipeline(), | ||
| Outputs: outputActions, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| if _, err := vlan.dataplane.PortUpdate(ctx, portUpd); err != nil { | ||
| return nil, err | ||
| } |
There was a problem hiding this comment.
Per-port KernelPortUpdateDesc.Outputs VLAN decap breaks hybrid ports and LAGs:
Setting DecapAction(PACKET_HEADER_ID_ETHERNET_VLAN) on KernelPortUpdateDesc.Outputs (in ports.go:309-311 and routing.go:1421-1441) applies a single egress action to the entire port rather than per-(port, VLAN):
- Hybrid ports (untagged native VLAN + tagged VLANs on the same port): Whichever
CreateVlanMembercall runs last overwritesOutputsfor the entire port. If anUNTAGGEDVLAN (e.g.Vlan42) is programmed after aTAGGEDVLAN (e.g.Vlan666),Outputsis set toDecapAction, stripping the 802.1Q tag fromVlan666egress frames. Conversely, ifTAGGEDruns afterUNTAGGED,Outputsis cleared ([]), leaving 802.1Q tags onVlan42egress frames. - LAG (
OBJECT_TYPE_LAG) members: WhenportIDis a LAG OID (PortChannel), callingPortUpdatewithPortUpdateDesc_Kernelfails because the port is anAggregatePort, not aKernelPort. RemoveVlanMember:RemoveVlanMembernever updates or restoresOutputs.
Suggested fix for Chunk 3:
Instead of mutating KernelPortUpdateDesc.Outputs, add an exact-match EgressVlanTable ("egress-vlan", keyed on (PACKET_FIELD_NUM_TARGET_EGRESS_PORT, PACKET_FIELD_NUM_VLAN_TAG) with default ContinueAction) right before ACTION_TYPE_OUTPUT in getL2Pipeline() (ports.go:103-113), and program DecapAction(PACKET_HEADER_ID_ETHERNET_VLAN) into EgressVlanTable only for (portNID, vid) entries whose mode is UNTAGGED or PRIORITY_TAGGED (see #756 for the reference implementation). For ingress hybrid port classification ((PACKET_PORT_INPUT, VLAN_TAG) composite key in VlanTable), see #764.
| floodID := fmt.Sprintf("vlan_flood_%d", vId) | ||
| _, _ = vlan.dataplane.PortUpdate(ctx, &fwdpb.PortUpdateRequest{ | ||
| ContextId: &fwdpb.ContextId{Id: vlan.dataplane.ID()}, | ||
| PortId: &fwdpb.PortId{ObjectId: &fwdpb.ObjectId{Id: floodID}}, | ||
| Update: &fwdpb.PortUpdateDesc{ | ||
| Port: &fwdpb.PortUpdateDesc_AggregateAdd{ | ||
| AggregateAdd: &fwdpb.AggregatePortAddMemberUpdateDesc{ | ||
| PortId: &fwdpb.PortId{ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(portID)}}, | ||
| InstanceCount: 1, | ||
| }, | ||
| }, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
vlan_flood_<vId> aggregate port updates need OBJECT_TYPE_LAG expansion and hybrid-port safety:
- If a port is a hybrid member of the same or multiple VLANs or if
RemoveVlanMemberremoves one membership while another remains,AggregateAdd/AggregateDelonvlan_flood_<vId>should ensure physical ports are not added twice or removed while still a member. - When
r.GetBridgePortId()wraps a LAG (OBJECT_TYPE_LAG),portIDis the LAG OID rather than physical port OIDs, and member ports can also be added/removed dynamically viaCreateLagMember/RemoveLagMember. Once fix(saiserver): program LAG VLAN memberships on LAG member ports #766 lands,FloodTable/vlan_flood_<vId>in Chunk 3 can usetargetPhysicalPorts(portID)so LAG VLAN members flood across constituent physical ports.
| var vlanOID uint64 | ||
| var vlanID uint32 = uint32(DefaultVlanId) | ||
|
|
||
| if sw.vlan != nil { | ||
| if member := sw.vlan.memberByPortId(portNum); member != nil { | ||
| vlanID = member.Vid | ||
| if member.BridgePortID != 0 { | ||
| bridgePortOID = member.BridgePortID | ||
| } | ||
| if oid, ok := sw.vlan.oidByVid(vlanID); ok { | ||
| vlanOID = oid | ||
| slog.Info("onBridgeLearn: found VLAN for port", "portNum", portNum, "vlanID", vlanID, "vlanOID", vlanOID) | ||
| } else { | ||
| slog.Warn("onBridgeLearn: failed to get OID for VLAN", "vlanID", vlanID) | ||
| } | ||
| } else { | ||
| slog.Warn("onBridgeLearn: no VLAN member found for port", "portNum", portNum) | ||
| } | ||
| } else { |
There was a problem hiding this comment.
onBridgeLearn resolves VLAN via memberByPortId(portNum), which is ambiguous on hybrid/trunk ports and fails on LAG member ports:
- In
bridge.go:169(merged in Chunk 1 feat(forwarding): add BridgeLearnAction builder, exact RemoveKey, and… #757),t.LearnCallback(req.mac, string(fwdport.GetID(port).GetObjectId().GetId()))passes only(mac, portID)without the packet'sPACKET_FIELD_NUM_VLAN_TAG. - In
switch.go:1331,sw.vlan.memberByPortId(portNum)iteratesvlan.vlansand returns the first matchingvlanMemberforportNum.- On a hybrid or trunk port belonging to multiple VLANs (e.g.
Vlan42untagged +Vlan666tagged), Go map iteration order is non-deterministic, so learned MACs get attributed to the wrongvlanOID. - On a LAG member physical port,
member.PortIDinvlan.vlansis the LAG OID (PortChannel), somemberByPortId(portNum)returnsniland falls back toDefaultVlanId(VLAN 1), and theBRIDGE_PORT_ATTR_PORT_IDlookup (lines 1349–1362) fails to find the LAG's bridge port OID.
- On a hybrid or trunk port belonging to multiple VLANs (e.g.
Suggested fix for Chunk 7:
- Update
learnReq/Learn()indataplane/forwarding/fwdtable/bridge/bridge.goto readPACKET_FIELD_NUM_VLAN_TAGfrom the packet and passvid uint16toLearnCallback(req.mac, portID, vid). - In
onBridgeLearn, look up thevlanMemberby(portNum, uint32(vid))(checking both direct physical port membership and parent LAG membership viasw.LagwhenportNumis a LAG member port; see feat(dataplane): L2 FDB learning, per-(port, VLAN) ingress/egress tables, hybrid VLANs, and LAG VLAN programming (ref for #722) #756).
No description provided.