Skip to content

feat(plugins): Add Fritz!Box device scanner plugin via TR-064 protocol#1592

Merged
jokob-sk merged 9 commits intonetalertx:mainfrom
sebingel:fritzbox-plugin
Apr 6, 2026
Merged

feat(plugins): Add Fritz!Box device scanner plugin via TR-064 protocol#1592
jokob-sk merged 9 commits intonetalertx:mainfrom
sebingel:fritzbox-plugin

Conversation

@sebingel
Copy link
Copy Markdown
Contributor

@sebingel sebingel commented Apr 6, 2026

📌 Description

Adds a new scanner plugin that discovers devices connected to a Fritz!Box router using the TR-064 protocol (UPnP-based device management API). The plugin reports MAC address, IP address, hostname, and interface type (WiFi / LAN) for each device. An optional guest WiFi monitoring feature creates a synthetic Access Point device when the guest network is active.

Full i18n coverage across 21 languages and 32 automated tests are included.


🔍 Related Issues

N/A


📋 Type of Change

Please check the relevant option(s):

  • 🐛 Bug fix
  • ✨ New feature
  • ♻️ Code refactor
  • 📚 Documentation update
  • 🧪 Test addition or change
  • 🔧 Build/config update
  • 🚀 Performance improvement
  • 🔨 CI/CD or automation
  • 🧹 Cleanup / chore

📷 Screenshots or Logs (if applicable)

Example log output (verbose mode):

09:41:06 [FRITZBOX] In script
09:41:06 [FRITZBOX] Settings: host=fritz.box, port=49443, use_tls=True, active_only=True
09:41:06 [FRITZBOX] Attempting connection to fritz.box:49443 (TLS: True)
09:41:07 [FRITZBOX] Successfully connected to Fritz!Box
09:41:07 [FRITZBOX] Model: FRITZ!Box 7530 (UI), Software: 8.2
09:41:07 [FRITZBOX] Found 68 total hosts in Fritz!Box
09:41:08 [FRITZBOX] Device: a8:96:09:33:a2:12 (PC-172-31-179-4) - 172.31.179.4 - WiFi
09:41:08 [FRITZBOX] Device: 02:42:c0:a8:b2:0b (PC-192-168-178-11) - 192.168.178.11 - LAN
[...]
09:41:10 [FRITZBOX] Processed 26 devices
09:41:11 [FRITZBOX] Guest WiFi active on service 3: GuestSSID
09:41:12 [FRITZBOX] Created guest WiFi device: 02:00:00:00:00:01
09:41:12 [FRITZBOX] Successfully processed 27 devices
grafik grafik

🧪 Testing Steps

  1. Enable TR-064 on Fritz!Box: Home Network > Network > Network Settings > Allow access for applications
  2. Configure plugin in NetAlertX Settings: set host (fritz.box), port (49443), username, password
  3. Set When to run to schedule and verify devices appear on the Devices page
  4. Check logs at /tmp/log/plugins/script.FRITZBOX.log for successful queries
  5. Optional: enable Report Guest WiFi and verify a synthetic "Guest WiFi Network" Access Point device appears when guest network is active
  6. Run automated tests: devcontainer exec --workspace-folder . pytest -q test/plugins/test_fritzbox.py — expect 32 passed

✅ Checklist

  • I have read the Contribution Guidelines
  • I have tested my changes locally
  • I have updated relevant documentation (if applicable)
  • I have verified my changes do not break existing behavior
  • I am willing to respond to requested changes and feedback

🙋 Additional Notes

Technical implementation details:

  • Uses fritzconnection >= 1.15.1 library via TR-064 (no web scraping)
  • Connection timeout: 10s per request (prevents indefinite blocking on unreachable Fritz!Box)
  • Guest WiFi MAC: deterministic locally-administered MAC (02:xx:xx:xx:xx:xx) derived from Fritz!Box MAC via MD5 with usedforsecurity=False (FIPS-compatible)
  • MAC normalization via normalize_mac() before every DB write
  • No direct DB access — uses Plugin_Objects result file mechanism throughout

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Fritz!Box plugin to discover network devices and optionally expose a synthetic Guest WiFi device when guest networks are active.
  • Documentation

    • Added a detailed README with setup, configuration, operation, troubleshooting, and limitations.
  • Tests

    • Added unit tests covering scanning, guest WiFi logic, connection handling, and main execution flows.
  • Chores

    • Updated installation requirements to include the Fritz!Box integration dependency.

sebingel added 4 commits April 6, 2026 07:34
NetAlertX had no native support for discovering devices connected to
Fritz!Box routers. Users relying on Fritz!Box as their primary home
router had to use generic network scanning (ARP/ICMP), missing
Fritz!Box-specific details like interface type (WiFi/LAN) and
connection status per device.

Changes:
- Add plugin implementation (front/plugins/fritzbox/fritzbox.py)
  Queries all hosts via FritzHosts TR-064 service, normalizes MACs,
  maps interface types (802.11→WiFi, Ethernet→LAN), and writes results
  to CurrentScan via Plugin_Objects. Supports filtering to active-only
  devices and optional guest WiFi monitoring via a synthetic AP device
  with a deterministic locally-administered MAC (02:xx derived from
  Fritz!Box MAC via MD5).

- Add plugin configuration (front/plugins/fritzbox/config.json)
  Defines plugin_type "device_scanner" with settings for host, port,
  credentials, guest WiFi reporting, and active-only filtering.
  Maps scan columns to CurrentScan fields (scanMac, scanLastIP, scanName,
  scanType). Default schedule: every 5 minutes.

- Add plugin documentation (front/plugins/fritzbox/README.md)
  Covers TR-064 protocol basics, quick setup guide, all settings with
  defaults, troubleshooting for common issues (connection refused, auth
  failures, no devices found), and technical details.

- Add fritzconnection>=1.15.1 dependency (requirements.txt)
  Required Python library for TR-064 communication with Fritz!Box.

- Add test suite (test/plugins/test_fritzbox.py:1-298)
  298 lines covering get_connected_devices (active filtering, MAC
  normalization, interface mapping, error resilience), check_guest_wifi_status
  (service detection, SSID-based guest detection, fallback behavior), and
  create_guest_wifi_device (deterministic MAC generation, locally-administered
  bit, fallback MAC, regression anchor with precomputed hash).

Users can now scan Fritz!Box-connected devices natively, seeing per-device
connection status and interface type directly in NetAlertX. Guest WiFi
monitoring provides visibility into guest network state. The plugin
defaults to HTTPS on port 49443 with active-only filtering enabled.
The Fritz!Box plugin config.json only contained English (en_us) strings
for all translatable fields. NetAlertX supports 21 languages and shows
the plugin description and all setting labels in the user's chosen
language. Without translations, every non-English user sees raw English
text for the plugin card description, setting names, and setting
explanations regardless of their language preference.

Changes:
- front/plugins/fritzbox/config.json: added 20 translations for the
  top-level plugin `description` field (all 21 supported languages)

- front/plugins/fritzbox/config.json: added translations for `name` and
  `description` fields in all 14 settings (RUN, RUN_SCHD, HOST, PORT,
  USER, PASS, USE_TLS, REPORT_GUEST, GUEST_SERVICE, ACTIVE_ONLY, CMD,
  RUN_TIMEOUT, SET_ALWAYS, SET_EMPTY)

  Selectively translated by field type:
  - 12 settings: 21 languages for both name and description
  - HOST (name "Fritz!Box Host") and PORT (name "TR-064 Port"): name
    kept as en_us only — these are language-neutral proper names and
    standard identifiers; description translated in all 21 languages

  Technical terms left untranslated in all languages: Fritz!Box, TR-064,
  HTTPS, HTTP, WLANConfiguration, and all code identifiers referenced
  in descriptions (schedule, NEWDEV, Source = USER, Source = LOCKED)

Total: 544 localized strings added across 21 languages (ar_ar, ca_ca,
cs_cz, de_de, es_es, fa_fa, fr_fr, id_id, it_it, ja_jp, nb_no, pl_pl,
pt_br, pt_pt, ru_ru, sv_sv, tr_tr, uk_ua, vi_vn, zh_cn).

Users in all supported languages now see the plugin description card and
every setting label in their own language. The existing en_us fallback
mechanism ensures forward compatibility with any future languages added
to the project.
Two independent reliability problems were identified during PR readiness
review. First, FritzConnection had no explicit timeout, meaning an
unreachable or slow Fritz!Box would block the plugin process indefinitely
until the OS TCP timeout fired (typically 2+ minutes), making the 60s
RUN_TIMEOUT in config.json ineffective. Second, hashlib.md5() called
without usedforsecurity=False raises ValueError on FIPS-enforced systems
(common in enterprise Docker hosts), silently breaking the guest WiFi
synthetic device feature for those users.

Changes:
- Add timeout=10 to FritzConnection(...) call (fritzbox.py:57)
  The fritzconnection library accepts a timeout parameter directly in
  __init__; it applies per individual HTTP request to the Fritz!Box,
  bounding each TR-064 call including the initial connection handshake.

- Add usedforsecurity=False to hashlib.md5() call (fritzbox.py:191)
  The MD5 hash is used only for deterministic MAC derivation (not for
  any security purpose), so the flag is semantically correct and lifts
  the FIPS restriction without changing the computed value.

- Update test assertion to include timeout=10 (test_fritzbox.py:307)
  assert_called_once_with checks the exact call signature; the test
  expectation must match the updated production code.

The plugin now fails fast on unreachable Fritz!Box (within 10s per
request) and works correctly on FIPS-enabled hosts. Default behavior
for standard deployments is unchanged.
The device information table in README.md incorrectly stated that the
Connection Status field ("Active"/"Inactive") maps to devVendor in the
devices table. In reality, watchedValue2 has no mapped_to_column entry
in config.json, meaning the value is stored only in the plugin's own
Plugins_FRITZBOX table and never promoted to the Devices table. A user
following the documentation to filter or display Connection Status via
devVendor would find no data there.

Changes:
- Correct the "Mapped To" column for Connection Status (README.md:86)
  Changed from "`devVendor` (shown as vendor field)" to "Plugin table
  only (not mapped to device fields)" to accurately reflect config.json
  behavior.

Users now have a correct expectation: Connection Status is visible in
the Fritz!Box plugin view but not in standard device columns. No
functional code was changed.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb093673-b02c-47e7-8cf3-26e769cf642f

📥 Commits

Reviewing files that changed from the base of the PR and between 13f840b and 4b6203a.

📒 Files selected for processing (1)
  • docs/PLUGINS.md
✅ Files skipped from review due to trivial changes (1)
  • docs/PLUGINS.md

📝 Walkthrough

Walkthrough

Adds a new Fritz!Box TR-064 device-discovery plugin, README, dependency entries, and tests; the plugin queries Fritz!Box hosts, normalizes and maps device data, optionally synthesizes a Guest WiFi device, and writes plugin result objects.

Changes

Cohort / File(s) Summary
Plugin implementation
front/plugins/fritzbox/fritzbox.py
New executable plugin: creates FritzConnection (host/port/TLS/creds/timeout), enumerates hosts via TR-064, filters by active state, normalizes MACs, maps interface types, handles per-host exceptions, synthesizes Guest WiFi device when configured, and writes results. Review connection error paths, guest MAC derivation, and result persistence.
Documentation
front/plugins/fritzbox/README.md
New README describing plugin behavior, TR-064 setup, configuration variables, guest WiFi mode, troubleshooting, limitations, runtime/log locations, and technical details.
Dependencies
requirements.txt, install/proxmox/requirements.txt, install/ubuntu24/requirements.txt
Added fritzconnection>=1.15.1 to main and install-specific requirement files.
Tests
test/plugins/test_fritzbox.py
New pytest suite covering connection creation, host scanning, MAC normalization, interface mapping, guest WiFi checks and guest-device synthesis, create_guest_wifi_device behavior, and main() orchestration; patches external modules and side effects for deterministic tests.

Sequence Diagram(s)

sequenceDiagram
    participant Plugin as fritzbox.py
    participant TR064 as Fritz!Box TR-064
    participant Hosts as FritzHosts Service
    participant WLAN as WLANConfiguration Service
    participant Results as Plugin Results

    Plugin->>TR064: Establish FritzConnection (host, port, TLS, creds, timeout)
    alt connection fails
        Plugin->>Results: Write empty/failed result file
        Plugin-->>Plugin: return 1
    else connection succeeds
        Plugin->>Hosts: Query host list
        Hosts-->>Plugin: Return host entries
        loop per host
            Plugin->>Plugin: Extract MAC, IP, hostname, active, iface
            Plugin->>Plugin: Normalize MAC, map interface type
            Plugin->>Results: Add device object
        end
        alt guest reporting enabled
            Plugin->>WLAN: Query WLANConfiguration{N} for guest status
            WLAN-->>Plugin: Guest enabled? SSID
            alt guest active
                Plugin->>Plugin: Synthesize Guest WiFi device (derived MAC)
                Plugin->>Results: Add guest device object
            end
        end
        Plugin->>Results: Write result file
        Plugin-->>Plugin: return 0
    end
Loading

Poem

🐰 I hopped upon the router's trail,
Sniffed hosts and names with floppy tail,
I spun a MAC from box and clue,
A guestly ghost with a hop — so new! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the primary change: introducing a new Fritz!Box device scanner plugin using the TR-064 protocol.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front/plugins/fritzbox/README.md`:
- Line 192: The README timeout (60s) and the code are out of sync; update the
timeout used in fritzbox.py (where timeout=10 is passed) to use 60 seconds by
default and/or read from the RUN_TIMEOUT environment variable so the code honors
the documented behavior; modify the function or constructor that sets the
timeout (the call passing timeout=10 in fritzbox.py) to use
int(os.getenv("RUN_TIMEOUT", "60")) or equivalent so the value matches the
README and remains configurable.

In `@requirements.txt`:
- Line 37: Update the invalid dependency constraint for the fritzconnection
package in requirements.txt: replace the non-existent version specifier
"fritzconnection>=1.15.1" with a valid one such as "fritzconnection>=1.9.1" (or
the intended correct version), ensuring the requirements.txt entry for the
fritzconnection package is a valid PyPI version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05e9afa0-40a5-4ede-9a6b-7d9ca84d46ad

📥 Commits

Reviewing files that changed from the base of the PR and between 83de79b and 706ef1a.

📒 Files selected for processing (5)
  • front/plugins/fritzbox/README.md
  • front/plugins/fritzbox/config.json
  • front/plugins/fritzbox/fritzbox.py
  • requirements.txt
  • test/plugins/test_fritzbox.py

Comment thread front/plugins/fritzbox/README.md
Comment thread requirements.txt
Copy link
Copy Markdown
Collaborator

@jokob-sk jokob-sk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a LOT for a very solid PR - there a a few small things, but I hope nothing too difficult to fix.

Comment thread front/plugins/fritzbox/fritzbox.py Outdated
Comment thread requirements.txt
Comment thread front/plugins/fritzbox/README.md Outdated
Comment thread front/plugins/fritzbox/README.md Outdated
Comment thread front/plugins/fritzbox/README.md Outdated
Comment thread front/plugins/fritzbox/README.md Outdated
Comment thread front/plugins/fritzbox/fritzbox.py
@jokob-sk
Copy link
Copy Markdown
Collaborator

jokob-sk commented Apr 6, 2026

oh, one more thing - the docs should be updated here as well: https://github.com/netalertx/NetAlertX/blob/main/docs/PLUGINS.md

sebingel added 5 commits April 6, 2026 10:20
The fritzconnection imports were originally placed inside the function
bodies as a defensive pattern: by catching ImportError locally,
get_fritzbox_connection() and get_connected_devices() could each return
None or an empty list with a user-friendly log message instead of
crashing at import time. This kept the plugin runnable even when the
dependency was missing.

Requested by reviewer jokob-sk in PR netalertx#1592: move all imports to the top
of the module, treating fritzconnection as a required dependency that is
assumed to be installed via requirements.txt.

Changes:
- Add top-level imports for FritzConnection and FritzHosts
  (fritzbox.py:16-17)

- Remove inline import and ImportError handler from
  get_fritzbox_connection() (fritzbox.py:48, 64-67)

- Remove inline import and ImportError handler from
  get_connected_devices() (fritzbox.py:79, 133-134)

Functional behavior of the plugin is unchanged.
The fritzconnection dependency was added to the top-level requirements.txt
when the Fritz!Box plugin was introduced, but the install-specific files
for Proxmox and Ubuntu 24 were not updated. Without the entry in these
files, fresh installations via the install scripts would not install the
dependency.

Requested by reviewer jokob-sk in PR netalertx#1592.

Changes:
- Add fritzconnection>=1.15.1 to install/proxmox/requirements.txt
- Add fritzconnection>=1.15.1 to install/ubuntu24/requirements.txt

All three requirements files now declare the fritzconnection dependency
consistently.
Requested by reviewer jokob-sk in PR netalertx#1592.

Changes:
- Replace generic author "NetAlertX Community" with @sebingel
  (README.md:204)

- Update release date from January 2026 to April 2026
  (README.md:205)

- Remove license field from version section (README.md:206)
  Project license is defined at repository level and does not need
  to be repeated in individual plugin READMEs.

- Update repository link from jokob-sk/NetAlertX to netalertx/NetAlertX
  (README.md:211)
  The project was transferred to the netalertx organisation; the
  canonical URL is now github.com/netalertx/NetAlertX.
The previous implementation derived the guest WiFi device MAC using a
custom MD5 hash of the Fritz!Box hardware MAC, producing a
locally-administered address with a 02: prefix. This was inconsistent
with the project-wide convention of using string_to_fake_mac() from
crypto_utils, which produces a fa:ce: prefixed address and is used by
all other plugins (nmap_dev_scan, adguard_import, pihole_api_scan, etc.).

A naive switch to string_to_fake_mac(host) would have introduced a
stability problem: if the user reconfigures FRITZBOX_HOST from an IP
address (e.g. 192.168.178.1) to a hostname (e.g. fritz.box), the fake
MAC would change and the guest device would re-appear as a new unknown
device in NetAlertX. The Fritz!Box hardware MAC is a stable identifier
that does not change with the configured host string.

Requested by reviewer jokob-sk in PR netalertx#1592.

Changes:
- Remove import hashlib (fritzbox.py:3) — no longer needed

- Add import string_to_fake_mac from utils.crypto_utils (fritzbox.py:15)

- Replace custom MD5-based MAC derivation in create_guest_wifi_device()
  with string_to_fake_mac(normalize_mac(fritzbox_mac)) (fritzbox.py:178)
  The Fritz!Box hardware MAC is fetched via TR-064 as before, but is now
  passed to the shared project utility instead of a custom hash.

- Add host parameter to create_guest_wifi_device(fc, host) (fritzbox.py:169)
  Used as fallback input to string_to_fake_mac() if the hardware MAC
  cannot be retrieved.

- Update call site in main() to pass host (fritzbox.py:224)

The guest WiFi device MAC is now stable across host configuration changes
and consistent with the fa:ce: prefix convention used across the project.
The FRITZBOX plugin was not listed in the central plugin registry at
docs/PLUGINS.md. Requested by reviewer jokob-sk in PR netalertx#1592.

Changes:
- Add FRITZBOX entry to the Available Plugins table (docs/PLUGINS.md:60)
  Inserted alphabetically between FREEBOX and ICMP, with type 🔍
  (device_scanner) and a link to the plugin directory.
@sebingel
Copy link
Copy Markdown
Contributor Author

sebingel commented Apr 6, 2026

Done in 4b6203a1. Added FRITZBOX to the Available Plugins table in docs/PLUGINS.md, alphabetically between FREEBOX and ICMP.

@sebingel sebingel requested a review from jokob-sk April 6, 2026 11:01
@jokob-sk jokob-sk merged commit ae089f5 into netalertx:main Apr 6, 2026
5 checks passed
@jokob-sk
Copy link
Copy Markdown
Collaborator

jokob-sk commented Apr 6, 2026

Thanks a lot! Merging

@sebingel sebingel deleted the fritzbox-plugin branch April 6, 2026 11:04
@jokob-sk jokob-sk mentioned this pull request Apr 6, 2026
7 tasks
@jokob-sk
Copy link
Copy Markdown
Collaborator

Hi @sebingel - can you please re-test the netalertx-dev image? The tests were failing so I had to make a few adjustments, but unsure if I introduced regression bugs: cc507f2#diff-0104eb1afee5afc393a0000ba35682997ab948b2f8d396097bf7eae0affc166d

@sebingel
Copy link
Copy Markdown
Contributor Author

@jokob-sk I am so sorry that I forgot to execute the tests after this change 🤦🏻
Your fix is absolutely on point. Using host for the fallback is not necessary. Using FRITZBOX_GUEST does the trick just fine.

@jokob-sk
Copy link
Copy Markdown
Collaborator

All good -tahnsk for checking! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants