diff --git a/pkg/networkservice/chains/client/client.go b/pkg/networkservice/chains/client/client.go index dda83f8ad..394a55122 100644 --- a/pkg/networkservice/chains/client/client.go +++ b/pkg/networkservice/chains/client/client.go @@ -23,8 +23,6 @@ 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" @@ -32,6 +30,7 @@ import ( "github.com/networkservicemesh/sdk/pkg/networkservice/common/dial" "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" diff --git a/pkg/networkservice/common/excludedprefixes/client.go b/pkg/networkservice/common/excludedprefixes/client.go new file mode 100644 index 000000000..f9607df8e --- /dev/null +++ b/pkg/networkservice/common/excludedprefixes/client.go @@ -0,0 +1,173 @@ +// 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" + "net" + + "github.com/edwarnicke/serialize" + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/pkg/errors" + "google.golang.org/grpc" + + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "github.com/networkservicemesh/sdk/pkg/tools/ippool" + "github.com/networkservicemesh/sdk/pkg/tools/log" + "github.com/networkservicemesh/sdk/pkg/tools/postpone" +) + +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{} + } + + logger := log.FromContext(ctx).WithField("ExcludedPrefixesClient", "Request") + ipCtx := conn.GetContext().GetIpContext() + + var newExcludedPrefixes []string + oldExcludedPrefixes := ipCtx.GetExcludedPrefixes() + if len(epc.excludedPrefixes) > 0 { + <-epc.executor.AsyncExec(func() { + logger.Debugf("Adding new excluded IPs to the request: %+v", epc.excludedPrefixes) + newExcludedPrefixes = ipCtx.GetExcludedPrefixes() + newExcludedPrefixes = append(newExcludedPrefixes, epc.excludedPrefixes...) + newExcludedPrefixes = removeDuplicates(newExcludedPrefixes) + + // excluding IPs for current request/connection before calling next client for the refresh use-case + newExcludedPrefixes = exclude(newExcludedPrefixes, append(ipCtx.GetSrcIpAddrs(), ipCtx.GetDstIpAddrs()...)) + + logger.Debugf("Excluded prefixes from request - %+v", newExcludedPrefixes) + ipCtx.ExcludedPrefixes = newExcludedPrefixes + }) + } + + postponeCtxFunc := postpone.ContextWithValues(ctx) + + resp, err := next.Client(ctx).Request(ctx, request, opts...) + if err != nil { + ipCtx.ExcludedPrefixes = oldExcludedPrefixes + return resp, err + } + + respIPContext := resp.GetContext().GetIpContext() + + err = validateIPs(respIPContext, newExcludedPrefixes) + if err != nil { + closeCtx, cancelFunc := postponeCtxFunc() + defer cancelFunc() + + logger.Errorf("Source or destination IPs are overlapping with excluded prefixes, srcIPs: %+v, dstIPs: %+v, excluded prefixes: %+v, error: %s", + respIPContext.GetSrcIpAddrs(), respIPContext.GetDstIpAddrs(), newExcludedPrefixes, err.Error()) + + if _, closeErr := next.Client(ctx).Close(closeCtx, conn, opts...); closeErr != nil { + err = errors.Wrapf(err, "connection closed with error: %s", closeErr.Error()) + } + + return nil, err + } + + logger.Debugf("Request excluded IPs - srcIPs: %v, dstIPs: %v, excluded prefixes: %v", respIPContext.GetSrcIpAddrs(), + respIPContext.GetDstIpAddrs(), respIPContext.GetExcludedPrefixes()) + + <-epc.executor.AsyncExec(func() { + epc.excludedPrefixes = append(epc.excludedPrefixes, respIPContext.GetSrcIpAddrs()...) + epc.excludedPrefixes = append(epc.excludedPrefixes, respIPContext.GetDstIpAddrs()...) + epc.excludedPrefixes = append(epc.excludedPrefixes, getRoutePrefixes(respIPContext.GetSrcRoutes())...) + epc.excludedPrefixes = append(epc.excludedPrefixes, getRoutePrefixes(respIPContext.GetDstRoutes())...) + epc.excludedPrefixes = append(epc.excludedPrefixes, respIPContext.GetExcludedPrefixes()...) + epc.excludedPrefixes = removeDuplicates(epc.excludedPrefixes) + logger.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) { + logger := log.FromContext(ctx).WithField("ExcludedPrefixesClient", "Close") + 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, getRoutePrefixes(ipCtx.GetSrcRoutes())) + epc.excludedPrefixes = exclude(epc.excludedPrefixes, getRoutePrefixes(ipCtx.GetDstRoutes())) + epc.excludedPrefixes = exclude(epc.excludedPrefixes, ipCtx.GetExcludedPrefixes()) + logger.Debugf("Excluded prefixes after closing connection: %+v", epc.excludedPrefixes) + }) + + return next.Client(ctx).Close(ctx, conn, opts...) +} + +func getRoutePrefixes(routes []*networkservice.Route) []string { + var rv []string + for _, route := range routes { + rv = append(rv, route.GetPrefix()) + } + + return rv +} + +func validateIPs(ipContext *networkservice.IPContext, excludedPrefixes []string) error { + ip4Pool := ippool.New(net.IPv4len) + ip6Pool := ippool.New(net.IPv6len) + + for _, prefix := range excludedPrefixes { + _, ipNet, err := net.ParseCIDR(prefix) + if err != nil { + return err + } + + ip4Pool.AddNet(ipNet) + ip6Pool.AddNet(ipNet) + } + + prefixes := make([]string, 0, len(ipContext.GetSrcIpAddrs())+len(ipContext.GetDstIpAddrs())) + prefixes = append(prefixes, ipContext.GetSrcIpAddrs()...) + prefixes = append(prefixes, ipContext.GetDstIpAddrs()...) + + for _, prefix := range prefixes { + ip, _, err := net.ParseCIDR(prefix) + if err != nil { + return err + } + + if ip4Pool.Contains(ip) || ip6Pool.Contains(ip) { + return errors.Errorf("IP %v is excluded, but it was found in response IPs", ip) + } + } + + return nil +} diff --git a/pkg/networkservice/common/excludedprefixes/client_test.go b/pkg/networkservice/common/excludedprefixes/client_test.go new file mode 100644 index 000000000..ecb397b65 --- /dev/null +++ b/pkg/networkservice/common/excludedprefixes/client_test.go @@ -0,0 +1,453 @@ +// 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/pkg/errors" + "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/injecterror" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/inject/injectexcludedprefixes" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/inject/injectipcontext" +) + +func TestExcludedPrefixesClient_Request_SanityCheck(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) + + excludedPrefixes := []string{"172.16.0.96/32", "172.16.0.98/32", "172.16.0.100/32"} + + server1 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + injectexcludedprefixes.NewServer(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) + + 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]) +} + +func TestExcludedPrefixesClient_Request_SrcAndDestPrefixesAreDifferent(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), + ) + + request1 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + resp, err := chain.NewNetworkServiceClient(client, server1).Request(ctx, request1) + require.NoError(t, err) + + expectedExcludedIPs := []string{"172.16.0.100/32", "172.16.0.101/32"} + + srcIPs := resp.GetContext().GetIpContext().GetSrcIpAddrs() + require.Len(t, srcIPs, 1) + srcIP1 := srcIPs[0] + + server2 := adapters.NewServerToClient( + point2pointipam.NewServer(ipNet), + ) + + request2 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + resp, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request2) + require.NoError(t, err) + + srcIPs = resp.GetContext().GetIpContext().GetSrcIpAddrs() + require.Len(t, srcIPs, 1) + srcIP2 := srcIPs[0] + + require.NotEqual(t, srcIP1, srcIP2) + + excludedIPs := resp.GetContext().GetIpContext().GetExcludedPrefixes() + require.ElementsMatch(t, expectedExcludedIPs, excludedIPs) +} + +func TestExcludedPrefixesClient_Close_PrefixesAreRemoved(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx := context.Background() + + client := excludedprefixes.NewClient() + + request1 := &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"}, + }, + }, + }, + } + + resp1, err := client.Request(ctx, request1) + require.NoError(t, err) + + request2 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + _, err = client.Close(ctx, resp1) + require.NoError(t, err) + + respCheckEmpty, err := client.Request(ctx, request2) + require.NoError(t, err) + + require.Empty(t, respCheckEmpty.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/32"} + + server1 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + injectexcludedprefixes.NewServer(excludedPrefixes)), + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + request1 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + resp, err := chain.NewNetworkServiceClient(client, server1).Request(ctx, request1) + require.NoError(t, err) + + srcIPs := resp.GetContext().GetIpContext().GetSrcIpAddrs() + require.Len(t, srcIPs, 1) + + destIPs := resp.GetContext().GetIpContext().GetDstIpAddrs() + require.Len(t, destIPs, 1) + + expectedExcludedPrefixes := []string{"172.16.0.99/32", "172.16.0.97/32", "172.16.0.96/32", "172.16.0.98/32", "172.16.0.100/32"} + + request2 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + server2 := adapters.NewServerToClient( + point2pointipam.NewServer(ipNet), + ) + + resp, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request2) + require.NoError(t, err) + + require.ElementsMatch(t, expectedExcludedPrefixes, resp.GetContext().GetIpContext().GetExcludedPrefixes()) +} + +func TestExcludedPrefixesClient_Request_PrefixesUnchangedAfterError(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/32"} + + server1 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + injectexcludedprefixes.NewServer(excludedPrefixes)), + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + request1 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + resp, err := chain.NewNetworkServiceClient(client, server1).Request(ctx, request1) + require.NoError(t, err) + + srcIPs := resp.GetContext().GetIpContext().GetSrcIpAddrs() + require.Len(t, srcIPs, 1) + + destIPs := resp.GetContext().GetIpContext().GetDstIpAddrs() + require.Len(t, destIPs, 1) + + expectedExcludedPrefixes := []string{"172.16.0.99/32", "172.16.0.97/32", "172.16.0.96/32", "172.16.0.98/32", "172.16.0.100/32"} + + // request with error + request2 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + server2 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + injecterror.NewClient(injecterror.WithError(errors.Errorf("Test error"))), + ) + + _, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request2) + require.Error(t, err) + + // third request to get the final prefixes + request3 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + server3 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + resp, err = chain.NewNetworkServiceClient(client, server3).Request(ctx, request3) + require.NoError(t, err) + + require.ElementsMatch(t, expectedExcludedPrefixes, resp.GetContext().GetIpContext().GetExcludedPrefixes()) +} + +func TestExcludedPrefixesClient_Request_SuccessfulRefresh(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/32"} + + server1 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + injectexcludedprefixes.NewServer(excludedPrefixes)), + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + resp, err := chain.NewNetworkServiceClient(client, server1).Request(ctx, request) + require.NoError(t, err) + + srcIPs := resp.GetContext().GetIpContext().GetSrcIpAddrs() + require.Len(t, srcIPs, 1) + + destIPs := resp.GetContext().GetIpContext().GetDstIpAddrs() + require.Len(t, destIPs, 1) + + // expected excluded prefixes for refresh use-case + // src/dest IPs won't be present in IPContext + expectedExcludedPrefixes := []string{"172.16.0.96/32", "172.16.0.98/32", "172.16.0.100/32"} + + // expected excluded prefixes for a non-refresh use-case + // src/dest IPs from first server should still be present in a request to another server + expectedExcludedPrefixes2 := []string{"172.16.0.99/32", "172.16.0.97/32", "172.16.0.96/32", "172.16.0.98/32", "172.16.0.100/32"} + + resp, err = chain.NewNetworkServiceClient(client, server1).Request(ctx, request) + require.NoError(t, err) + + require.ElementsMatch(t, expectedExcludedPrefixes, resp.GetContext().GetIpContext().GetExcludedPrefixes()) + + server2 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + request2 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + resp, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request2) + require.NoError(t, err) + + require.ElementsMatch(t, expectedExcludedPrefixes2, resp.GetContext().GetIpContext().GetExcludedPrefixes()) +} + +func TestExcludedPrefixesClient_Request_EndpointConflicts(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.100/32"} + + server1 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + injectexcludedprefixes.NewServer(excludedPrefixes)), + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + _, err = chain.NewNetworkServiceClient(client, server1).Request(ctx, request.Clone()) + require.NoError(t, err) + + // conflict with existing routes (172.16.0.96/32) + server2 := chain.NewNetworkServiceClient( + adapters.NewServerToClient(injectipcontext.NewServer( + &networkservice.IPContext{ + SrcIpAddrs: []string{"172.16.0.101/32"}, + DstIpAddrs: []string{"172.16.0.96/32"}, + SrcRoutes: []*networkservice.Route{ + { + Prefix: "172.16.0.101/32", + NextHop: "", + }, + }, + DstRoutes: []*networkservice.Route{ + { + Prefix: "172.16.0.96/32", + NextHop: "", + }, + }, + }))) + + _, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request.Clone()) + require.Error(t, err) + + // conflict with already excluded prefixes (172.16.0.100/32) + server3 := chain.NewNetworkServiceClient( + adapters.NewServerToClient(injectipcontext.NewServer( + &networkservice.IPContext{ + SrcIpAddrs: []string{"172.16.0.100/32"}, + DstIpAddrs: []string{"172.16.0.103/32"}, + SrcRoutes: []*networkservice.Route{ + { + Prefix: "172.16.0.103/32", + NextHop: "", + }, + }, + DstRoutes: []*networkservice.Route{ + { + Prefix: "172.16.0.100/32", + NextHop: "", + }, + }, + }))) + + _, err = chain.NewNetworkServiceClient(client, server3).Request(ctx, request.Clone()) + require.Error(t, err) +} + +func TestExcludedPrefixesClient_Request_EndpointConflictCloseError(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.100/32"} + + server1 := chain.NewNetworkServiceClient( + adapters.NewServerToClient( + injectexcludedprefixes.NewServer(excludedPrefixes)), + adapters.NewServerToClient( + point2pointipam.NewServer(ipNet)), + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{}, + } + + _, err = chain.NewNetworkServiceClient(client, server1).Request(ctx, request.Clone()) + require.NoError(t, err) + + // conflict with existing routes (172.16.0.96/32) + server2 := chain.NewNetworkServiceClient( + adapters.NewServerToClient(injectipcontext.NewServer( + &networkservice.IPContext{ + SrcIpAddrs: []string{"172.16.0.101/32"}, + DstIpAddrs: []string{"172.16.0.96/32"}, + SrcRoutes: []*networkservice.Route{ + { + Prefix: "172.16.0.101/32", + NextHop: "", + }, + }, + DstRoutes: []*networkservice.Route{ + { + Prefix: "172.16.0.96/32", + NextHop: "", + }, + }, + })), + injecterror.NewClient(injecterror.WithCloseErrorTimes(-1), injecterror.WithRequestErrorTimes(-2))) + + _, err = chain.NewNetworkServiceClient(client, server2).Request(ctx, request.Clone()) + require.Error(t, err) + require.Contains(t, err.Error(), "connection closed") +} diff --git a/pkg/networkservice/common/excludedprefixes/utils.go b/pkg/networkservice/common/excludedprefixes/utils.go index 51090b091..78a3cdbf9 100644 --- a/pkg/networkservice/common/excludedprefixes/utils.go +++ b/pkg/networkservice/common/excludedprefixes/utils.go @@ -1,6 +1,6 @@ -// Copyright (c) 2020 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2021 Doc.ai and/or its affiliates. // -// Copyright (c) 2020 Cisco and/or its affiliates. +// Copyright (c) 2020-2021 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -31,3 +31,22 @@ func removeDuplicates(elements []string) []string { } return result } + +func exclude(source, exclude []string) []string { + var s string + var excludeMap = make(map[string]struct{}) + + for _, e := range exclude { + excludeMap[e] = struct{}{} + } + + for i := 0; i < len(source); i++ { + s = source[i] + if _, ok := excludeMap[s]; ok { + source = append(source[:i], source[i+1:]...) + i-- + } + } + + return source +} diff --git a/pkg/networkservice/utils/inject/injectexcludedprefixes/doc.go b/pkg/networkservice/utils/inject/injectexcludedprefixes/doc.go new file mode 100644 index 000000000..1f170cf9c --- /dev/null +++ b/pkg/networkservice/utils/inject/injectexcludedprefixes/doc.go @@ -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 diff --git a/pkg/networkservice/utils/inject/injectexcludedprefixes/server.go b/pkg/networkservice/utils/inject/injectexcludedprefixes/server.go new file mode 100644 index 000000000..9e57f8f28 --- /dev/null +++ b/pkg/networkservice/utils/inject/injectexcludedprefixes/server.go @@ -0,0 +1,56 @@ +// 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 + +import ( + "context" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" +) + +type injectExcludedPrefixesServer struct { + prefixes []string +} + +func (ieps *injectExcludedPrefixesServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*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() + ipCtx.ExcludedPrefixes = ieps.prefixes + + return next.Server(ctx).Request(ctx, request) +} + +func (ieps *injectExcludedPrefixesServer) Close(ctx context.Context, connection *networkservice.Connection) (*empty.Empty, error) { + return next.Server(ctx).Close(ctx, connection) +} + +// NewServer - creates a networkservice.NetworkServiceServer chain element injecting specified excluded prefixes on Request into IP context +func NewServer(excludedPrefixes []string) networkservice.NetworkServiceServer { + return &injectExcludedPrefixesServer{ + prefixes: excludedPrefixes, + } +} diff --git a/pkg/networkservice/utils/inject/injectipcontext/doc.go b/pkg/networkservice/utils/inject/injectipcontext/doc.go new file mode 100644 index 000000000..b899186e2 --- /dev/null +++ b/pkg/networkservice/utils/inject/injectipcontext/doc.go @@ -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 injectipcontext provides a chain element injecting specified IPContext on Request +package injectipcontext diff --git a/pkg/networkservice/utils/inject/injectipcontext/server.go b/pkg/networkservice/utils/inject/injectipcontext/server.go new file mode 100644 index 000000000..6f298b3ac --- /dev/null +++ b/pkg/networkservice/utils/inject/injectipcontext/server.go @@ -0,0 +1,59 @@ +// 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 injectipcontext + +import ( + "context" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" +) + +type injectIPContext struct { + ipContext *networkservice.IPContext +} + +func (s *injectIPContext) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*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() + ipCtx.SrcIpAddrs = s.ipContext.GetSrcIpAddrs() + ipCtx.DstIpAddrs = s.ipContext.GetDstIpAddrs() + ipCtx.SrcRoutes = s.ipContext.GetSrcRoutes() + ipCtx.DstRoutes = s.ipContext.GetDstRoutes() + + return next.Server(ctx).Request(ctx, request) +} + +func (s *injectIPContext) Close(ctx context.Context, connection *networkservice.Connection) (*empty.Empty, error) { + return next.Server(ctx).Close(ctx, connection) +} + +// NewServer - creates a networkservice.NetworkServiceServer chain element injecting specified IPContext on Request +func NewServer(ipContext *networkservice.IPContext) networkservice.NetworkServiceServer { + return &injectIPContext{ + ipContext: ipContext, + } +}