Speed / duplex / auto-negotiation / link. Wireless & virtual ifaces typically don't support this -> fields stay None.
(iface)
| 504 | if not _have('lldpctl'): |
| 505 | return {'success': False, |
| 506 | 'error': 'lldpd/lldpctl is not installed. Click Install to add it ' |
| 507 | '(configured for LLDP + CDPv1/v2, EDP, FDP and SONMP so ' |
| 508 | 'Cisco, Extreme, Foundry and Nortel switches are seen too).', |
| 509 | 'missing_tool': 'lldpd', |
| 510 | 'neighbors': []} |
| 511 | if interface: |
| 512 | # Name/injection guard plus an existence check, so a typo surfaces as a |
| 513 | # clear error rather than an empty neighbour list that looks like "no |
| 514 | # switch found". |
| 515 | if not _valid_iface(interface) or interface not in _list_iface_names(include_virtual=True): |
| 516 | return {'success': False, 'error': f'Unknown interface: {interface}', |
| 517 | 'neighbors': []} |
| 518 | # lldpctl takes trailing interface names to filter natively. |
| 519 | cmd = ['lldpctl', '-f', 'json'] + ([interface] if interface else []) |
| 520 | res = _run(cmd, timeout=15) |
| 521 | if res['rc'] != 0 and not res['out']: |
| 522 | return {'success': False, 'error': res['err'] or 'lldpctl failed', |
| 523 | 'interface': interface, 'neighbors': []} |
| 524 | neighbors = [] |
| 525 | try: |
| 526 | data = json.loads(res['out']) |
| 527 | ifaces = data.get('lldp', {}).get('interface', []) |
| 528 | if isinstance(ifaces, dict): |
| 529 | ifaces = [ifaces] |