Classify device type from a set of open port numbers.
(ports)
| 206 | # Port-based classification rules (applied when vendor is inconclusive) |
| 207 | # --------------------------------------------------------------------------- |
| 208 | def _classify_by_ports(ports): |
| 209 | """Classify device type from a set of open port numbers.""" |
| 210 | if not ports: |
| 211 | return None |
| 212 | |
| 213 | port_set = set() |
| 214 | for p in ports: |
| 215 | try: |
| 216 | port_set.add(int(str(p).split("/")[0])) |
| 217 | except (ValueError, IndexError): |
| 218 | continue |
| 219 | |
| 220 | # Printer protocols (very specific, check first) |
| 221 | if 9100 in port_set or 631 in port_set or 515 in port_set: |
| 222 | return "printer" |
| 223 | # RTSP → IP camera |
| 224 | if 554 in port_set or 8554 in port_set: |
| 225 | return "camera" |
| 226 | # ONVIF → IP camera |
| 227 | if 8899 in port_set or 37777 in port_set: |
| 228 | return "camera" |
| 229 | # Chromecast / smart TV casting |
| 230 | if 8008 in port_set and 8009 in port_set: |
| 231 | return "smart_tv" |
| 232 | # AirPlay → Apple TV / HomePod (port 7000 = AirPlay, 3689 = DAAP) |
| 233 | if 7000 in port_set and 5353 in port_set: |
| 234 | return "smart_tv" |
| 235 | # Apple TV often exposes AirPlay alone on port 7000 |
| 236 | if 7000 in port_set and 3689 in port_set: |
| 237 | return "smart_tv" |
| 238 | # Sonos / smart speaker (UPnP + HTTP) |
| 239 | if 1400 in port_set: |
| 240 | return "speaker" |
| 241 | # NAS protocols (AFP + SMB or NFS) |
| 242 | if 548 in port_set or (2049 in port_set and 445 in port_set): |
| 243 | return "nas" |
| 244 | # SMB + SSH + Synology/QNAP web port → NAS |
| 245 | if 445 in port_set and 22 in port_set and 5000 in port_set: |
| 246 | return "nas" |
| 247 | # DHCP server → router (only if also serving DNS — real routers do both) |
| 248 | if 67 in port_set and 53 in port_set: |
| 249 | return "router" |
| 250 | # Router: serves DNS + HTTP (typical home router) |
| 251 | # BUT only if there are few ports — SBCs/servers running pi-hole also have 53+80 |
| 252 | if 53 in port_set and (80 in port_set or 443 in port_set) and len(port_set) <= 4: |
| 253 | return "router" |
| 254 | # RDP → Windows workstation |
| 255 | if 3389 in port_set: |
| 256 | return "workstation" |
| 257 | # SMB/CIFS without SSH → workstation |
| 258 | if 445 in port_set and 22 not in port_set: |
| 259 | return "workstation" |
| 260 | # MQTT → IoT hub |
| 261 | if 1883 in port_set or 8883 in port_set: |
| 262 | return "iot" |
| 263 | # Media streaming ports |
| 264 | if 8009 in port_set or 5353 in port_set: |
| 265 | return "media" |
no test coverage detected