Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial VLAN implementation #422

Merged
merged 20 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
11 changes: 11 additions & 0 deletions dataplane/dplanerc/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (

log "github.com/golang/glog"

"github.com/openconfig/lemming/dataplane/proto/sai"
saipb "github.com/openconfig/lemming/dataplane/proto/sai"
fwdpb "github.com/openconfig/lemming/proto/forwarding"
)
Expand Down Expand Up @@ -112,6 +113,7 @@ type Reconciler struct {
fwdClient fwdpb.ForwardingClient
nextHopGroupClient saipb.NextHopGroupClient
lagClient saipb.LagClient
vlanClient saipb.VlanClient
stateMu sync.RWMutex
// state keeps track of the applied state of the device's interfaces so that we do not issue duplicate configuration commands to the device's interfaces.
state map[string]*oc.Interface
Expand Down Expand Up @@ -162,6 +164,7 @@ func New(conn grpc.ClientConnInterface, switchID, cpuPortID uint64, contextID st
nextHopGroupClient: saipb.NewNextHopGroupClient(conn),
fwdClient: fwdpb.NewForwardingClient(conn),
lagClient: saipb.NewLagClient(conn),
vlanClient: saipb.NewVlanClient(conn),
}
return r
}
Expand Down Expand Up @@ -940,6 +943,14 @@ func (ni *Reconciler) setupPorts(ctx context.Context) error {
}
data.portNID = nid.Nid

// Add this port to default VLAN.
if _, err := ni.vlanClient.CreateVlanMember(ctx, &saipb.CreateVlanMemberRequest{
VlanId: proto.Uint64(4095), // the default VLAN ID.
BridgePortId: proto.Uint64(portResp.Oid),
VlanTaggingMode: sai.VlanTaggingMode_VLAN_TAGGING_MODE_UNTAGGED.Enum(),
}); err != nil {
return fmt.Errorf("failed to add port to the default VLAN: %v", err)
}
hostifName := i.Attrs().Name + internalSuffix
hostifResp, err := ni.hostifClient.CreateHostif(ctx, &saipb.CreateHostifRequest{
Switch: ni.switchID,
Expand Down
1 change: 1 addition & 0 deletions dataplane/saiserver/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ go_test(
"routing_test.go",
"switch_test.go",
"tunnel_test.go",
"vlan_test.go",
],
embed = [":saiserver"],
deps = [
Expand Down
2 changes: 1 addition & 1 deletion dataplane/saiserver/attrmgr/attrmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (mgr *AttrMgr) PopulateAttributes(req, resp proto.Message) error {
enumVal := reqList.Get(i).Enum()
val, ok := mgr.attrs[id][int32(enumVal)]
if !ok {
return fmt.Errorf("requested attribute not set: %v", reqList.Get(i))
return fmt.Errorf("requested attribute not set: %v in OID: %v", reqList.Get(i), id)
}
// Empty lists exist so they are not errors, but are not settable.
if val != nil {
Expand Down
28 changes: 15 additions & 13 deletions dataplane/saiserver/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,21 @@ var getInterface = net.InterfaceByName

func getForwardingPipeline() []*fwdpb.ActionDesc {
return []*fwdpb.ActionDesc{
fwdconfig.Action(fwdconfig.LookupAction(MyMacTable)).Build(), // Decide whether to process the packet.
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.
fwdconfig.Action(fwdconfig.DecapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET)).Build(), // Decap L2 header.
fwdconfig.Action(fwdconfig.LookupAction(tunTermTable)).Build(), // Decap the packet if we have a tunnel.
fwdconfig.Action(fwdconfig.LookupAction(IngressActionTable)).Build(), // Run ingress action.
fwdconfig.Action(fwdconfig.LookupAction(FIBSelectorTable)).Build(), // Lookup in FIB.
fwdconfig.Action(fwdconfig.EncapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET)).Build(), // Encap L2 header.
fwdconfig.Action(fwdconfig.LookupAction(outputIfaceTable)).Build(), // Match interface to port
fwdconfig.Action(fwdconfig.LookupAction(NeighborTable)).Build(), // Lookup in the neighbor table.
fwdconfig.Action(fwdconfig.LookupAction(EgressActionTable)).Build(), // Run egress actions
fwdconfig.Action(fwdconfig.LookupAction(SRCMACTable)).Build(), // Lookup interface's MAC addr.
fwdconfig.Action(fwdconfig.LookupAction(VlanTable)).Build(), // Tag VLAN.
fwdconfig.Action(fwdconfig.LookupAction(MyMacTable)).Build(), // Decide whether to process the packet.
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.
fwdconfig.Action(fwdconfig.DecapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET)).Build(), // Decap L2 header.
fwdconfig.Action(fwdconfig.LookupAction(tunTermTable)).Build(), // Decap the packet if we have a tunnel.
fwdconfig.Action(fwdconfig.LookupAction(IngressActionTable)).Build(), // Run ingress action.
fwdconfig.Action(fwdconfig.LookupAction(FIBSelectorTable)).Build(), // Lookup in FIB.
fwdconfig.Action(fwdconfig.EncapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET)).Build(), // Encap L2 header.
fwdconfig.Action(fwdconfig.LookupAction(outputIfaceTable)).Build(), // Match interface to port
fwdconfig.Action(fwdconfig.LookupAction(NeighborTable)).Build(), // Lookup in the neighbor table.
fwdconfig.Action(fwdconfig.LookupAction(EgressActionTable)).Build(), // Run egress actions
fwdconfig.Action(fwdconfig.LookupAction(SRCMACTable)).Build(), // Lookup interface's MAC addr.
fwdconfig.Action(fwdconfig.DecapAction(fwdpb.PacketHeaderId_PACKET_HEADER_ID_ETHERNET_VLAN)).Build(), // TODO: Revise the code if trunk mode needs to be supported.
{
ActionType: fwdpb.ActionType_ACTION_TYPE_OUTPUT,
},
Expand Down
153 changes: 151 additions & 2 deletions dataplane/saiserver/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

log "github.com/golang/glog"

"github.com/openconfig/lemming/dataplane/proto/sai"
saipb "github.com/openconfig/lemming/dataplane/proto/sai"
fwdpb "github.com/openconfig/lemming/proto/forwarding"
)
Expand Down Expand Up @@ -630,6 +631,8 @@ func (ri *routerInterface) CreateRouterInterface(ctx context.Context, req *saipb
return nil, err
}

// Here we associate a port to its output interface. How is output port assigned?
// Does that mean that we trigger the xmit of port GetPortId()?
_, err = ri.dataplane.TableEntryAdd(ctx, fwdconfig.TableEntryAddRequest(ri.dataplane.ID(), outputIfaceTable).
AppendEntry(
fwdconfig.EntryDesc(fwdconfig.ExactEntry(fwdconfig.PacketFieldBytes(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_OUTPUT_IFACE).WithUint64(id))),
Expand Down Expand Up @@ -753,24 +756,38 @@ func (ri *routerInterface) GetRouterInterfaceStats(ctx context.Context, req *sai
return &saipb.GetRouterInterfaceStatsResponse{Values: vals}, nil
}

// vlanMember contains the info of a VLAN member.
type vlanMember struct {
Oid uint64
PortID uint64
Vid uint32
Mode sai.VlanTaggingMode
}

type vlan struct {
saipb.UnimplementedVlanServer
mgr *attrmgr.AttrMgr
dataplane switchDataplaneAPI
oidByVId map[uint32]uint64 // VID -> VLAN_OID
vlans map[uint64]map[uint64]*vlanMember // VLAN_OID -> VLAN_Member_OID (port)
}

func newVlan(mgr *attrmgr.AttrMgr, dataplane switchDataplaneAPI, s *grpc.Server) *vlan {
v := &vlan{
mgr: mgr,
dataplane: dataplane,
oidByVId: map[uint32]uint64{},
vlans: map[uint64]map[uint64]*vlanMember{},
}
saipb.RegisterVlanServer(s, v)
return v
}

func (vlan *vlan) CreateVlan(context.Context, *saipb.CreateVlanRequest) (*saipb.CreateVlanResponse, error) {
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())
}
id := vlan.mgr.NextID()

req := &saipb.GetSwitchAttributeRequest{Oid: 1, AttrType: []saipb.SwitchAttr{saipb.SwitchAttr_SWITCH_ATTR_DEFAULT_STP_INST_ID}}
resp := &saipb.GetSwitchAttributeResponse{}

Expand All @@ -781,6 +798,7 @@ func (vlan *vlan) CreateVlan(context.Context, *saipb.CreateVlanRequest) (*saipb.
attrs := &saipb.VlanAttribute{
MemberList: []uint64{},
StpInstance: resp.Attr.DefaultStpInstId,
VlanId: r.VlanId,
UnknownNonIpMcastOutputGroupId: proto.Uint64(0),
UnknownIpv4McastOutputGroupId: proto.Uint64(0),
UnknownIpv6McastOutputGroupId: proto.Uint64(0),
Expand All @@ -793,11 +811,142 @@ func (vlan *vlan) CreateVlan(context.Context, *saipb.CreateVlanRequest) (*saipb.
TamObject: []uint64{},
}
vlan.mgr.StoreAttributes(id, attrs)
vlan.vlans[id] = map[uint64]*vlanMember{}
vlan.oidByVId[*r.VlanId] = id
return &saipb.CreateVlanResponse{
Oid: id,
}, nil
}

func (vlan *vlan) RemoveVlan(ctx context.Context, req *saipb.RemoveVlanRequest) (*saipb.RemoveVlanResponse, error) {
memberMap, ok := vlan.vlans[req.GetOid()]
if !ok {
return nil, fmt.Errorf("VLAN not found for OID: %d", req.GetOid())
}
// Assign this VLAN's associated ports back to the default VLAN.
dVid := vlan.oidByVId[DefaultVlanId]
for k, v := range memberMap {
_, err := attrmgr.InvokeAndSave(ctx, vlan.mgr, vlan.RemoveVlanMember, &saipb.RemoveVlanMemberRequest{
Oid: req.GetOid(),
})
if err != nil {
return nil, err
}
vlan.vlans[dVid][k] = v
}
// Update VLAN attributes.
rmVlanAttrReq := &saipb.GetVlanAttributeRequest{Oid: req.GetOid(), AttrType: []saipb.VlanAttr{saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST}}
rmVlanAttrResp := &saipb.GetVlanAttributeResponse{}
if err := vlan.mgr.PopulateAttributes(rmVlanAttrReq, rmVlanAttrResp); err != nil {
return nil, err
}
defVlanAttrReq := &saipb.GetVlanAttributeRequest{Oid: dVid, AttrType: []saipb.VlanAttr{saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST}}
defVlanAttrResp := &saipb.GetVlanAttributeResponse{}
if err := vlan.mgr.PopulateAttributes(defVlanAttrReq, defVlanAttrResp); err != nil {
return nil, err
}
defVlanAttrResp.GetAttr().MemberList = append(defVlanAttrResp.GetAttr().MemberList, rmVlanAttrResp.GetAttr().MemberList...)
vlan.mgr.StoreAttributes(dVid, defVlanAttrResp)
rmVlanAttrResp.GetAttr().MemberList = []uint64{}
vlan.mgr.StoreAttributes(req.GetOid(), rmVlanAttrResp)

// Update the internal map.
delete(vlan.vlans, req.GetOid())
return &saipb.RemoveVlanResponse{}, nil
}

func (vlan *vlan) CreateVlanMember(ctx context.Context, req *saipb.CreateVlanMemberRequest) (*saipb.CreateVlanMemberResponse, error) {
// Locate the OID of the VLAN.
vOid, ok := vlan.oidByVId[uint32(*req.VlanId)]
if !ok {
return nil, status.Errorf(codes.FailedPrecondition, "VLAN %d does not exsit", req.GetVlanId())
}
mOid := vlan.mgr.NextID()
nid, err := vlan.dataplane.ObjectNID(ctx, &fwdpb.ObjectNIDRequest{
ContextId: &fwdpb.ContextId{Id: vlan.dataplane.ID()},
ObjectId: &fwdpb.ObjectId{Id: fmt.Sprint(req.GetBridgePortId())},
})
if err != nil {
return nil, err
}
vlanReq := fwdconfig.TableEntryAddRequest(vlan.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(fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG))).Build(),
fwdconfig.Action(fwdconfig.UpdateAction(fwdpb.UpdateType_UPDATE_TYPE_SET, fwdpb.PacketFieldNum_PACKET_FIELD_NUM_VLAN_TAG).WithUint64Value(req.GetVlanId())).Build(),
}
if _, err := vlan.dataplane.TableEntryAdd(ctx, vlanReq); err != nil {
return nil, err
}
vlan.vlans[vOid][mOid] = &vlanMember{Oid: mOid, PortID: req.GetBridgePortId(), Vid: uint32(req.GetVlanId()), Mode: req.GetVlanTaggingMode()}
// Update attributes
vlanAttrReq := &saipb.GetVlanAttributeRequest{Oid: vOid, AttrType: []saipb.VlanAttr{saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST}}
vlanAttrResp := &saipb.GetVlanAttributeResponse{}
if err := vlan.mgr.PopulateAttributes(vlanAttrReq, vlanAttrResp); err != nil {
return nil, err
}
vlanAttrResp.GetAttr().MemberList = append(vlanAttrResp.GetAttr().MemberList, mOid)
vlan.mgr.StoreAttributes(vOid, vlanAttrResp)
log.Infof("Add port %d to VLAN %d", req.GetBridgePortId(), req.GetVlanId())
return &saipb.CreateVlanMemberResponse{Oid: mOid}, nil
}

func (vlan *vlan) RemoveVlanMember(ctx context.Context, r *saipb.RemoveVlanMemberRequest) (*saipb.RemoveVlanMemberResponse, error) {
locateMember := func(oid uint64) (uint64, uint64, error) {
for vOid, v := range vlan.vlans {
for mOid := range v {
if mOid == oid {
return vOid, mOid, nil
}
}
}
return 0, 0, fmt.Errorf("cannot find member with id=%d", oid)
}
// Move the port back to the default VLAN.
vOid, mOid, err := locateMember(r.GetOid())
if err != nil {
return nil, err
}
_, err = attrmgr.InvokeAndSave(ctx, vlan.mgr, vlan.CreateVlanMember, &saipb.CreateVlanMemberRequest{
VlanId: proto.Uint64(4095), // the default VLAN ID.
BridgePortId: proto.Uint64(mOid),
VlanTaggingMode: sai.VlanTaggingMode_VLAN_TAGGING_MODE_UNTAGGED.Enum(),
})
if err != nil {
return nil, err
}
// Update attributes.
vlanAttrReq := &saipb.GetVlanAttributeRequest{Oid: vOid, AttrType: []saipb.VlanAttr{saipb.VlanAttr_VLAN_ATTR_MEMBER_LIST}}
vlanAttrResp := &saipb.GetVlanAttributeResponse{}
if err := vlan.mgr.PopulateAttributes(vlanAttrReq, vlanAttrResp); err != nil {
return nil, err
}
vlanAttrResp.GetAttr().MemberList = append(vlanAttrResp.GetAttr().MemberList, mOid)
newMemList := []uint64{}
for _, i := range vlanAttrResp.Attr.GetMemberList() {
if i != mOid {
newMemList = append(newMemList, i)
}
}
vlanAttrResp.GetAttr().MemberList = newMemList
vlan.mgr.StoreAttributes(vOid, vlanAttrResp)
// Update internal map.
delete(vlan.vlans[vOid], mOid)
return &saipb.RemoveVlanMemberResponse{}, nil
}

func (vlan *vlan) CreateVlanMembers(ctx context.Context, req *saipb.CreateVlanMembersRequest) (*saipb.CreateVlanMembersResponse, error) {
resp := &saipb.CreateVlanMembersResponse{}
for _, req := range req.GetReqs() {
res, err := attrmgr.InvokeAndSave(ctx, vlan.mgr, vlan.CreateVlanMember, req)
if err != nil {
return nil, err
}
resp.Resps = append(resp.Resps, res)
}
return resp, nil
}

type bridge struct {
saipb.UnimplementedBridgeServer
mgr *attrmgr.AttrMgr
Expand Down
28 changes: 26 additions & 2 deletions dataplane/saiserver/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ const (
hostifToPortTable = "cpu-input"
portToHostifTable = "cpu-output"
tunTermTable = "tun-term"
VlanTable = "vlan"
DefaultVlanId = 4095 // ID of the default VLAN.
)

func newSwitch(mgr *attrmgr.AttrMgr, engine switchDataplaneAPI, s *grpc.Server, opts *dplaneopts.Options) (*saiSwitch, error) {
Expand Down Expand Up @@ -231,6 +233,26 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ
if _, err := sw.dataplane.TableCreate(ctx, portMAC); err != nil {
return nil, err
}
vlanReq := &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: VlanTable}},
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_PACKET_PORT_INPUT,
},
}},
},
},
},
}
if _, err := sw.dataplane.TableCreate(ctx, vlanReq); err != nil {
return nil, err
}
myMAC := &fwdpb.TableCreateRequest{
ContextId: &fwdpb.ContextId{Id: sw.dataplane.ID()},
Desc: &fwdpb.TableDesc{
Expand Down Expand Up @@ -578,11 +600,14 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ
sw.mgr.StoreAttributes(swID, &saipb.SwitchAttribute{DefaultStpInstId: &stpResp.Oid})

vlanResp, err := attrmgr.InvokeAndSave(ctx, sw.mgr, sw.vlan.CreateVlan, &saipb.CreateVlanRequest{
Switch: swID,
Switch: swID,
VlanId: proto.Uint32(DefaultVlanId), // Create the default VLAN.
LearnDisable: proto.Bool(true), // TODO: figure out what does this do?
})
if err != nil {
return nil, err
}
sw.mgr.StoreAttributes(swID, &saipb.SwitchAttribute{DefaultVlanId: &vlanResp.Oid})

vrResp, err := attrmgr.InvokeAndSave(ctx, sw.mgr, sw.vr.CreateVirtualRouter, &saipb.CreateVirtualRouterRequest{
Switch: swID,
Expand Down Expand Up @@ -695,7 +720,6 @@ func (sw *saiSwitch) CreateSwitch(ctx context.Context, _ *saipb.CreateSwitchRequ
NatZoneCounterObjectId: proto.Uint64(0),
}
sw.mgr.StoreAttributes(swID, attrs)

return &saipb.CreateSwitchResponse{
Oid: swID,
}, nil
Expand Down
Loading
Loading