Skip to content

Commit

Permalink
Add kernel forwarder and vlan mechanism
Browse files Browse the repository at this point in the history
Signed-off-by: Laszlo Kiraly <laszlo.kiraly@est.tech>
  • Loading branch information
ljkiraly committed Jun 22, 2021
1 parent 6125208 commit 450d0d2
Show file tree
Hide file tree
Showing 9 changed files with 594 additions and 0 deletions.
100 changes: 100 additions & 0 deletions pkg/kernel/networkservice/chains/xconnectns/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2020-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.

//+build !windows

// Package xconnectns provides an Endpoint implementing the kernel Forwarder networks service
package xconnectns

import (
"context"
"net/url"

"google.golang.org/grpc"

"github.com/networkservicemesh/api/pkg/api/networkservice"
vlanmech "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/vlan"
"github.com/networkservicemesh/sdk-kernel/pkg/kernel/networkservice/mechanisms/vlan"
"github.com/networkservicemesh/sdk-kernel/pkg/kernel/networkservice/netnsconnectioncontext"
"github.com/networkservicemesh/sdk/pkg/networkservice/chains/client"
"github.com/networkservicemesh/sdk/pkg/networkservice/chains/endpoint"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/clienturl"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/connect"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/heal"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms/recvfd"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms/sendfd"
"github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanismtranslation"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters"
"github.com/networkservicemesh/sdk/pkg/tools/addressof"
"github.com/networkservicemesh/sdk/pkg/tools/token"
)

type kernelServer struct {
endpoint.Endpoint
}

// NewServer - returns an Endpoint implementing the Kernel Forwarder networks service
// - name - name of the Forwarder
// - authzServer - policy for allowing or rejecting requests
// - tokenGenerator - token.GeneratorFunc - generates tokens for use in Path
// - clientUrl - *url.URL for the talking to the NSMgr
// - ...clientDialOptions - dialOptions for dialing the NSMgr
func NewServer(
ctx context.Context,
name string,
authzServer networkservice.NetworkServiceServer,
tokenGenerator token.GeneratorFunc,
clientURL *url.URL,
clientDialOptions ...grpc.DialOption,
) endpoint.Endpoint {
rv := kernelServer{}

rv.Endpoint = endpoint.NewServer(ctx,
tokenGenerator,
endpoint.WithName(name),
endpoint.WithAuthorizeServer(authzServer),
endpoint.WithAdditionalFunctionality(
recvfd.NewServer(),
clienturl.NewServer(clientURL),
heal.NewServer(ctx, addressof.NetworkServiceClient(adapters.NewServerToClient(rv))),
connect.NewServer(ctx,
client.NewCrossConnectClientFactory(
client.WithName(name),
client.WithAdditionalFunctionality(
mechanismtranslation.NewClient(),
// setup IP and route context
netnsconnectioncontext.NewClient(),
// mechanism
vlan.NewClient(),
recvfd.NewClient(),
sendfd.NewClient(),
),
),
connect.WithDialOptions(clientDialOptions...),
),
mechanisms.NewServer(map[string]networkservice.NetworkServiceServer{
vlanmech.MECHANISM: vlan.NewServer(),
//noopmech.MECHANISM: connectChainFactory(cls.LOCAL),
}),
// setup IP and route context
netnsconnectioncontext.NewServer(),
sendfd.NewServer(),
),
)

return rv
}
79 changes: 79 additions & 0 deletions pkg/kernel/networkservice/common/mechanisms/vlan/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package vlan

import (
"context"
"net/url"

"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"

"github.com/networkservicemesh/api/pkg/api/networkservice"
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/cls"
vlanmech "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/vlan"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/next"
)

const (
netNSFilename = "/proc/thread-self/ns/net"
)

type vlanClient struct {
interfaceName string
}

func NewClient(options ...Option) networkservice.NetworkServiceClient {
v := &vlanClient{}
for _, opt := range options {
opt(v)
}
return v
}

func (v *vlanClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) {
if !v.updateMechanismPreferences(request) {
request.MechanismPreferences = append(request.GetMechanismPreferences(), &networkservice.Mechanism{
Cls: cls.LOCAL,
Type: vlanmech.MECHANISM,
Parameters: map[string]string{
vlanmech.NetNSURL: (&url.URL{Scheme: "file", Path: netNSFilename}).String(),
vlanmech.InterfaceNameKey: v.interfaceName,
},
})
}
return next.Client(ctx).Request(ctx, request, opts...)
}

// updateMechanismPreferences returns true if MechanismPreferences has updated
func (v *vlanClient) updateMechanismPreferences(request *networkservice.NetworkServiceRequest) bool {
var updated = false

for _, m := range request.GetRequestMechanismPreferences() {
if m.Type == vlanmech.MECHANISM {
if m.Parameters == nil {
m.Parameters = make(map[string]string)
}
if m.Parameters[vlanmech.InterfaceNameKey] == "" {
m.Parameters[vlanmech.InterfaceNameKey] = v.interfaceName
}
if m.Parameters[vlanmech.NetNSURL] == "" {
m.Parameters[vlanmech.NetNSURL] = (&url.URL{Scheme: "file", Path: netNSFilename}).String()
}
updated = true
}
}

return updated
}

func (v *vlanClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) {
return next.Client(ctx).Close(ctx, conn, opts...)
}

type Option func(v *vlanClient)

// WithInterfaceName sets interface name
func WithInterfaceName(interfaceName string) Option {
return func(v *vlanClient) {
v.interfaceName = interfaceName
}
}
31 changes: 31 additions & 0 deletions pkg/kernel/networkservice/common/mechanisms/vlan/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package vlan

import (
"context"
"net/url"

"github.com/golang/protobuf/ptypes/empty"
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/vlan"

"github.com/networkservicemesh/api/pkg/api/networkservice"

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

type vlanMechanismServer struct{}

// NewServer - creates a NetworkServiceServer that requests a vlan interface and populates the netns inode
func NewServer() networkservice.NetworkServiceServer {
return &vlanMechanismServer{}
}

func (m *vlanMechanismServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) {
if mechanism := vlan.ToMechanism(request.GetConnection().GetMechanism()); mechanism != nil {
mechanism.SetNetNSURL((&url.URL{Scheme: "file", Path: netNSFilename}).String())
}
return next.Server(ctx).Request(ctx, request)
}

func (m *vlanMechanismServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) {
return next.Server(ctx).Close(ctx, conn)
}
49 changes: 49 additions & 0 deletions pkg/kernel/networkservice/mechanisms/vlan/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package vlan

import (
"context"

"github.com/golang/protobuf/ptypes/empty"
"github.com/networkservicemesh/api/pkg/api/networkservice"
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/cls"
vlanmech "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/vlan"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/next"
"github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata"

"google.golang.org/grpc"
)

type vlanClient struct{}

func NewClient() networkservice.NetworkServiceClient {
return &vlanClient{}
}

func (k *vlanClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) {
getMechPref := func(class string) networkservice.Mechanism {
return networkservice.Mechanism{
Cls: class,
Type: vlanmech.MECHANISM,
Parameters: make(map[string]string),
}
}
localMechanism := getMechPref(cls.LOCAL)
request.MechanismPreferences = append(request.MechanismPreferences, &localMechanism)
remoteMechanism := getMechPref(cls.REMOTE)
request.MechanismPreferences = append(request.MechanismPreferences, &remoteMechanism)

conn, err := next.Client(ctx).Request(ctx, request, opts...)
if err != nil {
return nil, err
}
if err := create(ctx, conn, metadata.IsClient(k)); err != nil {
_, _ = k.Close(ctx, conn, opts...)
return nil, err
}
return conn, nil
}

func (k *vlanClient) Close(
ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) {
return next.Client(ctx).Close(ctx, conn, opts...)
}
136 changes: 136 additions & 0 deletions pkg/kernel/networkservice/mechanisms/vlan/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package vlan

import (
"context"
"crypto/rand"
"encoding/hex"
"io"

"github.com/networkservicemesh/api/pkg/api/networkservice"
vlanmech "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/vlan"
"github.com/networkservicemesh/sdk-kernel/pkg/kernel/tools/nshandle"
"github.com/networkservicemesh/sdk/pkg/tools/log"
"github.com/pkg/errors"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)

func create(ctx context.Context, conn *networkservice.Connection, isClient bool) error {
if mechanism := vlanmech.ToMechanism(conn.GetMechanism()); mechanism != nil {
nsFilename := mechanism.GetNetNSURL()
hostIfName := mechanism.GetInterfaceName(conn)
vlanID := mechanism.VlanID()
baseInterface := mechanism.GetBaseInterfaceName(conn)
logger := log.FromContext(ctx).WithField("vlan", "create").
WithField("HostIfName", hostIfName).
WithField("HostNamespace", nsFilename).
WithField("VlanID", vlanID).
WithField("baseInterface", baseInterface).
WithField("isClient", isClient)
logger.Debug("request")

if nsFilename == "" || vlanID == 0 || baseInterface == "" {
return nil
}

// TODO generate this based on conn id
tmpName, _ := generateRandomName(7)

link, err := createLink(tmpName, baseInterface, vlanID)
if err != nil {
return err
}
logger.Debugf("Temporary link created Name = %s", tmpName)

var clientNetNS netns.NsHandle
clientNetNS, err = nshandle.FromURL(nsFilename)
if err != nil {
return errors.Wrapf(err, "handle can not get for client namespace %s", nsFilename)
}
defer func() { _ = clientNetNS.Close() }()

err = moveInterfaceToNamespace(link, clientNetNS)
if err != nil {
return err
}
logger.Debugf("Moved temporary network interface %s into the client's namespace", tmpName)

var currNetNS netns.NsHandle
currNetNS, err = nshandle.Current()
if err != nil {
return errors.Wrap(err, "handle can not get for current namespace")
}
defer func() { _ = currNetNS.Close() }()

err = renameInterfaceInNamespace(link, hostIfName, currNetNS, clientNetNS)
if err != nil {
return err
}
logger.Debug("Network interface set in client namespace")
}
return nil
}

func generateRandomName(size int) (string, error) {
id := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, id); err != nil {
return "", err
}
return hex.EncodeToString(id)[:size], nil
}

func createLink(name, hostInterface string, vlanID int) (netlink.Link, error) {
base, err := netlink.LinkByName(hostInterface)
if err != nil {
return nil, errors.Wrapf(err, "failed to get base interface %s", hostInterface)
}
newLink := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
Name: name,
ParentIndex: base.Attrs().Index,
},
VlanId: vlanID,
VlanProtocol: netlink.VLAN_PROTOCOL_8021Q,
}
if err := netlink.LinkAdd(newLink); err != nil {
return nil, errors.Wrapf(err, "failed to create vlan interface %s", name)
}

if base.Attrs().OperState != netlink.OperUp {
if err = netlink.LinkSetUp(base); err != nil {
return nil, errors.Wrapf(err, "failed to set up host interface: %s", hostInterface)
}
}

return newLink, nil
}

func moveInterfaceToNamespace(link netlink.Link, toNetNS netns.NsHandle) error {
if err := netlink.LinkSetDown(link); err != nil {
return errors.Errorf("failed to set %s down: %s", link, err)
}

if int(toNetNS) < 0 {
return errors.Errorf("failed to conver ns handle %s to valid file descriptor", toNetNS)
}

if err := netlink.LinkSetNsFd(link, int(toNetNS)); err != nil {
return errors.Wrapf(err, "failed to move net interface to net NS: %s %s", link, toNetNS)
}

return nil
}

func renameInterfaceInNamespace(link netlink.Link, newName string, currNetNS, toNetNS netns.NsHandle) error {
return nshandle.RunIn(currNetNS, toNetNS, func() error {
if err := netlink.LinkSetName(link, newName); err != nil {
return errors.Wrapf(err, "failed to rename net interface:%s -> %s", link, newName)
}
err := netlink.LinkSetUp(link)
if err != nil {
return errors.Errorf("failed to set %s down: %s", link, err)
}

return nil
})
}
Loading

0 comments on commit 450d0d2

Please sign in to comment.