Classify a network device by its MAC vendor string and open ports. Args: vendor: MAC OUI vendor string (e.g. "TP-Link Technologies") ports: list of port strings or ints (e.g. ["22", "80", "443"]) gateway_ip: the network's default gateway IP (if known) device_ip:
(vendor, ports, gateway_ip=None, device_ip=None)
| 378 | |
| 379 | |
| 380 | def classify_device(vendor, ports, gateway_ip=None, device_ip=None): |
| 381 | """Classify a network device by its MAC vendor string and open ports. |
| 382 | |
| 383 | Args: |
| 384 | vendor: MAC OUI vendor string (e.g. "TP-Link Technologies") |
| 385 | ports: list of port strings or ints (e.g. ["22", "80", "443"]) |
| 386 | gateway_ip: the network's default gateway IP (if known) |
| 387 | device_ip: this device's IP address |
| 388 | |
| 389 | Returns: |
| 390 | dict with keys: device_type, label, confidence (0.0-1.0) |
| 391 | """ |
| 392 | # Gateway always wins |
| 393 | if gateway_ip and device_ip and device_ip == gateway_ip: |
| 394 | return { |
| 395 | "device_type": "router", |
| 396 | "label": DEVICE_TYPE_LABELS["router"], |
| 397 | "confidence": 1.0, |
| 398 | } |
| 399 | |
| 400 | device_type = None |
| 401 | confidence = 0.3 # base confidence for unknown |
| 402 | |
| 403 | # Pass 1: vendor keyword match |
| 404 | if vendor: |
| 405 | vendor_lower = vendor.lower() |
| 406 | for keyword, dtype in _VENDOR_LOOKUP: |
| 407 | if keyword in vendor_lower: |
| 408 | device_type = dtype |
| 409 | confidence = 0.8 |
| 410 | break |
| 411 | |
| 412 | # Apple, Inc. makes phones, TVs, speakers, watches, laptops, tablets — |
| 413 | # the vendor string alone cannot distinguish them. Set low confidence |
| 414 | # so hostname / port / AI refinement takes over. |
| 415 | if vendor_lower.startswith("apple") and device_type is None: |
| 416 | device_type = "apple" |
| 417 | confidence = 0.4 # low enough for AI / hostname to override |
| 418 | |
| 419 | # Pass 2: port-based classification (refine or override) |
| 420 | port_type = _classify_by_ports(ports) |
| 421 | if port_type: |
| 422 | if device_type is None: |
| 423 | device_type = port_type |
| 424 | confidence = 0.6 |
| 425 | elif device_type == "workstation" and port_type == "server": |
| 426 | device_type = "server" |
| 427 | confidence = 0.7 |
| 428 | elif device_type == "sbc" and port_type == "router": |
| 429 | # SBCs running pi-hole / DNS look like routers but aren't — |
| 430 | # keep the SBC classification |
| 431 | pass |
| 432 | elif device_type == port_type: |
| 433 | confidence = 0.9 # vendor + ports agree |
| 434 | |
| 435 | if device_type is None: |
| 436 | device_type = "unknown" |
| 437 |
no test coverage detected