Read one \\n-terminated line, blocking up to timeout_s. Returns b'' if nothing arrives in time. Bypasses pyserial's select() path.
(timeout_s: float)
| 3334 | r = subprocess.run(['iw', 'dev', interface, 'info'], |
| 3335 | capture_output=True, text=True, timeout=3) |
| 3336 | if r.returncode == 0: |
| 3337 | m = re.search(r'^\s*type\s+(\S+)', r.stdout, re.MULTILINE) |
| 3338 | if m: |
| 3339 | return m.group(1).lower() |
| 3340 | except Exception: |
| 3341 | pass |
| 3342 | return '' |
| 3343 | |
| 3344 | def _wifidef_owned(self): |
| 3345 | """Interfaces + phys that WiFi Defense is actively monitoring right now. |
| 3346 | Wardriving must not reclaim/delete these (deleting ragmon0 kills the |
| 3347 | capture with ENODEV mid-scan). Empty when no monitor is active.""" |
| 3348 | ifaces, phys = set(), set() |
| 3349 | try: |
| 3350 | with open(_WIFIDEF_STATE_FILE) as f: |
| 3351 | st = json.load(f) |
| 3352 | except Exception: |
| 3353 | return ifaces, phys |
| 3354 | if not st.get("mon_iface"): |
| 3355 | return ifaces, phys # monitor not active → nothing to protect |
| 3356 | for key in ("mon_iface", "base_iface"): |
| 3357 | name = st.get(key) |
| 3358 | if not name: |
| 3359 | continue |
| 3360 | ifaces.add(name) |
| 3361 | ph = self._iface_phy(name) |