Skip to content

Commit

Permalink
Added client chain element that excludes already used prefixes
Browse files Browse the repository at this point in the history
Signed-off-by: Oleg Solodkov <oleg.solodkov@xored.com>
  • Loading branch information
sol-0 committed Dec 10, 2021
1 parent 8e96470 commit ac31b7c
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 2 deletions.
5 changes: 3 additions & 2 deletions pkg/networkservice/chains/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import (
"github.com/google/uuid"
"github.com/networkservicemesh/api/pkg/api/networkservice"

"github.com/networkservicemesh/sdk/pkg/networkservice/common/trimpath"

"github.com/networkservicemesh/sdk/pkg/networkservice/common/begin"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/clientconn"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/clienturl"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/connect"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/dial"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/excludedprefixes"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/null"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/refresh"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/trimpath"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/updatepath"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/chain"
"github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata"
Expand All @@ -58,6 +58,7 @@ func NewClient(ctx context.Context, clientOpts ...Option) networkservice.Network
metadata.NewClient(),
opts.refreshClient,
clienturl.NewClient(opts.clientURL),
excludedprefixes.NewClient(),
clientconn.NewClient(opts.cc),
opts.healClient,
dial.NewClient(ctx,
Expand Down
97 changes: 97 additions & 0 deletions pkg/networkservice/common/excludedprefixes/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2021 Doc.ai 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 excludedprefixes

import (
"context"

"github.com/edwarnicke/serialize"
"github.com/golang/protobuf/ptypes/empty"
"github.com/networkservicemesh/api/pkg/api/networkservice"
"google.golang.org/grpc"

"github.com/networkservicemesh/sdk/pkg/networkservice/core/next"
"github.com/networkservicemesh/sdk/pkg/tools/log"
)

type excludedPrefixesClient struct {
excludedPrefixes []string
executor serialize.Executor
}

// NewClient - creates a networkservice.NetworkServiceClient chain element that excludes prefixes already used by other NetworkServices
func NewClient() networkservice.NetworkServiceClient {
return &excludedPrefixesClient{
excludedPrefixes: make([]string, 0),
}
}

func (epc *excludedPrefixesClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) {
conn := request.GetConnection()
if conn.GetContext() == nil {
conn.Context = &networkservice.ConnectionContext{}
}

if conn.GetContext().GetIpContext() == nil {
conn.Context.IpContext = &networkservice.IPContext{}
}

ipCtx := conn.GetContext().GetIpContext()

if len(epc.excludedPrefixes) > 0 {
<-epc.executor.AsyncExec(func() {
log.FromContext(ctx).Debugf("ExcludedPrefixesClient: adding new excluded IPs to the request: %v", epc.excludedPrefixes)
excludedPrefixes := ipCtx.GetExcludedPrefixes()
excludedPrefixes = append(excludedPrefixes, epc.excludedPrefixes...)
excludedPrefixes = removeDuplicates(excludedPrefixes)

log.FromContext(ctx).Debugf("ExcludedPrefixesClient: excluded prefixes from request - %v", excludedPrefixes)
ipCtx.ExcludedPrefixes = excludedPrefixes
})
}

resp, err := next.Client(ctx).Request(ctx, request, opts...)
if err != nil {
return resp, err
}

log.FromContext(ctx).Debugf("ExcludedPrefixesClient: request excluded IPs - srcIPs: %v, dstIPs: %v",
resp.GetContext().GetIpContext().GetSrcIpAddrs(), resp.GetContext().GetIpContext().GetDstIpAddrs())

<-epc.executor.AsyncExec(func() {
epc.excludedPrefixes = append(epc.excludedPrefixes, ipCtx.GetSrcIpAddrs()...)
epc.excludedPrefixes = append(epc.excludedPrefixes, ipCtx.GetDstIpAddrs()...)
epc.excludedPrefixes = append(epc.excludedPrefixes, ipCtx.GetExcludedPrefixes()...)
epc.excludedPrefixes = removeDuplicates(epc.excludedPrefixes)
log.FromContext(ctx).Debugf("Added excluded prefixes: %v", epc.excludedPrefixes)
})

return resp, err
}

func (epc *excludedPrefixesClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) {
ipCtx := conn.GetContext().GetIpContext()

<-epc.executor.AsyncExec(func() {
epc.excludedPrefixes = exclude(epc.excludedPrefixes, ipCtx.GetSrcIpAddrs())
epc.excludedPrefixes = exclude(epc.excludedPrefixes, ipCtx.GetDstIpAddrs())
epc.excludedPrefixes = exclude(epc.excludedPrefixes, ipCtx.GetExcludedPrefixes())
log.FromContext(ctx).Debugf("Excluded prefixes after closing connection: %v", epc.excludedPrefixes)
})

return next.Client(ctx).Close(ctx, conn, opts...)
}
183 changes: 183 additions & 0 deletions pkg/networkservice/common/excludedprefixes/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright (c) 2021 Doc.ai 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 excludedprefixes_test

import (
"context"
"net"
"testing"

"github.com/networkservicemesh/api/pkg/api/networkservice"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"

"github.com/networkservicemesh/sdk/pkg/networkservice/common/excludedprefixes"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/chain"
"github.com/networkservicemesh/sdk/pkg/networkservice/ipam/point2pointipam"
"github.com/networkservicemesh/sdk/pkg/networkservice/utils/inject/injectexcludedprefixes"
)

func TestExcludedPrefixesClient_Request_PrefixesAreDifferent(t *testing.T) {
t.Cleanup(func() { goleak.VerifyNone(t) })

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

client := excludedprefixes.NewClient()

srcCidr := "172.16.0.100/30"
_, ipNet, err := net.ParseCIDR(srcCidr)
require.NoError(t, err)

server1 := adapters.NewServerToClient(
point2pointipam.NewServer(ipNet),
)

request := &networkservice.NetworkServiceRequest{
Connection: &networkservice.Connection{},
}

resp, err := chain.NewNetworkServiceClient(client, server1).Request(ctx, request.Clone())
require.NoError(t, err)

expectedExcludedIPs := append(resp.GetContext().GetIpContext().GetSrcIpAddrs(),
resp.GetContext().GetIpContext().GetDstIpAddrs()...)

destIPs := resp.GetContext().GetIpContext().GetSrcIpAddrs()
require.Len(t, destIPs, 1)
firstSrcIP := destIPs[0]

server2 := adapters.NewServerToClient(
point2pointipam.NewServer(ipNet),
)

resp, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request.Clone())
require.NoError(t, err)

destIPs = resp.GetContext().GetIpContext().GetSrcIpAddrs()
require.Len(t, destIPs, 1)
secondSrcIP := destIPs[0]

require.NotEqual(t, firstSrcIP, secondSrcIP)

excludedIPs := resp.GetContext().GetIpContext().GetExcludedPrefixes()
require.Equal(t, excludedIPs, expectedExcludedIPs)
}

func TestExcludedPrefixesClient_Close_PrefixesAreEmpty(t *testing.T) {
t.Cleanup(func() { goleak.VerifyNone(t) })

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

client := excludedprefixes.NewClient()

firstRequest := &networkservice.NetworkServiceRequest{
Connection: &networkservice.Connection{
Context: &networkservice.ConnectionContext{
IpContext: &networkservice.IPContext{
SrcIpAddrs: []string{"172.16.0.100/32"},
DstIpAddrs: []string{"172.16.0.103/32"},
},
},
},
}

secondRequest := &networkservice.NetworkServiceRequest{
Connection: &networkservice.Connection{},
}

firstResp, err := client.Request(ctx, firstRequest.Clone())
require.NoError(t, err)

expectedExcludedIPs := append(firstRequest.GetConnection().GetContext().GetIpContext().GetSrcIpAddrs(),
firstRequest.GetConnection().GetContext().GetIpContext().GetDstIpAddrs()...)

secondResp, err := client.Request(ctx, secondRequest.Clone())
require.NoError(t, err)

excludedIPs := secondResp.GetContext().GetIpContext().GetExcludedPrefixes()
require.Equal(t, excludedIPs, expectedExcludedIPs)

_, err = client.Close(ctx, firstResp)
require.NoError(t, err)

secondResp, err = client.Request(ctx, secondRequest.Clone())
require.NoError(t, err)

require.Empty(t, secondResp.GetContext().GetIpContext().GetExcludedPrefixes())

_, err = client.Close(ctx, secondResp)
require.NoError(t, err)

require.Empty(t, secondResp.GetContext().GetIpContext().GetExcludedPrefixes())
}

func TestExcludedPrefixesClient_Request_WithExcludedPrefixes(t *testing.T) {
t.Cleanup(func() { goleak.VerifyNone(t) })

client := excludedprefixes.NewClient()

_, ipNet, err := net.ParseCIDR("172.16.0.96/29")
require.NoError(t, err)

ctx := context.Background()

excludedPrefixes := []string{"172.16.0.96/32", "172.16.0.98/32", "172.16.0.100"}

server1 := chain.NewNetworkServiceClient(
adapters.NewServerToClient(
injectexcludedprefixes.NewServer(ctx, excludedPrefixes)),
adapters.NewServerToClient(
point2pointipam.NewServer(ipNet)),
)

request := &networkservice.NetworkServiceRequest{
Connection: &networkservice.Connection{},
}

resp, err := chain.NewNetworkServiceClient(client, server1).Request(context.Background(), request.Clone())
require.NoError(t, err)

// sanity checks
possibleIPs := []string{"172.16.0.97/32", "172.16.0.99/32", "172.16.0.101/32", "172.16.0.103/32"}
srcIPs := resp.GetContext().GetIpContext().GetSrcIpAddrs()
require.Len(t, srcIPs, 1)
require.Contains(t, possibleIPs, srcIPs[0])

destIPs := resp.GetContext().GetIpContext().GetDstIpAddrs()
require.Len(t, destIPs, 1)
require.Contains(t, possibleIPs, destIPs[0])

require.NotEqual(t, srcIPs[0], destIPs[0])

expectedExcludedPrefixes := make([]string, 0)
expectedExcludedPrefixes = append(expectedExcludedPrefixes, srcIPs...)
expectedExcludedPrefixes = append(expectedExcludedPrefixes, destIPs...)
expectedExcludedPrefixes = append(expectedExcludedPrefixes, excludedPrefixes...)

// second request
server2 := adapters.NewServerToClient(
point2pointipam.NewServer(ipNet),
)

resp, err = chain.NewNetworkServiceClient(client, server2).Request(context.Background(), request.Clone())
require.NoError(t, err)

require.Equal(t, resp.GetContext().GetIpContext().GetExcludedPrefixes(), expectedExcludedPrefixes)
}
20 changes: 20 additions & 0 deletions pkg/networkservice/common/excludedprefixes/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,23 @@ func removeDuplicates(elements []string) []string {
}
return result
}

func exclude(source, exclude []string) []string {
var result []string

var isExcluded bool
for _, s := range source {
isExcluded = false
for _, e := range exclude {
if s == e {
isExcluded = true
break
}
}

if !isExcluded {
result = append(result, s)
}
}
return result
}
18 changes: 18 additions & 0 deletions pkg/networkservice/utils/inject/injectexcludedprefixes/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2021 Doc.ai 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 injectexcludedprefixes provides a chain element injecting specified excluded prefixes on Request into IP context
package injectexcludedprefixes
Loading

0 comments on commit ac31b7c

Please sign in to comment.