AES-256 ciphers (GCMP-256 / CCMP-256) won't form a mesh link on MM8108

Short version

On an 802.11s mesh, 128-bit ciphers work fine. 256-bit ciphers never form a link.
Same hardware, same firmware, same passphrase — the only thing I change is the cipher.

Setting Cipher used Mesh link?
encryption='sae' CCMP-128 Works
encryption='sae+gcmp' GCMP-128 Works
encryption='sae+gcmp256' GCMP-256 Never links
encryption='sae+ccmp256' CCMP-256 Never links

Both 128-bit options work and both 256-bit options fail, so it looks like the problem is
the 256-bit key length itself, not GCM vs CCM.

The chip says it supports these. iw phy0 info lists CCMP-256 and GCMP-256 alongside the
128-bit ones.

Setup

  • Raspberry Pi 4, OpenWrt 23.05.5
  • MM8108 (B2), SDIO. Driver log line at boot:
    Loaded firmware from morse/mm8108b2-rl.bin
    Morse Micro Dot11ah driver registration. Version 0-rel_1_16_4_2025_Sep_18
  • wpa_supplicant_s1g 1.16.4-2, morse-feed pinned at 263b4c4 (Aug 2025)
  • Two radios, 802.11ah 8 MHz, 802.11s mesh with batman-adv on top
  • Signal −7 to −28 dBm, so this isn’t a weak-signal problem

First problem: the 256-bit setting is silently ignored

Before I could even test this, I found that setting a 256-bit cipher does nothing at all.

/lib/netifd/morse/morse_overrides.sh (around line 1055) only recognises two exact values:

[ "$wpa_cipher" = GCMP ] && { ...pairwise=GCMP... }
[ "$wpa_cipher" = CCMP ] && ...pairwise=CCMP...

GCMP-256 and CCMP-256 match neither, so no cipher line gets written and wpa_supplicant
quietly falls back to CCMP-128. No warning, no error — it just looks like nothing happened.

I think that’s a bug by itself. Even if 256-bit isn’t supported, it shouldn’t silently
downgrade.

I patched that locally to pass the value through, which is how I got to the real issue.

Second problem: with the cipher applied, the link never forms

$ wpa_cli_s1g -i wlan0 status
pairwise_cipher=GCMP-256
group_cipher=GCMP-256
wpa_state=COMPLETED          <- looks fine

$ iw dev wlan0 station dump | grep 'mesh plink'
	mesh plink:	HOLDING      <- never reaches ESTAB

$ batctl n
(no neighbours)

Looking at the keys actually loaded into the driver:

idx1  00-0f-ac:9  32B     <- the 32-byte group key installs fine
                          <- but no idx0 entry: the per-link key is never created

So the 256-bit group key works. The per-link key never gets made, because the two
radios never finish their handshake.

With a 128-bit cipher you instead get three keys (including the per-link one) and
mesh plink: ESTAB within about 70 seconds.

What it looks like from outside

Stations show up in the list and then vanish, over and over. That’s not partial success —
the radio hears its neighbour’s beacons and creates an entry, tries to peer, fails, deletes
the entry, then rediscovers it on the next beacon and repeats.

Throughout all of it: no per-link key, no batman neighbour, no traffic.

Where it actually fails

My first look at the logs was misleading. I saw SAE: Confirm mismatch and lots of
MESH-SAE-AUTH-FAILURE and assumed the password exchange was failing. It isn’t —
MESH-SAE-AUTH-FAILURE is just a 10-second timeout firing, and the short timeout was
causing retries to trip over each other.

I rebuilt with a longer timeout (60s instead of 10s, 10 retries instead of 3) and ran it
for 8 minutes. Much clearer picture:

Log line Count
SAE completed successfully (added PMKSA cache entry) 424
SAE: Confirm mismatch 0
MESH-SAE-AUTH-FAILURE 1
Mesh RSN: invalid peer nonce 60
Link established 0

The password exchange (SAE) is completely fine — it succeeds 424 times. What fails is
the step after it: the peering handshake that exchanges keys. The peer keeps rejecting it
with “invalid peer nonce” and then closes the connection:

MPM: Received PLINK action 3        <- peer sends a Close
MPM: close reason=52
MPM: peer_lid mismatch: 0x0 != 0xccf7
MPM set <peer> from OPN_SNT into HOLDING

Also worth noting: the longer timeout didn’t help at all, so this isn’t a speed problem.

Things I checked in the source

From GitHub - MorseMicro/hostap · GitHub , in case any of these are relevant:

  • The peering element carries the group key, and its size changes with the cipher —
    16 bytes for 128-bit, 32 bytes for 256-bit (mesh_rsn.c:570-574 and :787-794). That’s
    the only part of the handshake whose size depends on the cipher, and it’s the same
    element whose nonce check is failing.
  • SAE-EXT-KEY can’t be used on a mesh. This is the standard way to get longer keys.
    It’s compiled in and in the shipped binary, but mesh.c only checks for plain
    WPA_KEY_MGMT_SAE (bit 10), while SAE_EXT_KEY is bit 26 — so choosing it just turns
    mesh security off entirely.
  • Heads-up for anyone else trying this: using a stronger SAE group (sae_groups=20,
    P-384) does not give you longer keys on a mesh. Key length depends on the AKM, not the
    group. I wasted time on this.
  • Minor: the peering element always claims CCMP as the cipher regardless of what’s
    configured (mesh_rsn.c:592). Nothing reads that field, so it’s harmless today, but it’s
    wrong on the wire.

I tried a lot of variations

All of these fail the same way:

  1. Changing the cipher on a running mesh
  2. Restarting wifi on both radios at once
  3. Using sae_groups=20
  4. Cold-rebooting both radios straight into the 256-bit setting
  5. Using CCMP-256 instead of GCMP-256
  6. Rebuilding with much longer timeouts and retry counts

About newer releases

I’m on 1.16.4 and I know 1.17.8, 1.17.9, mm8108-2.0.0 and mm6108-2.0.1 have since
come out. I checked the mesh source at the mm8108-2.0.0 tag and it’s unchanged in every
place I looked — same hardcoded CCMP, same SAE_PMK_LEN, same timeouts, same SAE-EXT-KEY
lockout, still no 256-bit handling.

But I haven’t tested 2.0.0 on hardware yet. Since the group key installs fine and only the
per-link key fails, a driver or firmware fix would be in exactly the part I can’t inspect —
so if 2.0.0 already fixes this, please say so and I’ll go test it.

Questions

  1. Is GCMP-256 / CCMP-256 supposed to work on an 802.11s mesh with the MM8108? If not,
    I’d rather know now and stop chasing it.
  2. Can the silent downgrade in morse_overrides.sh be fixed? Setting a 256-bit cipher and
    silently getting CCMP-128 is an easy trap to fall into.
  3. Any chance of enabling SAE-EXT-KEY for mesh mode? That’s the proper route to 256-bit
    mesh keys.
  4. Does the firmware handle a 32-byte per-link key at all? I could never get far enough to
    find out.
  5. Is there anything in the peering element handling that assumes a 16-byte group key?

Happy to run more tests, try patches, or send full debug logs.

I wanted to follow up on this as I am stuck on it. Is this a quick fix by Morse Micro or should I setup my own 256 bit encryption?

Hey @codbu13

Sorry for the late reply here!

To my knowledge mesh with 256 bit keys is not something that we have tested internally, so no I can’t claim that it is “supposed to work”. I can raise the morse_overrides bug with the team - I agree it should warn you.

The MM8108 itself should support CCMP-256 and GCMP-256 and the firmware 32-byte link keys. However it looks like you’ve spotted a genuine bug in the morse_overrides.sh script.

Regarding the failed peering handshake - it looks like wpa_supplicant’s mesh path has assumed 128-bit keys. Our changes to this file aren’t significant so I wonder if 256 bit keys has been exercised in wpa_supplicant’s upstream! If the 802.11s standard supports 256-bit keys then this is likely a bug (or maybe just something yet to be implemented) in wpa_supplicant.

Hey @ajudge

Thanks, that’s really helpful — it answers what I needed to know.

Confirming the useful part: you say the MM8108 and firmware support CCMP-256/GCMP-256 and 32-byte link keys. That matches what I’m seeing — the 32-byte group key installs into the driver fine. It’s the pairwise key that never gets derived, because peering never completes. So I agree the silicon isn’t the constraint.

Yes please on the morse_overrides.sh fix. Even if 256-bit stays unsupported, a configured sae+gcmp256 currently emits no pairwise= line at all and silently falls back to CCMP-128 with no warning. Failing loudly would have saved me days.

On the wpa_supplicant hypothesis — I agree, and here’s what I found

You’re right that the mesh path assumes 128-bit keys. Three specific places, all in code identical to upstream:

1. SAE-EXT-KEY is unreachable in mesh mode. It’s the AKM 802.11 defines for longer keys, and the only route to a PMK longer than 256 bits — src/common/sae.c:1552 gates the longer PMK on wpa_key_mgmt_sae_ext_key(sae->akmp). But wpa_supplicant/mesh.c:96, :312 and :780 gate all mesh SAE setup on:

if (ssid->key_mgmt & WPA_KEY_MGMT_SAE)

WPA_KEY_MGMT_SAE is BIT(10) and WPA_KEY_MGMT_SAE_EXT_KEY is BIT(26) (src/common/defs.h:42 and :58). So selecting SAE-EXT-KEY disables mesh SAE entirely rather than enabling longer keys.

2. The AKM is hardcoded into the derivation contexts. wpa_supplicant/mesh_rsn.c:454 and :514 both write RSN_AUTH_KEY_MGMT_SAE into the AEK and MTK contexts, so a different negotiated AKM couldn’t be reflected in the derived keys even if it were selectable.

3. MTK derivation uses a constant PMK length. mesh_rsn.c:529:

sha256_prf(sta->sae->pmk, SAE_PMK_LEN, ...)

rather than sta->sae->pmk_len. Correct for plain SAE, but it blocks any longer-PMK AKM.

One side effect worth flagging for other users: because the longer PMK is gated on the AKM and not on the SAE group, sae_groups=20 (P-384) has no effect on key length in mesh mode. Anyone trying to reach AES-256 that way silently gets 256-bit PMK material and no error. I lost time to that one before spotting it.

Offer

I still have a three-radio bench set up. If it would help narrow this down, I can run the mixed-cipher combinations that isolate which key is the problem — pairwise=GCMP-256 with group=CCMP, and the inverse. The two lengths come from different config values (mesh_rsn.c:528 from the pairwise cipher, :202 from the group cipher), so both combinations are expressible. A result either way should show whether the failure follows the 32-byte pairwise key or the 32-byte MGTK through the AMPE element, which ought to halve the search space.

Say the word and I’ll run it and post the logs. Happy to apply test patches on my hardware too.