go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/nsplugin/ns_plugin.go (about)

     1  // Copyright (c) 2018 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nsplugin
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  
    21  	"github.com/pkg/errors"
    22  	"github.com/vishvananda/netns"
    23  
    24  	"go.ligato.io/cn-infra/v2/infra"
    25  	"go.ligato.io/cn-infra/v2/logging"
    26  
    27  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    28  
    29  	"go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/descriptor"
    30  	nsLinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls"
    31  	nsmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/namespace"
    32  )
    33  
    34  // NsPlugin is a plugin to handle namespaces and microservices for other linux
    35  // plugins (ifplugin, l3plugin ...).
    36  // It does not follow the standard concept of CRUD, but provides a set of methods
    37  // other plugins can use to manage namespaces.
    38  type NsPlugin struct {
    39  	Deps
    40  
    41  	// From configuration file
    42  	disabled bool
    43  
    44  	// Default namespace
    45  	defaultNs netns.NsHandle
    46  
    47  	// Handlers
    48  	sysHandler     nsLinuxcalls.SystemAPI
    49  	namedNsHandler nsLinuxcalls.NamedNetNsAPI
    50  
    51  	// Descriptor
    52  	msDescriptor *descriptor.MicroserviceDescriptor
    53  }
    54  
    55  // Deps lists dependencies of the NsPlugin.
    56  type Deps struct {
    57  	infra.PluginDeps
    58  	KVScheduler kvs.KVScheduler
    59  }
    60  
    61  // Config holds the nsplugin configuration.
    62  type Config struct {
    63  	Disabled bool `json:"disabled"`
    64  }
    65  
    66  // UnavailableMicroserviceErr is error implementation used when a given microservice is not deployed.
    67  type UnavailableMicroserviceErr struct {
    68  	label string
    69  }
    70  
    71  func (e *UnavailableMicroserviceErr) Error() string {
    72  	return fmt.Sprintf("Microservice '%s' is not available", e.label)
    73  }
    74  
    75  // Init namespace handler caches and create config namespace
    76  func (p *NsPlugin) Init() error {
    77  	// Parse configuration file
    78  	config, err := p.retrieveConfig()
    79  	if err != nil {
    80  		return err
    81  	}
    82  	if config != nil {
    83  		if config.Disabled {
    84  			p.disabled = true
    85  			p.Log.Infof("Disabling Linux Namespace plugin")
    86  			return nil
    87  		}
    88  	}
    89  
    90  	// Handlers
    91  	p.sysHandler = nsLinuxcalls.NewSystemHandler()
    92  	p.namedNsHandler = nsLinuxcalls.NewNamedNetNsHandler(p.sysHandler, p.Log)
    93  
    94  	// Default namespace
    95  	p.defaultNs, err = p.sysHandler.GetCurrentNamespace()
    96  	if err != nil {
    97  		return errors.Errorf("failed to init default namespace: %v", err)
    98  	}
    99  
   100  	// Microservice descriptor
   101  	p.msDescriptor, err = descriptor.NewMicroserviceDescriptor(p.KVScheduler, p.Log)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	err = p.KVScheduler.RegisterKVDescriptor(p.msDescriptor.GetDescriptor())
   106  	if err != nil {
   107  		return err
   108  	}
   109  	p.msDescriptor.StartTracker()
   110  
   111  	p.Log.Debugf("Namespace plugin initialized")
   112  
   113  	return nil
   114  }
   115  
   116  // Close stops microservice tracker
   117  func (p *NsPlugin) Close() error {
   118  	if p.disabled {
   119  		return nil
   120  	}
   121  	p.msDescriptor.StopTracker()
   122  
   123  	return nil
   124  }
   125  
   126  // GetNamespaceHandle returns low-level run-time handle for the given namespace
   127  // to be used with Netlink API. Do not forget to eventually close the handle using
   128  // the netns.NsHandle.Close() method.
   129  func (p *NsPlugin) GetNamespaceHandle(ctx nsLinuxcalls.NamespaceMgmtCtx, namespace *nsmodel.NetNamespace) (handle netns.NsHandle, err error) {
   130  	if p.disabled {
   131  		return 0, errors.New("NsPlugin is disabled")
   132  	}
   133  	// Convert microservice namespace
   134  	if namespace != nil && namespace.Type == nsmodel.NetNamespace_MICROSERVICE {
   135  		// Convert namespace
   136  		reference := namespace.Reference
   137  		namespace = p.convertMicroserviceNsToPidNs(reference)
   138  		if namespace == nil {
   139  			return 0, &UnavailableMicroserviceErr{label: reference}
   140  		}
   141  	}
   142  
   143  	// Get network namespace file descriptor
   144  	ns, err := p.getOrCreateNs(ctx, namespace)
   145  	if err != nil {
   146  		return 0, errors.Errorf("failed to get or create namespace (%v): %v", namespace, err)
   147  	}
   148  
   149  	return ns, nil
   150  }
   151  
   152  // SwitchToNamespace switches the network namespace of the current thread.
   153  // Caller should eventually call the returned "revert" function in order to get back to the original
   154  // network namespace (for example using "defer revert()").
   155  func (p *NsPlugin) SwitchToNamespace(ctx nsLinuxcalls.NamespaceMgmtCtx, ns *nsmodel.NetNamespace) (revert func(), err error) {
   156  	if p.disabled {
   157  		return func() {}, errors.New("NsPlugin is disabled")
   158  	}
   159  
   160  	// Save the current network namespace.
   161  	origns, err := netns.Get()
   162  	if err != nil {
   163  		return func() {}, err
   164  	}
   165  
   166  	l := p.Log.WithFields(logging.Fields{
   167  		"orig-ns":    origns.String(),
   168  		"orig-ns-fd": int(origns),
   169  	})
   170  	closeNs := func(ns netns.NsHandle) {
   171  		if err := ns.Close(); err != nil {
   172  			l.Debugf("closing NsHandle (%v) failed: %v", err)
   173  		}
   174  	}
   175  
   176  	// Get network namespace file descriptor.
   177  	nsHandle, err := p.GetNamespaceHandle(ctx, ns)
   178  	if err != nil {
   179  		closeNs(origns)
   180  		return func() {}, err
   181  	}
   182  	defer closeNs(nsHandle)
   183  
   184  	// Lock the OS Thread so we don't accidentally switch namespaces later.
   185  	ctx.LockOSThread()
   186  
   187  	l = p.Log.WithFields(logging.Fields{
   188  		"ns":         nsHandle.String(),
   189  		"ns-fd":      int(nsHandle),
   190  		"orig-ns":    origns.String(),
   191  		"orig-ns-fd": int(origns),
   192  	})
   193  
   194  	// Switch the namespace.
   195  	if err := p.sysHandler.SetNamespace(nsHandle); err != nil {
   196  		l.Errorf("Failed to switch to Linux network namespace (%v): %v", ns, err)
   197  		ctx.UnlockOSThread()
   198  		closeNs(origns)
   199  		return func() {}, err
   200  	}
   201  
   202  	return func() {
   203  		if err := p.sysHandler.SetNamespace(origns); err != nil {
   204  			l.Errorf("Failed to switch to original Linux network namespace: %v", err)
   205  		}
   206  		closeNs(origns)
   207  		ctx.UnlockOSThread()
   208  	}, nil
   209  }
   210  
   211  // retrieveConfig loads NsPlugin configuration file.
   212  func (p *NsPlugin) retrieveConfig() (*Config, error) {
   213  	config := &Config{}
   214  	found, err := p.Cfg.LoadValue(config)
   215  	if !found {
   216  		p.Log.Debug("Linux NsPlugin config not found")
   217  		return nil, nil
   218  	}
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	p.Log.Debug("Linux NsPlugin config found")
   223  	return config, err
   224  }
   225  
   226  // getOrCreateNs returns an existing Linux network namespace or creates a new one if it doesn't exist yet.
   227  // It is, however, only possible to create "named" namespaces. For PID-based namespaces, process with
   228  // the given PID must exists, otherwise the function returns an error.
   229  func (p *NsPlugin) getOrCreateNs(ctx nsLinuxcalls.NamespaceMgmtCtx, ns *nsmodel.NetNamespace) (netns.NsHandle, error) {
   230  	var nsHandle netns.NsHandle
   231  	var err error
   232  
   233  	if ns == nil {
   234  		return p.sysHandler.DuplicateNamespaceHandle(p.defaultNs)
   235  	}
   236  
   237  	switch ns.Type {
   238  	case nsmodel.NetNamespace_PID:
   239  		pid, err := strconv.Atoi(ns.Reference)
   240  		if err != nil {
   241  			return netns.None(), errors.Errorf("failed to parse network namespace PID reference: %v", err)
   242  		}
   243  		nsHandle, err = p.sysHandler.GetNamespaceFromPid(int(pid))
   244  		if err != nil {
   245  			return netns.None(), errors.Errorf("failed to get namespace handle from PID: %v", err)
   246  		}
   247  
   248  	case nsmodel.NetNamespace_NSID:
   249  		nsHandle, err = p.sysHandler.GetNamespaceFromName(ns.Reference)
   250  		if err != nil {
   251  			p.Log.Warnf("GetNamespaceFromName %s failed: %v", ns.Reference, err)
   252  			// Create named namespace if it doesn't exist yet.
   253  			_, err = p.namedNsHandler.CreateNamedNetNs(ctx, ns.Reference)
   254  			if err != nil {
   255  				return netns.None(), errors.Errorf("failed to create named net namspace: %v", err)
   256  			}
   257  			nsHandle, err = p.sysHandler.GetNamespaceFromName(ns.Reference)
   258  			if err != nil {
   259  				return netns.None(), errors.Errorf("unable to get namespace by name")
   260  			}
   261  		}
   262  
   263  	case nsmodel.NetNamespace_FD:
   264  		if ns.Reference == "" {
   265  			return p.sysHandler.DuplicateNamespaceHandle(p.defaultNs)
   266  		}
   267  		nsHandle, err = p.sysHandler.GetNamespaceFromPath(ns.Reference)
   268  		if err != nil {
   269  			return netns.None(), errors.Errorf("failed to get file %s from path: %v", ns.Reference, err)
   270  		}
   271  
   272  	case nsmodel.NetNamespace_MICROSERVICE:
   273  		return netns.None(), errors.Errorf("unable to convert microservice label to PID at this level")
   274  
   275  	default:
   276  		return netns.None(), errors.Errorf("undefined network namespace reference")
   277  	}
   278  
   279  	return nsHandle, nil
   280  }
   281  
   282  // convertMicroserviceNsToPidNs converts microservice-referenced namespace into the PID-referenced namespace.
   283  func (p *NsPlugin) convertMicroserviceNsToPidNs(microserviceLabel string) (pidNs *nsmodel.NetNamespace) {
   284  	if microservice, found := p.msDescriptor.GetMicroserviceStateData(microserviceLabel); found {
   285  		pidNamespace := &nsmodel.NetNamespace{}
   286  		pidNamespace.Type = nsmodel.NetNamespace_PID
   287  		pidNamespace.Reference = strconv.Itoa(microservice.PID)
   288  		return pidNamespace
   289  	}
   290  	return nil
   291  }