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{