Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 99 additions & 6 deletions dataplane/saiserver/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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{
Expand All @@ -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).
Expand Down Expand Up @@ -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{}
Expand Down