#25190·openwrt

odhcp6c: S46_PORTPARAMS sub-option silently ignored, MAP-T uses wrong PSID offset (Bouygues Telecom, FR)

Author: kuxtuxCreated Sep 15, 2026Updated Sep 17, 2026
Labelstarget/x86bugOfficial ImageSupported Devicerelease/25.12

Describe the bug

On a Bouygues Telecom (France) FTTH line using MAP-T, odhcp6c never parses the S46_PORTPARAMS sub-option carried inside S46_RULE (DHCPv6 option 95). The PSID offset therefore falls back to the RFC 7597 default of 6, while the network actually announces 0.

The router then sources all its traffic from a port range that belongs to a different subscriber, and the Border Relay drops every packet silently.

What makes this hard to diagnose is that every observable indicator looks healthy: the prefix is delegated, the MAP-T interface comes up, the derived IPv4 address and CE IPv6 address are correct, and the translated packets are well-formed with valid checksums and physically leave the interface. Nothing comes back — not even an ICMPv6 error. Only a byte-level decode of option 95 reveals the mismatch.

Captured on the WAN with tcpdump -i eth1.100 -n -vv -X 'udp port 546 or udp port 547':

00 5f 00 29 OPTION_S46_CONT_MAPT (95), length 41 00 59 00 18 OPTION_S46_RULE (89), length 24 00 flags = 0 0c ea-len = 12 17 prefix4-len = 23 b0 bf fc 00 ipv4-prefix = 176.191.252.0 30 prefix6-len = 48 20 01 08 61 5b 91 00 00 ipv6-prefix = 2001:861:5b91:: <-- 8 octets, not 6 00 5d 00 04 OPTION_S46_PORTPARAMS (93), length 4 00 offset = 0 03 psid-len = 3 40 00 psid = 0x4000 (left-aligned => 2) 00 5b 00 09 OPTION_S46_DMR (91), length 9 40 dmr-len = 64 20 01 08 60 bb ef 04 3d dmr = 2001:860:bbef:43d::/64

Both readings of the 24-byte rule consume it exactly, so the encoding is ambiguous on its face — but the trailing bytes are not zeros and they form a structurally valid S46_PORTPARAMS.

Resulting (wrong) configuration

bash
# cat /proc/net/nat46/control
config map-wan6_4 local.v4 176.191.252.0/23 local.v6 2001:861:5b91::/48 \
  local.style MAP local.ea-len 12 local.psid-offset 6 \
  remote.v4 0.0.0.0/0 remote.v6 2001:860:bbef:43d::/64 remote.style RFC6052

local.psid-offset 6 instead of 0. map.sh consequently generates 63 SNAT rules over scattered 128-port ranges instead of one contiguous range:

offset 6 (applied) offset 0 (announced)
Port sets 63 × 128 ports, from 1280 16384-24575
Total ports 8064 8192

mapcalc honours offset=0 correctly when it is present:

# offset=0 explicitly in the rule
RULE_1_OFFSET=0
RULE_1_PORTSETS='16384-24575 '

# offset absent (what odhcp6c produces)
RULE_1_OFFSET=6
RULE_1_PORTSETS='1280-1407 2304-2431 3328-3455 ... 64768-64895 '

Forcing local.psid-offset 0 into nat46 and moving the SNAT rules to 16384-24575 makes IPv4 work instantly and reliably.

s46_to_env_portparams():

c
fprintf(fp, "offset=%d,psidlen=%d,psid=%d,",
		params->offset, params->psid_len, ntohs(params->psid));

RFC 7598 §4.5 specifies that the 16-bit psid field is left-aligned: only the psid-len most significant bits are meaningful. With psid-len = 3 and psid = 0x4000 the PSID is 2, but odhcp6c would emit psid=16384.

This is currently unreachable because the sub-option is never parsed. Fixing the first defect alone would expose it and produce a different wrong configuration, so both should be fixed together. Suggested:

c
uint16_t psid = ntohs(params->psid);
if (params->psid_len > 0 && params->psid_len <= 16)
	psid >>= 16 - params->psid_len;
else
	psid = 0;

fprintf(fp, "offset=%d,psidlen=%d,psid=%d,",
		params->offset, params->psid_len, psid);

### OpenWrt version

r33051-f5dae5ece4

### OpenWrt release

25.12.5

### OpenWrt target/subtarget

x86/64

### Device

Lenovo ThinkCentre M720q (10T8S0D200)

### Image kind

Official downloaded image

### Steps to reproduce

1. Connect a router running OpenWrt 25.12.5 to a Bouygues Telecom FTTH line
   (VLAN 100, DHCPv6 with prefix delegation, MAP-T for IPv4).
2. Install `map`; let `dhcpv6.script` auto-create the MAP interface from
   option 95.
3. `cat /proc/net/nat46/control` and compare `local.psid-offset` with the
   `S46_PORTPARAMS` offset in the DHCPv6 Reply.

Without such a line: feed `odhcp6c` an option 95 whose `S46_RULE` carries a
`prefix6-len` of 48 with an 8-octet `ipv6-prefix` field followed by
`S46_PORTPARAMS`. The sub-option is dropped without any log entry.

### Actual behaviour

`local.psid-offset` is set to 6 (the RFC default). SNAT uses port ranges that
are not allocated to this subscriber. No IPv4 traffic works at all, and no error
or warning is produced at any layer.

### Expected behaviour

The announced offset of 0 is used, SNAT uses ports 16384-24575, and IPv4 works.
Failing that, odhcp6c should log that it could not parse the sub-option area of
`S46_RULE` rather than silently falling back to defaults.

### Additional info

Workaround — disable the auto-created MAP interface and declare it statically:

config interface 'wan6' option proto 'dhcpv6' option iface_map '0' # ...

config interface 'wanmap' option proto 'map' option maptype 'map-t' option rule 'type=map-t,ipv6prefix=2001:861:5b91::,prefix6len=48,ipv4prefix=176.191.252.0,prefix4len=23,ealen=12,psidlen=3,offset=0,dmr=2001:860:bbef:43d::/64' option tunlink 'wan6' option mtu '1480' option zone 'wan'


Note `dmr=` (MAP-T), not `br=` — `mapcalc` sets `RULE_n_BR` for the latter while
`map.sh` reads `RULE_n_DMR`, leaving nat46 with
`remote.v6 ::/0 remote.style NONE`.

A pcap of the DHCPv6 exchange can be provided on request.

### Diffconfig

```text

Terms

  • I am reporting an issue for OpenWrt, not an unsupported fork.