From 5a4b8b3771e4f03ac9968301ef6aabf8b47dfe51 Mon Sep 17 00:00:00 2001 From: Ed Warnicke Date: Wed, 18 Nov 2020 06:14:41 -0600 Subject: [PATCH] Add connectioncontextkernel chain elements for ipaddress and routes Signed-off-by: Ed Warnicke --- .../ipcontext/ipaddress/client.go | 75 +++++++++++++++ .../ipcontext/ipaddress/common.go | 68 ++++++++++++++ .../ipcontext/ipaddress/doc.go | 18 ++++ .../ipcontext/ipaddress/server.go | 70 ++++++++++++++ .../ipcontext/routes/client.go | 75 +++++++++++++++ .../ipcontext/routes/common.go | 92 +++++++++++++++++++ .../ipcontext/routes/doc.go | 18 ++++ .../ipcontext/routes/server.go | 70 ++++++++++++++ 8 files changed, 486 insertions(+) create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/client.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/common.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/doc.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/server.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/routes/client.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/routes/common.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/routes/doc.go create mode 100644 pkg/networkservice/connectioncontextkernel/ipcontext/routes/server.go diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/client.go b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/client.go new file mode 100644 index 00000000..5b468f75 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/client.go @@ -0,0 +1,75 @@ +// Copyright (c) 2020 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. + +// +build linux + +package ipaddress + +import ( + "context" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "google.golang.org/grpc" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type ipaddressClient struct{} + +// NewClient provides a NetworkServiceClient that sets the IP on a kernel interface +// It sets the IP Address on the *kernel* side of an interface leaving the +// Client. Generally only used by privileged Clients like those implementing +// the Cross Connect Network Service for K8s (formerly known as NSM Forwarder). +// Client +// +---------------------------+ +// | | +// | | +// | | +// | | +// | | +// | | +// | | +// | +-------------------+ +// | | ipaddress.NewClient() +// | | +// | | +// | | +// | | +// | | +// | | +// +---------------------------+ +// +func NewClient() networkservice.NetworkServiceClient { + return &ipaddressClient{} +} + +func (i *ipaddressClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { + conn, err := next.Client(ctx).Request(ctx, request, opts...) + if err != nil { + return nil, err + } + if err := create(ctx, conn, metadata.IsClient(i)); err != nil { + _, _ = i.Close(ctx, conn, opts...) + return nil, err + } + return conn, nil +} + +func (i *ipaddressClient) 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/connectioncontextkernel/ipcontext/ipaddress/common.go b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/common.go new file mode 100644 index 00000000..e8560b67 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/common.go @@ -0,0 +1,68 @@ +// Copyright (c) 2020 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. + +// +build linux + +package ipaddress + +import ( + "context" + "time" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/kernel" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/trace" + "github.com/pkg/errors" + "github.com/vishvananda/netlink" + + "github.com/networkservicemesh/sdk-vpp/pkg/tools/link" + "github.com/networkservicemesh/sdk-vpp/pkg/tools/netlinkhandle" +) + +func create(ctx context.Context, conn *networkservice.Connection, isClient bool) error { + if mechanism := kernel.ToMechanism(conn.GetMechanism()); mechanism != nil { + l, ok := link.Load(ctx, isClient) + if !ok { + return nil + } + handle, ok := netlinkhandle.Load(ctx, isClient) + if !ok { + return errors.Errorf("did not find netlink handle with which to program routes") + } + // Note: These are switched from normal because if we are the client, we need to assign the IP + // in the Endpoints NetNS for the Dst. If we are the *server* we need to assign the IP for the + // clients NetNS (ie the source). + ipNet := conn.GetContext().GetIpContext().GetSrcIPNet() + if isClient { + ipNet = conn.GetContext().GetIpContext().GetDstIPNet() + } + if ipNet == nil { + return nil + } + now := time.Now() + if err := handle.AddrAdd(l, &netlink.Addr{ + IPNet: ipNet, + }); err != nil { + return err + } + trace.Log(ctx). + WithField("link.Name", l.Attrs().Name). + WithField("Addr", ipNet.String()). + WithField("duration", time.Since(now)). + WithField("netlink", "AddrAdd").Debug("completed") + } + return nil +} diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/doc.go b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/doc.go new file mode 100644 index 00000000..e24aaaa8 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2020 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 ipaddress provides networkservice chain elements that support setting ip addresses on kernel interfaces +package ipaddress diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/server.go b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/server.go new file mode 100644 index 00000000..20e659a5 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/ipaddress/server.go @@ -0,0 +1,70 @@ +// Copyright (c) 2020 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. + +// +build linux + +package ipaddress + +import ( + "context" + + "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 ipaddressServer struct { +} + +// NewServer provides a NetworkServiceServer that sets the IP on a kernel interface +// It sets the IP Address on the *kernel* side of an interface plugged into the +// Endpoint. Generally only used by privileged Endpoints like those implementing +// the Cross Connect Network Service for K8s (formerly known as NSM Forwarder). +// Endpoint +// +---------------------------+ +// | | +// | | +// | | +// | | +// | | +// | | +// | | +// +-------------------+ | +// ipaddress.NewServer() | | +// | | +// | | +// | | +// | | +// | | +// | | +// +---------------------------+ +// +func NewServer() networkservice.NetworkServiceServer { + return &ipaddressServer{} +} + +func (i *ipaddressServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { + if err := create(ctx, request.GetConnection(), metadata.IsClient(i)); err != nil { + return nil, err + } + return next.Server(ctx).Request(ctx, request) +} + +func (i *ipaddressServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { + return next.Server(ctx).Close(ctx, conn) +} diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/routes/client.go b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/client.go new file mode 100644 index 00000000..d691e0f1 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/client.go @@ -0,0 +1,75 @@ +// Copyright (c) 2020 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. + +// +build linux + +package routes + +import ( + "context" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "google.golang.org/grpc" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type routesClient struct{} + +// NewClient creates a NetworkServiceClient that will put the routes from the connection context into +// the kernel network namespace kernel interface being inserted iff the +// selected mechanism for the connection is a kernel mechanism +// Client +// +- - - - - - - - - - - - - - - -+ +---------------------------+ +// | | | kernel network namespace | +// | | +// | | | | +// | | +// | | | | +// | | +// | | | | +// +--------- ---------+ | +// | | | | +// | | +// | | | | +// | routes.Client() | +// | | | | +// | | +// | | | | +// +- - - - - - - - - - - - - - - -+ +---------------------------+ +// +func NewClient() networkservice.NetworkServiceClient { + return &routesClient{} +} + +func (i *routesClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { + conn, err := next.Client(ctx).Request(ctx, request, opts...) + if err != nil { + return nil, err + } + if err := create(ctx, conn, metadata.IsClient(i)); err != nil { + _, _ = i.Close(ctx, conn, opts...) + return nil, err + } + return conn, nil +} + +func (i *routesClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { + // We do not have to delete routes here because the kernel deletes routes for us when we delete the interface + return next.Client(ctx).Close(ctx, conn, opts...) +} diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/routes/common.go b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/common.go new file mode 100644 index 00000000..d28389e0 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/common.go @@ -0,0 +1,92 @@ +// Copyright (c) 2020 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. + +// +build linux + +package routes + +import ( + "context" + "net" + "time" + + "github.com/networkservicemesh/sdk/pkg/networkservice/core/trace" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/kernel" + "github.com/pkg/errors" + "github.com/vishvananda/netlink" + + "github.com/networkservicemesh/sdk-vpp/pkg/tools/link" + "github.com/networkservicemesh/sdk-vpp/pkg/tools/netlinkhandle" +) + +func create(ctx context.Context, conn *networkservice.Connection, isClient bool) error { + if mechanism := kernel.ToMechanism(conn.GetMechanism()); mechanism != nil { + l, ok := link.Load(ctx, isClient) + if !ok { + return nil + } + handle, ok := netlinkhandle.Load(ctx, isClient) + if !ok { + return errors.Errorf("did not find netlink handle with which to program routes") + } + from := conn.GetContext().GetIpContext().GetSrcIPNet() + to := conn.GetContext().GetIpContext().GetDstIPNet() + if isClient { + from = conn.GetContext().GetIpContext().GetDstIPNet() + to = conn.GetContext().GetIpContext().GetSrcIPNet() + } + routes := conn.GetContext().GetIpContext().GetDstRoutes() + if isClient { + routes = conn.GetContext().GetIpContext().GetSrcRoutes() + } + for _, route := range routes { + if err := routeAdd(ctx, handle, l, netlink.SCOPE_UNIVERSE, route.GetPrefixIPNet(), to); err != nil { + return err + } + } + if to != nil && !to.Contains(from.IP) { + if err := routeAdd(ctx, handle, l, netlink.SCOPE_LINK, to, nil); err != nil { + return err + } + } + } + return nil +} + +func routeAdd(ctx context.Context, handle *netlink.Handle, l netlink.Link, scope netlink.Scope, prefix, gw *net.IPNet) error { + route := &netlink.Route{ + LinkIndex: l.Attrs().Index, + Scope: scope, + Dst: prefix, + } + if gw != nil { + route.Gw = gw.IP + } + now := time.Now() + if err := handle.RouteAdd(route); err != nil { + return errors.WithStack(err) + } + trace.Log(ctx). + WithField("link.Name", l.Attrs().Name). + WithField("route.Dst", route.Dst). + WithField("route.Gw", route.Gw). + WithField("route.Scope", route.Scope). + WithField("duration", time.Since(now)). + WithField("netlink", "RouteAdd").Debug("completed") + return nil +} diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/routes/doc.go b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/doc.go new file mode 100644 index 00000000..36155520 --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2020 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 routes provides a NetworkServiceServer that sets the routes in the kernel from the connection context +package routes diff --git a/pkg/networkservice/connectioncontextkernel/ipcontext/routes/server.go b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/server.go new file mode 100644 index 00000000..f2c7219d --- /dev/null +++ b/pkg/networkservice/connectioncontextkernel/ipcontext/routes/server.go @@ -0,0 +1,70 @@ +// Copyright (c) 2020 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. + +// +build linux + +package routes + +import ( + "context" + + "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 routesServer struct { +} + +// NewServer creates a NetworkServiceServer that will put the routes from the connection context into +// connection context into the kernel network namespace kernel interface being inserted iff the +// selected mechanism for the connection is a kernel mechanism +// Endpoint +// +- - - - - - - - - - - - - - - -+ +---------------------------+ +// | kernel network namespace | | | +// | | +// | | | | +// | | +// | | | | +// | | +// | | | | +// +--------- ---------+ | +// | | | | +// | | +// | | | | +// routes.NewServer() | | +// | | | | +// | | +// | | | | +// +- - - - - - - - - - - - - - - -+ +---------------------------+ +// +func NewServer() networkservice.NetworkServiceServer { + return &routesServer{} +} + +func (i *routesServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { + if err := create(ctx, request.GetConnection(), metadata.IsClient(i)); err != nil { + return nil, err + } + return next.Server(ctx).Request(ctx, request) +} + +func (i *routesServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { + // We do not have to delete routes here because the kernel deletes routes for us when we delete the interface + return next.Server(ctx).Close(ctx, conn) +}