diff --git a/go.mod b/go.mod index 918437ab..7d2330fd 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( git.fd.io/govpp.git v0.3.6-0.20210927044411-385ccc0d8ba9 github.com/edwarnicke/govpp v0.0.0-20220311182453-f32f292e0e91 + github.com/edwarnicke/serialize v1.0.7 github.com/golang/protobuf v1.5.2 github.com/hashicorp/go-multierror v1.1.1 github.com/networkservicemesh/api v1.2.1-0.20220315001249-f33f8c3f2feb diff --git a/pkg/networkservice/loopback/common.go b/pkg/networkservice/loopback/common.go new file mode 100644 index 00000000..d3300851 --- /dev/null +++ b/pkg/networkservice/loopback/common.go @@ -0,0 +1,109 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// 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 loopback + +import ( + "context" + "time" + + "github.com/pkg/errors" + + "git.fd.io/govpp.git/api" + interfaces "github.com/edwarnicke/govpp/binapi/interface" + "github.com/edwarnicke/govpp/binapi/interface_types" + "github.com/edwarnicke/serialize" + + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +type loopInfo struct { + swIfIndex interface_types.InterfaceIndex + count uint32 +} + +type loopbackMap struct { + serialize.Executor + entries map[string]*loopInfo +} + +/* Create loopback interface and store it in metadata */ +func createLoopback(ctx context.Context, vppConn api.Connection, networkService string, t *loopbackMap, isClient bool) (err error) { + if _, ok := Load(ctx, isClient); !ok { + <-t.AsyncExec(func() { + /* Check if we have already created loopback for a given NetworkService previously */ + info, ok := t.entries[networkService] + if !ok { + var swIfIndex interface_types.InterfaceIndex + swIfIndex, err = createLoopbackVPP(ctx, vppConn) + if err != nil { + return + } + info = &loopInfo{ + swIfIndex: swIfIndex, + } + t.entries[networkService] = info + } + info.count++ + Store(ctx, isClient, info.swIfIndex) + }) + } + return +} + +func createLoopbackVPP(ctx context.Context, vppConn api.Connection) (interface_types.InterfaceIndex, error) { + now := time.Now() + reply, err := interfaces.NewServiceClient(vppConn).CreateLoopback(ctx, &interfaces.CreateLoopback{}) + if err != nil { + return interface_types.InterfaceIndex(^uint32(0)), errors.WithStack(err) + } + log.FromContext(ctx). + WithField("swIfIndex", reply.SwIfIndex). + WithField("duration", time.Since(now)). + WithField("vppapi", "CreateLoopback").Debug("completed") + return reply.SwIfIndex, nil +} + +func del(ctx context.Context, vppConn api.Connection, networkService string, t *loopbackMap, isClient bool) { + if swIfIndex, ok := LoadAndDelete(ctx, isClient); ok { + t.AsyncExec(func() { + t.entries[networkService].count-- + + /* If there are no more clients using the loopback - delete it */ + if t.entries[networkService].count == 0 { + delete(t.entries, networkService) + if err := delVPP(ctx, vppConn, swIfIndex); err != nil { + log.FromContext(ctx).Errorf("unable to delete loopback interface: %v", err) + } + } + }) + } +} + +func delVPP(ctx context.Context, vppConn api.Connection, swIfIndex interface_types.InterfaceIndex) error { + now := time.Now() + _, err := interfaces.NewServiceClient(vppConn).DeleteLoopback(ctx, &interfaces.DeleteLoopback{ + SwIfIndex: swIfIndex, + }) + if err != nil { + return errors.WithStack(err) + } + log.FromContext(ctx). + WithField("swIfIndex", swIfIndex). + WithField("duration", time.Since(now)). + WithField("vppapi", "DeleteLoopback").Debug("completed") + return nil +} diff --git a/pkg/networkservice/loopback/doc.go b/pkg/networkservice/loopback/doc.go new file mode 100644 index 00000000..2ee342a4 --- /dev/null +++ b/pkg/networkservice/loopback/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// 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 loopback provides networkservice.NetworkService chain elements for creating loopback interface +package loopback diff --git a/pkg/networkservice/loopback/metadata.go b/pkg/networkservice/loopback/metadata.go new file mode 100644 index 00000000..d32cd410 --- /dev/null +++ b/pkg/networkservice/loopback/metadata.go @@ -0,0 +1,72 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// 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 loopback + +import ( + "context" + + "github.com/edwarnicke/govpp/binapi/interface_types" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type key struct{} + +// Store sets the loopback swIfIndex stored in per Connection.Id metadata. +func Store(ctx context.Context, isClient bool, swIfIndex interface_types.InterfaceIndex) { + metadata.Map(ctx, isClient).Store(key{}, swIfIndex) +} + +// Delete deletes the swIfIndex stored in per Connection.Id metadata +func Delete(ctx context.Context, isClient bool) { + metadata.Map(ctx, isClient).Delete(key{}) +} + +// Load returns the swIfIndex 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 interface_types.InterfaceIndex, ok bool) { + rawValue, ok := metadata.Map(ctx, isClient).Load(key{}) + if !ok { + return + } + value, ok = rawValue.(interface_types.InterfaceIndex) + return value, ok +} + +// LoadOrStore returns the existing swIfIndex stored in per Connection.Id metadata if present. +// Otherwise, it stores and returns the given swIfIndex. +// The loaded result is true if the value was loaded, false if stored. +func LoadOrStore(ctx context.Context, isClient bool, swIfIndex interface_types.InterfaceIndex) (value interface_types.InterfaceIndex, ok bool) { + rawValue, ok := metadata.Map(ctx, isClient).LoadOrStore(key{}, swIfIndex) + if !ok { + return + } + value, ok = rawValue.(interface_types.InterfaceIndex) + return value, ok +} + +// LoadAndDelete deletes the swIfIndex stored in per Connection.Id metadata, +// returning the previous value if any. The loaded result reports whether the key was present. +func LoadAndDelete(ctx context.Context, isClient bool) (value interface_types.InterfaceIndex, ok bool) { + rawValue, ok := metadata.Map(ctx, isClient).LoadAndDelete(key{}) + if !ok { + return + } + value, ok = rawValue.(interface_types.InterfaceIndex) + return value, ok +} diff --git a/pkg/networkservice/loopback/server.go b/pkg/networkservice/loopback/server.go new file mode 100644 index 00000000..e93e5769 --- /dev/null +++ b/pkg/networkservice/loopback/server.go @@ -0,0 +1,61 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// 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 loopback + +import ( + "context" + + "git.fd.io/govpp.git/api" + "github.com/golang/protobuf/ptypes/empty" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type loopbackServer struct { + vppConn api.Connection + + loopbacks *loopbackMap +} + +// NewServer creates a NetworkServiceServer chain element to create the loopback vpp-interface +func NewServer(vppConn api.Connection) networkservice.NetworkServiceServer { + return &loopbackServer{ + vppConn: vppConn, + loopbacks: &loopbackMap{ + entries: make(map[string]*loopInfo), + }, + } +} + +func (l *loopbackServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { + networkService := request.GetConnection().NetworkService + if err := createLoopback(ctx, l.vppConn, networkService, l.loopbacks, metadata.IsClient(l)); err != nil { + return nil, err + } + conn, err := next.Server(ctx).Request(ctx, request) + if err != nil { + del(ctx, l.vppConn, networkService, l.loopbacks, metadata.IsClient(l)) + } + return conn, err +} + +func (l *loopbackServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { + del(ctx, l.vppConn, conn.NetworkService, l.loopbacks, metadata.IsClient(l)) + return next.Server(ctx).Close(ctx, conn) +}