go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/ifplugin/vppcalls/vpp2106/dump_interface_vppcalls.go (about) 1 // Copyright (c) 2021 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 vpp2106 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/hex" 21 "fmt" 22 "net" 23 "strings" 24 25 vpp_bond "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/bond" 26 vpp_dhcp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/dhcp" 27 vpp_gre "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/gre" 28 vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/interface" 29 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/interface_types" 30 vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/ip" 31 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/ip_types" 32 vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/ipsec" 33 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/ipsec_types" 34 vpp_memif "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/memif" 35 vpp_tapv2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/tapv2" 36 vpp_vxlan "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/vxlan" 37 vpp_vxlangpe "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2106/vxlan_gpe" 38 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" 39 ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 40 ipsec "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipsec" 41 ) 42 43 const ( 44 // allInterfaces defines unspecified interface index 45 allInterfaces = ^uint32(0) 46 47 // prefix prepended to internal names of untagged interfaces to construct unique 48 // logical names 49 untaggedIfPreffix = "UNTAGGED-" 50 ) 51 52 const ( 53 // Default VPP MTU value 54 defaultVPPMtu = 9216 55 56 // MAC length 57 macLength = 6 58 ) 59 60 func getMtu(vppMtu uint16) uint32 { 61 // If default VPP MTU value is set, return 0 (it means MTU was not set in the NB config) 62 if vppMtu == defaultVPPMtu { 63 return 0 64 } 65 return uint32(vppMtu) 66 } 67 68 func (h *InterfaceVppHandler) DumpInterfacesByType(ctx context.Context, reqType ifs.Interface_Type) (map[uint32]*vppcalls.InterfaceDetails, error) { 69 // Dump all 70 ifs, err := h.DumpInterfaces(ctx) 71 if err != nil { 72 return nil, err 73 } 74 // Filter by type 75 for ifIdx, ifData := range ifs { 76 if ifData.Interface.Type != reqType { 77 delete(ifs, ifIdx) 78 } 79 } 80 81 return ifs, nil 82 } 83 84 func (h *InterfaceVppHandler) dumpInterfaces(ifIdxs ...uint32) (map[uint32]*vppcalls.InterfaceDetails, error) { 85 // map for the resulting interfaces 86 interfaces := make(map[uint32]*vppcalls.InterfaceDetails) 87 88 ifIdx := allInterfaces 89 if len(ifIdxs) > 0 { 90 ifIdx = ifIdxs[0] 91 } 92 // First, dump all interfaces to create initial data. 93 reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceDump{ 94 SwIfIndex: interface_types.InterfaceIndex(ifIdx), 95 }) 96 for { 97 ifDetails := &vpp_ifs.SwInterfaceDetails{} 98 stop, err := reqCtx.ReceiveReply(ifDetails) 99 if stop { 100 break 101 } 102 if err != nil { 103 return nil, fmt.Errorf("failed to dump interface: %v", err) 104 } 105 106 internalName := strings.TrimRight(ifDetails.InterfaceName, "\x00") 107 ifaceDevType := strings.TrimRight(ifDetails.InterfaceDevType, "\x00") 108 physAddr := make(net.HardwareAddr, macLength) 109 copy(physAddr, ifDetails.L2Address[:]) 110 111 details := &vppcalls.InterfaceDetails{ 112 Interface: &ifs.Interface{ 113 Name: strings.TrimRight(ifDetails.Tag, "\x00"), 114 // the type may be amended later by further dumps 115 Type: guessInterfaceType(ifaceDevType, internalName), 116 Enabled: isAdminStateUp(ifDetails.Flags), 117 PhysAddress: net.HardwareAddr(ifDetails.L2Address[:]).String(), 118 Mtu: getMtu(ifDetails.LinkMtu), 119 }, 120 Meta: &vppcalls.InterfaceMeta{ 121 SwIfIndex: uint32(ifDetails.SwIfIndex), 122 SupSwIfIndex: ifDetails.SupSwIfIndex, 123 L2Address: physAddr, 124 InternalName: internalName, 125 DevType: ifaceDevType, 126 IsAdminStateUp: isAdminStateUp(ifDetails.Flags), 127 IsLinkStateUp: isLinkStateUp(ifDetails.Flags), 128 LinkDuplex: uint32(ifDetails.LinkDuplex), 129 LinkMTU: ifDetails.LinkMtu, 130 MTU: ifDetails.Mtu, 131 LinkSpeed: ifDetails.LinkSpeed, 132 SubID: ifDetails.SubID, 133 Tag: strings.TrimRight(ifDetails.Tag, "\x00"), 134 }, 135 } 136 137 // sub interface 138 if ifDetails.SupSwIfIndex != uint32(ifDetails.SwIfIndex) { 139 details.Interface.Type = ifs.Interface_SUB_INTERFACE 140 details.Interface.Link = &ifs.Interface_Sub{ 141 Sub: &ifs.SubInterface{ 142 ParentName: interfaces[ifDetails.SupSwIfIndex].Interface.Name, 143 SubId: ifDetails.SubID, 144 TagRwOption: getTagRwOption(ifDetails.VtrOp), 145 PushDot1Q: uintToBool(uint8(ifDetails.VtrPushDot1q)), 146 Tag1: ifDetails.VtrTag1, 147 Tag2: ifDetails.VtrTag2, 148 }, 149 } 150 } 151 // Fill name for physical interfaces (they are mostly without tag) 152 switch details.Interface.Type { 153 case ifs.Interface_DPDK: 154 details.Interface.Name = internalName 155 case ifs.Interface_AF_PACKET: 156 details.Interface.Link = &ifs.Interface_Afpacket{ 157 Afpacket: &ifs.AfpacketLink{ 158 HostIfName: strings.TrimPrefix(internalName, "host-"), 159 }, 160 } 161 } 162 if details.Interface.Name == "" { 163 // untagged interface - generate a logical name for it 164 // (apart from local0 it will get removed by resync) 165 details.Interface.Name = untaggedIfPreffix + internalName 166 } 167 interfaces[uint32(ifDetails.SwIfIndex)] = details 168 } 169 170 return interfaces, nil 171 } 172 173 func (h *InterfaceVppHandler) DumpInterfaces(ctx context.Context) (map[uint32]*vppcalls.InterfaceDetails, error) { 174 // Dump all interfaces 175 interfaces, err := h.dumpInterfaces() 176 if err != nil { 177 return nil, err 178 } 179 180 // Get DHCP clients 181 dhcpClients, err := h.DumpDhcpClients() 182 if err != nil { 183 return nil, fmt.Errorf("failed to dump interface DHCP clients: %v", err) 184 } 185 186 // Get IP addresses before VRF 187 err = h.dumpIPAddressDetails(interfaces, false, dhcpClients) 188 if err != nil { 189 return nil, err 190 } 191 err = h.dumpIPAddressDetails(interfaces, true, dhcpClients) 192 if err != nil { 193 return nil, err 194 } 195 196 // Get unnumbered interfaces 197 unnumbered, err := h.dumpUnnumberedDetails() 198 if err != nil { 199 return nil, fmt.Errorf("failed to dump unnumbered interfaces: %v", err) 200 } 201 202 // dump VXLAN details before VRFs (used by isIpv6Interface) 203 err = h.dumpVxlanDetails(interfaces) 204 if err != nil { 205 return nil, err 206 } 207 208 err = h.dumpVxLanGpeDetails(interfaces) 209 if err != nil { 210 return nil, err 211 } 212 213 // Get interface VRF for every IP family, fill DHCP if set and resolve unnumbered interface setup 214 for _, ifData := range interfaces { 215 // VRF is stored in metadata for both, IPv4 and IPv6. If the interface is an IPv6 interface (it contains at least 216 // one IPv6 address), appropriate VRF is stored also in modelled data 217 ipv4Vrf, err := h.GetInterfaceVrf(ifData.Meta.SwIfIndex) 218 if err != nil { 219 return nil, fmt.Errorf("interface dump: failed to get IPv4 VRF from interface %d: %v", 220 ifData.Meta.SwIfIndex, err) 221 } 222 ifData.Meta.VrfIPv4 = ipv4Vrf 223 ipv6Vrf, err := h.GetInterfaceVrfIPv6(ifData.Meta.SwIfIndex) 224 if err != nil { 225 return nil, fmt.Errorf("interface dump: failed to get IPv6 VRF from interface %d: %v", 226 ifData.Meta.SwIfIndex, err) 227 } 228 ifData.Meta.VrfIPv6 = ipv6Vrf 229 if isIPv6If, err := isIpv6Interface(ifData.Interface); err != nil { 230 return interfaces, err 231 } else if isIPv6If { 232 ifData.Interface.Vrf = ipv6Vrf 233 } else { 234 ifData.Interface.Vrf = ipv4Vrf 235 } 236 237 // DHCP 238 dhcpData, ok := dhcpClients[ifData.Meta.SwIfIndex] 239 if ok { 240 ifData.Interface.SetDhcpClient = true 241 ifData.Meta.Dhcp = dhcpData 242 } 243 // Unnumbered 244 ifWithIPIdx, ok := unnumbered[ifData.Meta.SwIfIndex] 245 if ok { 246 // Find unnumbered interface 247 var ifWithIPName string 248 ifWithIP, ok := interfaces[ifWithIPIdx] 249 if ok { 250 ifWithIPName = ifWithIP.Interface.Name 251 } else { 252 h.log.Debugf("cannot find name of the ip-interface for unnumbered %s", ifData.Interface.Name) 253 ifWithIPName = "<unknown>" 254 } 255 ifData.Interface.Unnumbered = &ifs.Interface_Unnumbered{ 256 InterfaceWithIp: ifWithIPName, 257 } 258 } 259 } 260 261 err = h.dumpMemifDetails(ctx, interfaces) 262 if err != nil { 263 return nil, err 264 } 265 266 err = h.dumpTapDetails(interfaces) 267 if err != nil { 268 return nil, err 269 } 270 271 err = h.dumpIPSecTunnelDetails(interfaces) 272 if err != nil { 273 return nil, err 274 } 275 276 err = h.dumpVmxNet3Details(interfaces) 277 if err != nil { 278 return nil, err 279 } 280 281 err = h.dumpBondDetails(interfaces) 282 if err != nil { 283 return nil, err 284 } 285 286 err = h.dumpGreDetails(interfaces) 287 if err != nil { 288 return nil, err 289 } 290 291 err = h.dumpGtpuDetails(interfaces) 292 if err != nil { 293 return nil, err 294 } 295 296 err = h.dumpIpipDetails(interfaces) 297 if err != nil { 298 return nil, err 299 } 300 301 err = h.dumpWireguardDetails(interfaces) 302 if err != nil { 303 return nil, err 304 } 305 306 // Rx-placement dump is last since it uses interface type-specific data 307 err = h.dumpRxPlacement(interfaces) 308 if err != nil { 309 return nil, err 310 } 311 312 return interfaces, nil 313 } 314 315 // DumpDhcpClients returns a slice of DhcpMeta with all interfaces and other DHCP-related information available 316 func (h *InterfaceVppHandler) DumpDhcpClients() (map[uint32]*vppcalls.Dhcp, error) { 317 dhcpData := make(map[uint32]*vppcalls.Dhcp) 318 reqCtx := h.callsChannel.SendMultiRequest(&vpp_dhcp.DHCPClientDump{}) 319 320 for { 321 dhcpDetails := &vpp_dhcp.DHCPClientDetails{} 322 last, err := reqCtx.ReceiveReply(dhcpDetails) 323 if last { 324 break 325 } 326 if err != nil { 327 return nil, err 328 } 329 client := dhcpDetails.Client 330 lease := dhcpDetails.Lease 331 332 // DHCP client data 333 dhcpClient := &vppcalls.Client{ 334 SwIfIndex: uint32(client.SwIfIndex), 335 Hostname: strings.TrimRight(client.Hostname, "\x00"), 336 ID: string(bytes.SplitN(client.ID, []byte{0x00}, 2)[0]), 337 WantDhcpEvent: client.WantDHCPEvent, 338 SetBroadcastFlag: client.SetBroadcastFlag, 339 PID: client.PID, 340 } 341 342 // DHCP lease data 343 dhcpLease := &vppcalls.Lease{ 344 SwIfIndex: uint32(lease.SwIfIndex), 345 State: uint8(lease.State), 346 Hostname: strings.TrimRight(lease.Hostname, "\x00"), 347 IsIPv6: lease.IsIPv6, 348 HostAddress: dhcpAddressToString(lease.HostAddress, uint32(lease.MaskWidth), lease.IsIPv6), 349 RouterAddress: dhcpAddressToString(lease.RouterAddress, uint32(lease.MaskWidth), lease.IsIPv6), 350 HostMac: net.HardwareAddr(lease.HostMac[:]).String(), 351 } 352 353 // DHCP metadata 354 dhcpData[uint32(client.SwIfIndex)] = &vppcalls.Dhcp{ 355 Client: dhcpClient, 356 Lease: dhcpLease, 357 } 358 } 359 360 return dhcpData, nil 361 } 362 363 // DumpInterfaceStates dumps link and administrative state of every interface. 364 func (h *InterfaceVppHandler) DumpInterfaceStates(ifIdxs ...uint32) (map[uint32]*vppcalls.InterfaceState, error) { 365 // Dump all interface states if not specified. 366 if len(ifIdxs) == 0 { 367 ifIdxs = []uint32{allInterfaces} 368 } 369 370 ifStates := make(map[uint32]*vppcalls.InterfaceState) 371 for _, ifIdx := range ifIdxs { 372 reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceDump{ 373 SwIfIndex: interface_types.InterfaceIndex(ifIdx), 374 }) 375 for { 376 ifDetails := &vpp_ifs.SwInterfaceDetails{} 377 stop, err := reqCtx.ReceiveReply(ifDetails) 378 if stop { 379 break // Break from the loop. 380 } 381 if err != nil { 382 return nil, fmt.Errorf("failed to dump interface states: %v", err) 383 } 384 385 physAddr := make(net.HardwareAddr, macLength) 386 copy(physAddr, ifDetails.L2Address[:]) 387 388 ifaceState := vppcalls.InterfaceState{ 389 SwIfIndex: uint32(ifDetails.SwIfIndex), 390 InternalName: strings.TrimRight(ifDetails.InterfaceName, "\x00"), 391 PhysAddress: physAddr, 392 AdminState: adminStateToInterfaceStatus(ifDetails.Flags), 393 LinkState: linkStateToInterfaceStatus(ifDetails.Flags), 394 LinkDuplex: toLinkDuplex(ifDetails.LinkDuplex), 395 LinkSpeed: toLinkSpeed(ifDetails.LinkSpeed), 396 LinkMTU: ifDetails.LinkMtu, 397 } 398 ifStates[uint32(ifDetails.SwIfIndex)] = &ifaceState 399 } 400 } 401 402 return ifStates, nil 403 } 404 405 func toLinkDuplex(duplex interface_types.LinkDuplex) ifs.InterfaceState_Duplex { 406 switch duplex { 407 case 1: 408 return ifs.InterfaceState_HALF 409 case 2: 410 return ifs.InterfaceState_FULL 411 default: 412 return ifs.InterfaceState_UNKNOWN_DUPLEX 413 } 414 } 415 416 const megabit = 1000000 // one megabit in bytes 417 418 func toLinkSpeed(speed uint32) uint64 { 419 switch speed { 420 case 1: 421 return 10 * megabit // 10M 422 case 2: 423 return 100 * megabit // 100M 424 case 4: 425 return 1000 * megabit // 1G 426 case 8: 427 return 10000 * megabit // 10G 428 case 16: 429 return 40000 * megabit // 40G 430 case 32: 431 return 100000 * megabit // 100G 432 default: 433 return 0 434 } 435 } 436 437 // Returns true if given interface contains at least one IPv6 address. For VxLAN, source and destination 438 // addresses are also checked 439 func isIpv6Interface(iface *ifs.Interface) (bool, error) { 440 if iface.Type == ifs.Interface_VXLAN_TUNNEL && iface.GetVxlan() != nil { 441 if ipAddress := net.ParseIP(iface.GetVxlan().SrcAddress); ipAddress.To4() == nil { 442 return true, nil 443 } 444 if ipAddress := net.ParseIP(iface.GetVxlan().DstAddress); ipAddress.To4() == nil { 445 return true, nil 446 } 447 } 448 for _, ifAddress := range iface.IpAddresses { 449 if ipAddress, _, err := net.ParseCIDR(ifAddress); err != nil { 450 return false, err 451 } else if ipAddress.To4() == nil { 452 return true, nil 453 } 454 } 455 return false, nil 456 } 457 458 // dumpIPAddressDetails dumps IP address details of interfaces from VPP and fills them into the provided interface map. 459 func (h *InterfaceVppHandler) dumpIPAddressDetails(ifs map[uint32]*vppcalls.InterfaceDetails, isIPv6 bool, dhcpClients map[uint32]*vppcalls.Dhcp) error { 460 // Dump IP addresses of each interface. 461 for idx := range ifs { 462 reqCtx := h.callsChannel.SendMultiRequest(&vpp_ip.IPAddressDump{ 463 SwIfIndex: interface_types.InterfaceIndex(idx), 464 IsIPv6: isIPv6, 465 }) 466 for { 467 ipDetails := &vpp_ip.IPAddressDetails{} 468 stop, err := reqCtx.ReceiveReply(ipDetails) 469 if stop { 470 break // Break from the loop. 471 } 472 if err != nil { 473 return fmt.Errorf("failed to dump interface %d IP address details: %v", idx, err) 474 } 475 h.processIPDetails(ifs, ipDetails, dhcpClients) 476 } 477 } 478 479 return nil 480 } 481 482 // processIPDetails processes ip.IPAddressDetails binary API message and fills the details into the provided interface map. 483 func (h *InterfaceVppHandler) processIPDetails(ifs map[uint32]*vppcalls.InterfaceDetails, ipDetails *vpp_ip.IPAddressDetails, dhcpClients map[uint32]*vppcalls.Dhcp) { 484 ifDetails, ifIdxExists := ifs[uint32(ipDetails.SwIfIndex)] 485 if !ifIdxExists { 486 return 487 } 488 489 var ipAddr string 490 ipByte := make([]byte, 16) 491 copy(ipByte[:], ipDetails.Prefix.Address.Un.XXX_UnionData[:]) 492 if ipDetails.Prefix.Address.Af == ip_types.ADDRESS_IP6 { 493 ipAddr = fmt.Sprintf("%s/%d", net.IP(ipByte).To16().String(), uint32(ipDetails.Prefix.Len)) 494 } else { 495 ipAddr = fmt.Sprintf("%s/%d", net.IP(ipByte[:4]).To4().String(), uint32(ipDetails.Prefix.Len)) 496 } 497 498 // skip IP addresses given by DHCP 499 if dhcpClient, hasDhcpClient := dhcpClients[uint32(ipDetails.SwIfIndex)]; hasDhcpClient { 500 if dhcpClient.Lease != nil && dhcpClient.Lease.HostAddress == ipAddr { 501 return 502 } 503 } 504 505 ifDetails.Interface.IpAddresses = append(ifDetails.Interface.IpAddresses, ipAddr) 506 } 507 508 // dumpTapDetails dumps tap interface details from VPP and fills them into the provided interface map. 509 func (h *InterfaceVppHandler) dumpTapDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 510 // Original TAP v1 was DEPRECATED 511 512 // TAP v2 513 reqCtx := h.callsChannel.SendMultiRequest(&vpp_tapv2.SwInterfaceTapV2Dump{ 514 SwIfIndex: ^interface_types.InterfaceIndex(0), 515 }) 516 for { 517 tapDetails := &vpp_tapv2.SwInterfaceTapV2Details{} 518 stop, err := reqCtx.ReceiveReply(tapDetails) 519 if stop { 520 break // Break from the loop. 521 } 522 if err != nil { 523 return fmt.Errorf("failed to dump TAPv2 interface details: %v", err) 524 } 525 _, ifIdxExists := interfaces[tapDetails.SwIfIndex] 526 if !ifIdxExists { 527 continue 528 } 529 interfaces[tapDetails.SwIfIndex].Interface.Link = &ifs.Interface_Tap{ 530 Tap: &ifs.TapLink{ 531 Version: 2, 532 HostIfName: cleanString(tapDetails.HostIfName), 533 RxRingSize: uint32(tapDetails.RxRingSz), 534 TxRingSize: uint32(tapDetails.TxRingSz), 535 EnableGso: tapDetails.TapFlags&vpp_tapv2.TAP_API_FLAG_GSO == vpp_tapv2.TAP_API_FLAG_GSO, 536 EnableTunnel: tapDetails.TapFlags&vpp_tapv2.TAP_API_FLAG_TUN == vpp_tapv2.TAP_API_FLAG_TUN, 537 }, 538 } 539 interfaces[tapDetails.SwIfIndex].Interface.Type = ifs.Interface_TAP 540 } 541 542 return nil 543 } 544 545 // dumpVxlanDetails dumps VXLAN interface details from VPP and fills them into the provided interface map. 546 func (h *InterfaceVppHandler) dumpVxlanDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 547 reqCtx := h.callsChannel.SendMultiRequest(&vpp_vxlan.VxlanTunnelDump{ 548 SwIfIndex: ^interface_types.InterfaceIndex(0), 549 }) 550 for { 551 vxlanDetails := &vpp_vxlan.VxlanTunnelDetails{} 552 stop, err := reqCtx.ReceiveReply(vxlanDetails) 553 if stop { 554 break // Break from the loop. 555 } 556 if err != nil { 557 return fmt.Errorf("failed to dump VxLAN tunnel interface details: %v", err) 558 } 559 _, ifIdxExists := interfaces[uint32(vxlanDetails.SwIfIndex)] 560 if !ifIdxExists { 561 continue 562 } 563 // Multicast interface 564 var multicastIfName string 565 _, exists := interfaces[uint32(vxlanDetails.McastSwIfIndex)] 566 if exists { 567 multicastIfName = interfaces[uint32(vxlanDetails.McastSwIfIndex)].Interface.Name 568 } 569 570 if vxlanDetails.SrcAddress.Af == ip_types.ADDRESS_IP6 { 571 srcIP := vxlanDetails.SrcAddress.Un.GetIP6() 572 dstIP := vxlanDetails.DstAddress.Un.GetIP6() 573 interfaces[uint32(vxlanDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Vxlan{ 574 Vxlan: &ifs.VxlanLink{ 575 Multicast: multicastIfName, 576 SrcAddress: net.IP(srcIP[:]).To16().String(), 577 DstAddress: net.IP(dstIP[:]).To16().String(), 578 Vni: vxlanDetails.Vni, 579 }, 580 } 581 } else { 582 srcIP := vxlanDetails.SrcAddress.Un.GetIP4() 583 dstIP := vxlanDetails.DstAddress.Un.GetIP4() 584 interfaces[uint32(vxlanDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Vxlan{ 585 Vxlan: &ifs.VxlanLink{ 586 Multicast: multicastIfName, 587 SrcAddress: net.IP(srcIP[:4]).To4().String(), 588 DstAddress: net.IP(dstIP[:4]).To4().String(), 589 Vni: vxlanDetails.Vni, 590 }, 591 } 592 } 593 interfaces[uint32(vxlanDetails.SwIfIndex)].Interface.Type = ifs.Interface_VXLAN_TUNNEL 594 } 595 596 return nil 597 } 598 599 // dumpVxlanDetails dumps VXLAN-GPE interface details from VPP and fills them into the provided interface map. 600 func (h *InterfaceVppHandler) dumpVxLanGpeDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 601 reqCtx := h.callsChannel.SendMultiRequest(&vpp_vxlangpe.VxlanGpeTunnelDump{SwIfIndex: ^interface_types.InterfaceIndex(0)}) 602 for { 603 vxlanGpeDetails := &vpp_vxlangpe.VxlanGpeTunnelDetails{} 604 stop, err := reqCtx.ReceiveReply(vxlanGpeDetails) 605 if stop { 606 break // Break from the loop. 607 } 608 if err != nil { 609 return fmt.Errorf("failed to dump VxLAN-GPE tunnel interface details: %v", err) 610 } 611 _, ifIdxExists := interfaces[uint32(vxlanGpeDetails.SwIfIndex)] 612 if !ifIdxExists { 613 continue 614 } 615 // Multicast interface 616 var multicastIfName string 617 _, exists := interfaces[uint32(vxlanGpeDetails.McastSwIfIndex)] 618 if exists { 619 multicastIfName = interfaces[uint32(vxlanGpeDetails.McastSwIfIndex)].Interface.Name 620 } 621 622 vxLan := &ifs.VxlanLink{ 623 Multicast: multicastIfName, 624 Vni: vxlanGpeDetails.Vni, 625 Gpe: &ifs.VxlanLink_Gpe{ 626 DecapVrfId: vxlanGpeDetails.DecapVrfID, 627 Protocol: getVxLanGpeProtocol(vxlanGpeDetails.Protocol), 628 }, 629 } 630 631 if vxlanGpeDetails.IsIPv6 { 632 localIP := vxlanGpeDetails.Local.Un.GetIP6() 633 remoteIP := vxlanGpeDetails.Remote.Un.GetIP6() 634 vxLan.SrcAddress = net.IP(localIP[:]).To16().String() 635 vxLan.DstAddress = net.IP(remoteIP[:]).To16().String() 636 } else { 637 localIP := vxlanGpeDetails.Local.Un.GetIP4() 638 remoteIP := vxlanGpeDetails.Remote.Un.GetIP4() 639 vxLan.SrcAddress = net.IP(localIP[:4]).To4().String() 640 vxLan.DstAddress = net.IP(remoteIP[:4]).To4().String() 641 } 642 643 interfaces[uint32(vxlanGpeDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Vxlan{Vxlan: vxLan} 644 interfaces[uint32(vxlanGpeDetails.SwIfIndex)].Interface.Type = ifs.Interface_VXLAN_TUNNEL 645 } 646 647 return nil 648 } 649 650 // dumpIPSecTunnelDetails dumps IPSec tunnel interfaces from the VPP and fills them into the provided interface map. 651 func (h *InterfaceVppHandler) dumpIPSecTunnelDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 652 // tunnel interfaces are a part of security association dump 653 var tunnels []*vpp_ipsec.IpsecSaDetails 654 req := &vpp_ipsec.IpsecSaDump{ 655 SaID: ^uint32(0), 656 } 657 requestCtx := h.callsChannel.SendMultiRequest(req) 658 659 for { 660 saDetails := &vpp_ipsec.IpsecSaDetails{} 661 stop, err := requestCtx.ReceiveReply(saDetails) 662 if stop { 663 break 664 } 665 if err != nil { 666 return err 667 } 668 // skip non-tunnel security associations 669 if saDetails.SwIfIndex != ^interface_types.InterfaceIndex(0) { 670 tunnels = append(tunnels, saDetails) 671 } 672 } 673 674 // every tunnel interface is returned in two API calls. To reconstruct the correct proto-modelled data, 675 // first appearance is cached, and when the second part arrives, data are completed and stored. 676 tunnelParts := make(map[uint32]*vpp_ipsec.IpsecSaDetails) 677 678 for _, tunnel := range tunnels { 679 // first appearance is stored in the map, the second one is used in configuration. 680 firstSaData, ok := tunnelParts[uint32(tunnel.SwIfIndex)] 681 if !ok { 682 tunnelParts[uint32(tunnel.SwIfIndex)] = tunnel 683 continue 684 } 685 686 local := firstSaData 687 remote := tunnel 688 689 // verify data for local & remote 690 if err := verifyIPSecTunnelDetails(local, remote); err != nil { 691 h.log.Warnf("IPSec SA dump for tunnel interface data does not match: %v", err) 692 continue 693 } 694 695 var localIP, remoteIP net.IP 696 if tunnel.Entry.TunnelDst.Af == ip_types.ADDRESS_IP6 { 697 localSrc := local.Entry.TunnelSrc.Un.GetIP6() 698 remoteSrc := remote.Entry.TunnelSrc.Un.GetIP6() 699 localIP, remoteIP = net.IP(localSrc[:]), net.IP(remoteSrc[:]) 700 } else { 701 localSrc := local.Entry.TunnelSrc.Un.GetIP4() 702 remoteSrc := remote.Entry.TunnelSrc.Un.GetIP4() 703 localIP, remoteIP = net.IP(localSrc[:]), net.IP(remoteSrc[:]) 704 } 705 706 ifDetails, ok := interfaces[uint32(tunnel.SwIfIndex)] 707 if !ok { 708 h.log.Warnf("ipsec SA dump returned unrecognized swIfIndex: %v", tunnel.SwIfIndex) 709 continue 710 } 711 ifDetails.Interface.Type = ifs.Interface_IPSEC_TUNNEL 712 ifDetails.Interface.Link = &ifs.Interface_Ipsec{ 713 Ipsec: &ifs.IPSecLink{ 714 Esn: (tunnel.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_USE_ESN) != 0, 715 AntiReplay: (tunnel.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY) != 0, 716 LocalIp: localIP.String(), 717 RemoteIp: remoteIP.String(), 718 LocalSpi: local.Entry.Spi, 719 RemoteSpi: remote.Entry.Spi, 720 CryptoAlg: ipsec.CryptoAlg(tunnel.Entry.CryptoAlgorithm), 721 LocalCryptoKey: hex.EncodeToString(local.Entry.CryptoKey.Data[:local.Entry.CryptoKey.Length]), 722 RemoteCryptoKey: hex.EncodeToString(remote.Entry.CryptoKey.Data[:remote.Entry.CryptoKey.Length]), 723 IntegAlg: ipsec.IntegAlg(tunnel.Entry.IntegrityAlgorithm), 724 LocalIntegKey: hex.EncodeToString(local.Entry.IntegrityKey.Data[:local.Entry.IntegrityKey.Length]), 725 RemoteIntegKey: hex.EncodeToString(remote.Entry.IntegrityKey.Data[:remote.Entry.IntegrityKey.Length]), 726 EnableUdpEncap: (tunnel.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_UDP_ENCAP) != 0, 727 }, 728 } 729 } 730 731 return nil 732 } 733 734 func verifyIPSecTunnelDetails(local, remote *vpp_ipsec.IpsecSaDetails) error { 735 if local.SwIfIndex != remote.SwIfIndex { 736 return fmt.Errorf("swIfIndex data mismatch (local: %v, remote: %v)", 737 local.SwIfIndex, remote.SwIfIndex) 738 } 739 localIsTunnel := local.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL 740 remoteIsTunnel := remote.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL 741 if localIsTunnel != remoteIsTunnel { 742 return fmt.Errorf("tunnel data mismatch (local: %v, remote: %v)", 743 localIsTunnel, remoteIsTunnel) 744 } 745 746 localSrc, localDst := local.Entry.TunnelSrc.Un.XXX_UnionData, local.Entry.TunnelDst.Un.XXX_UnionData 747 remoteSrc, remoteDst := remote.Entry.TunnelSrc.Un.XXX_UnionData, remote.Entry.TunnelDst.Un.XXX_UnionData 748 if (local.Entry.Flags&ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6) != (remote.Entry.Flags&ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6) || 749 !bytes.Equal(localSrc[:], remoteDst[:]) || 750 !bytes.Equal(localDst[:], remoteSrc[:]) { 751 return fmt.Errorf("src/dst IP mismatch (local: %+v, remote: %+v)", 752 local.Entry, remote.Entry) 753 } 754 755 return nil 756 } 757 758 // dumpBondDetails dumps bond interface details from VPP and fills them into the provided interface map. 759 func (h *InterfaceVppHandler) dumpBondDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 760 bondIndexes := make([]uint32, 0) 761 reqCtx := h.callsChannel.SendMultiRequest(&vpp_bond.SwInterfaceBondDump{}) 762 for { 763 bondDetails := &vpp_bond.SwInterfaceBondDetails{} 764 stop, err := reqCtx.ReceiveReply(bondDetails) 765 if err != nil { 766 return fmt.Errorf("failed to dump bond interface details: %v", err) 767 } 768 if stop { 769 break 770 } 771 _, ifIdxExists := interfaces[uint32(bondDetails.SwIfIndex)] 772 if !ifIdxExists { 773 continue 774 } 775 interfaces[uint32(bondDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Bond{ 776 Bond: &ifs.BondLink{ 777 Id: bondDetails.ID, 778 Mode: getBondIfMode(bondDetails.Mode), 779 Lb: getBondLoadBalance(bondDetails.Lb), 780 }, 781 } 782 interfaces[uint32(bondDetails.SwIfIndex)].Interface.Type = ifs.Interface_BOND_INTERFACE 783 bondIndexes = append(bondIndexes, uint32(bondDetails.SwIfIndex)) 784 } 785 786 // get slave interfaces for bonds 787 for _, bondIdx := range bondIndexes { 788 var bondSlaves []*ifs.BondLink_BondedInterface 789 reqSlCtx := h.callsChannel.SendMultiRequest(&vpp_bond.SwInterfaceSlaveDump{ 790 SwIfIndex: interface_types.InterfaceIndex(bondIdx), 791 }) 792 for { 793 slaveDetails := &vpp_bond.SwInterfaceSlaveDetails{} 794 stop, err := reqSlCtx.ReceiveReply(slaveDetails) 795 if err != nil { 796 return fmt.Errorf("failed to dump bond slave details: %v", err) 797 } 798 if stop { 799 break 800 } 801 slaveIf, ifIdxExists := interfaces[uint32(slaveDetails.SwIfIndex)] 802 if !ifIdxExists { 803 continue 804 } 805 bondSlaves = append(bondSlaves, &ifs.BondLink_BondedInterface{ 806 Name: slaveIf.Interface.Name, 807 IsPassive: slaveDetails.IsPassive, 808 IsLongTimeout: slaveDetails.IsLongTimeout, 809 }) 810 interfaces[bondIdx].Interface.GetBond().BondedInterfaces = bondSlaves 811 } 812 } 813 814 return nil 815 } 816 817 func (h *InterfaceVppHandler) dumpGreDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 818 msg := &vpp_gre.GreTunnelDump{SwIfIndex: interface_types.InterfaceIndex(^uint32(0))} 819 reqCtx := h.callsChannel.SendMultiRequest(msg) 820 for { 821 greDetails := &vpp_gre.GreTunnelDetails{} 822 stop, err := reqCtx.ReceiveReply(greDetails) 823 if stop { 824 break 825 } 826 if err != nil { 827 return fmt.Errorf("failed to dump span: %v", err) 828 } 829 830 tunnel := greDetails.Tunnel 831 swIfIndex := uint32(tunnel.SwIfIndex) 832 833 var srcAddr, dstAddr net.IP 834 if tunnel.Src.Af == ip_types.ADDRESS_IP4 { 835 srcAddrArr := tunnel.Src.Un.GetIP4() 836 srcAddr = net.IP(srcAddrArr[:]) 837 } else { 838 srcAddrArr := tunnel.Src.Un.GetIP6() 839 srcAddr = net.IP(srcAddrArr[:]) 840 } 841 if tunnel.Dst.Af == ip_types.ADDRESS_IP4 { 842 dstAddrArr := tunnel.Dst.Un.GetIP4() 843 dstAddr = net.IP(dstAddrArr[:]) 844 } else { 845 dstAddrArr := tunnel.Dst.Un.GetIP6() 846 dstAddr = net.IP(dstAddrArr[:]) 847 } 848 849 interfaces[swIfIndex].Interface.Link = &ifs.Interface_Gre{ 850 Gre: &ifs.GreLink{ 851 TunnelType: getGreTunnelType(tunnel.Type), 852 SrcAddr: srcAddr.String(), 853 DstAddr: dstAddr.String(), 854 OuterFibId: tunnel.OuterTableID, 855 SessionId: uint32(tunnel.SessionID), 856 }, 857 } 858 interfaces[swIfIndex].Interface.Type = ifs.Interface_GRE_TUNNEL 859 } 860 return nil 861 } 862 863 // dumpUnnumberedDetails returns a map of unnumbered interface indexes, every with interface index of element with IP 864 func (h *InterfaceVppHandler) dumpUnnumberedDetails() (map[uint32]uint32, error) { 865 unIfMap := make(map[uint32]uint32) // unnumbered/ip-interface 866 reqCtx := h.callsChannel.SendMultiRequest(&vpp_ip.IPUnnumberedDump{ 867 SwIfIndex: ^interface_types.InterfaceIndex(0), 868 }) 869 870 for { 871 unDetails := &vpp_ip.IPUnnumberedDetails{} 872 last, err := reqCtx.ReceiveReply(unDetails) 873 if last { 874 break 875 } 876 if err != nil { 877 return nil, err 878 } 879 880 unIfMap[uint32(unDetails.SwIfIndex)] = uint32(unDetails.IPSwIfIndex) 881 } 882 883 return unIfMap, nil 884 } 885 886 func (h *InterfaceVppHandler) dumpRxPlacement(interfaces map[uint32]*vppcalls.InterfaceDetails) error { 887 reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceRxPlacementDump{ 888 SwIfIndex: interface_types.InterfaceIndex(^uint32(0)), 889 }) 890 for { 891 rxDetails := &vpp_ifs.SwInterfaceRxPlacementDetails{} 892 stop, err := reqCtx.ReceiveReply(rxDetails) 893 if err != nil { 894 return fmt.Errorf("failed to dump rx-placement details: %v", err) 895 } 896 if stop { 897 break 898 } 899 900 ifData, ok := interfaces[uint32(rxDetails.SwIfIndex)] 901 if !ok { 902 h.log.Warnf("Received rx-placement data for unknown interface with index %d", rxDetails.SwIfIndex) 903 continue 904 } 905 906 ifData.Interface.RxModes = append(ifData.Interface.RxModes, 907 &ifs.Interface_RxMode{ 908 Queue: rxDetails.QueueID, 909 Mode: getRxModeType(rxDetails.Mode), 910 }) 911 912 var worker uint32 913 if rxDetails.WorkerID > 0 { 914 worker = rxDetails.WorkerID - 1 915 } 916 ifData.Interface.RxPlacements = append(ifData.Interface.RxPlacements, 917 &ifs.Interface_RxPlacement{ 918 Queue: rxDetails.QueueID, 919 Worker: worker, 920 MainThread: rxDetails.WorkerID == 0, 921 }) 922 } 923 return nil 924 } 925 926 func dhcpAddressToString(address ip_types.Address, maskWidth uint32, isIPv6 bool) string { 927 dhcpIPByte := make([]byte, 16) 928 copy(dhcpIPByte[:], address.Un.XXX_UnionData[:]) 929 if isIPv6 { 930 return fmt.Sprintf("%s/%d", net.IP(dhcpIPByte).To16().String(), maskWidth) 931 } 932 return fmt.Sprintf("%s/%d", net.IP(dhcpIPByte[:4]).To4().String(), maskWidth) 933 } 934 935 // guessInterfaceType attempts to guess the correct interface type from its internal name (as given by VPP). 936 // This is required mainly for those interface types, that do not provide dump binary API, 937 // such as loopback of af_packet. 938 func guessInterfaceType(ifDevType, ifName string) ifs.Interface_Type { 939 switch { 940 case ifDevType == "RDMA interface": 941 return ifs.Interface_RDMA 942 943 case strings.HasPrefix(ifName, "loop"), 944 strings.HasPrefix(ifName, "local"): 945 return ifs.Interface_SOFTWARE_LOOPBACK 946 947 case strings.HasPrefix(ifName, "memif"): 948 return ifs.Interface_MEMIF 949 950 case strings.HasPrefix(ifName, "tap"), 951 strings.HasPrefix(ifName, "tun"): 952 return ifs.Interface_TAP 953 954 case strings.HasPrefix(ifName, "host"): 955 return ifs.Interface_AF_PACKET 956 957 case strings.HasPrefix(ifName, "vxlan"): 958 return ifs.Interface_VXLAN_TUNNEL 959 960 case strings.HasPrefix(ifName, "ipsec"): 961 return ifs.Interface_IPSEC_TUNNEL 962 963 case strings.HasPrefix(ifName, "vmxnet3"): 964 return ifs.Interface_VMXNET3_INTERFACE 965 966 case strings.HasPrefix(ifName, "Bond"): 967 return ifs.Interface_BOND_INTERFACE 968 969 case strings.HasPrefix(ifName, "gtpu"): 970 return ifs.Interface_GTPU_TUNNEL 971 972 case strings.HasPrefix(ifName, "ipip"): 973 return ifs.Interface_IPIP_TUNNEL 974 975 case strings.HasPrefix(ifName, "wireguard"): 976 return ifs.Interface_WIREGUARD_TUNNEL 977 978 default: 979 return ifs.Interface_DPDK 980 } 981 } 982 983 // memifModetoNB converts binary API type of memif mode to the northbound API type memif mode. 984 func memifModetoNB(mode vpp_memif.MemifMode) ifs.MemifLink_MemifMode { 985 switch mode { 986 case vpp_memif.MEMIF_MODE_API_IP: 987 return ifs.MemifLink_IP 988 case vpp_memif.MEMIF_MODE_API_PUNT_INJECT: 989 return ifs.MemifLink_PUNT_INJECT 990 default: 991 return ifs.MemifLink_ETHERNET 992 } 993 } 994 995 // Convert binary API rx-mode to northbound representation 996 func getRxModeType(mode interface_types.RxMode) ifs.Interface_RxMode_Type { 997 switch mode { 998 case 1: 999 return ifs.Interface_RxMode_POLLING 1000 case 2: 1001 return ifs.Interface_RxMode_INTERRUPT 1002 case 3: 1003 return ifs.Interface_RxMode_ADAPTIVE 1004 case 4: 1005 return ifs.Interface_RxMode_DEFAULT 1006 default: 1007 return ifs.Interface_RxMode_UNKNOWN 1008 } 1009 } 1010 1011 func getBondIfMode(mode vpp_bond.BondMode) ifs.BondLink_Mode { 1012 switch mode { 1013 case vpp_bond.BOND_API_MODE_ROUND_ROBIN: 1014 return ifs.BondLink_ROUND_ROBIN 1015 case vpp_bond.BOND_API_MODE_ACTIVE_BACKUP: 1016 return ifs.BondLink_ACTIVE_BACKUP 1017 case vpp_bond.BOND_API_MODE_XOR: 1018 return ifs.BondLink_XOR 1019 case vpp_bond.BOND_API_MODE_BROADCAST: 1020 return ifs.BondLink_BROADCAST 1021 case vpp_bond.BOND_API_MODE_LACP: 1022 return ifs.BondLink_LACP 1023 default: 1024 // UNKNOWN 1025 return 0 1026 } 1027 } 1028 1029 func getBondLoadBalance(lb vpp_bond.BondLbAlgo) ifs.BondLink_LoadBalance { 1030 switch lb { 1031 case vpp_bond.BOND_API_LB_ALGO_L34: 1032 return ifs.BondLink_L34 1033 case vpp_bond.BOND_API_LB_ALGO_L23: 1034 return ifs.BondLink_L23 1035 case vpp_bond.BOND_API_LB_ALGO_RR: 1036 return ifs.BondLink_RR 1037 case vpp_bond.BOND_API_LB_ALGO_BC: 1038 return ifs.BondLink_BC 1039 case vpp_bond.BOND_API_LB_ALGO_AB: 1040 return ifs.BondLink_AB 1041 default: 1042 return ifs.BondLink_L2 1043 } 1044 } 1045 1046 func getTagRwOption(op uint32) ifs.SubInterface_TagRewriteOptions { 1047 switch op { 1048 case 1: 1049 return ifs.SubInterface_PUSH1 1050 case 2: 1051 return ifs.SubInterface_PUSH2 1052 case 3: 1053 return ifs.SubInterface_POP1 1054 case 4: 1055 return ifs.SubInterface_POP2 1056 case 5: 1057 return ifs.SubInterface_TRANSLATE11 1058 case 6: 1059 return ifs.SubInterface_TRANSLATE12 1060 case 7: 1061 return ifs.SubInterface_TRANSLATE21 1062 case 8: 1063 return ifs.SubInterface_TRANSLATE22 1064 default: // disabled 1065 return ifs.SubInterface_DISABLED 1066 } 1067 } 1068 1069 func getGreTunnelType(tt vpp_gre.GreTunnelType) ifs.GreLink_Type { 1070 switch tt { 1071 case vpp_gre.GRE_API_TUNNEL_TYPE_L3: 1072 return ifs.GreLink_L3 1073 case vpp_gre.GRE_API_TUNNEL_TYPE_TEB: 1074 return ifs.GreLink_TEB 1075 case vpp_gre.GRE_API_TUNNEL_TYPE_ERSPAN: 1076 return ifs.GreLink_ERSPAN 1077 default: 1078 return ifs.GreLink_UNKNOWN 1079 } 1080 } 1081 1082 func getVxLanGpeProtocol(p ip_types.IPProto) ifs.VxlanLink_Gpe_Protocol { 1083 1084 // TODO: Fix conversion from IPProto, it seems those enums are incompatible now 1085 1086 switch p { 1087 case 1: 1088 return ifs.VxlanLink_Gpe_IP4 1089 case 2: 1090 return ifs.VxlanLink_Gpe_IP6 1091 case 3: 1092 return ifs.VxlanLink_Gpe_ETHERNET 1093 case 4: 1094 return ifs.VxlanLink_Gpe_NSH 1095 default: 1096 return ifs.VxlanLink_Gpe_UNKNOWN 1097 } 1098 } 1099 1100 func isAdminStateUp(flags interface_types.IfStatusFlags) bool { 1101 return flags&interface_types.IF_STATUS_API_FLAG_ADMIN_UP != 0 1102 } 1103 1104 func isLinkStateUp(flags interface_types.IfStatusFlags) bool { 1105 return flags&interface_types.IF_STATUS_API_FLAG_LINK_UP != 0 1106 } 1107 1108 func adminStateToInterfaceStatus(flags interface_types.IfStatusFlags) ifs.InterfaceState_Status { 1109 if isAdminStateUp(flags) { 1110 return ifs.InterfaceState_UP 1111 } 1112 return ifs.InterfaceState_DOWN 1113 } 1114 1115 func linkStateToInterfaceStatus(flags interface_types.IfStatusFlags) ifs.InterfaceState_Status { 1116 if isLinkStateUp(flags) { 1117 return ifs.InterfaceState_UP 1118 } 1119 return ifs.InterfaceState_DOWN 1120 }