From 13f75ade8b5f347bafb689eb5880453ec86e9639 Mon Sep 17 00:00:00 2001 From: thineshvs Date: Wed, 21 Oct 2026 13:29:52 +0000 Subject: [PATCH] feat(saiserver): add FDB/Flood tables, VLAN flood ports, and L2 tagging --- dataplane/saiserver/ports.go | 20 +-------- dataplane/saiserver/routing.go | 74 +++++++++++++++++++++++++++++++- dataplane/saiserver/switch.go | 37 +++++++++++++++- dataplane/saiserver/vlan_test.go | 8 ++-- 4 files changed, 115 insertions(+), 24 deletions(-) diff --git a/dataplane/saiserver/ports.go b/dataplane/saiserver/ports.go index 72406d1e..858f8c65 100644 --- a/dataplane/saiserver/ports.go +++ b/dataplane/saiserver/ports.go @@ -64,6 +64,7 @@ var getInterface = net.InterfaceByName func getPreIngressPipeline() []*fwdpb.ActionDesc { return []*fwdpb.ActionDesc{ fwdconfig.Action(fwdconfig.LookupAction(tunTermTable)).Build(), // Decap the packet if we have a tunnel. + fwdconfig.Action(fwdconfig.LookupAction(VlanTable)).Build(), // Classify incoming packets to VLAN. fwdconfig.Action(fwdconfig.LookupAction(inputIfaceTable)).Build(), // Match packet to interface. fwdconfig.Action(fwdconfig.LookupAction(IngressVRFTable)).Build(), // Match interface to VRF. fwdconfig.Action(fwdconfig.LookupAction(PreIngressActionTable)).Build(), // Run pre-ingress actions. @@ -101,7 +102,7 @@ func getL3Pipeline(skipIPValidation bool) []*fwdpb.ActionDesc { func getL2Pipeline() []*fwdpb.ActionDesc { return []*fwdpb.ActionDesc{ fwdconfig.Action(fwdconfig.LookupAction(IngressActionTable)).Build(), // Run ingress action. - fwdconfig.Action(fwdconfig.LookupAction(outputTable)).Build(), // Take final decision on forward, drop, or trap. + fwdconfig.Action(fwdconfig.LookupAction(FloodTable)).Build(), // Match VLAN tag or fallback to outputTable. { ActionType: fwdpb.ActionType_ACTION_TYPE_OUTPUT, }, @@ -327,23 +328,6 @@ func (port *port) CreatePort(ctx context.Context, req *saipb.CreatePortRequest) NumberOfActivePorts: proto.Uint32(uint32(len(portList))), }) - nid, err := port.dataplane.ObjectNID(ctx, &fwdpb.ObjectNIDRequest{ - ContextId: &fwdpb.ContextId{Id: port.dataplane.ID()}, - ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(id)}, - }) - if err != nil { - slog.InfoContext(ctx, "Failed to find NID for port", "port", id, "err", err) - return nil, err - } - vlanReq := fwdconfig.TableEntryAddRequest(port.dataplane.ID(), VlanTable).AppendEntry( - fwdconfig.EntryDesc(fwdconfig.ExactEntry(fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_PACKET_PORT_INPUT).WithUint64(nid.GetNid())))).Build() - vlanReq.Entries[0].Actions = []*fwdpb.ActionDesc{ - fwdconfig.Action(fwdconfig.EncapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET_VLAN)).Build(), - fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithUint64Value(uint64(attrs.GetPortVlanId()))).Build(), - } - if _, err := port.dataplane.TableEntryAdd(ctx, vlanReq); err != nil { - return nil, err - } return &saipb.CreatePortResponse{ Oid: id, }, nil diff --git a/dataplane/saiserver/routing.go b/dataplane/saiserver/routing.go index 759d9033..31b28e4e 100644 --- a/dataplane/saiserver/routing.go +++ b/dataplane/saiserver/routing.go @@ -1360,6 +1360,24 @@ func (vlan *vlan) CreateVlan(ctx context.Context, r *saipb.CreateVlanRequest) (* vlan.mgr.StoreAttributes(id, attrs) vlan.vlans[id] = map[uint64]*vlanMember{} vlan.oidByVId[r.GetVlanId()] = id + + floodID := fmt.Sprintf("vlan_flood_%d", r.GetVlanId()) + if _, err := vlan.dataplane.PortCreate(ctx, &fwdpb.PortCreateRequest{ + ContextId: &fwdpb.ContextId{Id: vlan.dataplane.ID()}, + Port: &fwdpb.PortDesc{PortType: fwdpb.PortType_PORT_TYPE_AGGREGATE_PORT, PortId: &fwdpb.PortId{ObjectId: &fwdpb.ObjectId{Id: floodID}}}, + }); err != nil { + return nil, err + } + if _, err := 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_AggregateAlgo{ + AggregateAlgo: &fwdpb.AggregatePortAlgorithmUpdateDesc{Hash: fwdpb.AggregateHashAlgorithm_AGGREGATE_HASH_ALGORITHM_FLOOD}, + }}, + }); err != nil { + return nil, err + } + return &saipb.CreateVlanResponse{ Oid: id, }, nil @@ -1442,12 +1460,56 @@ func (vlan *vlan) CreateVlanMember(ctx context.Context, r *saipb.CreateVlanMembe if r.GetVlanTaggingMode() != saipb.VlanTaggingMode_VLAN_TAGGING_MODE_TAGGED { vlanReq.Entries[0].Actions = []*fwdpb.ActionDesc{ fwdconfig.Action(fwdconfig.EncapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET_VLAN)).Build(), - fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithUint64Value(uint64(vId))).Build(), + fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithValue(binary.BigEndian.AppendUint16(nil, uint16(vId)))).Build(), } } if _, err := vlan.dataplane.TableEntryAdd(ctx, vlanReq); err != nil { return nil, err } + 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 + } + + floodID := fmt.Sprintf("vlan_flood_%d", vId) + if _, err := 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}, + }}, + }); err != nil { + return nil, err + } + floodReq := fwdconfig.TableEntryAddRequest(vlan.dataplane.ID(), FloodTable).AppendEntry( + fwdconfig.EntryDesc(fwdconfig.ExactEntry( + fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithBytes(binary.BigEndian.AppendUint16(nil, uint16(vId))), + fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_PACKET_ACTION).WithBytes([]byte{1}), + )), + fwdconfig.TransmitAction(floodID), + fwdconfig.BridgeLearnAction(FDBTable), + fwdconfig.LookupAction(FDBTable), + ).Build() + if _, err := vlan.dataplane.TableEntryAdd(ctx, floodReq); err != nil { + return nil, err + } // Update the attributes and internal data. vlanAttrReq := &saipb.GetVlanAttributeRequest{Oid: vOid, AttrType: []saipb.VlanAttr{saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST}} vlanAttrResp := &saipb.GetVlanAttributeResponse{} @@ -1516,6 +1578,16 @@ func (vlan *vlan) RemoveVlanMember(ctx context.Context, r *saipb.RemoveVlanMembe fwdconfig.EntryDesc(fwdconfig.ExactEntry(fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_PACKET_PORT_INPUT).WithUint64(nid.GetNid())))).Build()); err != nil { return nil, err } + floodID := fmt.Sprintf("vlan_flood_%d", member.Vid) + if _, err := 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_AggregateDel{ + AggregateDel: &fwdpb.AggregatePortRemoveMemberUpdateDesc{PortId: &fwdpb.PortId{ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(member.PortID)}}}, + }}, + }); err != nil { + return nil, err + } delete(vlan.vlans[targetVlanOid], r.GetOid()) diff --git a/dataplane/saiserver/switch.go b/dataplane/saiserver/switch.go index d76d9ecb..b6e3f8af 100644 --- a/dataplane/saiserver/switch.go +++ b/dataplane/saiserver/switch.go @@ -191,6 +191,8 @@ const ( tunTermTable = "tun-term" VlanTable = "vlan" L2MCGroupTable = "l2mcg" + FDBTable = "fdb" + FloodTable = "flood" policerTabler = "policerTable" invalidIngress = "invalid-ingress" invalidIngressV4Table = "invalid-ingress-v4" @@ -410,7 +412,7 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ Desc: &fwdpb.TableDesc{ TableType: fwdpb.TableType_TABLE_TYPE_EXACT, TableId: &fwdpb.TableId{ObjectId: &fwdpb.ObjectId{Id: VlanTable}}, - Actions: []*fwdpb.ActionDesc{{ActionType: fwdpb.ActionType_ACTION_TYPE_DROP}}, + Actions: []*fwdpb.ActionDesc{{ActionType: fwdpb.ActionType_ACTION_TYPE_CONTINUE}}, Table: &fwdpb.TableDesc_Exact{ Exact: &fwdpb.ExactTableDesc{ FieldIds: []*fwdpb.PacketFieldId{{ @@ -773,6 +775,39 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ return nil, err } + fdbReq := &fwdpb.TableCreateRequest{ + ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()}, + Desc: &fwdpb.TableDesc{ + TableType: fwdpb.TableType_TABLE_TYPE_BRIDGE, + TableId: &fwdpb.TableId{ObjectId: &fwdpb.ObjectId{Id: FDBTable}}, + Table: &fwdpb.TableDesc_Bridge{ + Bridge: &fwdpb.BridgeTableDesc{}, + }, + }, + } + if _, err := sw.dataplane.TableCreate(ctx, fdbReq); err != nil { + return nil, err + } + floodReq := &fwdpb.TableCreateRequest{ + ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()}, + Desc: &fwdpb.TableDesc{ + TableType: fwdpb.TableType_TABLE_TYPE_EXACT, + TableId: &fwdpb.TableId{ObjectId: &fwdpb.ObjectId{Id: FloodTable}}, + Actions: []*fwdpb.ActionDesc{fwdconfig.Action(fwdconfig.LookupAction(outputTable)).Build()}, + Table: &fwdpb.TableDesc_Exact{ + Exact: &fwdpb.ExactTableDesc{ + FieldIds: []*fwdpb.PacketFieldId{ + {Field: &fwdpb.PacketField{FieldNum: fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG}}, + {Field: &fwdpb.PacketField{FieldNum: fwdpb.PacketFieldNum_PACKET_FIELD_NUM_PACKET_ACTION}}, + }, + }, + }, + }, + } + if _, err := sw.dataplane.TableCreate(ctx, floodReq); err != nil { + return nil, err + } + myMAC := &fwdpb.TableCreateRequest{ ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()}, Desc: &fwdpb.TableDesc{ diff --git a/dataplane/saiserver/vlan_test.go b/dataplane/saiserver/vlan_test.go index 75f5a548..c1f5e3b1 100644 --- a/dataplane/saiserver/vlan_test.go +++ b/dataplane/saiserver/vlan_test.go @@ -16,6 +16,7 @@ package saiserver import ( "context" + "encoding/binary" "fmt" "testing" @@ -403,7 +404,7 @@ func TestCreateVlanMemberTaggingMode(t *testing.T) { taggingMode: saipb.VlanTaggingMode_VLAN_TAGGING_MODE_UNTAGGED, wantActions: []*fwdpb.ActionDesc{ fwdconfig.Action(fwdconfig.EncapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET_VLAN)).Build(), - fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithUint64Value(uint64(vlanID))).Build(), + fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithValue(binary.BigEndian.AppendUint16(nil, uint16(vlanID)))).Build(), }, }, { desc: "tagged member preserves existing 802.1Q header without re-encapsulating", @@ -447,8 +448,8 @@ func TestCreateVlanMemberTaggingMode(t *testing.T) { t.Fatalf("CreateVlanMember failed: %v", err) } - if len(dplane.gotEntryAddReqs) != 1 { - t.Fatalf("got %d TableEntryAdd requests, want 1", len(dplane.gotEntryAddReqs)) + if len(dplane.gotEntryAddReqs) != 2 { + t.Fatalf("got %d TableEntryAdd requests, want 2", len(dplane.gotEntryAddReqs)) } gotEntries := dplane.gotEntryAddReqs[0].GetEntries() if len(gotEntries) != 1 { @@ -460,4 +461,3 @@ func TestCreateVlanMemberTaggingMode(t *testing.T) { }) } } -