Collect Ethernet interface metadata using nmcli + ip link fallbacks.
(default_interface: str = 'eth0')
| 282 | |
| 283 | |
| 284 | def gather_ethernet_interfaces(default_interface: str = 'eth0') -> List[Dict]: |
| 285 | """Collect Ethernet interface metadata using nmcli + ip link fallbacks.""" |
| 286 | interfaces: Dict[str, Dict] = {} |
| 287 | |
| 288 | # Try nmcli first |
| 289 | try: |
| 290 | nmcli_result = subprocess.run( |
| 291 | ['nmcli', '-t', '-f', 'DEVICE,TYPE,STATE,CONNECTION', 'dev', 'status'], |
| 292 | capture_output=True, |
| 293 | text=True, |
| 294 | timeout=5 |
| 295 | ) |
| 296 | if nmcli_result.returncode == 0: |
| 297 | for line in nmcli_result.stdout.strip().split('\n'): |
| 298 | if not line: |
| 299 | continue |
| 300 | parts = line.split(':', 3) |
| 301 | if len(parts) < 4: |
| 302 | continue |
| 303 | device, dev_type, state, connection = parts |
| 304 | if dev_type != 'ethernet': |
| 305 | continue |
| 306 | normalized_connection = connection if connection and connection != '--' else None |
| 307 | has_carrier = _check_ethernet_carrier(device) |
| 308 | interfaces[device] = { |
| 309 | 'name': device, |
| 310 | 'type': 'ethernet', |
| 311 | 'state': state or 'UNKNOWN', |
| 312 | 'is_default': device == default_interface, |
| 313 | 'connection': normalized_connection, |
| 314 | 'connected': (state or '').lower() == 'connected' and has_carrier, |
| 315 | 'has_carrier': has_carrier, |
| 316 | } |
| 317 | except Exception as exc: |
| 318 | logger.debug(f"nmcli dev status failed for ethernet: {exc}") |
| 319 | |
| 320 | # Fallback to ip link show |
| 321 | try: |
| 322 | ip_result = subprocess.run( |
| 323 | ['ip', '-o', 'link', 'show'], |
| 324 | capture_output=True, |
| 325 | text=True, |
| 326 | timeout=5 |
| 327 | ) |
| 328 | if ip_result.returncode == 0: |
| 329 | for line in ip_result.stdout.strip().split('\n'): |
| 330 | if not line: |
| 331 | continue |
| 332 | name_match = re.match(r'\d+:\s+(\S+):', line) |
| 333 | if not name_match: |
| 334 | continue |
| 335 | iface_name = name_match.group(1) |
| 336 | if not _ETHERNET_NAME_PATTERN.match(iface_name): |
| 337 | continue |
| 338 | |
| 339 | has_carrier = _check_ethernet_carrier(iface_name) |
| 340 | entry = interfaces.setdefault(iface_name, { |
| 341 | 'name': iface_name, |
no test coverage detected