Bluetooth Mesh Proxy Filters: Explain Why a Connected Client Still Misses Messages

A GATT connection to a Mesh Proxy Server is only the bearer. The server’s proxy filter can still exclude the destination addresses your management client needs. Diagnose that per-connection filter separately from GATT notification readiness and the client’s application-model subscriptions.

Applicability: historical Bluetooth Mesh Protocol 1.1 ordinary proxy filtering, with Zephyr 4.2.0 proxy_srv.c behavior explicitly identified. Directed Proxy control and path establishment are excluded. The address/count sequence below is hypothetical; no connection, filter operation or traffic capture was executed.

Five accept-list operations produce counts zero, two, two, one and zero, with destination direction and count-only evidence limits shown.
Original proxy-filter fixture. It assumes no intervening client Network PDUs and identifies the Zephyr all-nodes exception; no filter or radio test was run.

1. Filter destinations, not interesting senders

Section 6.4 describes a filter on Network PDUs sent from the Proxy Server toward the connected client. An accept list admits listed destination addresses; a reject list excludes listed destinations.

If remote node R sends a reply to client element C, the relevant filter address is C. Adding R merely because it is the device being managed does not select packets whose destination is C. Likewise, group publication to G requires G in the intended accept set.

Prepare a destination inventory: the client’s required unicast element addresses, group addresses and any qualified virtual addresses. The proxy configuration messages carry the 16-bit virtual address, not the full Label UUID. Application authentication and model selection still resolve their own semantics.

A proxy filter is delivery selection, not an authorization firewall. Passing the filter does not grant an AppKey, satisfy a model binding or make an application message trustworthy. Excluding a destination also does not erase it from the rest of the mesh.

2. Rebuild state for each GATT connection

The protocol initializes a new connection with an empty accept list. Keep desired membership separately from the observed filter state for the current connection generation. Reconnecting to the same server does not preserve the old list by contract.

Set Filter Type both chooses the type and clears the list. Repeating “set accept list” after adding addresses therefore empties them, even when the type was already accept. Sequence initialization once, then apply additions and verify responses.

The exact Zephyr implementation clears its filter array in filter_set for either valid type. Its filter_add ignores the unassigned address, avoids duplicate entries and stops adding when no empty slot remains.

There is also an implementation-specific observation worth isolating: client_filter_match admits the all-nodes address 0xFFFF on the accept-filter path even if it is absent from the list. Therefore, receiving an all-nodes packet is not a useful test that a required ordinary group address was installed. Use explicit unicast and dynamic-group fixtures.

3. Understand what Filter Status proves

Filter Status contains FilterType and ListSize. It does not enumerate the addresses. A count of two cannot distinguish the intended pair from two wrong addresses, and it does not identify which address was omitted when capacity was exhausted.

The standard Add and Remove messages accept zero through five addresses per message. Batch within that bound and serialize requests through one connection owner so responses can be associated with the intended operation. Use the stack’s supported proxy configuration path rather than sending ad hoc raw messages.

In a quiet fixture with no intervening client Network PDUs, start with Set Filter Type=accept and expect size zero. Add 0x0100 and 0xC123 and expect size two. Add 0xC123 again and expect no growth. Remove 0xC123 and expect size one.

Now set the same accept type again and expect size zero. This last step deliberately catches code that assumes type-setting is nondestructive. Rebuild the required membership before enabling normal application traffic.

A zero-length Add Addresses operation is permitted by the historical message format and yields Filter Status, but still only exposes type and count. There is no address-list enumeration hidden in that response.

4. Account for automatic source learning

Under section 6.7, receiving a valid Network PDU from a connected client adds that PDU’s unicast source to an accept list. With a reject list, it removes that source instead. This supports replies but means membership can change outside an explicit Add/Remove command sequence.

In the Zephyr source, bt_mesh_proxy_addr_add selects filter_add or filter_remove according to the current type. Include such client transmissions in a diagnostic timeline. An unexpected count increment can be legitimate source learning rather than another process corrupting the filter.

For a deterministic initialization test, keep ordinary Network-PDU traffic quiescent until filter reconstruction finishes. If production code cannot pause it, include expected automatic changes in the owner’s ledger and validate delivery with destination-specific probes.

Do not rely on automatic learning to add every secondary element or group interest. Explicitly establish the destination set required by the product, and qualify capacity for that complete set.

5. Distinguish capacity loss from downstream rejection

The server does not add new addresses when its list is full. A response with fewer entries than expected requires investigation; it is not permission to declare every requested address installed. Record the configured capacity for the chosen implementation.

When membership is uncertain, a controlled clear-and-rebuild sequence can re-establish a known basis. It temporarily changes delivery, so schedule it under the connection owner’s policy and keep required work gated until complete.

Then inject one authorized test message to each required destination. Observe the packet at the proxy path and the client’s model result separately. If the proxy forwarded it but the model did not accept it, investigate keys, replay state, subscriptions and opcode handling rather than widening the filter indefinitely.

Mesh beacons and proxy configuration responses are different Proxy PDU types. Their presence does not prove that an application Network PDU with a specific destination passes the filter.

6. Test membership changes and reconnect boundaries

  • Add a required group, deliver a probe, remove it and repeat: verify the selected destination changes behavior.
  • Add duplicate addresses and 0x0000: verify count expectations without inventing extra membership.
  • Exceed the qualified capacity in a lab fixture: verify incomplete installation is visible and readiness remains blocked.
  • Transmit from a new client element: verify automatic source learning is reflected in the diagnostic record.
  • Reconnect to the same server: verify an empty initial accept list is rebuilt.
  • Reconnect to another server with a different capacity: verify desired membership is requalified.
  • Deliver a delayed old-generation Filter Status: verify it cannot mark the new connection ready.

Acceptance requires the intended destination set, compatible capacity, explainable type/count responses and successful destination-specific probes. “GATT connected” and “Filter Status received” are useful intermediate facts; neither establishes the complete Mesh receive path.

Similar Posts