github.com/fafucoder/cilium@v1.6.11/plugins/cilium-cni/cilium-cni.go (about)

     1  // Copyright 2016-2019 Authors of Cilium
     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 main
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"os"
    22  	"path/filepath"
    23  	"runtime"
    24  	"sort"
    25  	"syscall"
    26  
    27  	"github.com/cilium/cilium/api/v1/models"
    28  	"github.com/cilium/cilium/common/addressing"
    29  	"github.com/cilium/cilium/pkg/client"
    30  	"github.com/cilium/cilium/pkg/datapath/linux/route"
    31  	"github.com/cilium/cilium/pkg/defaults"
    32  	"github.com/cilium/cilium/pkg/endpoint/connector"
    33  	endpointid "github.com/cilium/cilium/pkg/endpoint/id"
    34  	"github.com/cilium/cilium/pkg/labels"
    35  	"github.com/cilium/cilium/pkg/logging"
    36  	"github.com/cilium/cilium/pkg/logging/logfields"
    37  	"github.com/cilium/cilium/pkg/netns"
    38  	"github.com/cilium/cilium/pkg/option"
    39  	"github.com/cilium/cilium/pkg/uuid"
    40  	"github.com/cilium/cilium/pkg/version"
    41  	chainingapi "github.com/cilium/cilium/plugins/cilium-cni/chaining/api"
    42  	_ "github.com/cilium/cilium/plugins/cilium-cni/chaining/awscni"
    43  	_ "github.com/cilium/cilium/plugins/cilium-cni/chaining/azure"
    44  	_ "github.com/cilium/cilium/plugins/cilium-cni/chaining/flannel"
    45  	_ "github.com/cilium/cilium/plugins/cilium-cni/chaining/generic-veth"
    46  	_ "github.com/cilium/cilium/plugins/cilium-cni/chaining/portmap"
    47  	"github.com/cilium/cilium/plugins/cilium-cni/types"
    48  
    49  	"github.com/containernetworking/cni/pkg/skel"
    50  	cniTypes "github.com/containernetworking/cni/pkg/types"
    51  	cniTypesVer "github.com/containernetworking/cni/pkg/types/current"
    52  	cniVersion "github.com/containernetworking/cni/pkg/version"
    53  	"github.com/containernetworking/plugins/pkg/ns"
    54  	"github.com/sirupsen/logrus"
    55  	"github.com/vishvananda/netlink"
    56  
    57  	"golang.org/x/sys/unix"
    58  )
    59  
    60  var (
    61  	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "cilium-cni")
    62  )
    63  
    64  func init() {
    65  	logging.SetLogLevel(logrus.DebugLevel)
    66  	runtime.LockOSThread()
    67  }
    68  
    69  type CmdState struct {
    70  	Endpoint  *models.EndpointChangeRequest
    71  	IP6       addressing.CiliumIPv6
    72  	IP6routes []route.Route
    73  	IP4       addressing.CiliumIPv4
    74  	IP4routes []route.Route
    75  	Client    *client.Client
    76  	HostAddr  *models.NodeAddressing
    77  }
    78  
    79  func main() {
    80  	skel.PluginMain(cmdAdd,
    81  		nil,
    82  		cmdDel,
    83  		cniVersion.PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1"),
    84  		"Cilium CNI plugin "+version.Version)
    85  }
    86  
    87  func ipv6IsEnabled(ipam *models.IPAMResponse) bool {
    88  	if ipam == nil || ipam.Address.IPV6 == "" {
    89  		return false
    90  	}
    91  
    92  	if ipam.HostAddressing != nil && ipam.HostAddressing.IPV6 != nil {
    93  		return ipam.HostAddressing.IPV6.Enabled
    94  	}
    95  
    96  	return true
    97  }
    98  
    99  func ipv4IsEnabled(ipam *models.IPAMResponse) bool {
   100  	if ipam == nil || ipam.Address.IPV4 == "" {
   101  		return false
   102  	}
   103  
   104  	if ipam.HostAddressing != nil && ipam.HostAddressing.IPV4 != nil {
   105  		return ipam.HostAddressing.IPV4.Enabled
   106  	}
   107  
   108  	return true
   109  }
   110  
   111  func releaseIP(client *client.Client, ip string) {
   112  	if ip != "" {
   113  		if err := client.IPAMReleaseIP(ip); err != nil {
   114  			log.WithError(err).WithField(logfields.IPAddr, ip).Warn("Unable to release IP")
   115  		}
   116  	}
   117  }
   118  
   119  func releaseIPs(client *client.Client, addr *models.AddressPair) {
   120  	releaseIP(client, addr.IPV6)
   121  	releaseIP(client, addr.IPV4)
   122  }
   123  
   124  func addIPConfigToLink(ip addressing.CiliumIP, routes []route.Route, link netlink.Link, ifName string) error {
   125  	log.WithFields(logrus.Fields{
   126  		logfields.IPAddr:    ip,
   127  		"netLink":           logfields.Repr(link),
   128  		logfields.Interface: ifName,
   129  	}).Debug("Configuring link")
   130  
   131  	addr := &netlink.Addr{IPNet: ip.EndpointPrefix()}
   132  	if ip.IsIPv6() {
   133  		addr.Flags = syscall.IFA_F_NODAD
   134  	}
   135  	if err := netlink.AddrAdd(link, addr); err != nil {
   136  		return fmt.Errorf("failed to add addr to %q: %v", ifName, err)
   137  	}
   138  
   139  	// ipvlan needs to be UP before we add routes, and can only be UPed after
   140  	// we added an IP address.
   141  	if err := netlink.LinkSetUp(link); err != nil {
   142  		return fmt.Errorf("failed to set %q UP: %v", ifName, err)
   143  	}
   144  
   145  	// Sort provided routes to make sure we apply any more specific
   146  	// routes first which may be used as nexthops in wider routes
   147  	sort.Sort(route.ByMask(routes))
   148  
   149  	for _, r := range routes {
   150  		log.WithField("route", logfields.Repr(r)).Debug("Adding route")
   151  		rt := &netlink.Route{
   152  			LinkIndex: link.Attrs().Index,
   153  			Scope:     netlink.SCOPE_UNIVERSE,
   154  			Dst:       &r.Prefix,
   155  			MTU:       r.MTU,
   156  		}
   157  
   158  		if r.Nexthop == nil {
   159  			rt.Scope = netlink.SCOPE_LINK
   160  		} else {
   161  			rt.Gw = *r.Nexthop
   162  		}
   163  
   164  		if err := netlink.RouteAdd(rt); err != nil {
   165  			if !os.IsExist(err) {
   166  				return fmt.Errorf("failed to add route '%s via %v dev %v': %v",
   167  					r.Prefix.String(), r.Nexthop, ifName, err)
   168  			}
   169  		}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func configureIface(ipam *models.IPAMResponse, ifName string, state *CmdState) (string, error) {
   176  	l, err := netlink.LinkByName(ifName)
   177  	if err != nil {
   178  		return "", fmt.Errorf("failed to lookup %q: %v", ifName, err)
   179  	}
   180  
   181  	if err := netlink.LinkSetUp(l); err != nil {
   182  		return "", fmt.Errorf("failed to set %q UP: %v", ifName, err)
   183  	}
   184  
   185  	if ipv4IsEnabled(ipam) {
   186  		if err := addIPConfigToLink(state.IP4, state.IP4routes, l, ifName); err != nil {
   187  			return "", fmt.Errorf("error configuring IPv4: %s", err.Error())
   188  		}
   189  	}
   190  
   191  	if ipv6IsEnabled(ipam) {
   192  		if err := addIPConfigToLink(state.IP6, state.IP6routes, l, ifName); err != nil {
   193  			return "", fmt.Errorf("error configuring IPv6: %s", err.Error())
   194  		}
   195  	}
   196  
   197  	if err := netlink.LinkSetUp(l); err != nil {
   198  		return "", fmt.Errorf("failed to set %q UP: %v", ifName, err)
   199  	}
   200  
   201  	if l.Attrs() != nil {
   202  		return l.Attrs().HardwareAddr.String(), nil
   203  	}
   204  
   205  	return "", nil
   206  }
   207  
   208  func newCNIRoute(r route.Route) *cniTypes.Route {
   209  	rt := &cniTypes.Route{
   210  		Dst: r.Prefix,
   211  	}
   212  	if r.Nexthop != nil {
   213  		rt.GW = *r.Nexthop
   214  	}
   215  
   216  	return rt
   217  }
   218  
   219  func prepareIP(ipAddr string, isIPv6 bool, state *CmdState, mtu int) (*cniTypesVer.IPConfig, []*cniTypes.Route, error) {
   220  	var (
   221  		routes    []route.Route
   222  		err       error
   223  		gw        string
   224  		ipVersion string
   225  		ip        addressing.CiliumIP
   226  	)
   227  
   228  	if isIPv6 {
   229  		if state.IP6, err = addressing.NewCiliumIPv6(ipAddr); err != nil {
   230  			return nil, nil, err
   231  		}
   232  		if state.IP6routes, err = connector.IPv6Routes(state.HostAddr, mtu); err != nil {
   233  			return nil, nil, err
   234  		}
   235  		routes = state.IP6routes
   236  		ip = state.IP6
   237  		gw = connector.IPv6Gateway(state.HostAddr)
   238  		ipVersion = "6"
   239  	} else {
   240  		if state.IP4, err = addressing.NewCiliumIPv4(ipAddr); err != nil {
   241  			return nil, nil, err
   242  		}
   243  		if state.IP4routes, err = connector.IPv4Routes(state.HostAddr, mtu); err != nil {
   244  			return nil, nil, err
   245  		}
   246  		routes = state.IP4routes
   247  		ip = state.IP4
   248  		gw = connector.IPv4Gateway(state.HostAddr)
   249  		ipVersion = "4"
   250  	}
   251  
   252  	rt := []*cniTypes.Route{}
   253  	for _, r := range routes {
   254  		rt = append(rt, newCNIRoute(r))
   255  	}
   256  
   257  	gwIP := net.ParseIP(gw)
   258  	if gwIP == nil {
   259  		return nil, nil, fmt.Errorf("Invalid gateway address: %s", gw)
   260  	}
   261  
   262  	return &cniTypesVer.IPConfig{
   263  		Address: *ip.EndpointPrefix(),
   264  		Gateway: gwIP,
   265  		Version: ipVersion,
   266  	}, rt, nil
   267  }
   268  
   269  func cmdAdd(args *skel.CmdArgs) (err error) {
   270  	var (
   271  		ipConfig *cniTypesVer.IPConfig
   272  		routes   []*cniTypes.Route
   273  		ipam     *models.IPAMResponse
   274  		n        *types.NetConf
   275  		c        *client.Client
   276  		netNs    ns.NetNS
   277  	)
   278  
   279  	logger := log.WithField("eventUUID", uuid.NewUUID())
   280  	logger.Debugf("Processing CNI ADD request %#v", args)
   281  
   282  	n, err = types.LoadNetConf(args.StdinData)
   283  	if err != nil {
   284  		err = fmt.Errorf("unable to parse CNI configuration \"%s\": %s", args.StdinData, err)
   285  		return
   286  	}
   287  	logger.Debugf("CNI NetConf: %#v", n)
   288  	if n.PrevResult != nil {
   289  		logger.Debugf("CNI Previous result: %#v", n.PrevResult)
   290  	}
   291  
   292  	cniArgs := types.ArgsSpec{}
   293  	if err = cniTypes.LoadArgs(args.Args, &cniArgs); err != nil {
   294  		err = fmt.Errorf("unable to extract CNI arguments: %s", err)
   295  		return
   296  	}
   297  	logger.Debugf("CNI Args: %#v", cniArgs)
   298  
   299  	c, err = client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout)
   300  	if err != nil {
   301  		err = fmt.Errorf("unable to connect to Cilium daemon: %s", client.Hint(err))
   302  		return
   303  	}
   304  
   305  	if len(n.NetConf.RawPrevResult) != 0 && n.Name != chainingapi.DefaultConfigName {
   306  		if chainAction := chainingapi.Lookup(n.Name); chainAction != nil {
   307  			var (
   308  				res *cniTypesVer.Result
   309  				ctx = chainingapi.PluginContext{
   310  					Logger:  logger,
   311  					Args:    args,
   312  					CniArgs: cniArgs,
   313  					NetConf: n,
   314  					Client:  c,
   315  				}
   316  			)
   317  
   318  			if chainAction.ImplementsAdd() {
   319  				res, err = chainAction.Add(context.TODO(), ctx)
   320  				if err != nil {
   321  					return
   322  				}
   323  				logger.Debugf("Returning result %#v", res)
   324  				err = cniTypes.PrintResult(res, n.CNIVersion)
   325  				return
   326  			}
   327  		} else {
   328  			logger.Warnf("Unknown CNI chaining configuration name '%s'", n.Name)
   329  		}
   330  	}
   331  
   332  	netNs, err = ns.GetNS(args.Netns)
   333  	if err != nil {
   334  		err = fmt.Errorf("failed to open netns %q: %s", args.Netns, err)
   335  	}
   336  	defer netNs.Close()
   337  
   338  	if err = netns.RemoveIfFromNetNSIfExists(netNs, args.IfName); err != nil {
   339  		err = fmt.Errorf("failed removing interface %q from namespace %q: %s",
   340  			args.IfName, args.Netns, err)
   341  		return
   342  	}
   343  
   344  	addLabels := models.Labels{}
   345  
   346  	for _, label := range n.Args.Mesos.NetworkInfo.Labels.Labels {
   347  		addLabels = append(addLabels, fmt.Sprintf("%s:%s=%s", labels.LabelSourceMesos, label.Key, label.Value))
   348  	}
   349  
   350  	configResult, err := c.ConfigGet()
   351  	if err != nil {
   352  		err = fmt.Errorf("unable to retrieve configuration from cilium-agent: %s", err)
   353  		return
   354  	}
   355  
   356  	if configResult == nil || configResult.Status == nil {
   357  		err = fmt.Errorf("did not receive configuration from cilium-agent")
   358  		return
   359  	}
   360  
   361  	conf := *configResult.Status
   362  
   363  	ep := &models.EndpointChangeRequest{
   364  		ContainerID:  args.ContainerID,
   365  		Labels:       addLabels,
   366  		State:        models.EndpointStateWaitingForIdentity,
   367  		Addressing:   &models.AddressPair{},
   368  		K8sPodName:   string(cniArgs.K8S_POD_NAME),
   369  		K8sNamespace: string(cniArgs.K8S_POD_NAMESPACE),
   370  	}
   371  
   372  	switch conf.DatapathMode {
   373  	case option.DatapathModeVeth:
   374  		var (
   375  			veth      *netlink.Veth
   376  			peer      *netlink.Link
   377  			tmpIfName string
   378  		)
   379  		veth, peer, tmpIfName, err = connector.SetupVeth(ep.ContainerID, int(conf.DeviceMTU), ep)
   380  		if err != nil {
   381  			err = fmt.Errorf("unable to set up veth on host side: %s", err)
   382  			return err
   383  		}
   384  		defer func() {
   385  			if err != nil {
   386  				if err2 := netlink.LinkDel(veth); err2 != nil {
   387  					logger.WithError(err2).WithField(logfields.Veth, veth.Name).Warn("failed to clean up and delete veth")
   388  				}
   389  			}
   390  		}()
   391  
   392  		if err = netlink.LinkSetNsFd(*peer, int(netNs.Fd())); err != nil {
   393  			err = fmt.Errorf("unable to move veth pair '%v' to netns: %s", peer, err)
   394  			return
   395  		}
   396  
   397  		_, _, err = connector.SetupVethRemoteNs(netNs, tmpIfName, args.IfName)
   398  		if err != nil {
   399  			err = fmt.Errorf("unable to set up veth on container side: %s", err)
   400  			return
   401  		}
   402  	case option.DatapathModeIpvlan:
   403  		ipvlanConf := *conf.IpvlanConfiguration
   404  		index := int(ipvlanConf.MasterDeviceIndex)
   405  
   406  		var mapFD int
   407  		mapFD, err = connector.CreateAndSetupIpvlanSlave(
   408  			ep.ContainerID, args.IfName, netNs,
   409  			int(conf.DeviceMTU), index, ipvlanConf.OperationMode, ep,
   410  		)
   411  		if err != nil {
   412  			err = fmt.Errorf("unable to setup ipvlan datapath: %s", err)
   413  			return
   414  		}
   415  		defer unix.Close(mapFD)
   416  	}
   417  
   418  	podName := string(cniArgs.K8S_POD_NAMESPACE) + "/" + string(cniArgs.K8S_POD_NAME)
   419  	ipam, err = c.IPAMAllocate("", podName, true)
   420  	if err != nil {
   421  		err = fmt.Errorf("unable to allocate IP via local cilium agent: %s", err)
   422  		return
   423  	}
   424  
   425  	if ipam.Address == nil {
   426  		err = fmt.Errorf("Invalid IPAM response, missing addressing")
   427  		return
   428  	}
   429  
   430  	// release addresses on failure
   431  	defer func() {
   432  		if err != nil {
   433  			releaseIP(c, ipam.Address.IPV4)
   434  			releaseIP(c, ipam.Address.IPV6)
   435  		}
   436  	}()
   437  
   438  	if err = connector.SufficientAddressing(ipam.HostAddressing); err != nil {
   439  		err = fmt.Errorf("IP allocation addressing in insufficient: %s", err)
   440  		return
   441  	}
   442  
   443  	state := CmdState{
   444  		Endpoint: ep,
   445  		Client:   c,
   446  		HostAddr: ipam.HostAddressing,
   447  	}
   448  
   449  	res := &cniTypesVer.Result{}
   450  
   451  	if !ipv6IsEnabled(ipam) && !ipv4IsEnabled(ipam) {
   452  		err = fmt.Errorf("IPAM did not provide IPv4 or IPv6 address")
   453  		return
   454  	}
   455  
   456  	if ipv6IsEnabled(ipam) {
   457  		ep.Addressing.IPV6 = ipam.Address.IPV6
   458  		ep.Addressing.IPV6ExpirationUUID = ipam.IPV6.ExpirationUUID
   459  
   460  		ipConfig, routes, err = prepareIP(ep.Addressing.IPV6, true, &state, int(conf.RouteMTU))
   461  		if err != nil {
   462  			err = fmt.Errorf("unable to prepare IP addressing for '%s': %s", ep.Addressing.IPV6, err)
   463  			return
   464  		}
   465  		res.IPs = append(res.IPs, ipConfig)
   466  		res.Routes = append(res.Routes, routes...)
   467  	}
   468  
   469  	if ipv4IsEnabled(ipam) {
   470  		ep.Addressing.IPV4 = ipam.Address.IPV4
   471  		ep.Addressing.IPV4ExpirationUUID = ipam.IPV4.ExpirationUUID
   472  
   473  		ipConfig, routes, err = prepareIP(ep.Addressing.IPV4, false, &state, int(conf.RouteMTU))
   474  		if err != nil {
   475  			err = fmt.Errorf("unable to prepare IP addressing for '%s': %s", ep.Addressing.IPV4, err)
   476  			return
   477  		}
   478  		res.IPs = append(res.IPs, ipConfig)
   479  		res.Routes = append(res.Routes, routes...)
   480  
   481  		if conf.IPAMMode == option.IPAMENI {
   482  			err = eniAdd(ipConfig, ipam.IPV4, conf)
   483  			if err != nil {
   484  				err = fmt.Errorf("unable to setup ENI datapath: %s", err)
   485  				return
   486  			}
   487  		}
   488  	}
   489  
   490  	var macAddrStr string
   491  	if err = netNs.Do(func(_ ns.NetNS) error {
   492  		allInterfacesPath := filepath.Join("/proc", "sys", "net", "ipv6", "conf", "all", "disable_ipv6")
   493  		err = connector.WriteSysConfig(allInterfacesPath, "0\n")
   494  		if err != nil {
   495  			logger.WithError(err).Warn("unable to enable ipv6 on all interfaces")
   496  		}
   497  		macAddrStr, err = configureIface(ipam, args.IfName, &state)
   498  		return err
   499  	}); err != nil {
   500  		err = fmt.Errorf("unable to configure interfaces in container namespace: %s", err)
   501  		return
   502  	}
   503  
   504  	res.Interfaces = append(res.Interfaces, &cniTypesVer.Interface{
   505  		Name:    args.IfName,
   506  		Mac:     macAddrStr,
   507  		Sandbox: "/proc/" + args.Netns + "/ns/net",
   508  	})
   509  
   510  	// Specify that endpoint must be regenerated synchronously. See GH-4409.
   511  	ep.SyncBuildEndpoint = true
   512  	if err = c.EndpointCreate(ep); err != nil {
   513  		logger.WithError(err).WithFields(logrus.Fields{
   514  			logfields.ContainerID: ep.ContainerID}).Warn("Unable to create endpoint")
   515  		err = fmt.Errorf("Unable to create endpoint: %s", err)
   516  		return
   517  	}
   518  
   519  	logger.WithFields(logrus.Fields{
   520  		logfields.ContainerID: ep.ContainerID}).Debug("Endpoint successfully created")
   521  	return cniTypes.PrintResult(res, n.CNIVersion)
   522  }
   523  
   524  // cmdDel is invoked on CNI DEL
   525  //
   526  // Note: ENI specific attributes do not need to be released as the ENIs and ENI
   527  // IPs can be reused and are not released until the node terminates.
   528  func cmdDel(args *skel.CmdArgs) error {
   529  	// Note about when to return errors: kubelet will retry the deletion
   530  	// for a long time. Therefore, only return an error for errors which
   531  	// are guaranteed to be recoverable.
   532  
   533  	logger := log.WithField("eventUUID", uuid.NewUUID())
   534  	logger.Debugf("Processing CNI DEL request %#v", args)
   535  
   536  	n, err := types.LoadNetConf(args.StdinData)
   537  	if err != nil {
   538  		return err
   539  	}
   540  	logger.Debugf("CNI NetConf: %#v", n)
   541  
   542  	cniArgs := types.ArgsSpec{}
   543  	if err = cniTypes.LoadArgs(args.Args, &cniArgs); err != nil {
   544  		return fmt.Errorf("unable to extract CNI arguments: %s", err)
   545  	}
   546  	logger.Debugf("CNI Args: %#v", cniArgs)
   547  
   548  	c, err := client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout)
   549  	if err != nil {
   550  		// this error can be recovered from
   551  		return fmt.Errorf("unable to connect to Cilium daemon: %s", client.Hint(err))
   552  	}
   553  
   554  	if n.Name != chainingapi.DefaultConfigName {
   555  		if chainAction := chainingapi.Lookup(n.Name); chainAction != nil {
   556  			var (
   557  				ctx = chainingapi.PluginContext{
   558  					Logger:  logger,
   559  					Args:    args,
   560  					CniArgs: cniArgs,
   561  					NetConf: n,
   562  					Client:  c,
   563  				}
   564  			)
   565  
   566  			if chainAction.ImplementsDelete() {
   567  				return chainAction.Delete(context.TODO(), ctx)
   568  			}
   569  		} else {
   570  			logger.Warnf("Unknown CNI chaining configuration name '%s'", n.Name)
   571  		}
   572  	}
   573  
   574  	id := endpointid.NewID(endpointid.ContainerIdPrefix, args.ContainerID)
   575  	if err := c.EndpointDelete(id); err != nil {
   576  		// EndpointDelete returns an error in the following scenarios:
   577  		// DeleteEndpointIDInvalid: Invalid delete parameters, no need to retry
   578  		// DeleteEndpointIDNotFound: No need to retry
   579  		// DeleteEndpointIDErrors: Errors encountered while deleting,
   580  		//                         the endpoint is always deleted though, no
   581  		//                         need to retry
   582  		log.WithError(err).Warning("Errors encountered while deleting endpoint")
   583  	}
   584  
   585  	netNs, err := ns.GetNS(args.Netns)
   586  	if err != nil {
   587  		log.WithError(err).Warningf("Unable to enter namespace %q, will not delete interface", args.Netns)
   588  		// We are not returning an error as this is very unlikely to be recoverable
   589  		return nil
   590  	}
   591  	defer netNs.Close()
   592  
   593  	err = netns.RemoveIfFromNetNSIfExists(netNs, args.IfName)
   594  	if err != nil {
   595  		log.WithError(err).Warningf("Unable to delete interface %s in namespace %q, will not delete interface", args.IfName, args.Netns)
   596  		// We are not returning an error as this is very unlikely to be recoverable
   597  	}
   598  
   599  	return nil
   600  }