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 }