From ca43c611caba61c8df17fed156f22c0c0a0c689a Mon Sep 17 00:00:00 2001 From: thineshvs Date: Mon, 27 Jul 2026 13:24:54 +0000 Subject: [PATCH 1/4] Add SAI L2 bridging, FDB learning, and BUM flooding support --- dataplane/forwarding/fwdconfig/action.go | 22 +++++++ dataplane/saiserver/fdb.go | 73 ++++++++++++++++++++++++ dataplane/saiserver/ports.go | 14 +++-- dataplane/saiserver/routing.go | 57 +++++++++++++++++- dataplane/saiserver/switch.go | 35 ++++++++++++ 5 files changed, 196 insertions(+), 5 deletions(-) diff --git a/dataplane/forwarding/fwdconfig/action.go b/dataplane/forwarding/fwdconfig/action.go index a64e2e21..803af4fd 100644 --- a/dataplane/forwarding/fwdconfig/action.go +++ b/dataplane/forwarding/fwdconfig/action.go @@ -409,3 +409,25 @@ func (m *MirrorActionBuilder) set(a *fwdpb.ActionDesc) { func (m *MirrorActionBuilder) actionType() fwdpb.ActionType { return fwdpb.ActionType_ACTION_TYPE_MIRROR } + +// BridgeLearnActionBuilder is a builder for a bridge learn action. +type BridgeLearnActionBuilder struct { + tableID string +} + +// BridgeLearnAction returns a new bridge learn action builder. +func BridgeLearnAction(tableID string) *BridgeLearnActionBuilder { + return &BridgeLearnActionBuilder{tableID: tableID} +} + +func (b *BridgeLearnActionBuilder) set(a *fwdpb.ActionDesc) { + a.Action = &fwdpb.ActionDesc_Bridge{ + Bridge: &fwdpb.BridgeLearnActionDesc{ + TableId: &fwdpb.TableId{ObjectId: &fwdpb.ObjectId{Id: b.tableID}}, + }, + } +} + +func (b *BridgeLearnActionBuilder) actionType() fwdpb.ActionType { + return fwdpb.ActionType_ACTION_TYPE_BRIDGE_LEARN +} diff --git a/dataplane/saiserver/fdb.go b/dataplane/saiserver/fdb.go index 2f1350ec..f21174aa 100644 --- a/dataplane/saiserver/fdb.go +++ b/dataplane/saiserver/fdb.go @@ -16,11 +16,16 @@ package saiserver import ( "context" + "fmt" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/openconfig/lemming/dataplane/forwarding/fwdconfig" saipb "github.com/openconfig/lemming/dataplane/proto/sai" "github.com/openconfig/lemming/dataplane/saiserver/attrmgr" + fwdpb "github.com/openconfig/lemming/proto/forwarding" ) type fdb struct { @@ -41,3 +46,71 @@ func newFdb(mgr *attrmgr.AttrMgr, dataplane switchDataplaneAPI, s *grpc.Server) func (f *fdb) FlushFdbEntries(ctx context.Context, req *saipb.FlushFdbEntriesRequest) (*saipb.FlushFdbEntriesResponse, error) { return &saipb.FlushFdbEntriesResponse{}, nil } + +func (f *fdb) CreateFdbEntry(ctx context.Context, req *saipb.CreateFdbEntryRequest) (*saipb.CreateFdbEntryResponse, error) { + entry := req.GetEntry() + if entry == nil { + return nil, status.Errorf(codes.InvalidArgument, "FDB entry is required") + } + + mac := entry.GetMacAddress() + if len(mac) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "MAC address is required") + } + + portOID := req.GetBridgePortId() + if portOID == 0 { + return nil, status.Errorf(codes.InvalidArgument, "Bridge port ID is required") + } + + bpReq := &saipb.GetBridgePortAttributeRequest{ + Oid: portOID, + AttrType: []saipb.BridgePortAttr{saipb.BridgePortAttr_BRIDGE_PORT_ATTR_PORT_ID}, + } + bpResp := &saipb.GetBridgePortAttributeResponse{} + if err := f.mgr.PopulateAttributes(bpReq, bpResp); err != nil { + return nil, fmt.Errorf("failed to populate bridge port %d: %v", portOID, err) + } + + portID := bpResp.GetAttr().GetPortId() + if portID == 0 { + return nil, fmt.Errorf("cannot find port ID for bridge port %d", portOID) + } + + addReq := fwdconfig.TableEntryAddRequest(f.dataplane.ID(), FDBTable).AppendEntry( + fwdconfig.EntryDesc(fwdconfig.ExactEntry( + fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_ETHER_MAC_DST).WithBytes(mac), + )), + fwdconfig.TransmitAction(fmt.Sprint(portID)), + ).Build() + + if _, err := f.dataplane.TableEntryAdd(ctx, addReq); err != nil { + return nil, fmt.Errorf("failed to add FDB entry to dataplane: %v", err) + } + + return &saipb.CreateFdbEntryResponse{}, nil +} + +func (f *fdb) RemoveFdbEntry(ctx context.Context, req *saipb.RemoveFdbEntryRequest) (*saipb.RemoveFdbEntryResponse, error) { + entry := req.GetEntry() + if entry == nil { + return nil, status.Errorf(codes.InvalidArgument, "FDB entry is required") + } + + mac := entry.GetMacAddress() + if len(mac) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "MAC address is required") + } + + delReq := fwdconfig.TableEntryRemoveRequest(f.dataplane.ID(), FDBTable).AppendEntry( + fwdconfig.EntryDesc(fwdconfig.ExactEntry( + fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_ETHER_MAC_DST).WithBytes(mac), + )), + ).Build() + + if _, err := f.dataplane.TableEntryRemove(ctx, delReq); err != nil { + return nil, fmt.Errorf("failed to remove FDB entry from dataplane: %v", err) + } + + return &saipb.RemoveFdbEntryResponse{}, nil +} diff --git a/dataplane/saiserver/ports.go b/dataplane/saiserver/ports.go index 9ab6f834..d94901f3 100644 --- a/dataplane/saiserver/ports.go +++ b/dataplane/saiserver/ports.go @@ -16,6 +16,7 @@ package saiserver import ( "context" + "encoding/binary" "fmt" "log/slog" "net" @@ -64,6 +65,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 +103,9 @@ 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.BridgeLearnAction(FDBTable)).Build(), // Learn source MAC into FDB. + fwdconfig.Action(fwdconfig.LookupAction(FDBTable)).Build(), // Match dest MAC in FDB. + fwdconfig.Action(fwdconfig.LookupAction(FloodTable)).Build(), // Match VLAN tag if FDB misses. { ActionType: fwdpb.ActionType_ACTION_TYPE_OUTPUT, }, @@ -301,8 +305,10 @@ func (port *port) CreatePort(ctx context.Context, req *saipb.CreatePortRequest) Update: &fwdpb.PortUpdateDesc{ Port: &fwdpb.PortUpdateDesc_Kernel{ Kernel: &fwdpb.KernelPortUpdateDesc{ - Inputs: getPreIngressPipeline(), - Outputs: nil, + Inputs: getPreIngressPipeline(), + Outputs: []*fwdpb.ActionDesc{ + fwdconfig.Action(fwdconfig.DecapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET_VLAN)).Build(), + }, }, }, }, @@ -361,7 +367,7 @@ func (port *port) CreatePort(ctx context.Context, req *saipb.CreatePortRequest) 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(), + fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithValue(binary.BigEndian.AppendUint16(nil, uint16(attrs.GetPortVlanId())))).Build(), } if _, err := port.dataplane.TableEntryAdd(ctx, vlanReq); err != nil { return nil, err diff --git a/dataplane/saiserver/routing.go b/dataplane/saiserver/routing.go index 2ad1f061..4c75b22b 100644 --- a/dataplane/saiserver/routing.go +++ b/dataplane/saiserver/routing.go @@ -1290,6 +1290,36 @@ 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()) + _, _ = 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}}, + }, + }) + _, _ = 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, + }, + }, + }, + }) + 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(r.GetVlanId()))), + )), + fwdconfig.TransmitAction(floodID), + ).Build() + if _, err := vlan.dataplane.TableEntryAdd(ctx, floodReq); err != nil { + return nil, err + } + return &saipb.CreateVlanResponse{ Oid: id, }, nil @@ -1371,11 +1401,24 @@ func (vlan *vlan) CreateVlanMember(ctx context.Context, r *saipb.CreateVlanMembe 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(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 } + 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, + }, + }, + }, + }) // Update the attributes and intenal data. vlanAttrReq := &saipb.GetVlanAttributeRequest{Oid: vOid, AttrType: []saipb.VlanAttr{saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST}} vlanAttrResp := &saipb.GetVlanAttributeResponse{} @@ -1444,6 +1487,18 @@ 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) + _, _ = 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)}}, + }, + }, + }, + }) delete(vlan.vlans[targetVlanOid], r.GetOid()) diff --git a/dataplane/saiserver/switch.go b/dataplane/saiserver/switch.go index 7ada9f3d..e77d926f 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" @@ -445,6 +447,39 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ if _, err := sw.dataplane.TableCreate(ctx, l2mcGroupReq); err != nil { 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{{ActionType: fwdpb.ActionType_ACTION_TYPE_DROP}}, + Table: &fwdpb.TableDesc_Exact{ + Exact: &fwdpb.ExactTableDesc{ + FieldIds: []*fwdpb.PacketFieldId{{ + Field: &fwdpb.PacketField{ + FieldNum: fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG, + }, + }}, + }, + }, + }, + } + if _, err := sw.dataplane.TableCreate(ctx, floodReq); err != nil { + return nil, err + } action := &fwdpb.TableCreateRequest{ ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()}, Desc: &fwdpb.TableDesc{ From c06fd33c2044136bde2e91c61547b6a9a0c6ba07 Mon Sep 17 00:00:00 2001 From: Abhishek Verma Date: Wed, 5 Aug 2026 10:01:58 +0000 Subject: [PATCH 2/4] add notification to syncd for fdb updates --- dataplane/saiserver/fdb.go | 68 ++++++++++++++++++++++++++++++-- dataplane/saiserver/saiserver.go | 1 + dataplane/saiserver/switch.go | 26 ++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/dataplane/saiserver/fdb.go b/dataplane/saiserver/fdb.go index f21174aa..78a2e0dc 100644 --- a/dataplane/saiserver/fdb.go +++ b/dataplane/saiserver/fdb.go @@ -17,10 +17,13 @@ package saiserver import ( "context" "fmt" + "log/slog" + "sync" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "github.com/openconfig/lemming/dataplane/forwarding/fwdconfig" saipb "github.com/openconfig/lemming/dataplane/proto/sai" @@ -30,19 +33,48 @@ import ( type fdb struct { saipb.UnimplementedFdbServer - mgr *attrmgr.AttrMgr - dataplane switchDataplaneAPI + mgr *attrmgr.AttrMgr + dataplane switchDataplaneAPI + mu sync.RWMutex + subscribers map[chan *saipb.FdbEventNotificationResponse]struct{} } func newFdb(mgr *attrmgr.AttrMgr, dataplane switchDataplaneAPI, s *grpc.Server) (*fdb, error) { f := &fdb{ - mgr: mgr, - dataplane: dataplane, + mgr: mgr, + dataplane: dataplane, + subscribers: make(map[chan *saipb.FdbEventNotificationResponse]struct{}), } saipb.RegisterFdbServer(s, f) return f, nil } +func (f *fdb) subscribe(ch chan *saipb.FdbEventNotificationResponse) func() { + f.mu.Lock() + defer f.mu.Unlock() + f.subscribers[ch] = struct{}{} + return func() { + f.mu.Lock() + defer f.mu.Unlock() + delete(f.subscribers, ch) + } +} + +func (f *fdb) sendNotification(data *saipb.FdbEventNotificationData) { + f.mu.RLock() + defer f.mu.RUnlock() + resp := &saipb.FdbEventNotificationResponse{ + Data: []*saipb.FdbEventNotificationData{data}, + } + for ch := range f.subscribers { + select { + case ch <- resp: + default: + slog.Warn("fdb notification channel full, dropping event") + } + } +} + func (f *fdb) FlushFdbEntries(ctx context.Context, req *saipb.FlushFdbEntriesRequest) (*saipb.FlushFdbEntriesResponse, error) { return &saipb.FlushFdbEntriesResponse{}, nil } @@ -88,6 +120,25 @@ func (f *fdb) CreateFdbEntry(ctx context.Context, req *saipb.CreateFdbEntryReque return nil, fmt.Errorf("failed to add FDB entry to dataplane: %v", err) } + f.sendNotification(&saipb.FdbEventNotificationData{ + EventType: saipb.FdbEvent_FDB_EVENT_LEARNED, + FdbEntry: &saipb.FdbEntry{ + SwitchId: entry.GetSwitchId(), + MacAddress: mac, + BvId: entry.GetBvId(), + }, + Attrs: []*saipb.FdbEntryAttribute{ + { + AttributeId: proto.Int32(int32(saipb.FdbEntryAttr_FDB_ENTRY_ATTR_BRIDGE_PORT_ID)), + Value: &saipb.AttributeValue{ + Value: &saipb.AttributeValue_Oid{ + Oid: portOID, + }, + }, + }, + }, + }) + return &saipb.CreateFdbEntryResponse{}, nil } @@ -112,5 +163,14 @@ func (f *fdb) RemoveFdbEntry(ctx context.Context, req *saipb.RemoveFdbEntryReque return nil, fmt.Errorf("failed to remove FDB entry from dataplane: %v", err) } + f.sendNotification(&saipb.FdbEventNotificationData{ + EventType: saipb.FdbEvent_FDB_EVENT_AGED, + FdbEntry: &saipb.FdbEntry{ + SwitchId: entry.GetSwitchId(), + MacAddress: mac, + BvId: entry.GetBvId(), + }, + }) + return &saipb.RemoveFdbEntryResponse{}, nil } diff --git a/dataplane/saiserver/saiserver.go b/dataplane/saiserver/saiserver.go index 15c8bc59..66307d87 100644 --- a/dataplane/saiserver/saiserver.go +++ b/dataplane/saiserver/saiserver.go @@ -200,6 +200,7 @@ func New(ctx context.Context, mgr *attrmgr.AttrMgr, s *grpc.Server, opts *dplane if err != nil { return nil, err } + sw.fdb = fdb srv := &Server{ mgr: mgr, diff --git a/dataplane/saiserver/switch.go b/dataplane/saiserver/switch.go index e77d926f..2eb844ad 100644 --- a/dataplane/saiserver/switch.go +++ b/dataplane/saiserver/switch.go @@ -24,6 +24,8 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "github.com/openconfig/lemming/dataplane/dplaneopts" @@ -45,6 +47,7 @@ type saiSwitch struct { vlan *vlan stp *stp bridge *bridge + fdb *fdb hostif *hostif hash *hash isolationGroup *isolationGroup @@ -1264,6 +1267,29 @@ func (sw *saiSwitch) PortStateChangeNotification(_ *saipb.PortStateChangeNotific } } +func (sw *saiSwitch) FdbEventNotification(_ *saipb.FdbEventNotificationRequest, srv grpc.ServerStreamingServer[saipb.FdbEventNotificationResponse]) error { + if sw.fdb == nil { + return status.Error(codes.Unimplemented, "FDB notification service unavailable") + } + ch := make(chan *saipb.FdbEventNotificationResponse, 100) + unsubscribe := sw.fdb.subscribe(ch) + defer unsubscribe() + + for { + select { + case <-srv.Context().Done(): + return srv.Context().Err() + case resp, ok := <-ch: + if !ok { + return nil + } + if err := srv.Send(resp); err != nil { + return err + } + } + } +} + func (sw *saiSwitch) Reset() { sw.vlan.Reset() sw.port.Reset() From 0a282c1af0ddea2d3633c6df257075816dd1be15 Mon Sep 17 00:00:00 2001 From: Abhishek Verma Date: Wed, 5 Aug 2026 11:58:18 +0000 Subject: [PATCH 3/4] Add dynamic bridge MAC learning notification handler --- .../forwarding/fwdtable/bridge/bridge.go | 16 +++++++-- dataplane/saiserver/switch.go | 35 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/dataplane/forwarding/fwdtable/bridge/bridge.go b/dataplane/forwarding/fwdtable/bridge/bridge.go index 7d115ffa..ca06420b 100644 --- a/dataplane/forwarding/fwdtable/bridge/bridge.go +++ b/dataplane/forwarding/fwdtable/bridge/bridge.go @@ -69,9 +69,17 @@ func (req *learnRequest) DebugString(port fwdport.Port) string { // is buffered. type Table struct { *exact.Table // exact table containing mac entries - learn *queue.Queue // unbounded queue for learn requests - ctx *fwdcontext.Context // context for finding objects - notify chan bool // if not nil, a notification is generated when an entry is learned (test only) + learn *queue.Queue // unbounded queue for learn requests + ctx *fwdcontext.Context // context for finding objects + notify chan bool // if not nil, a notification is generated when an entry is learned (test only) + LearnCallback func(mac []byte, portID string) +} + +// SetLearnCallback sets a callback invoked when a new MAC entry is dynamically learned. +func (t *Table) SetLearnCallback(cb func(mac []byte, portID string)) { + t.ctx.Lock() + defer t.ctx.Unlock() + t.LearnCallback = cb } // Clear clears the table by deleting all its entries. @@ -152,6 +160,8 @@ func (t *Table) processLearn(v interface{}) { // we try to learn a mac address that has a static entry. if err := t.AddEntry(desc, []*fwdpb.ActionDesc{&ad}); err != nil { log.Infof("bridge: Skipping learn for %v %v.", req.DebugString(port), err) + } else if t.LearnCallback != nil { + t.LearnCallback(req.mac, string(fwdport.GetID(port).GetObjectId().GetId())) } } diff --git a/dataplane/saiserver/switch.go b/dataplane/saiserver/switch.go index 2eb844ad..a277952b 100644 --- a/dataplane/saiserver/switch.go +++ b/dataplane/saiserver/switch.go @@ -30,6 +30,7 @@ import ( "github.com/openconfig/lemming/dataplane/dplaneopts" "github.com/openconfig/lemming/dataplane/forwarding/fwdconfig" + "github.com/openconfig/lemming/dataplane/forwarding/fwdtable/bridge" "github.com/openconfig/lemming/dataplane/forwarding/infra/fwdcontext" "github.com/openconfig/lemming/dataplane/saiserver/attrmgr" @@ -463,6 +464,13 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ if _, err := sw.dataplane.TableCreate(ctx, fdbReq); err != nil { return nil, err } + if fwdCtx, err := sw.dataplane.FindContext(&fwdpb.ContextId{Id: sw.dataplane.ID()}); err == nil { + if obj, err := fwdCtx.Objects.FindID(&fwdpb.ObjectId{Id: FDBTable}); err == nil { + if brTable, ok := obj.(*bridge.Table); ok { + brTable.SetLearnCallback(sw.onBridgeLearn) + } + } + } floodReq := &fwdpb.TableCreateRequest{ ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()}, Desc: &fwdpb.TableDesc{ @@ -1290,6 +1298,33 @@ func (sw *saiSwitch) FdbEventNotification(_ *saipb.FdbEventNotificationRequest, } } +func (sw *saiSwitch) onBridgeLearn(mac []byte, portID string) { + if sw.fdb == nil { + return + } + portNum, err := strconv.ParseUint(portID, 10, 64) + if err != nil { + slog.Warn("onBridgeLearn: failed to parse numeric port ID", "portID", portID, "err", err) + return + } + sw.fdb.sendNotification(&saipb.FdbEventNotificationData{ + EventType: saipb.FdbEvent_FDB_EVENT_LEARNED, + FdbEntry: &saipb.FdbEntry{ + MacAddress: mac, + }, + Attrs: []*saipb.FdbEntryAttribute{ + { + AttributeId: proto.Int32(int32(saipb.FdbEntryAttr_FDB_ENTRY_ATTR_BRIDGE_PORT_ID)), + Value: &saipb.AttributeValue{ + Value: &saipb.AttributeValue_Oid{ + Oid: portNum, + }, + }, + }, + }, + }) +} + func (sw *saiSwitch) Reset() { sw.vlan.Reset() sw.port.Reset() From 8a29b02bcd6969b7ca318ad8969397e6cbf2b2c8 Mon Sep 17 00:00:00 2001 From: thineshvs Date: Wed, 19 Aug 2026 06:15:30 +0000 Subject: [PATCH 4/4] l2 corrected - show vlan brief --- dataplane/saiserver/BUILD | 1 + dataplane/saiserver/attrmgr/attrmgr.go | 16 ++++ dataplane/saiserver/fdb.go | 9 +-- dataplane/saiserver/routing.go | 9 +++ dataplane/saiserver/switch.go | 101 ++++++++++++++++++++++--- dataplane/standalone/sai/common.cc | 97 +++++++++++++++++++++++- dataplane/standalone/sai/common.h | 82 ++++++++++++++++++++ dataplane/standalone/sai/switch.cc | 21 ++++- 8 files changed, 317 insertions(+), 19 deletions(-) diff --git a/dataplane/saiserver/BUILD b/dataplane/saiserver/BUILD index 4bae291e..a7837e76 100644 --- a/dataplane/saiserver/BUILD +++ b/dataplane/saiserver/BUILD @@ -23,6 +23,7 @@ go_library( deps = [ "//dataplane/dplaneopts", "//dataplane/forwarding", + "//dataplane/forwarding/fwdtable/bridge", "//dataplane/forwarding/fwdconfig", "//dataplane/forwarding/infra/fwdcontext", "//dataplane/proto/packetio", diff --git a/dataplane/saiserver/attrmgr/attrmgr.go b/dataplane/saiserver/attrmgr/attrmgr.go index 976ed38f..70e16074 100644 --- a/dataplane/saiserver/attrmgr/attrmgr.go +++ b/dataplane/saiserver/attrmgr/attrmgr.go @@ -18,6 +18,7 @@ package attrmgr import ( + "strconv" "context" "fmt" "log/slog" @@ -385,3 +386,18 @@ func (mgr *AttrMgr) getID(req, resp proto.Message) (string, error) { } return string(pBytes), nil } + +// GetOIDsByType returns a list of OIDs that match the given ObjectType. +func (mgr *AttrMgr) GetOIDsByType(t saipb.ObjectType) []uint64 { + mgr.mu.Lock() + defer mgr.mu.Unlock() + var oids []uint64 + for id, ty := range mgr.idToType { + if ty == t { + if oid, err := strconv.ParseUint(id, 10, 64); err == nil { + oids = append(oids, oid) + } + } + } + return oids +} diff --git a/dataplane/saiserver/fdb.go b/dataplane/saiserver/fdb.go index 78a2e0dc..256dfffc 100644 --- a/dataplane/saiserver/fdb.go +++ b/dataplane/saiserver/fdb.go @@ -80,6 +80,7 @@ func (f *fdb) FlushFdbEntries(ctx context.Context, req *saipb.FlushFdbEntriesReq } func (f *fdb) CreateFdbEntry(ctx context.Context, req *saipb.CreateFdbEntryRequest) (*saipb.CreateFdbEntryResponse, error) { + slog.InfoContext(ctx, "CreateFdbEntry called", "mac", req.GetEntry().GetMacAddress(), "vlan", req.GetEntry().GetBvId(), "bridge_port", req.GetBridgePortId()) entry := req.GetEntry() if entry == nil { return nil, status.Errorf(codes.InvalidArgument, "FDB entry is required") @@ -129,12 +130,7 @@ func (f *fdb) CreateFdbEntry(ctx context.Context, req *saipb.CreateFdbEntryReque }, Attrs: []*saipb.FdbEntryAttribute{ { - AttributeId: proto.Int32(int32(saipb.FdbEntryAttr_FDB_ENTRY_ATTR_BRIDGE_PORT_ID)), - Value: &saipb.AttributeValue{ - Value: &saipb.AttributeValue_Oid{ - Oid: portOID, - }, - }, + BridgePortId: proto.Uint64(portOID), }, }, }) @@ -143,6 +139,7 @@ func (f *fdb) CreateFdbEntry(ctx context.Context, req *saipb.CreateFdbEntryReque } func (f *fdb) RemoveFdbEntry(ctx context.Context, req *saipb.RemoveFdbEntryRequest) (*saipb.RemoveFdbEntryResponse, error) { + slog.InfoContext(ctx, "RemoveFdbEntry called", "mac", req.GetEntry().GetMacAddress(), "vlan", req.GetEntry().GetBvId()) entry := req.GetEntry() if entry == nil { return nil, status.Errorf(codes.InvalidArgument, "FDB entry is required") diff --git a/dataplane/saiserver/routing.go b/dataplane/saiserver/routing.go index 4c75b22b..b5ed8b9b 100644 --- a/dataplane/saiserver/routing.go +++ b/dataplane/saiserver/routing.go @@ -1250,6 +1250,8 @@ func (vlan *vlan) memberByOid(oid uint64) *vlanMember { } func (vlan *vlan) memberByPortId(oid uint64) *vlanMember { + vlan.mu.Lock() + defer vlan.mu.Unlock() for _, v := range vlan.vlans { for _, member := range v { if member.PortID == oid { @@ -1260,6 +1262,13 @@ func (vlan *vlan) memberByPortId(oid uint64) *vlanMember { return nil } +func (vlan *vlan) oidByVid(vid uint32) (uint64, bool) { + vlan.mu.Lock() + defer vlan.mu.Unlock() + oid, ok := vlan.oidByVId[vid] + return oid, ok +} + func (vlan *vlan) CreateVlan(ctx context.Context, r *saipb.CreateVlanRequest) (*saipb.CreateVlanResponse, error) { if _, ok := vlan.oidByVId[r.GetVlanId()]; ok { return nil, fmt.Errorf("found existing VLAN %d", r.GetVlanId()) diff --git a/dataplane/saiserver/switch.go b/dataplane/saiserver/switch.go index a277952b..3c08fc80 100644 --- a/dataplane/saiserver/switch.go +++ b/dataplane/saiserver/switch.go @@ -30,7 +30,7 @@ import ( "github.com/openconfig/lemming/dataplane/dplaneopts" "github.com/openconfig/lemming/dataplane/forwarding/fwdconfig" - "github.com/openconfig/lemming/dataplane/forwarding/fwdtable/bridge" + fwdbridge "github.com/openconfig/lemming/dataplane/forwarding/fwdtable/bridge" "github.com/openconfig/lemming/dataplane/forwarding/infra/fwdcontext" "github.com/openconfig/lemming/dataplane/saiserver/attrmgr" @@ -464,12 +464,19 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ if _, err := sw.dataplane.TableCreate(ctx, fdbReq); err != nil { return nil, err } - if fwdCtx, err := sw.dataplane.FindContext(&fwdpb.ContextId{Id: sw.dataplane.ID()}); err == nil { + if fwdCtx, err := sw.dataplane.FindContext(&fwdpb.ContextId{Id: sw.dataplane.ID()}); err == nil && fwdCtx != nil { if obj, err := fwdCtx.Objects.FindID(&fwdpb.ObjectId{Id: FDBTable}); err == nil { - if brTable, ok := obj.(*bridge.Table); ok { + if brTable, ok := obj.(*fwdbridge.Table); ok { brTable.SetLearnCallback(sw.onBridgeLearn) + slog.Info("CreateSwitch: successfully set learn callback on FDB table") + } else { + slog.Error("CreateSwitch: FDB table object is not a fwdbridge.Table", "type", fmt.Sprintf("%T", obj)) } + } else { + slog.Error("CreateSwitch: failed to find FDB table object", "err", err) } + } else { + slog.Error("CreateSwitch: failed to find forwarding context", "err", err, "fwdCtx", fwdCtx) } floodReq := &fwdpb.TableCreateRequest{ ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()}, @@ -1275,13 +1282,17 @@ func (sw *saiSwitch) PortStateChangeNotification(_ *saipb.PortStateChangeNotific } } -func (sw *saiSwitch) FdbEventNotification(_ *saipb.FdbEventNotificationRequest, srv grpc.ServerStreamingServer[saipb.FdbEventNotificationResponse]) error { +func (sw *saiSwitch) FdbEventNotification(req *saipb.FdbEventNotificationRequest, srv grpc.ServerStreamingServer[saipb.FdbEventNotificationResponse]) error { if sw.fdb == nil { return status.Error(codes.Unimplemented, "FDB notification service unavailable") } + slog.InfoContext(srv.Context(), "FdbEventNotification: subscriber connected", "req", req) ch := make(chan *saipb.FdbEventNotificationResponse, 100) unsubscribe := sw.fdb.subscribe(ch) - defer unsubscribe() + defer func() { + unsubscribe() + slog.InfoContext(srv.Context(), "FdbEventNotification: subscriber disconnected", "err", srv.Context().Err()) + }() for { select { @@ -1289,9 +1300,12 @@ func (sw *saiSwitch) FdbEventNotification(_ *saipb.FdbEventNotificationRequest, return srv.Context().Err() case resp, ok := <-ch: if !ok { + slog.InfoContext(srv.Context(), "FdbEventNotification: subscription channel closed") return nil } + slog.InfoContext(srv.Context(), "FdbEventNotification: sending event", "resp", resp) if err := srv.Send(resp); err != nil { + slog.ErrorContext(srv.Context(), "FdbEventNotification: failed to send event", "err", err) return err } } @@ -1307,19 +1321,84 @@ func (sw *saiSwitch) onBridgeLearn(mac []byte, portID string) { slog.Warn("onBridgeLearn: failed to parse numeric port ID", "portID", portID, "err", err) return } + + // Find the Bridge Port OID that wraps this physical port. + var bridgePortOID uint64 + bpOIDs := sw.mgr.GetOIDsByType(saipb.ObjectType_OBJECT_TYPE_BRIDGE_PORT) + for _, bpOID := range bpOIDs { + val := sw.mgr.GetAttribute(strconv.FormatUint(bpOID, 10), int32(saipb.BridgePortAttr_BRIDGE_PORT_ATTR_PORT_ID)) + if val != nil { + if pID, ok := val.(uint64); ok && pID == portNum { + bridgePortOID = bpOID + break + } + } + } + + if bridgePortOID == 0 { + slog.Warn("onBridgeLearn: failed to find bridge port for physical port", "portNum", portNum) + return + } + + // Find the VLAN ID and VLAN OID for this port. + vlanID := uint32(DefaultVlanId) + var vlanOID uint64 + if sw.vlan != nil { + if member := sw.vlan.memberByPortId(portNum); member != nil { + vlanID = member.Vid + 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 { + slog.Warn("onBridgeLearn: vlan server is nil, using default vlan") + } + + swIDStr, ok := sw.mgr.GetSwitchID() + if !ok { + slog.Warn("onBridgeLearn: failed to get switch ID") + return + } + swID, err := strconv.ParseUint(swIDStr, 10, 64) + if err != nil { + slog.Warn("onBridgeLearn: failed to parse switch ID", "swIDStr", swIDStr, "err", err) + return + } + + // If we couldn't resolve the VLAN OID, fallback to default VLAN OID. + if vlanOID == 0 { + req := &saipb.GetSwitchAttributeRequest{Oid: swID, AttrType: []saipb.SwitchAttr{saipb.SwitchAttr_SWITCH_ATTR_DEFAULT_VLAN_ID}} + resp := &saipb.GetSwitchAttributeResponse{} + if err := sw.mgr.PopulateAttributes(req, resp); err == nil { + vlanOID = resp.GetAttr().GetDefaultVlanId() + slog.Info("onBridgeLearn: fallback to default VLAN", "defaultVlanOID", vlanOID) + } else { + slog.Error("onBridgeLearn: failed to get default VLAN OID", "err", err) + } + } + + slog.Info("onBridgeLearn: successfully translated and sending FdbEventNotification", "mac", mac, "portNum", portNum, "bridgePortOID", bridgePortOID, "vlanOID", vlanOID) sw.fdb.sendNotification(&saipb.FdbEventNotificationData{ EventType: saipb.FdbEvent_FDB_EVENT_LEARNED, FdbEntry: &saipb.FdbEntry{ + SwitchId: swID, MacAddress: mac, + BvId: vlanOID, }, Attrs: []*saipb.FdbEntryAttribute{ { - AttributeId: proto.Int32(int32(saipb.FdbEntryAttr_FDB_ENTRY_ATTR_BRIDGE_PORT_ID)), - Value: &saipb.AttributeValue{ - Value: &saipb.AttributeValue_Oid{ - Oid: portNum, - }, - }, + BridgePortId: proto.Uint64(bridgePortOID), + }, + { + Type: saipb.FdbEntryType_FDB_ENTRY_TYPE_DYNAMIC.Enum(), + }, + { + PacketAction: saipb.PacketAction_PACKET_ACTION_FORWARD.Enum(), }, }, }) diff --git a/dataplane/standalone/sai/common.cc b/dataplane/standalone/sai/common.cc index 798b977a..52e7e6a7 100644 --- a/dataplane/standalone/sai/common.cc +++ b/dataplane/standalone/sai/common.cc @@ -320,4 +320,99 @@ sai_acl_field_data_t convert_to_acl_field_data_ip_type( out.enable = in.enable(); out.data.s32 = convert_sai_acl_ip_type_t_to_sai(type); return out; -} \ No newline at end of file +} +lemming::dataplane::sai::FdbEntry convert_from_fdb_entry( + const sai_fdb_entry_t& entry) { + lemming::dataplane::sai::FdbEntry proto; + proto.set_switch_id(entry.switch_id); + proto.set_mac_address(entry.mac_address, sizeof(sai_mac_t)); + proto.set_bv_id(entry.bv_id); + return proto; +} + +sai_fdb_entry_t convert_to_fdb_entry( + const lemming::dataplane::sai::FdbEntry& proto) { + sai_fdb_entry_t entry; + entry.switch_id = proto.switch_id(); + if (proto.mac_address().size() == sizeof(sai_mac_t)) { + memcpy(entry.mac_address, proto.mac_address().data(), sizeof(sai_mac_t)); + } else { + memset(entry.mac_address, 0, sizeof(sai_mac_t)); + } + entry.bv_id = proto.bv_id(); + return entry; +} + +std::vector convert_to_fdb_attributes( + const google::protobuf::RepeatedPtrField& proto_attrs) { + std::vector attrs; + for (const auto& proto_attr : proto_attrs) { + if (proto_attr.has_type()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_TYPE; + attr.value.s32 = convert_sai_fdb_entry_type_t_to_sai(proto_attr.type()); + attrs.push_back(attr); + } + if (proto_attr.has_packet_action()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = convert_sai_packet_action_t_to_sai(proto_attr.packet_action()); + attrs.push_back(attr); + } + if (proto_attr.has_user_trap_id()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_USER_TRAP_ID; + attr.value.oid = proto_attr.user_trap_id(); + attrs.push_back(attr); + } + if (proto_attr.has_bridge_port_id()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID; + attr.value.oid = proto_attr.bridge_port_id(); + attrs.push_back(attr); + } + if (proto_attr.has_meta_data()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_META_DATA; + attr.value.u32 = proto_attr.meta_data(); + attrs.push_back(attr); + } + if (proto_attr.has_endpoint_ip()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_ENDPOINT_IP; + attr.value.ipaddr = convert_to_ip_address(proto_attr.endpoint_ip()); + attrs.push_back(attr); + } + if (proto_attr.has_counter_id()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_COUNTER_ID; + attr.value.oid = proto_attr.counter_id(); + attrs.push_back(attr); + } + if (proto_attr.has_allow_mac_move()) { + sai_attribute_t attr; + attr.id = SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE; + attr.value.booldata = proto_attr.allow_mac_move(); + attrs.push_back(attr); + } + } + return attrs; +} + +FdbNotificationDataHolder convert_to_fdb_event( + const lemming::dataplane::sai::FdbEventNotificationResponse& resp) { + FdbNotificationDataHolder holder; + for (const auto& d : resp.data()) { + sai_fdb_event_notification_data_t event; + event.event_type = convert_sai_fdb_event_t_to_sai(d.event_type()); + event.fdb_entry = convert_to_fdb_entry(d.fdb_entry()); + + holder.attributes_storage.push_back(convert_to_fdb_attributes(d.attrs())); + + event.attr_count = holder.attributes_storage.back().size(); + event.attr = holder.attributes_storage.back().data(); + + holder.events.push_back(event); + } + return holder; +} diff --git a/dataplane/standalone/sai/common.h b/dataplane/standalone/sai/common.h index 2ed7b09e..abca31e4 100644 --- a/dataplane/standalone/sai/common.h +++ b/dataplane/standalone/sai/common.h @@ -150,12 +150,22 @@ sai_ip_prefix_t convert_to_ip_prefix( const lemming::dataplane::sai::IpPrefix& ip_prefix); std::vector convert_to_oper_status( const lemming::dataplane::sai::PortStateChangeNotificationResponse& resp); +struct FdbNotificationDataHolder { + std::vector events; + std::vector> attributes_storage; +}; +FdbNotificationDataHolder convert_to_fdb_event( + const lemming::dataplane::sai::FdbEventNotificationResponse& resp); lemming::dataplane::sai::NeighborEntry convert_from_neighbor_entry( const sai_neighbor_entry_t& entry); sai_neighbor_entry_t convert_to_neighbor_entry( const lemming::dataplane::sai::NeighborEntry& entry); +lemming::dataplane::sai::FdbEntry convert_from_fdb_entry( + const sai_fdb_entry_t& entry); +sai_fdb_entry_t convert_to_fdb_entry( + const lemming::dataplane::sai::FdbEntry& entry); void convert_to_acl_capability( sai_acl_capability_t& out, @@ -316,4 +326,76 @@ class PortStateReactor }; #endif + +#ifndef GRPC_CALLBACK_API_NONEXPERIMENTAL +class FdbEventReactor + : public grpc::experimental::ClientReadReactor< + lemming::dataplane::sai::FdbEventNotificationResponse> { + public: + FdbEventReactor(std::shared_ptr stub, + sai_fdb_event_notification_fn callback) { + this->callback = callback; + lemming::dataplane::sai::FdbEventNotificationRequest req; + stub->experimental_async()->FdbEventNotification(&context, &req, this); + StartRead(&resp); + StartCall(); + } + + void OnReadDone(bool ok) override { + if (!ok) return; + LOG(INFO) << "FdbEventReactor: OnReadDone received " << resp.data_size() << " event(s). Invoking callback."; + FdbNotificationDataHolder holder = convert_to_fdb_event(resp); + callback(holder.events.size(), holder.events.data()); + StartRead(&resp); + } + + void OnDone(const grpc::Status& status) override { + if (status.ok()) { + LOG(INFO) << "FdbEventNotification RPC succeeded."; + } else { + LOG(ERROR) << "FdbEventNotification RPC failed: " << status.error_message(); + } + } + + private: + grpc::ClientContext context; + lemming::dataplane::sai::FdbEventNotificationResponse resp; + sai_fdb_event_notification_fn callback; +}; +#else +class FdbEventReactor + : public grpc::ClientReadReactor< + lemming::dataplane::sai::FdbEventNotificationResponse> { + public: + FdbEventReactor(std::shared_ptr stub, + sai_fdb_event_notification_fn callback) { + this->callback = callback; + lemming::dataplane::sai::FdbEventNotificationRequest req; + stub->async()->FdbEventNotification(&context, &req, this); + StartRead(&resp); + StartCall(); + } + + void OnReadDone(bool ok) override { + if (!ok) return; + LOG(INFO) << "FdbEventReactor: OnReadDone received " << resp.data_size() << " event(s). Invoking callback."; + FdbNotificationDataHolder holder = convert_to_fdb_event(resp); + callback(holder.events.size(), holder.events.data()); + StartRead(&resp); + } + + void OnDone(const grpc::Status& status) override { + if (status.ok()) { + LOG(INFO) << "FdbEventNotification RPC succeeded."; + } else { + LOG(ERROR) << "FdbEventNotification RPC failed: " << status.error_message(); + } + } + + private: + grpc::ClientContext context; + lemming::dataplane::sai::FdbEventNotificationResponse resp; + sai_fdb_event_notification_fn callback; +}; +#endif #endif // DATAPLANE_STANDALONE_SAI_COMMON_H_ diff --git a/dataplane/standalone/sai/switch.cc b/dataplane/standalone/sai/switch.cc index bbb37106..ebcebb49 100644 --- a/dataplane/standalone/sai/switch.cc +++ b/dataplane/standalone/sai/switch.cc @@ -36,6 +36,7 @@ const sai_switch_api_t l_switch = { }; std::unique_ptr port_state; +std::unique_ptr fdb_event; lemming::dataplane::sai::CreateSwitchRequest convert_create_switch( uint32_t attr_count, const sai_attribute_t* attr_list) { @@ -182,6 +183,11 @@ lemming::dataplane::sai::CreateSwitchRequest convert_create_switch( switch_, reinterpret_cast( attr_list[i].value.ptr)); break; + case SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY: + fdb_event = std::make_unique( + switch_, reinterpret_cast( + attr_list[i].value.ptr)); + break; case SAI_SWITCH_ATTR_FAST_API_ENABLE: msg.set_fast_api_enable(attr_list[i].value.booldata); break; @@ -441,6 +447,10 @@ lemming::dataplane::sai::CreateSwitchTunnelRequest convert_create_switch_tunnel( sai_status_t l_create_switch(sai_object_id_t* switch_id, uint32_t attr_count, const sai_attribute_t* attr_list) { LOG(INFO) << "Func: " << __PRETTY_FUNCTION__; + LOG(INFO) << "Compiled SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY ID = " << SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY; + for (uint32_t i = 0; i < attr_count; i++) { + LOG(INFO) << "l_create_switch attr ID=" << attr_list[i].id << " (0x" << std::hex << attr_list[i].id << std::dec << ") ptr=" << attr_list[i].value.ptr; + } lemming::dataplane::sai::CreateSwitchRequest req = convert_create_switch(attr_count, attr_list); @@ -619,7 +629,16 @@ sai_status_t l_set_switch_attribute(sai_object_id_t switch_id, port_state = std::make_unique( switch_, reinterpret_cast( attr->value.ptr)); - break; + return SAI_STATUS_SUCCESS; + case SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY: + fdb_event = std::make_unique( + switch_, reinterpret_cast( + attr->value.ptr)); + return SAI_STATUS_SUCCESS; + case SAI_SWITCH_ATTR_SHUTDOWN_REQUEST_NOTIFY: + case SAI_SWITCH_ATTR_SWITCH_STATE_CHANGE_NOTIFY: + case SAI_SWITCH_ATTR_PACKET_EVENT_NOTIFY: + return SAI_STATUS_SUCCESS; case SAI_SWITCH_ATTR_FAST_API_ENABLE: req.set_fast_api_enable(attr->value.booldata); break;