go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/ifplugin/linuxcalls/dump_interface_linuxcalls.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 //go:build !windows && !darwin 16 17 package linuxcalls 18 19 import ( 20 "strconv" 21 "strings" 22 23 "github.com/vishvananda/netlink" 24 "go.ligato.io/cn-infra/v2/logging" 25 "golang.org/x/sys/unix" 26 "google.golang.org/protobuf/proto" 27 28 "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" 29 interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" 30 namespaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/namespace" 31 ) 32 33 const ( 34 // defaultLoopbackName is the name used to access loopback interface in linux 35 // host_if_name field in config is effectively ignored 36 DefaultLoopbackName = "lo" 37 38 // minimum number of namespaces to be given to a single Go routine for processing 39 // in the Retrieve operation 40 minWorkForGoRoutine = 3 41 ) 42 43 // retrievedIfaces is used as the return value sent via channel by retrieveInterfaces(). 44 type retrievedInterfaces struct { 45 interfaces []*InterfaceDetails 46 stats []*InterfaceStatistics 47 err error 48 } 49 50 // DumpInterfaces retrieves all linux interfaces from default namespace and from all 51 // the other namespaces based on known linux interfaces from the index map. 52 func (h *NetLinkHandler) DumpInterfaces() ([]*InterfaceDetails, error) { 53 return h.DumpInterfacesFromNamespaces(h.getKnownNamespaces()) 54 } 55 56 // DumpInterfaceStats retrieves statistics for all linux interfaces from default namespace 57 // and from all the other namespaces based on known linux interfaces from the index map. 58 func (h *NetLinkHandler) DumpInterfaceStats() ([]*InterfaceStatistics, error) { 59 return h.DumpInterfaceStatsFromNamespaces(h.getKnownNamespaces()) 60 } 61 62 // DumpInterfacesFromNamespaces requires context in form of the namespace list of which linux interfaces 63 // will be retrieved. If no context is provided, interfaces only from the default namespace are retrieved. 64 func (h *NetLinkHandler) DumpInterfacesFromNamespaces(nsList []*namespaces.NetNamespace) ([]*InterfaceDetails, error) { 65 // Always retrieve from the default namespace 66 if len(nsList) == 0 { 67 nsList = []*namespaces.NetNamespace{nil} 68 } 69 // Determine the number of go routines to invoke 70 goRoutinesCnt := len(nsList) / minWorkForGoRoutine 71 if goRoutinesCnt == 0 { 72 goRoutinesCnt = 1 73 } 74 if goRoutinesCnt > h.goRoutineCount { 75 goRoutinesCnt = h.goRoutineCount 76 } 77 ch := make(chan retrievedInterfaces, goRoutinesCnt) 78 79 // Invoke multiple go routines for more efficient parallel interface retrieval 80 for idx := 0; idx < goRoutinesCnt; idx++ { 81 if goRoutinesCnt > 1 { 82 go h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch) 83 } else { 84 h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch) 85 } 86 } 87 88 // receive results from the go routines 89 var linuxIfs []*InterfaceDetails 90 for idx := 0; idx < goRoutinesCnt; idx++ { 91 retrieved := <-ch 92 if retrieved.err != nil { 93 return nil, retrieved.err 94 } 95 linuxIfs = append(linuxIfs, retrieved.interfaces...) 96 } 97 return linuxIfs, nil 98 } 99 100 // DumpInterfaceStatsFromNamespaces requires context in form of the namespace list of which linux interface stats 101 // will be retrieved. If no context is provided, interface stats only from the default namespace interfaces 102 // are retrieved. 103 func (h *NetLinkHandler) DumpInterfaceStatsFromNamespaces(nsList []*namespaces.NetNamespace) ([]*InterfaceStatistics, error) { 104 // Always retrieve from the default namespace 105 if len(nsList) == 0 { 106 nsList = []*namespaces.NetNamespace{nil} 107 } 108 // Determine the number of go routines to invoke 109 goRoutinesCnt := len(nsList) / minWorkForGoRoutine 110 if goRoutinesCnt == 0 { 111 goRoutinesCnt = 1 112 } 113 if goRoutinesCnt > h.goRoutineCount { 114 goRoutinesCnt = h.goRoutineCount 115 } 116 ch := make(chan retrievedInterfaces, goRoutinesCnt) 117 118 // Invoke multiple go routines for more efficient parallel interface retrieval 119 for idx := 0; idx < goRoutinesCnt; idx++ { 120 if goRoutinesCnt > 1 { 121 go h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch) 122 } else { 123 h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch) 124 } 125 } 126 127 // receive results from the go routines 128 var linuxStats []*InterfaceStatistics 129 for idx := 0; idx < goRoutinesCnt; idx++ { 130 retrieved := <-ch 131 if retrieved.err != nil { 132 return nil, retrieved.err 133 } 134 linuxStats = append(linuxStats, retrieved.stats...) 135 } 136 return linuxStats, nil 137 } 138 139 // Obtain all linux namespaces known to the Linux plugin 140 func (h *NetLinkHandler) getKnownNamespaces() []*namespaces.NetNamespace { 141 // Add default namespace 142 nsList := []*namespaces.NetNamespace{nil} 143 for _, ifName := range h.ifIndexes.ListAllInterfaces() { 144 if metadata, exists := h.ifIndexes.LookupByName(ifName); exists { 145 if metadata == nil { 146 h.log.Warnf("metadata for %s are nil", ifName) 147 continue 148 } 149 nsListed := false 150 for _, ns := range nsList { 151 if proto.Equal(ns, metadata.Namespace) { 152 nsListed = true 153 break 154 } 155 } 156 if !nsListed { 157 nsList = append(nsList, metadata.Namespace) 158 } 159 } 160 } 161 return nsList 162 } 163 164 // GetVethAlias returns alias for Linux VETH interface managed by the agent. 165 // The alias stores the VETH logical name together with the peer (logical) name. 166 func GetVethAlias(vethName, peerName string) string { 167 return vethName + "/" + peerName 168 } 169 170 // ParseVethAlias parses out VETH logical name together with the peer name from the alias. 171 func ParseVethAlias(alias string) (vethName, peerName string) { 172 aliasParts := strings.Split(alias, "/") 173 vethName = aliasParts[0] 174 if len(aliasParts) > 1 { 175 peerName = aliasParts[1] 176 } 177 return 178 } 179 180 // GetTapAlias returns alias for Linux TAP interface managed by the agent. 181 // The alias stores the TAP_TO_VPP logical name together with VPP-TAP logical name 182 // and the host interface name as originally set by VPP side. 183 func GetTapAlias(linuxIf *interfaces.Interface, origHostIfName string) string { 184 return linuxIf.Name + "/" + linuxIf.GetTap().GetVppTapIfName() + "/" + origHostIfName 185 } 186 187 // ParseTapAlias parses out TAP_TO_VPP logical name together with the name of the 188 // linked VPP-TAP and the original TAP host interface name. 189 func ParseTapAlias(alias string) (linuxTapName, vppTapName, origHostIfName string) { 190 aliasParts := strings.Split(alias, "/") 191 linuxTapName = aliasParts[0] 192 if len(aliasParts) > 1 { 193 vppTapName = aliasParts[1] 194 } 195 if len(aliasParts) > 2 { 196 origHostIfName = aliasParts[2] 197 } 198 return 199 } 200 201 // GetDummyIfAlias returns alias for Linux Dummy interface managed by the agent. 202 func GetDummyIfAlias(linuxIf *interfaces.Interface) string { 203 return linuxIf.Name 204 } 205 206 // ParseDummyIfAlias parses out logical name of a Dummy interface from the alias. 207 // Currently there are no other logical information stored in the alias so it is very straightforward. 208 func ParseDummyIfAlias(alias string) (ifName string) { 209 return alias 210 } 211 212 // GetVRFAlias returns alias for Linux VRF devices managed by the agent. 213 func GetVRFAlias(linuxIf *interfaces.Interface) string { 214 return linuxIf.Name 215 } 216 217 // ParseVRFAlias parses out logical name of a VRF devices from the alias. 218 // Currently there are no other logical information stored in the alias so it is very straightforward. 219 func ParseVRFAlias(alias string) (vrfName string) { 220 return alias 221 } 222 223 // retrieveInterfaces is run by a separate go routine to retrieve all interfaces 224 // present in every <goRoutineIdx>-th network namespace from the list. 225 func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, goRoutineIdx, goRoutinesCnt int, ch chan<- retrievedInterfaces) { 226 var retrieved retrievedInterfaces 227 228 nsCtx := linuxcalls.NewNamespaceMgmtCtx() 229 for i := goRoutineIdx; i < len(nsList); i += goRoutinesCnt { 230 nsRef := nsList[i] 231 // switch to the namespace 232 revert, err := h.nsPlugin.SwitchToNamespace(nsCtx, nsRef) 233 if err != nil { 234 h.log.WithField("namespace", nsRef).Warn("Failed to switch namespace:", err) 235 continue // continue with the next namespace 236 } 237 238 // get all links in the namespace 239 links, err := h.GetLinkList() 240 if err != nil { 241 h.log.Error("Failed to get link list:", err) 242 // switch back to the default namespace before returning error 243 revert() 244 retrieved.err = err 245 break 246 } 247 248 // retrieve every interface managed by this agent 249 var ifaces []*InterfaceDetails 250 vrfDevs := make(map[int]string) // vrf index -> vrf name 251 for _, link := range links { 252 iface := &interfaces.Interface{ 253 Namespace: nsRef, 254 HostIfName: link.Attrs().Name, 255 PhysAddress: link.Attrs().HardwareAddr.String(), 256 Mtu: uint32(link.Attrs().MTU), 257 } 258 259 alias := link.Attrs().Alias 260 if !strings.HasPrefix(alias, h.agentPrefix) { 261 // skip interface not configured by this agent 262 continue 263 } 264 alias = strings.TrimPrefix(alias, h.agentPrefix) 265 266 // parse alias to obtain logical references 267 if link.Type() == "veth" { 268 iface.Type = interfaces.Interface_VETH 269 var vethPeerIfName string 270 iface.Name, vethPeerIfName = ParseVethAlias(alias) 271 iface.Link = &interfaces.Interface_Veth{ 272 Veth: &interfaces.VethLink{ 273 PeerIfName: vethPeerIfName, 274 }, 275 } 276 } else if link.Type() == "dummy" { 277 iface.Type = interfaces.Interface_DUMMY 278 iface.Name = ParseDummyIfAlias(alias) 279 } else if link.Type() == "tuntap" || link.Type() == "tun" /* not defined in vishvananda */ { 280 iface.Type = interfaces.Interface_TAP_TO_VPP 281 var vppTapIfName string 282 iface.Name, vppTapIfName, _ = ParseTapAlias(alias) 283 iface.Link = &interfaces.Interface_Tap{ 284 Tap: &interfaces.TapLink{ 285 VppTapIfName: vppTapIfName, 286 }, 287 } 288 } else if link.Type() == "vrf" { 289 vrfDev, isVrf := link.(*netlink.Vrf) 290 if !isVrf { 291 h.log.WithFields(logging.Fields{ 292 "if-host-name": link.Attrs().Name, 293 "namespace": nsRef, 294 }).Warnf("Unable to retrieve VRF-specific attributes") 295 continue 296 } 297 iface.Type = interfaces.Interface_VRF_DEVICE 298 iface.Name = ParseVRFAlias(alias) 299 iface.Link = &interfaces.Interface_VrfDev{ 300 VrfDev: &interfaces.VrfDevLink{ 301 RoutingTable: vrfDev.Table, 302 }, 303 } 304 vrfDevs[link.Attrs().Index] = iface.Name 305 } else if link.Attrs().Name == DefaultLoopbackName { 306 iface.Type = interfaces.Interface_LOOPBACK 307 iface.Name = alias 308 } else { 309 // unsupported interface type supposedly configured by agent => print warning 310 h.log.WithFields(logging.Fields{ 311 "if-host-name": link.Attrs().Name, 312 "namespace": nsRef, 313 }).Warnf("Managed interface of unsupported type: %s", link.Type()) 314 continue 315 } 316 317 // skip interfaces with invalid aliases 318 if iface.Name == "" { 319 continue 320 } 321 322 // retrieve addresses, MTU, etc. 323 h.retrieveLinkDetails(link, iface, nsRef) 324 325 // build interface details 326 ifaces = append(ifaces, &InterfaceDetails{ 327 Interface: iface, 328 Meta: &InterfaceMeta{ 329 LinuxIfIndex: link.Attrs().Index, 330 ParentIndex: link.Attrs().ParentIndex, 331 MasterIndex: link.Attrs().MasterIndex, 332 OperState: uint8(link.Attrs().OperState), 333 Flags: link.Attrs().RawFlags, 334 Encapsulation: link.Attrs().EncapType, 335 NumRxQueues: link.Attrs().NumRxQueues, 336 NumTxQueues: link.Attrs().NumTxQueues, 337 TxQueueLen: link.Attrs().TxQLen, 338 }, 339 }) 340 341 // build interface statistics 342 retrieved.stats = append(retrieved.stats, &InterfaceStatistics{ 343 Name: iface.Name, 344 Type: iface.Type, 345 LinuxIfIndex: link.Attrs().Index, 346 RxPackets: link.Attrs().Statistics.RxPackets, 347 TxPackets: link.Attrs().Statistics.TxPackets, 348 RxBytes: link.Attrs().Statistics.RxBytes, 349 TxBytes: link.Attrs().Statistics.TxBytes, 350 RxErrors: link.Attrs().Statistics.RxErrors, 351 TxErrors: link.Attrs().Statistics.TxErrors, 352 RxDropped: link.Attrs().Statistics.TxDropped, 353 TxDropped: link.Attrs().Statistics.RxDropped, 354 }) 355 } 356 357 // fill VRF names 358 for _, iface := range ifaces { 359 if vrfDev, inVrf := vrfDevs[iface.Meta.MasterIndex]; inVrf { 360 iface.Interface.VrfMasterInterface = vrfDev 361 } 362 } 363 retrieved.interfaces = append(retrieved.interfaces, ifaces...) 364 365 // switch back to the default namespace 366 revert() 367 } 368 369 ch <- retrieved 370 } 371 372 // retrieveLinkDetails retrieves link details common to all interface types (e.g. addresses). 373 func (h *NetLinkHandler) retrieveLinkDetails(link netlink.Link, iface *interfaces.Interface, nsRef *namespaces.NetNamespace) { 374 var err error 375 // read interface status 376 iface.Enabled, err = h.IsInterfaceUp(link.Attrs().Name) 377 if err != nil { 378 h.log.WithFields(logging.Fields{ 379 "if-host-name": link.Attrs().Name, 380 "namespace": nsRef, 381 }).Warn("Failed to read interface status:", err) 382 } 383 384 // read assigned IP addresses 385 addressList, err := h.GetAddressList(link.Attrs().Name) 386 if err != nil { 387 h.log.WithFields(logging.Fields{ 388 "if-host-name": link.Attrs().Name, 389 "namespace": nsRef, 390 }).Warn("Failed to read address list:", err) 391 } 392 for _, address := range addressList { 393 if address.Scope == unix.RT_SCOPE_LINK { 394 // ignore link-local IPv6 addresses 395 continue 396 } 397 mask, _ := address.Mask.Size() 398 addrStr := address.IP.String() + "/" + strconv.Itoa(mask) 399 iface.IpAddresses = append(iface.IpAddresses, addrStr) 400 } 401 402 // read checksum offloading 403 if iface.Type == interfaces.Interface_VETH { 404 rxOn, txOn, err := h.GetChecksumOffloading(link.Attrs().Name) 405 if err != nil { 406 h.log.WithFields(logging.Fields{ 407 "if-host-name": link.Attrs().Name, 408 "namespace": nsRef, 409 }).Warn("Failed to read checksum offloading:", err) 410 } else { 411 if !rxOn { 412 iface.GetVeth().RxChecksumOffloading = interfaces.VethLink_CHKSM_OFFLOAD_DISABLED 413 } 414 if !txOn { 415 iface.GetVeth().TxChecksumOffloading = interfaces.VethLink_CHKSM_OFFLOAD_DISABLED 416 } 417 } 418 } 419 }