github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/machine/network_linux.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2022 The Katalyst Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package machine
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net"
    26  	"os"
    27  	"path"
    28  	"path/filepath"
    29  	"runtime"
    30  	"strings"
    31  	"syscall"
    32  
    33  	"github.com/vishvananda/netns"
    34  
    35  	"github.com/kubewharf/katalyst-core/pkg/config/agent/global"
    36  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    37  )
    38  
    39  const (
    40  	sysFSDirNormal   = "/sys"
    41  	sysFSDirNetNSTmp = "/tmp/net_ns_sysfs"
    42  )
    43  
    44  const (
    45  	nicPathNameDeviceFormatPCI = "devices/pci"
    46  	nicPathNAMEBaseDir         = "class/net"
    47  )
    48  
    49  const (
    50  	netFileNameSpeed    = "speed"
    51  	netFileNameNUMANode = "device/numa_node"
    52  	netFileNameEnable   = "device/enable"
    53  	netOperstate        = "operstate"
    54  	netUP               = "up"
    55  	netEnable           = 1
    56  )
    57  
    58  // GetExtraNetworkInfo get network info from /sys/class/net and system function net.Interfaces.
    59  // if multiple network namespace is enabled, we should exec into all namespaces and parse nics for them.
    60  func GetExtraNetworkInfo(conf *global.MachineInfoConfiguration) (*ExtraNetworkInfo, error) {
    61  	networkInfo := &ExtraNetworkInfo{}
    62  
    63  	nsList := []string{DefaultNICNamespace}
    64  	if conf.NetMultipleNS {
    65  		if conf.NetNSDirAbsPath == "" {
    66  			return nil, fmt.Errorf("GetNetworkInterfaces got nil netNSDirAbsPath")
    67  		}
    68  
    69  		if dirs, err := ioutil.ReadDir(conf.NetNSDirAbsPath); err != nil {
    70  			return nil, err
    71  		} else {
    72  			for _, dir := range dirs {
    73  				if !dir.IsDir() {
    74  					nsList = append(nsList, dir.Name())
    75  				}
    76  			}
    77  		}
    78  
    79  		runtime.LockOSThread()
    80  		defer runtime.UnlockOSThread()
    81  	}
    82  	general.Infof("namespace list: %v", nsList)
    83  
    84  	for _, ns := range nsList {
    85  		nicsInNs, err := getNSNetworkHardwareTopology(ns, conf.NetNSDirAbsPath)
    86  		if err != nil {
    87  			// if multiple ns is disabled, we should block agent start
    88  			// todo: to discuss if block agent start when any ns is parsed failed
    89  			if !conf.NetMultipleNS {
    90  				return nil, fmt.Errorf("failed to get network topology: %v", err)
    91  			}
    92  
    93  			general.Errorf("get network topology for ns %s failed: %v", ns, err)
    94  			continue
    95  		}
    96  
    97  		networkInfo.Interface = append(networkInfo.Interface, nicsInNs...)
    98  	}
    99  	return networkInfo, nil
   100  }
   101  
   102  // getNSNetworkHardwareTopology set given network namespaces and get nics inside if needed
   103  func getNSNetworkHardwareTopology(nsName, netNSDirAbsPath string) ([]InterfaceInfo, error) {
   104  	var nics []InterfaceInfo
   105  
   106  	nsAbsPath := ""
   107  	sysFsDir := sysFSDirNormal
   108  
   109  	if nsName != DefaultNICNamespace {
   110  		nsAbsPath = path.Join(netNSDirAbsPath, nsName)
   111  		sysFsDir = sysFSDirNetNSTmp
   112  
   113  		// save the current network namespace
   114  		originNS, _ := netns.Get()
   115  		defer func() {
   116  			// switch back to the original namespace
   117  			if err := netns.Set(originNS); err != nil {
   118  				general.Fatalf("failed to unmount sys fs: %v", err)
   119  			}
   120  			_ = originNS.Close()
   121  		}()
   122  
   123  		// exec into the new network namespace
   124  		newNS, err := netns.GetFromPath(nsAbsPath)
   125  		if err != nil {
   126  			return nil, fmt.Errorf("get handle from net ns path: %s failed with error: %v", nsAbsPath, err)
   127  		}
   128  		defer func() { _ = newNS.Close() }()
   129  
   130  		if err = netns.Set(newNS); err != nil {
   131  			return nil, fmt.Errorf("set newNS: %s failed with error: %v", nsAbsPath, err)
   132  		}
   133  
   134  		// create the target directory if it doesn't exist
   135  		if _, err := os.Stat(sysFSDirNetNSTmp); err != nil {
   136  			if os.IsNotExist(err) {
   137  				if err := os.MkdirAll(sysFSDirNetNSTmp, os.FileMode(0o755)); err != nil {
   138  					return nil, fmt.Errorf("make dir: %s failed with error: %v", sysFSDirNetNSTmp, err)
   139  				}
   140  			} else {
   141  				return nil, fmt.Errorf("check dir: %s failed with error: %v", sysFSDirNetNSTmp, err)
   142  			}
   143  		}
   144  
   145  		if err := syscall.Mount("sysfs", sysFSDirNetNSTmp, "sysfs", 0, ""); err != nil {
   146  			return nil, fmt.Errorf("mount sysfs to %s failed with error: %v", sysFSDirNetNSTmp, err)
   147  		}
   148  
   149  		// the sysfs needs to be remounted before switching network namespace back
   150  		defer func() {
   151  			if err := syscall.Unmount(sysFSDirNetNSTmp, 0); err != nil {
   152  				general.Fatalf("unmount sysfs: %s failed with error: %v", sysFSDirNetNSTmp, err)
   153  			}
   154  		}()
   155  	}
   156  
   157  	nicsBaseDirPath := path.Join(sysFsDir, nicPathNAMEBaseDir)
   158  	nicDirs, err := ioutil.ReadDir(nicsBaseDirPath)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	nicsAddrMap, err := getInterfaceAddr()
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	for _, nicDir := range nicDirs {
   169  		nicName := nicDir.Name()
   170  		nicPath := path.Join(nicsBaseDirPath, nicName)
   171  
   172  		devPath, err := filepath.EvalSymlinks(nicPath)
   173  		if err != nil {
   174  			general.Warningf("eval sym link: %s failed with error: %v", nicPath, err)
   175  			continue
   176  		}
   177  
   178  		// only return PCI NIC
   179  		if !strings.Contains(devPath, nicPathNameDeviceFormatPCI) {
   180  			general.Warningf("skip nic: %s with devPath: %s which isn't pci device", nicName, devPath)
   181  			continue
   182  		}
   183  
   184  		nic := InterfaceInfo{
   185  			Iface:          nicName,
   186  			NSName:         nsName,
   187  			NSAbsolutePath: nsAbsPath,
   188  		}
   189  		if nicAddr, exist := nicsAddrMap[nicName]; exist {
   190  			nic.Addr = nicAddr
   191  		}
   192  		getInterfaceAttr(&nic, nicPath)
   193  
   194  		general.Infof("discover nic: %#v", nic)
   195  		nics = append(nics, nic)
   196  	}
   197  
   198  	return nics, nil
   199  }
   200  
   201  // getInterfaceAttr parses key information from system files
   202  func getInterfaceAttr(info *InterfaceInfo, nicPath string) {
   203  	if nicNUMANode, err := general.ReadFileIntoInt(path.Join(nicPath, netFileNameNUMANode)); err != nil {
   204  		general.Errorf("ns %v name %v, read NUMA node failed with error: %v. Suppose it's associated with NUMA node 0", info.NSName, info.Iface, err)
   205  		// some net device files are missed on VMs (e.g. "device/numanode")
   206  		info.NumaNode = 0
   207  	} else {
   208  		if nicNUMANode != -1 {
   209  			info.NumaNode = nicNUMANode
   210  		} else {
   211  			// the "device/numanode" file is filled with -1 on some VMs (e.g. byte-vm), we should return 0 instead
   212  			general.Errorf("Invalid NUMA node %v for interface %v. Suppose it's associated with NUMA node 0", info.NumaNode, info.Iface)
   213  			info.NumaNode = 0
   214  		}
   215  	}
   216  
   217  	if general.IsPathExists(path.Join(nicPath, netFileNameEnable)) {
   218  		if nicEnabledStatus, err := general.ReadFileIntoInt(path.Join(nicPath, netFileNameEnable)); err != nil {
   219  			general.Errorf("ns %v name %v, read enable status failed with error: %v", info.NSName, info.Iface, err)
   220  			info.Enable = false
   221  		} else {
   222  			info.Enable = nicEnabledStatus == netEnable
   223  		}
   224  	} else {
   225  		// some VMs do not have "device/enable" file under nicPath, we can read "operstate" for nic status instead
   226  		if nicUPStatus, err := general.ReadFileIntoLines(path.Join(nicPath, netOperstate)); err != nil || len(nicUPStatus) == 0 {
   227  			general.Errorf("ns %v name %v, read operstate failed with error: %v", info.NSName, info.Iface, err)
   228  			info.Enable = false
   229  		} else {
   230  			info.Enable = nicUPStatus[0] == netUP
   231  		}
   232  	}
   233  
   234  	if nicSpeed, err := general.ReadFileIntoInt(path.Join(nicPath, netFileNameSpeed)); err != nil {
   235  		general.Errorf("ns %v name %v, read speed failed with error: %v", info.NSName, info.Iface, err)
   236  		info.Speed = -1
   237  	} else {
   238  		info.Speed = nicSpeed
   239  	}
   240  }
   241  
   242  // getInterfaceAddr get interface address which is map of interface name to
   243  // its interface address which includes both ipv6 and ipv4 address.
   244  func getInterfaceAddr() (map[string]*IfaceAddr, error) {
   245  	var err error
   246  
   247  	ias := make(map[string]*IfaceAddr)
   248  
   249  	interfaces, err := net.Interfaces()
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	for _, i := range interfaces {
   255  		// if the interface is down or a loopback interface, we just skip it
   256  		if i.Flags&net.FlagUp == 0 || i.Flags&net.FlagLoopback > 0 {
   257  			continue
   258  		}
   259  
   260  		address, err := i.Addrs()
   261  		if err != nil {
   262  			continue
   263  		}
   264  
   265  		if len(address) > 0 {
   266  			ia := &IfaceAddr{}
   267  
   268  			for _, addr := range address {
   269  				var ip net.IP
   270  				switch v := addr.(type) {
   271  				case *net.IPNet:
   272  					ip = v.IP
   273  				case *net.IPAddr:
   274  					ip = v.IP
   275  				default:
   276  					continue
   277  				}
   278  
   279  				// filter out ips that are not global uni-cast
   280  				if !ip.IsGlobalUnicast() {
   281  					continue
   282  				}
   283  
   284  				if ip.To4() != nil {
   285  					ia.IPV4 = append(ia.IPV4, &ip)
   286  				} else {
   287  					ia.IPV6 = append(ia.IPV6, &ip)
   288  				}
   289  			}
   290  
   291  			ias[i.Name] = ia
   292  		}
   293  	}
   294  
   295  	return ias, nil
   296  }