odhcp6c: S46_PORTPARAMS sub-option silently ignored, MAP-T uses wrong PSID offset (Bouygues Telecom, FR)
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
# 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 RFC6052local.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():
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:
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.
Source: openwrt/openwrt