From 125faddbbb077b0576bf819612f57d198fd6af73 Mon Sep 17 00:00:00 2001 From: Kalyan Pullela Date: Sat, 31 Jan 2026 19:44:44 +0000 Subject: [PATCH] Code now supports creating and managing router interfaces on VLAN types. When a VLAN interface is created, it retrieves the VLAN members and uses the first member's bridge port for forwarding table entries. Defers setup when a VLAN has no members yet. Also adds handling for stat queries when interfaces lack forwarding entries, and for removal of interfaces that were created without forwarding setup. --- dataplane/saiserver/routing.go | 105 +++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/dataplane/saiserver/routing.go b/dataplane/saiserver/routing.go index 4ffacb1ae..95377a6d4 100644 --- a/dataplane/saiserver/routing.go +++ b/dataplane/saiserver/routing.go @@ -846,10 +846,44 @@ func (ri *routerInterface) CreateRouterInterface(ctx context.Context, req *saipb id := ri.mgr.NextID() vlanID := uint16(0) + portID := req.GetPortId() switch req.GetType() { case saipb.RouterInterfaceType_ROUTER_INTERFACE_TYPE_PORT: case saipb.RouterInterfaceType_ROUTER_INTERFACE_TYPE_SUB_PORT: vlanID = uint16(req.GetOuterVlanId()) + case saipb.RouterInterfaceType_ROUTER_INTERFACE_TYPE_VLAN: + // Get VLAN attributes to find VLAN ID and members + vlanAttr := &saipb.GetVlanAttributeResponse{} + err := ri.mgr.PopulateAttributes(&saipb.GetVlanAttributeRequest{ + Oid: req.GetVlanId(), + AttrType: []saipb.VlanAttr{ + saipb.VlanAttr_VLAN_ATTR_VLAN_ID, + saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST, + }, + }, vlanAttr) + if err != nil { + return nil, fmt.Errorf("failed to get VLAN attributes: %v", err) + } + vlanID = uint16(vlanAttr.GetAttr().GetVlanId()) + + // Use the first VLAN member's bridge port if available + if len(vlanAttr.GetAttr().GetMemberList()) > 0 { + firstMember := vlanAttr.GetAttr().GetMemberList()[0] + memberAttr := &saipb.GetVlanMemberAttributeResponse{} + err = ri.mgr.PopulateAttributes(&saipb.GetVlanMemberAttributeRequest{ + Oid: firstMember, + AttrType: []saipb.VlanMemberAttr{saipb.VlanMemberAttr_VLAN_MEMBER_ATTR_BRIDGE_PORT_ID}, + }, memberAttr) + if err != nil { + return nil, fmt.Errorf("failed to get VLAN member attributes: %v", err) + } + portID = memberAttr.GetAttr().GetBridgePortId() + } else { + // No members yet - defer forwarding table setup until members are added + // Return success without creating forwarding entries (similar to loopback) + slog.InfoContext(ctx, "VLAN router interface created without members", "vlan_id", vlanID, "rif_id", id) + return &saipb.CreateRouterInterfaceResponse{Oid: id}, nil + } case saipb.RouterInterfaceType_ROUTER_INTERFACE_TYPE_LOOPBACK: // TODO: Support loopback interfaces slog.WarnContext(ctx, "loopback interfaces not supported") return &saipb.CreateRouterInterfaceResponse{Oid: id}, nil @@ -860,7 +894,7 @@ func (ri *routerInterface) CreateRouterInterface(ctx context.Context, req *saipb if err != nil { return nil, err } - obj, err := fwdCtx.Objects.FindID(&fwdpb.ObjectId{Id: fmt.Sprint(req.GetPortId())}) + obj, err := fwdCtx.Objects.FindID(&fwdpb.ObjectId{Id: fmt.Sprint(portID)}) if err != nil { return nil, err } @@ -913,9 +947,9 @@ func (ri *routerInterface) CreateRouterInterface(ctx context.Context, req *saipb outReq := fwdconfig.TableEntryAddRequest(ri.dataplane.ID(), outputIfaceTable). AppendEntry( fwdconfig.EntryDesc(fwdconfig.ExactEntry(fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_OUTPUT_IFACE).WithUint64(id))), - fwdconfig.TransmitAction(fmt.Sprint(req.GetPortId())), + fwdconfig.TransmitAction(fmt.Sprint(portID)), fwdconfig.FlowCounterAction(outCounter), - fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_TARGET_EGRESS_PORT).WithUint64Value(req.GetPortId()), + fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_TARGET_EGRESS_PORT).WithUint64Value(portID), ).Build() if vlanID != 0 { @@ -963,6 +997,7 @@ func (ri *routerInterface) RemoveRouterInterface(ctx context.Context, req *saipb } var vlanID uint16 + var portID uint64 if resp.GetAttr().GetType() == saipb.RouterInterfaceType_ROUTER_INTERFACE_TYPE_SUB_PORT { resp := &saipb.GetRouterInterfaceAttributeResponse{} err := ri.mgr.PopulateAttributes(&saipb.GetRouterInterfaceAttributeRequest{ @@ -973,14 +1008,59 @@ func (ri *routerInterface) RemoveRouterInterface(ctx context.Context, req *saipb return nil, err } vlanID = uint16(resp.GetAttr().GetOuterVlanId()) + portID = resp.GetAttr().GetPortId() + } else if resp.GetAttr().GetType() == saipb.RouterInterfaceType_ROUTER_INTERFACE_TYPE_VLAN { + // Get VLAN ID and port from VLAN object (same as creation) + vlanResp := &saipb.GetRouterInterfaceAttributeResponse{} + err := ri.mgr.PopulateAttributes(&saipb.GetRouterInterfaceAttributeRequest{ + Oid: req.GetOid(), + AttrType: []saipb.RouterInterfaceAttr{saipb.RouterInterfaceAttr_ROUTER_INTERFACE_ATTR_VLAN_ID}, + }, vlanResp) + if err != nil { + return nil, err + } + + vlanAttr := &saipb.GetVlanAttributeResponse{} + err = ri.mgr.PopulateAttributes(&saipb.GetVlanAttributeRequest{ + Oid: vlanResp.GetAttr().GetVlanId(), + AttrType: []saipb.VlanAttr{ + saipb.VlanAttr_VLAN_ATTR_VLAN_ID, + saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST, + }, + }, vlanAttr) + if err != nil { + return nil, err + } + vlanID = uint16(vlanAttr.GetAttr().GetVlanId()) + + // Use first member (matching creation behavior) + if len(vlanAttr.GetAttr().GetMemberList()) > 0 { + firstMember := vlanAttr.GetAttr().GetMemberList()[0] + memberAttr := &saipb.GetVlanMemberAttributeResponse{} + err = ri.mgr.PopulateAttributes(&saipb.GetVlanMemberAttributeRequest{ + Oid: firstMember, + AttrType: []saipb.VlanMemberAttr{saipb.VlanMemberAttr_VLAN_MEMBER_ATTR_BRIDGE_PORT_ID}, + }, memberAttr) + if err != nil { + return nil, err + } + portID = memberAttr.GetAttr().GetBridgePortId() + } else { + portID = resp.GetAttr().GetPortId() + } + } else { + portID = resp.GetAttr().GetPortId() } nid, err := ri.dataplane.ObjectNID(ctx, &fwdpb.ObjectNIDRequest{ ContextId: &fwdpb.ContextId{Id: ri.dataplane.ID()}, - ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(resp.GetAttr().GetPortId())}, + ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(portID)}, }) if err != nil { - return nil, err + // Port object not found - interface was created without forwarding entries + // (e.g., VLAN without members). Return success without removing entries. + slog.InfoContext(ctx, "Router interface removed without forwarding cleanup", "rif_id", req.GetOid(), "port_id", portID) + return &saipb.RemoveRouterInterfaceResponse{}, nil } _, err = ri.dataplane.TableEntryRemove(ctx, fwdconfig.TableEntryRemoveRequest(ri.dataplane.ID(), inputIfaceTable). @@ -1036,7 +1116,20 @@ func (ri *routerInterface) GetRouterInterfaceStats(ctx context.Context, req *sai }}, }) if err != nil { - return nil, err + // Counters don't exist - interface was created without forwarding entries + // (e.g., VLAN without members). Return zeros for all requested stats. + slog.InfoContext(ctx, "Router interface stats query failed, returning zeros", "rif_id", req.GetOid(), "error", err) + vals := make([]uint64, len(req.GetCounterIds())) + return &saipb.GetRouterInterfaceStatsResponse{Values: vals}, nil + } + + // Check if we have both inbound and outbound counters + if len(counters.GetCounters()) < 2 { + // Missing counters - interface created without forwarding entries + // Return zeros for all requested stats + slog.InfoContext(ctx, "Router interface has incomplete counters, returning zeros", "rif_id", req.GetOid(), "counter_count", len(counters.GetCounters())) + vals := make([]uint64, len(req.GetCounterIds())) + return &saipb.GetRouterInterfaceStatsResponse{Values: vals}, nil } vals := []uint64{}