#1804·NetAlertX

[FREEBOX] Dual-stack IPv6 addresses do not populate devPrimaryIPv6

Author: SvestisCreated Sep 21, 2026Updated Sep 22, 2026
Labelsbug 🐛Waiting for reply⏳next release/in dev image 🚀

What installation are you running?

Production (netalertx)

Is there an existing issue for this?

The issue occurs in the following browsers. Select at least 2.

  • Firefox
  • Chrome
  • Edge
  • Safari (unsupported) - PRs welcome
  • N/A - This is an issue with the backend

Current Behavior

Using the FREEBOX device scanner on NetAlertX v26.9.0, IPv6 addresses are discovered correctly for dual-stack devices, but Devices.devPrimaryIPv6 is not populated.

In a fresh FREEBOX scan, I observed:

  • 13 unique devices
  • 28 address records
  • 13 IPv4 records
  • 15 IPv6 records
  • 8 of the 13 devices had at least one IPv6 address

The IPv6 addresses are present in the raw FREEBOX plugin output and are also persisted in Plugins_Objects, so IPv6 discovery itself appears to be working.

However, the corresponding devices end up with IPv4 as devLastIP / devPrimaryIPv4, while devPrimaryIPv6 remains empty.

I traced the processing path and found that FREEBOX maps each discovered address to a separate CurrentScan row:

  • objectPrimaryIdscanMac
  • objectSecondaryIdscanLastIP

For a dual-stack device, this means multiple CurrentScan observations can exist for the same MAC and plugin.

LatestDeviceScan then reduces these observations to one row per (scanMac, scanSourcePlugin) using ROW_NUMBER() partitioned by scanMac and scanSourcePlugin, ordered by scanLastConnection DESC.

FREEBOX does not populate scanLastConnection, so its IPv4 and IPv6 observations have no meaningful value on which to rank them.

update_devices_data_from_scan() subsequently uses the single row selected by LatestDeviceScan to update devLastIP.

Later, update_ipv4_ipv6() derives devPrimaryIPv4 and devPrimaryIPv6 only from that single devLastIP.

My simplified understanding of the findings

A simple way to think about the issue is that a device can have several IP addresses.

For example:

Device A
├── IPv4
├── IPv6
└── IPv6

The FREEBOX plugin correctly discovers all three addresses.

At this point, NetAlertX effectively has:

Device A → IPv4
Device A → IPv6
Device A → IPv6

So IPv6 discovery itself appears to be working.

The problem seems to happen later.

LatestDeviceScan reduces the multiple observations for the same device and scanner to a single observation:

Device A → IPv4
Device A → IPv6
Device A → IPv6
        │
        ▼
     pick one
        │
        ▼
Device A → IPv4

Normally the selection is ordered using scanLastConnection.

However, FREEBOX does not populate scanLastConnection, so the IPv4 and IPv6 observations do not have a meaningful timestamp that can be used to decide which one should be selected.

In my latest scan, FREEBOX emitted IPv4 first for the dual-stack devices, and this is consistent with IPv4 being the address selected during this reduction.

Later, NetAlertX runs update_ipv4_ipv6().

At this stage it does not look at all of the IP addresses originally discovered for the device. It looks only at the single surviving devLastIP.

So the flow effectively becomes:

FREEBOX discovers:

IPv4 + IPv6 + IPv6
        │
        ▼
All addresses enter CurrentScan
        │
        ▼
LatestDeviceScan keeps one
        │
        ▼
      IPv4
        │
        ▼
   devLastIP
        │
        ▼
update_ipv4_ipv6()
        │
        ▼
devPrimaryIPv4 = populated
devPrimaryIPv6 = empty

In other words, the FREEBOX plugin is successfully discovering the IPv6 addresses.

The IPv6 information appears to be lost later because multiple addresses are reduced to one devLastIP before update_ipv4_ipv6() tries to populate the separate IPv4 and IPv6 fields.

In my latest scan, FREEBOX emitted IPv4 before IPv6 for the dual-stack devices, and IPv4 was retained as devLastIP. As a result, devPrimaryIPv4 is populated while the IPv6 addresses that were successfully discovered do not reach devPrimaryIPv6.

Expected Behavior

For a device where the scanner discovers both IPv4 and IPv6 addresses, I would expect NetAlertX to preserve both address families at the device level.

For example, if FREEBOX reports:

Device A
├── IPv4
├── IPv6
└── IPv6

I would expect the device to retain at least one appropriate address from each discovered address family, so that the device can have:

devPrimaryIPv4 → IPv4
devPrimaryIPv6 → IPv6

The presence of multiple addresses for the same MAC should not cause one address family to be lost simply because the scan observations are reduced to a single devLastIP.

In particular, devPrimaryIPv4 and devPrimaryIPv6 appear intended to allow NetAlertX to represent a dual-stack device. I would therefore expect both fields to be populated when valid addresses from both families are available from the scanner.

I do not have a strong opinion on where this should be implemented. The important expected outcome is that IPv4 and IPv6 observations already discovered by a scanner remain available to the device-level dual-stack handling rather than one family being discarded before devPrimaryIPv4 / devPrimaryIPv6 are updated.

Steps To Reproduce

  1. Run NetAlertX v26.9.0 with the FREEBOX device scanner enabled.

  2. Have at least one Freebox client that is reachable and has both IPv4 and IPv6 connectivity.

  3. Run the FREEBOX scanner and allow the scan and subsequent device processing to complete.

  4. Check the FREEBOX plugin output. Observe that the same device MAC is reported multiple times, with IPv4 and IPv6 addresses as separate observations.

  5. Check Plugins_Objects. Observe that both the IPv4 and IPv6 observations from FREEBOX have been persisted.

  6. Check the corresponding device in Devices.

  7. Observe that devPrimaryIPv4 is populated, while devPrimaryIPv6 remains empty despite the FREEBOX scanner having discovered IPv6 address(es) for that device.

  8. Repeat with other dual-stack devices. In my test environment, this was reproducible across multiple devices: 8 devices had IPv6 observations in the FREEBOX results, while no devices had devPrimaryIPv6 populated.

Relevant app.conf settings

python
# FREEBOX plugin is enabled in LOADED_PLUGINS.

FREEBOX_RUN='schedule'
FREEBOX_RUN_SCHD='*/5 * * * *'

FREEBOX_address='mafreebox.freebox.fr'
FREEBOX_api_version=6
FREEBOX_api_port=443

FREEBOX_CMD='python3 /app/server/plugins/freebox/freebox.py'
FREEBOX_RUN_TIMEOUT=30

FREEBOX_SET_ALWAYS=['devMac','devLastIP']
FREEBOX_SET_EMPTY=[]

docker-compose.yml

yaml
services:
  netalertx:
    image: ghcr.io/netalertx/netalertx:latest
    container_name: netalertx
    restart: unless-stopped

    network_mode: host

    volumes:
      - <HOST_DATA_PATH>:/data

    tmpfs:
      - "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"

    cap_drop:
      - ALL

    cap_add:
      - NET_ADMIN
      - NET_RAW
      - NET_BIND_SERVICE
      - CHOWN
      - SETUID
      - SETGID

    read_only: true

    security_opt:
      - no-new-privileges:true

    environment:
      - TZ=<TIMEZONE>

    deploy:
      resources:
        reservations:
          memory: 1G
        limits:
          memory: 2G

Debug or Trace enabled

  • I have read and followed the steps in the wiki link above and provided the required debug logs and the log section covers the time when the issue occurs.

Relevant app.log section

Sanitized DEBUG excerpt from a single FREEBOX scan on v26.9.0.

The FREEBOX plugin receives multiple IP observations for the same device, including both IPv4 and IPv6:


[Plugins] SUCCESS for FREEBOX received 32 entries

SQL sqlParams for mapping (sanitized):
device-01 | ipv4
device-01 | ipv6
device-01 | ipv6

device-02 | ipv4
device-02 | ipv6

device-03 | ipv4
device-03 | ipv6
device-03 | ipv6

Later in the same processing cycle, the device update uses only the IPv4
scanLastIP as devLastIP:

[Update Devices] device-01 | 192.168.x.x -> devLastIP
[Update Devices] device-02 | 192.168.x.x -> devLastIP
[Update Devices] device-03 | 192.168.x.x -> devLastIP

No corresponding IPv6 value is assigned to devLastIP for these dual-stack devices in this update cycle.

The raw log contains real MAC addresses, IPv6 addresses, hostnames and other network information, so identifiers and addresses have been anonymized here.

Docker Logs

Sanitized/condensed DEBUG excerpt from a single FREEBOX scan on v26.9.0. Device identifiers and addresses have been anonymized.

23:10:19 [Plugins] Executing: python3 /app/server/plugins/freebox/freebox.py
23:10:20 [Plugins] SUCCESS for FREEBOX received 32 entries

[Plugins] SQL sqlParams for mapping (sanitized):

device-01 | <IPv4> | Freebox
device-01 | <IPv6> | Freebox
device-01 | <IPv6> | Freebox

device-02 | <IPv4> | Freebox
device-02 | <IPv6> | Freebox
device-02 | <IPv6> | Freebox

device-03 | <IPv4> | Freebox
device-03 | <IPv6> | Freebox

Later in the same processing cycle:

23:10:20 [Update Devices] - (Freebox) <IPv4> -> devLastIP
23:10:20 [Update Devices] - (Freebox) scanLastIP -> devLastIP

23:10:20 [Update Devices] - (Freebox) <IPv4> -> devLastIP
23:10:20 [Update Devices] - (Freebox) scanLastIP -> devLastIP

23:10:20 [Update Devices] - (Freebox) <IPv4> -> devLastIP
23:10:20 [Update Devices] - (Freebox) scanLastIP -> devLastIP

For these dual-stack devices, the FREEBOX mapping contains both IPv4 and IPv6 observations, but the subsequent device update uses an IPv4 address as devLastIP.

No corresponding IPv6 value is assigned to devLastIP for these devices during this processing cycle.