diff --git a/go.mod b/go.mod index 4781b9e8..924a69f8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( git.fd.io/govpp.git v0.3.6-0.20210927044411-385ccc0d8ba9 - github.com/edwarnicke/govpp v0.0.0-20220509171552-731995b8f574 + github.com/edwarnicke/govpp v0.0.0-20221023154218-ef9a6adf6930 github.com/edwarnicke/serialize v1.0.7 github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.2.0 diff --git a/go.sum b/go.sum index 9cb818c6..a26a145c 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/edwarnicke/exechelper v1.0.2 h1:dD49Ui2U0FBFxxhalnKw6vLS0P0TkgnXBRvKL/xmC5w= github.com/edwarnicke/exechelper v1.0.2/go.mod h1:/T271jtNX/ND4De6pa2aRy2+8sNtyCDB1A2pp4M+fUs= -github.com/edwarnicke/govpp v0.0.0-20220509171552-731995b8f574 h1:2lYhLBDjflBOPJLd/42txk9QlSkVep6p1vKzlvkCKOI= -github.com/edwarnicke/govpp v0.0.0-20220509171552-731995b8f574/go.mod h1:kHDnxA+SSNFeMEHz7xvhub1zvx4mOTRlWWRCay2n5NM= +github.com/edwarnicke/govpp v0.0.0-20221023154218-ef9a6adf6930 h1:/gS3gW7jV6ljzWcnHGxu1Yn1guxtwq8IuwJpK5TUl1w= +github.com/edwarnicke/govpp v0.0.0-20221023154218-ef9a6adf6930/go.mod h1:kHDnxA+SSNFeMEHz7xvhub1zvx4mOTRlWWRCay2n5NM= github.com/edwarnicke/grpcfd v1.1.2 h1:2b8kCABQ1+JjSKGDoHadqSW7whCeTXMqtyo6jmB5B8k= github.com/edwarnicke/grpcfd v1.1.2/go.mod h1:rHihB9YvNMixz8rS+ZbwosI2kj65VLkeyYAI2M+/cGA= github.com/edwarnicke/serialize v0.0.0-20200705214914-ebc43080eecf/go.mod h1:XvbCO/QGsl3X8RzjBMoRpkm54FIAZH5ChK2j+aox7pw= diff --git a/pkg/networkservice/chains/forwarder/server.go b/pkg/networkservice/chains/forwarder/server.go index 59591be3..3c73233e 100644 --- a/pkg/networkservice/chains/forwarder/server.go +++ b/pkg/networkservice/chains/forwarder/server.go @@ -65,6 +65,7 @@ import ( "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/tag" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/up" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/xconnect" + "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/xconnect/l2bridgedomain" ) // Connection aggregates the api.Connection and api.ChannelProvider interfaces @@ -111,6 +112,7 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, vppConn stats.NewServer(ctx, opts.statsOpts...), up.NewServer(ctx, vppConn), xconnect.NewServer(vppConn), + l2bridgedomain.NewServer(vppConn), connectioncontextkernel.NewServer(), ethernetcontext.NewVFServer(), tag.NewServer(ctx, vppConn), diff --git a/pkg/networkservice/mechanisms/vlan/client.go b/pkg/networkservice/mechanisms/vlan/client.go index f6870a3b..813be16d 100644 --- a/pkg/networkservice/mechanisms/vlan/client.go +++ b/pkg/networkservice/mechanisms/vlan/client.go @@ -32,7 +32,6 @@ import ( "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" "github.com/networkservicemesh/sdk/pkg/tools/postpone" - "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vlan/hwaddress" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vlan/l2vtr" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vlan/mtu" ) @@ -49,7 +48,6 @@ type vlanClient struct { // NewClient returns a VLAN client chain element func NewClient(vppConn api.Connection, domain2Device map[string]string) networkservice.NetworkServiceClient { return chain.NewNetworkServiceClient( - hwaddress.NewClient(vppConn), mtu.NewClient(vppConn), l2vtr.NewClient(vppConn), &vlanClient{ @@ -89,6 +87,6 @@ func (v *vlanClient) Request(ctx context.Context, request *networkservice.Networ } func (v *vlanClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { - _ = delSubIf(ctx, conn, v.vppConn) + delSubIf(ctx, conn) return next.Client(ctx).Close(ctx, conn, opts...) } diff --git a/pkg/networkservice/mechanisms/vlan/common.go b/pkg/networkservice/mechanisms/vlan/common.go index 2b337cef..c572e624 100644 --- a/pkg/networkservice/mechanisms/vlan/common.go +++ b/pkg/networkservice/mechanisms/vlan/common.go @@ -19,6 +19,7 @@ package vlan import ( "context" "io" + "strings" "time" "git.fd.io/govpp.git/api" @@ -49,57 +50,69 @@ func addSubIf(ctx context.Context, conn *networkservice.Connection, vppConn api. if !ok { return errors.Errorf("no interface name for label %s", via) } - - now := time.Now() - client, err := interfaces.NewServiceClient(vppConn).SwInterfaceDump(ctx, &interfaces.SwInterfaceDump{ - NameFilterValid: true, - NameFilter: hostIFName, - }) + vlanID := mechanism.GetVlanID() + hostSwIfIndex, vlanSwIfIndex, err := getHostOrVlanInterface(ctx, vppConn, hostIFName, vlanID) if err != nil { - return errors.Wrapf(err, "error attempting to get interface dump client to set vlan subinterface on %q", hostIFName) + return err } - log.FromContext(ctx). - WithField("duration", time.Since(now)). - WithField("HostInterfaceName", hostIFName). - WithField("vppapi", "SwInterfaceDump").Debug("completed") - - for { - details, err := client.Recv() - if err == io.EOF { - break - } - if err != nil { - return errors.Wrapf(err, "error attempting to get interface details to set vlan subinterface on %q", hostIFName) - } - - if (hostIFName != details.InterfaceName) && (afPacketNamePrefix+hostIFName != details.InterfaceName) { + if vlanID != 0 { + if vlanSwIfIndex != 0 { log.FromContext(ctx). - WithField("InterfaceName", details.InterfaceName). - WithField("vppapi", "SwInterfaceDetails").Debug("skipped") - continue - } - - swIfIndex := details.SwIfIndex - vlanID := mechanism.GetVlanID() - if vlanID != 0 { - vlanIfIndex, shouldReturn, returnValue := vppAddSubIf(ctx, vppConn, swIfIndex, vlanID) + WithField("VlanInterfaceIndex", vlanSwIfIndex).Debug("Vlan Interface already created") + ifindex.Store(ctx, true, vlanSwIfIndex) + } else { + newVlanIfIndex, shouldReturn, returnValue := vppAddSubIf(ctx, vppConn, hostSwIfIndex, vlanID) if shouldReturn { return returnValue } - ifindex.Store(ctx, true, *vlanIfIndex) - } else { - log.FromContext(ctx). - WithField("HostInterfaceIndex", swIfIndex). - WithField("Details", details).Debug("QinQ disabled") - ifindex.Store(ctx, true, swIfIndex) + ifindex.Store(ctx, true, *newVlanIfIndex) } - return nil + } else { + log.FromContext(ctx). + WithField("HostInterfaceIndex", hostSwIfIndex).Debug("QinQ disabled") + ifindex.Store(ctx, true, hostSwIfIndex) } - return errors.Errorf("no interface name found %s", hostIFName) + /* Store vlanID used by bridge domain server */ + Store(ctx, true, vlanID) } return nil } +func getHostOrVlanInterface(ctx context.Context, vppConn api.Connection, hostIFName string, vlanID uint32) (hostSwIfIndex, vlanSwIfIndex interface_types.InterfaceIndex, err error) { + now := time.Now() + client, err := interfaces.NewServiceClient(vppConn).SwInterfaceDump(ctx, &interfaces.SwInterfaceDump{ + NameFilterValid: true, + NameFilter: hostIFName, + }) + if err != nil { + return 0, 0, errors.Wrapf(err, "error attempting to get interface dump client to set vlan subinterface on %q", hostIFName) + } + log.FromContext(ctx). + WithField("duration", time.Since(now)). + WithField("HostInterfaceName", hostIFName). + WithField("vppapi", "SwInterfaceDump").Debug("completed") + for { + details, err := client.Recv() + if err == io.EOF { + break + } + if err != nil { + return 0, 0, errors.Wrapf(err, "error attempting to get interface details to set vlan subinterface on %q", hostIFName) + } + if (vlanID != 0) && strings.Contains(details.InterfaceName, hostIFName) && (details.Type == interface_types.IF_API_TYPE_SUB) && (details.SubID == vlanID) { + return 0, details.SwIfIndex, nil + } + if (hostIFName == details.InterfaceName) || (afPacketNamePrefix+hostIFName == details.InterfaceName) { + hostSwIfIndex = details.SwIfIndex + } + } + + if hostSwIfIndex == 0 { + return 0, 0, errors.Errorf("no interface name found %s", hostIFName) + } + return hostSwIfIndex, 0, nil +} + func vppAddSubIf(ctx context.Context, vppConn api.Connection, swIfIndex interface_types.InterfaceIndex, vlanID uint32) (*interface_types.InterfaceIndex, bool, error) { now := time.Now() vlanSubif := &interfaces.CreateVlanSubif{ @@ -119,30 +132,14 @@ func vppAddSubIf(ctx context.Context, vppConn api.Connection, swIfIndex interfac WithField("vppapi", "CreateVlanSubIf").Debug("completed") return &rsp.SwIfIndex, false, nil } -func delSubIf(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection) error { +func delSubIf(ctx context.Context, conn *networkservice.Connection) { if mechanism := vlanmech.ToMechanism(conn.GetMechanism()); mechanism != nil { - swIfIndex, ok := ifindex.Load(ctx, true) + _, ok := ifindex.Load(ctx, true) if !ok { - return nil - } - - if mechanism.GetVlanID() == 0 { - ifindex.Delete(ctx, true) - return nil + return } - now := time.Now() - vlanSubif := &interfaces.DeleteSubif{ - SwIfIndex: swIfIndex, - } - _, err := interfaces.NewServiceClient(vppConn).DeleteSubif(ctx, vlanSubif) - if err != nil { - return errors.WithStack(err) - } - log.FromContext(ctx). - WithField("duration", time.Since(now)). - WithField("HostInterfaceIndex", swIfIndex). - WithField("vppapi", "DeleteSubif").Debug("completed") + /* Delete sub-interface together with the l2 bridge */ ifindex.Delete(ctx, true) + Delete(ctx, true) } - return nil } diff --git a/pkg/networkservice/mechanisms/vlan/hwaddress/client.go b/pkg/networkservice/mechanisms/vlan/hwaddress/client.go deleted file mode 100644 index a93931e8..00000000 --- a/pkg/networkservice/mechanisms/vlan/hwaddress/client.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2021 Nordix Foundation. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hwaddress - -import ( - "context" - - "git.fd.io/govpp.git/api" - "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" - "google.golang.org/grpc" - - "github.com/networkservicemesh/api/pkg/api/networkservice" - "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" - "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" - "github.com/networkservicemesh/sdk/pkg/tools/postpone" -) - -type hwaddressClient struct { - vppConn api.Connection -} - -// NewClient - updates ethernet context with hw address -func NewClient(vppConn api.Connection) networkservice.NetworkServiceClient { - return &hwaddressClient{ - vppConn: vppConn, - } -} - -func (h *hwaddressClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { - postponeCtxFunc := postpone.ContextWithValues(ctx) - - conn, err := next.Client(ctx).Request(ctx, request, opts...) - if err != nil { - return nil, err - } - - if err := setEthContextHwaddress(ctx, conn, h.vppConn, metadata.IsClient(h)); err != nil { - if closeErr := h.closeOnFailure(postponeCtxFunc, conn, opts); closeErr != nil { - err = errors.Wrapf(err, "connection closed with error: %s", closeErr.Error()) - } - return nil, err - } - return conn, nil -} - -func (h *hwaddressClient) closeOnFailure(postponeCtxFunc func() (context.Context, context.CancelFunc), conn *networkservice.Connection, opts []grpc.CallOption) error { - closeCtx, cancelClose := postponeCtxFunc() - defer cancelClose() - - _, err := h.Close(closeCtx, conn, opts...) - - return err -} - -func (h *hwaddressClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { - return next.Client(ctx).Close(ctx, conn, opts...) -} diff --git a/pkg/networkservice/mechanisms/vlan/hwaddress/common.go b/pkg/networkservice/mechanisms/vlan/hwaddress/common.go deleted file mode 100644 index 5082be68..00000000 --- a/pkg/networkservice/mechanisms/vlan/hwaddress/common.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2021 Nordix Foundation. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hwaddress - -import ( - "context" - "time" - - "git.fd.io/govpp.git/api" - "github.com/pkg/errors" - - interfaces "github.com/edwarnicke/govpp/binapi/interface" - "github.com/networkservicemesh/api/pkg/api/networkservice" - vlanmech "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/vlan" - "github.com/networkservicemesh/sdk/pkg/tools/log" - - "github.com/networkservicemesh/sdk-vpp/pkg/tools/ifindex" -) - -func setEthContextHwaddress(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection, isClient bool) error { - if mechanism := vlanmech.ToMechanism(conn.GetMechanism()); mechanism != nil { - now := time.Now() - swIfIndex, ok := ifindex.Load(ctx, isClient) - if !ok { - return nil - } - - rsp, err := interfaces.NewServiceClient(vppConn).SwInterfaceGetMacAddress(ctx, &interfaces.SwInterfaceGetMacAddress{ - SwIfIndex: swIfIndex}) - if err != nil { - return errors.WithStack(err) - } - log.FromContext(ctx). - WithField("duration", time.Since(now)). - WithField("HostInterfaceIndex", swIfIndex). - WithField("HwAddress", rsp.MacAddress). - WithField("vppapi", "SwInterfaceGetMacAddress").Debug("completed") - - if conn.GetContext().GetEthernetContext() == nil { - conn.GetContext().EthernetContext = new(networkservice.EthernetContext) - } - ethernetContext := conn.GetContext().GetEthernetContext() - if isClient { - ethernetContext.SrcMac = rsp.MacAddress.String() - } else { - ethernetContext.DstMac = rsp.MacAddress.String() - } - } - return nil -} diff --git a/pkg/networkservice/mechanisms/vlan/l2vtr/client.go b/pkg/networkservice/mechanisms/vlan/l2vtr/client.go index 5b24263d..73d978fc 100644 --- a/pkg/networkservice/mechanisms/vlan/l2vtr/client.go +++ b/pkg/networkservice/mechanisms/vlan/l2vtr/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Nordix Foundation. +// Copyright (c) 2021-2022 Nordix Foundation. // // SPDX-License-Identifier: Apache-2.0 // @@ -61,6 +61,5 @@ func (v *l2vtrClient) Request(ctx context.Context, request *networkservice.Netwo } func (v *l2vtrClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { - _ = disableVtr(ctx, conn, v.vppConn) return next.Client(ctx).Close(ctx, conn, opts...) } diff --git a/pkg/networkservice/mechanisms/vlan/l2vtr/common.go b/pkg/networkservice/mechanisms/vlan/l2vtr/common.go index f186363f..163d2ce7 100644 --- a/pkg/networkservice/mechanisms/vlan/l2vtr/common.go +++ b/pkg/networkservice/mechanisms/vlan/l2vtr/common.go @@ -58,29 +58,3 @@ func enableVtr(ctx context.Context, conn *networkservice.Connection, vppConn api } return nil } - -func disableVtr(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection) error { - if mechanism := vlanmech.ToMechanism(conn.GetMechanism()); mechanism != nil { - if mechanism.GetVlanID() == 0 { - return nil - } - swIfIndex, ok := ifindex.Load(ctx, true) - if !ok { - return nil - } - now := time.Now() - - if _, err := l2.NewServiceClient(vppConn).L2InterfaceVlanTagRewrite(ctx, &l2.L2InterfaceVlanTagRewrite{ - SwIfIndex: swIfIndex, - VtrOp: L2VtrDisabled, - }); err != nil { - return errors.WithStack(err) - } - log.FromContext(ctx). - WithField("duration", time.Since(now)). - WithField("SwIfIndex", swIfIndex). - WithField("operation", "DISABLE"). - WithField("vppapi", "L2InterfaceVlanTagRewrite").Debug("completed") - } - return nil -} diff --git a/pkg/networkservice/mechanisms/vlan/metadata.go b/pkg/networkservice/mechanisms/vlan/metadata.go new file mode 100644 index 00000000..e375e825 --- /dev/null +++ b/pkg/networkservice/mechanisms/vlan/metadata.go @@ -0,0 +1,47 @@ +// Copyright (c) 2022 Nordix Foundation. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vlan + +import ( + "context" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type key struct{} + +// Store sets the vlan ID stored in per Connection.Id metadata. +func Store(ctx context.Context, isClient bool, bdID uint32) { + metadata.Map(ctx, isClient).Store(key{}, bdID) +} + +// Delete deletes the vlan ID stored in per Connection.Id metadata +func Delete(ctx context.Context, isClient bool) { + metadata.Map(ctx, isClient).Delete(key{}) +} + +// Load returns the vlan ID stored in per Connection.Id metadata, or nil if no +// value is present. +// The ok result indicates whether value was found in the per Connection.Id metadata. +func Load(ctx context.Context, isClient bool) (value uint32, ok bool) { + rawValue, ok := metadata.Map(ctx, isClient).Load(key{}) + if !ok { + return + } + value, ok = rawValue.(uint32) + return value, ok +} diff --git a/pkg/networkservice/xconnect/l2bridgedomain/common.go b/pkg/networkservice/xconnect/l2bridgedomain/common.go new file mode 100644 index 00000000..cd1de89c --- /dev/null +++ b/pkg/networkservice/xconnect/l2bridgedomain/common.go @@ -0,0 +1,198 @@ +// Copyright (c) 2022 Nordix Foundation. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package l2bridgedomain + +import ( + "context" + "time" + + "git.fd.io/govpp.git/api" + interfaces "github.com/edwarnicke/govpp/binapi/interface" + "github.com/edwarnicke/govpp/binapi/interface_types" + "github.com/edwarnicke/govpp/binapi/l2" + "github.com/pkg/errors" + + "github.com/networkservicemesh/sdk/pkg/tools/log" + + "github.com/networkservicemesh/sdk-vpp/pkg/tools/ifindex" +) + +type bridgeDomain struct { + // BdID + id uint32 + + // attached interfaces + attached map[interface_types.InterfaceIndex]struct{} +} + +type bridgeDomainKey struct { + vlanID uint32 + clientIfIndex interface_types.InterfaceIndex +} + +func addBridgeDomain(ctx context.Context, vppConn api.Connection, bridges *l2BridgeDomain, vlanID uint32) error { + clientIfIndex, ok := ifindex.Load(ctx, true) + if !ok { + return nil + } + serverIfIndex, ok := ifindex.Load(ctx, false) + if !ok { + return nil + } + + // key to handle the vlanID == 0 case + key := bridgeDomainKey{ + vlanID: vlanID, + clientIfIndex: clientIfIndex, + } + l2Bridge, ok := bridges.Load(key) + if !ok { + bridgeID, err := addDelVppBridgeDomain(ctx, vppConn, ^uint32(0), true) + if err != nil { + return err + } + l2Bridge = &bridgeDomain{ + id: bridgeID, + attached: make(map[interface_types.InterfaceIndex]struct{}), + } + bridges.Store(key, l2Bridge) + } + if _, ok = l2Bridge.attached[serverIfIndex]; !ok { + err := addDelVppInterfaceBridgeDomain(ctx, vppConn, serverIfIndex, l2Bridge.id, 1, true) + if err != nil { + return err + } + l2Bridge.attached[serverIfIndex] = struct{}{} + bridges.Store(key, l2Bridge) + } + if _, ok = l2Bridge.attached[clientIfIndex]; !ok { + err := addDelVppInterfaceBridgeDomain(ctx, vppConn, clientIfIndex, l2Bridge.id, 0, true) + if err != nil { + return err + } + l2Bridge.attached[clientIfIndex] = struct{}{} + bridges.Store(key, l2Bridge) + } + return nil +} + +func delBridgeDomain(ctx context.Context, vppConn api.Connection, bridges *l2BridgeDomain, vlanID uint32) error { + if clientIfIndex, ok := ifindex.Load(ctx, true); ok { + key := bridgeDomainKey{ + vlanID: vlanID, + clientIfIndex: clientIfIndex, + } + l2Bridge, ok := bridges.Load(key) + if !ok { + return nil + } + if serverIfIndex, okey := ifindex.Load(ctx, false); okey { + if _, ok = l2Bridge.attached[serverIfIndex]; ok { + err := addDelVppInterfaceBridgeDomain(ctx, vppConn, serverIfIndex, l2Bridge.id, 0, false) + if err != nil { + return err + } + delete(l2Bridge.attached, serverIfIndex) + } + } + if len(l2Bridge.attached) == 1 { + // last interface -> delete the bridge and the sub-interface also + if _, ok = l2Bridge.attached[clientIfIndex]; ok { + err := addDelVppInterfaceBridgeDomain(ctx, vppConn, clientIfIndex, l2Bridge.id, 0, false) + if err != nil { + return err + } + err = delVppSubIf(ctx, vppConn, vlanID, clientIfIndex) + if err != nil { + return err + } + delete(l2Bridge.attached, clientIfIndex) + _, err = addDelVppBridgeDomain(ctx, vppConn, l2Bridge.id, false) + if err != nil { + return err + } + bridges.Delete(key) + } + } else { + bridges.Store(key, l2Bridge) + } + } + return nil +} + +func addDelVppBridgeDomain(ctx context.Context, vppConn api.Connection, bridgeID uint32, isAdd bool) (uint32, error) { + now := time.Now() + bridgeDomainAddDelV2 := &l2.BridgeDomainAddDelV2{ + IsAdd: isAdd, + BdID: bridgeID, + Flood: true, + Forward: true, + Learn: true, + UuFlood: true, + } + rsp, err := l2.NewServiceClient(vppConn).BridgeDomainAddDelV2(ctx, bridgeDomainAddDelV2) + if err != nil { + return 0, errors.WithStack(err) + } + log.FromContext(ctx). + WithField("bridgeID", rsp.BdID). + WithField("isAdd", isAdd). + WithField("duration", time.Since(now)). + WithField("vppapi", "BridgeDomainAddDelV2").Info("completed") + return rsp.BdID, nil +} + +func addDelVppInterfaceBridgeDomain(ctx context.Context, vppConn api.Connection, swIfIndex interface_types.InterfaceIndex, bridgeID uint32, shg uint8, isAdd bool) error { + now := time.Now() + _, err := l2.NewServiceClient(vppConn).SwInterfaceSetL2Bridge(ctx, &l2.SwInterfaceSetL2Bridge{ + RxSwIfIndex: swIfIndex, + Enable: isAdd, + BdID: bridgeID, + Shg: shg, + }) + if err != nil { + return errors.WithStack(err) + } + log.FromContext(ctx). + WithField("swIfIndex", swIfIndex). + WithField("bridgeID", bridgeID). + WithField("isAdd", isAdd). + WithField("shg", shg). + WithField("duration", time.Since(now)). + WithField("vppapi", "SwInterfaceSetL2Bridge").Info("completed") + return nil +} + +func delVppSubIf(ctx context.Context, vppConn api.Connection, vlanID uint32, swIfIndex interface_types.InterfaceIndex) error { + if vlanID == 0 { + ifindex.Delete(ctx, true) + return nil + } + now := time.Now() + vlanSubif := &interfaces.DeleteSubif{ + SwIfIndex: swIfIndex, + } + _, err := interfaces.NewServiceClient(vppConn).DeleteSubif(ctx, vlanSubif) + if err != nil { + return errors.WithStack(err) + } + log.FromContext(ctx). + WithField("duration", time.Since(now)). + WithField("InterfaceIndex", swIfIndex). + WithField("vppapi", "DeleteSubif").Debug("completed") + return nil +} diff --git a/pkg/networkservice/mechanisms/vlan/hwaddress/doc.go b/pkg/networkservice/xconnect/l2bridgedomain/doc.go similarity index 74% rename from pkg/networkservice/mechanisms/vlan/hwaddress/doc.go rename to pkg/networkservice/xconnect/l2bridgedomain/doc.go index 7d659cf4..e5c992e4 100644 --- a/pkg/networkservice/mechanisms/vlan/hwaddress/doc.go +++ b/pkg/networkservice/xconnect/l2bridgedomain/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Nordix Foundation. +// Copyright (c) 2022 Nordix Foundation. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,5 +14,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package hwaddress provides networkservice chain elements to set the hwaddress in ethernetcontext -package hwaddress +// Package l2bridgedomain provides chain elements for creating l2 bridge domain in vpp and adding client and server interfaces (if present) +package l2bridgedomain diff --git a/pkg/networkservice/xconnect/l2bridgedomain/gen.go b/pkg/networkservice/xconnect/l2bridgedomain/gen.go new file mode 100644 index 00000000..e1da13a7 --- /dev/null +++ b/pkg/networkservice/xconnect/l2bridgedomain/gen.go @@ -0,0 +1,26 @@ +// Copyright (c) 2022 Nordix Foundation. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package l2bridgedomain + +import ( + "sync" +) + +//go:generate go-syncmap -output l2_bridge_domain_map.gen.go -type l2BridgeDomain + +// l2BridgeDomain - sync.Map storing *bridgeDomain values to bridgeDomainKey(VLAN-ID, clientIfIndex) +type l2BridgeDomain sync.Map diff --git a/pkg/networkservice/xconnect/l2bridgedomain/l2_bridge_domain_map.gen.go b/pkg/networkservice/xconnect/l2bridgedomain/l2_bridge_domain_map.gen.go new file mode 100644 index 00000000..0f4d8c2f --- /dev/null +++ b/pkg/networkservice/xconnect/l2bridgedomain/l2_bridge_domain_map.gen.go @@ -0,0 +1,73 @@ +// Code generated by "-output l2_bridge_domain_map.gen.go -type l2BridgeDomain -output l2_bridge_domain_map.gen.go -type l2BridgeDomain"; DO NOT EDIT. +package l2bridgedomain + +import ( + "sync" // Used by sync.Map. +) + +// Generate code that will fail if the constants change value. +func _() { + // An "cannot convert l2BridgeDomain literal (type l2BridgeDomain) to type sync.Map" compiler error signifies that the base type have changed. + // Re-run the go-syncmap command to generate them again. + _ = (sync.Map)(l2BridgeDomain{}) +} + +var _nil_l2BridgeDomain_bridgeDomain_value = func() (val *bridgeDomain) { return }() + +// Load returns the value stored in the map for a key, or nil if no +// value is present. +// The ok result indicates whether value was found in the map. +func (m *l2BridgeDomain) Load(key bridgeDomainKey) (*bridgeDomain, bool) { + value, ok := (*sync.Map)(m).Load(key) + if value == nil { + return _nil_l2BridgeDomain_bridgeDomain_value, ok + } + return value.(*bridgeDomain), ok +} + +// Store sets the value for a key. +func (m *l2BridgeDomain) Store(key bridgeDomainKey, value *bridgeDomain) { + (*sync.Map)(m).Store(key, value) +} + +// LoadOrStore returns the existing value for the key if present. +// Otherwise, it stores and returns the given value. +// The loaded result is true if the value was loaded, false if stored. +func (m *l2BridgeDomain) LoadOrStore(key bridgeDomainKey, value *bridgeDomain) (*bridgeDomain, bool) { + actual, loaded := (*sync.Map)(m).LoadOrStore(key, value) + if actual == nil { + return _nil_l2BridgeDomain_bridgeDomain_value, loaded + } + return actual.(*bridgeDomain), loaded +} + +// LoadAndDelete deletes the value for a key, returning the previous value if any. +// The loaded result reports whether the key was present. +func (m *l2BridgeDomain) LoadAndDelete(key bridgeDomainKey) (value *bridgeDomain, loaded bool) { + actual, loaded := (*sync.Map)(m).LoadAndDelete(key) + if actual == nil { + return _nil_l2BridgeDomain_bridgeDomain_value, loaded + } + return actual.(*bridgeDomain), loaded +} + +// Delete deletes the value for a key. +func (m *l2BridgeDomain) Delete(key bridgeDomainKey) { + (*sync.Map)(m).Delete(key) +} + +// Range calls f sequentially for each key and value present in the map. +// If f returns false, range stops the iteration. +// +// Range does not necessarily correspond to any consistent snapshot of the Map's +// contents: no key will be visited more than once, but if the value for any key +// is stored or deleted concurrently, Range may reflect any mapping for that key +// from any point during the Range call. +// +// Range may be O(N) with the number of elements in the map even if f returns +// false after a constant number of calls. +func (m *l2BridgeDomain) Range(f func(key bridgeDomainKey, value *bridgeDomain) bool) { + (*sync.Map)(m).Range(func(key, value interface{}) bool { + return f(key.(bridgeDomainKey), value.(*bridgeDomain)) + }) +} diff --git a/pkg/networkservice/xconnect/l2bridgedomain/server.go b/pkg/networkservice/xconnect/l2bridgedomain/server.go new file mode 100644 index 00000000..8267936b --- /dev/null +++ b/pkg/networkservice/xconnect/l2bridgedomain/server.go @@ -0,0 +1,85 @@ +// Copyright (c) 2022 Nordix Foundation. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package l2bridgedomain + +import ( + "context" + + "git.fd.io/govpp.git/api" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/pkg/errors" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/payload" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "github.com/networkservicemesh/sdk/pkg/tools/log" + "github.com/networkservicemesh/sdk/pkg/tools/postpone" + + "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vlan" +) + +type l2BridgeDomainServer struct { + vppConn api.Connection + b l2BridgeDomain +} + +// NewServer returns a Client chain element that will add client and server vpp interface (if present) to a dridge domain +func NewServer(vppConn api.Connection) networkservice.NetworkServiceServer { + return &l2BridgeDomainServer{ + vppConn: vppConn, + } +} + +func (v *l2BridgeDomainServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { + vlanID, ok := vlan.Load(ctx, true) + // return if the belonging remote mechanism not vlan mechanism + if !ok || request.GetConnection().GetPayload() != payload.Ethernet { + return next.Server(ctx).Request(ctx, request) + } + + postponeCtxFunc := postpone.ContextWithValues(ctx) + + conn, err := next.Server(ctx).Request(ctx, request) + if err != nil { + return nil, err + } + + if err := addBridgeDomain(ctx, v.vppConn, &v.b, vlanID); err != nil { + closeCtx, cancelClose := postponeCtxFunc() + defer cancelClose() + + if _, closeErr := v.Close(closeCtx, conn); closeErr != nil { + err = errors.Wrapf(err, "connection closed with error: %s", closeErr.Error()) + } + + return nil, err + } + + return conn, nil +} + +func (v *l2BridgeDomainServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { + vlanID, ok := vlan.Load(ctx, true) + if !ok || conn.GetPayload() != payload.Ethernet { + return next.Server(ctx).Close(ctx, conn) + } + if err := delBridgeDomain(ctx, v.vppConn, &v.b, vlanID); err != nil { + log.FromContext(ctx).WithField("l2BridgeDomain", "server").Error("delBridgeDomain", err) + } + return next.Server(ctx).Close(ctx, conn) +} diff --git a/pkg/networkservice/xconnect/l2xconnect/common.go b/pkg/networkservice/xconnect/l2xconnect/common.go index 75e51dd5..2c9a74a8 100644 --- a/pkg/networkservice/xconnect/l2xconnect/common.go +++ b/pkg/networkservice/xconnect/l2xconnect/common.go @@ -1,5 +1,7 @@ // Copyright (c) 2020-2021 Cisco and/or its affiliates. // +// Copyright (c) 2022 Nordix Foundation. +// // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +28,7 @@ import ( "github.com/networkservicemesh/sdk/pkg/tools/log" + "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vlan" "github.com/networkservicemesh/sdk-vpp/pkg/tools/ifindex" ) @@ -39,6 +42,13 @@ func addDel(ctx context.Context, vppConn api.Connection, addDel bool) error { return nil } + vlanID, ok := vlan.Load(ctx, true) + if ok { + log.FromContext(ctx). + WithField("VLAN-ID", vlanID).Info("bridge is used instead of xconnect") + return nil + } + now := time.Now() if _, err := l2.NewServiceClient(vppConn).SwInterfaceSetL2Xconnect(ctx, &l2.SwInterfaceSetL2Xconnect{ RxSwIfIndex: clientIfIndex,