Skip to content

Commit 48a922a

Browse files
fix(proto-gtpv2c): correct IPCP receive handling in PCO
Two defects in the PCO/APCO IPCP decoder, both peer-triggerable. An IPCP Configure-Nak was accepted with no correlation. RFC 1661 5.3 requires the Identifier to match the last transmitted Configure-Request and says invalid packets are silently discarded; the decoder recorded the Identifier as carrying nothing it interprets, and the entry point took no expected value, so it structurally could not correlate. The first non-zero address in any Nak became the session's DNS answer. A malformed IPCP unit also destroyed unrelated siblings. RFC 1661's discard unit is the packet, and TS 24.008 10.5.6.3 maps one 0x8021 unit to one RFC 1661 packet. The unit's outer container boundary is already validated, so sibling boundaries are recoverable and the correct disposition is a unit-local discard. decode_network_contents_correlated takes an IpcpNakCorrelation carrying the caller's outstanding request. Default and none() fail closed and discard every Nak. expecting() is the RFC-permissive reading, since 5.3 lets a peer append options that were not requested; for_request() additionally declines unsolicited options, which is labelled engineering judgement rather than a requirement. The existing entry point keeps its signature and delegates with no correlation. Intra-unit atomicity is structural, not commentary: the unit decoder parses into scratch state and merges only on whole-unit success, so an option parsed before a later malformed one in the same packet cannot survive it. Discards are reported as bounded evidence carrying an index and a reason, never an address or an Identifier value. Malformed known address containers still reject the whole value. TS 24.008 states no receiver disposition there, so that is this codec's configuration-atomicity policy and is now documented as such rather than as a specification requirement. Refs #587 Signed-off-by: VerifiedOrganic <verifiedorganic@sent.com>
1 parent 5ef338d commit 48a922a

7 files changed

Lines changed: 1118 additions & 90 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
can install one sink allocation in both the gNMI and NETCONF server cores
1414
without a product-local forwarding newtype; calls and errors are forwarded
1515
unchanged.
16+
- **Correlated IPCP Configure-Nak receive in PCO/APCO — `opc-proto-gtpv2c`:**
17+
`IpcpNakCorrelation`, `PcoDecoded`, `PcoIpcpDiscard`, `PcoIpcpDiscardReason`
18+
and `PcoAddressConfiguration::decode_network_contents_correlated`. RFC 1661
19+
§5.3 states verbatim: "On reception of a Configure-Nak, the Identifier field
20+
MUST match that of the last transmitted Configure-Request. Invalid packets are
21+
silently discarded." The previous decoder took no expected Identifier and so
22+
structurally could not correlate, which let the first non-zero address in any
23+
Configure-Nak become the session's DNS answer. The new entry point takes the
24+
caller's outstanding-request position and discards a Nak that does not match
25+
it. `IpcpNakCorrelation::none` is the `Default` and discards every
26+
Configure-Nak, so the fail-closed position is the one a caller reaches by
27+
accident; `expecting(identifier)` is the RFC-permissive constructor;
28+
`for_request(sent)` correlates against a request this SDK encoded.
29+
Every unit dropped on correlation, every unit dropped as malformed, and every
30+
DNS option skipped as unsolicited is reported through
31+
`PcoDecoded::ipcp_discards` with a reason code and a unit position and never
32+
an address or an Identifier value, extending the existing
33+
`PcoAddressConfiguration` redaction contract. Four drops stay deliberately
34+
silent and record no entry, as before: a well-formed code other than
35+
Configure-Nak, an unknown option type, an echoed RFC 1877 all-zero address,
36+
and a repeated option whose slot is already filled.
37+
`for_request` additionally declines a DNS option this side never solicited.
38+
**That filter is engineering judgement, not a specification requirement:** RFC
39+
1661 §5.3 permits a Configure-Nak to append Configuration Options the peer
40+
desires that were not in the Configure-Request, so the unit is kept and only
41+
the option is skipped, reported as `UnsolicitedOption`. Use `expecting` for
42+
the permissive reading. It is not an on-path control either — the Identifier
43+
is visible on the wire, and `IpcpDnsRequest::identifier` is documented as
44+
opaque with nothing obliging a caller to vary it.
1645
- **First-owner activation for destination-scoped steering — `opc-ipsec-lb`:**
1746
in `HostXdpFenceDomain::PerOwnershipKey` the only public owner-map writer was
1847
the fenced re-pin coordinator, and a re-pin cannot be formed for a fresh SA --
@@ -122,6 +151,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122151
explicitly replace a v1 trail before enabling v2 writes; the SDK does not
123152
silently reseal it. A v2 anchor whose version tag alone is downgraded fails
124153
authentication and is not classified as benign legacy data.
154+
- **A malformed IPCP unit is discarded unit-locally instead of failing the whole
155+
PCO/APCO value — `opc-proto-gtpv2c` (breaking, behavioural; no signature
156+
changes):** `decode_network_contents` propagated an IPCP fault out of the
157+
whole value, destroying P-CSCF and DNS containers that had already parsed.
158+
RFC 1661's discard unit is the *packet*, and TS 24.008 10.5.6.3 maps one
159+
`0x8021` unit to one RFC 1661 packet stripped of Protocol and Padding; the
160+
unit's outer container boundary is validated before its contents are read, so
161+
the sibling boundaries are recoverable and the siblings are now kept. Within
162+
one unit the disposition is atomic: an option that parsed before a later
163+
malformed one in the same packet is dropped with it, which the unit decoder
164+
now expresses in its type by returning no `Result` at all and merging only on
165+
whole-unit success. `PcoDecodeError::IpcpHeaderTruncated`, `IpcpLengthInvalid`,
166+
`IpcpOptionTruncated`, `IpcpOptionLengthInvalid` and
167+
`IpcpDnsOptionLengthInvalid` stop being returned from the decode entry points;
168+
they remain public, constructible and reachable through
169+
`PcoIpcpDiscardReason::Malformed`, and no variant was removed or reordered.
170+
**This is a reject-to-accept flip: a caller matching on those five variants
171+
will stop seeing them.**
172+
- **`decode_network_contents` no longer surfaces IPCP-supplied DNS —
173+
`opc-proto-gtpv2c` (breaking, behavioural; signature unchanged):** holding no
174+
Identifier it cannot satisfy RFC 1661 §5.3, so the fail-closed answer is to
175+
supply nothing. `ipcp_primary_dns`/`ipcp_secondary_dns` stay `None`; for a
176+
value whose only DNS source was the IPCP reply `is_empty()` reports empty and
177+
the caller's configured-DNS fallback fires as that predicate already
178+
documents; and `dns_server_ipv4_all()` equals
179+
`dns_server_ipv4` under this path. Migration:
180+
`decode_network_contents_correlated(value, IpcpNakCorrelation::for_request(sent.ipcp_dns))?.into_configuration()`.
181+
The function is deliberately **not** `#[deprecated]`: it stays the correct call
182+
for a value that carries only containers.
183+
These two changes have opposite signs and are declared as such — the decoder
184+
is now **more** permissive about a malformed sibling unit and **less**
185+
permissive about an uncorrelated reply. Both move toward RFC 1661 fidelity;
186+
neither is simply hardening.
187+
Unchanged, stated explicitly: `PcoAddressConfiguration`'s fields, derives and
188+
redacting `Debug`; every `PcoDecodeError` variant and its `as_str`;
189+
container-framing fatality; whole-value rejection for a wrong-length address
190+
container; the IPv4 Link MTU local skip; and the entire encode path,
191+
`PcoRequest`, `IpcpDnsRequest`, `PcscfRequest` and `PcscfAddressRequest`.
125192
- **`RePinAuditEvent` carries a correlation digest, not the live transition
126193
secret — `opc-ipsec-lb` (breaking: `transition_id: OwnershipTransitionId` is
127194
replaced by `correlation_id: RePinAuditCorrelationId`):** the coordinator
@@ -319,6 +386,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
319386
fixture, so no message that already round-tripped moves on the wire.
320387

321388
### Fixed
389+
- **Documentation corrections in the PCO codec — `opc-proto-gtpv2c`:** the RFC
390+
1661 citation on the Configure-Ack option-echo rule said §5.3, which is
391+
Configure-Nak; §5.2 is Configure-Ack. `dns_server_ipv4_all` and the crate
392+
README claimed the accessor "drops duplicates" generally, but the container
393+
list is cloned verbatim and only the two IPCP-sourced addresses are checked
394+
against it; the wording now says so, and the behaviour is deliberately
395+
unchanged because a repeated address container is something the peer actually
396+
sent and collapsing it would destroy that evidence. The
397+
`decode_network_contents` contract said "parsing is all-or-nothing" beside a
398+
comment that does cite a clause, which read as spec-compelled; whole-value
399+
rejection for a wrong-length **address** container is relabelled as this
400+
codec's configuration-atomicity policy, which TS 24.008 does not require. The
401+
comment claiming the IPCP Identifier "carries nothing this decoder interprets"
402+
is deleted.
322403
- **P-CSCF Re-selection support is no longer emittable on its own --
323404
`opc-proto-gtpv2c` (breaking to `PcoRequest`):** TS 24.008 10.5.6.3 says of
324405
container `0x0012` that "This PCO parameter may be present only if a
@@ -610,16 +691,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
610691
- *Receive.* Emitting a request whose answer is discarded would deliver
611692
nothing, so `PcoAddressConfiguration` now decodes the reply into
612693
`ipcp_primary_dns` and `ipcp_secondary_dns`. Only a Configure-Nak is read
613-
for addresses: RFC 1661 5.3 has a Configure-Ack echo the request's options
694+
for addresses: RFC 1661 §5.2 has a Configure-Ack echo the request's options
614695
verbatim, so it conveys no server, and an echoed all-zero address is not
615-
treated as one. `dns_server_ipv4_all()` merges the container and IPCP
616-
sources and drops duplicates, so a caller cannot silently miss the
617-
mechanism its peer chose.
696+
treated as one. `dns_server_ipv4_all()` reports both the container and IPCP
697+
sources, so a caller cannot silently miss the mechanism its peer chose. Both
698+
the correlation requirement and the disposition of a malformed unit are
699+
superseded within this same unreleased section; see the correlated-receive
700+
entry under Added and the unit-local-discard entry under Changed.
618701
- Both structs gain fields, which breaks exhaustive struct literals;
619702
`..PcoRequest::none()` and `..Default::default()` are unaffected. Five new
620-
`PcoDecodeError` variants report malformed IPCP framing, and a malformed
621-
unit for this now-supported identifier rejects the whole value, matching
622-
how a known container with a bad length is already handled. `Debug` reports
703+
`PcoDecodeError` variants report malformed IPCP framing. `Debug` reports
623704
presence, never addresses.
624705

625706
- **Credential-rotation observability closes three residual gaps —

‎crates/opc-proto-gtpv2c/CONFORMANCE.md‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,19 @@ coverage.
364364
the all-zero address that requests a peer-supplied value. Because the
365365
configuration protocol options list occupies octets 4..w and the
366366
additional parameters list w+1..z, the unit is encoded ahead of every
367-
container. On receive, only a Configure-Nak is read for addresses: a
368-
Configure-Ack echoes the request's options verbatim and so conveys no
369-
server, and an echoed all-zero address is not treated as one. A malformed
370-
unit for this now-supported identifier rejects the whole value, matching
371-
how a known container with a bad length is handled.
367+
container. On receive, only a Configure-Nak whose Identifier matches the
368+
outstanding Configure-Request is read for addresses, as RFC 1661 5.3
369+
requires; the uncorrelated `decode_network_contents` entry point holds no
370+
Identifier and so reads none. A Configure-Ack echoes the request's options
371+
verbatim and so conveys no server, and an echoed all-zero address is not
372+
treated as one. A malformed unit for this identifier is discarded
373+
unit-locally and reported through `PcoDecoded::ipcp_discards`, and its
374+
sibling containers survive: RFC 1661's discard unit is the packet and
375+
10.5.6.3 maps one unit to one such packet, so a fault inside a unit whose
376+
outer container boundary already validated does not reach the value. That
377+
is unlike a known address container with a bad length, which still rejects
378+
the whole value under this codec's configuration-atomicity policy, for
379+
which the specification states no receiver disposition.
372380
- The IPv4 Link MTU container `0x0010` is supported in both directions, with
373381
the direction-dependent shape Table 10.5.154 assigns: zero-length request
374382
MS to network, two-octet value network to MS. 10.5.6.3 states that a

‎crates/opc-proto-gtpv2c/README.md‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,40 @@ control-plane stack.
125125
- `PcoRequest::ipcp_dns` emits an RFC 1332 IPCP Configure-Request carrying the
126126
RFC 1877 Primary (129) and Secondary (131) DNS Server Address options, which
127127
TS 24.008 10.5.6.3 places in the configuration protocol options list ahead of
128-
every container. The decoder reads the peer's Configure-Nak answer into
129-
`ipcp_primary_dns`/`ipcp_secondary_dns`. A peer may serve DNS by either
130-
mechanism, so prefer `PcoAddressConfiguration::dns_server_ipv4_all`, which
131-
merges both sources and drops duplicates.
128+
every container. `PcoAddressConfiguration::decode_network_contents_correlated`
129+
reads the peer's Configure-Nak answer into
130+
`ipcp_primary_dns`/`ipcp_secondary_dns`, but only once the reply is
131+
correlated: RFC 1661 5.3 says "On reception of a Configure-Nak, the Identifier
132+
field MUST match that of the last transmitted Configure-Request. Invalid
133+
packets are silently discarded." Pass `IpcpNakCorrelation::for_request` built
134+
from the `IpcpDnsRequest` that was sent. `IpcpNakCorrelation::none` is the
135+
`Default`, and the uncorrelated `decode_network_contents` uses it, so that
136+
entry point surfaces no IPCP-supplied DNS at all. Every unit dropped on
137+
correlation, every unit dropped as malformed, and every DNS option skipped as
138+
unsolicited is reported through `PcoDecoded::ipcp_discards`, which carries
139+
reason codes and unit positions and never an address or an Identifier value.
140+
Four drops are deliberately silent and record no entry, as before: a
141+
well-formed code other than Configure-Nak, an unknown option type, an echoed
142+
RFC 1877 all-zero address, and a repeated option whose slot is already filled.
143+
- A malformed `0x8021` unit is discarded unit-locally rather than failing the
144+
whole PCO/APCO value: RFC 1661's discard unit is the packet, TS 24.008
145+
10.5.6.3 maps one `0x8021` unit to one RFC 1661 packet, and the unit's outer
146+
container boundary has already been validated, so the sibling P-CSCF and DNS
147+
containers are recoverable and survive. Within one unit the disposition is
148+
atomic -- an option that parsed before a later malformed one in the same
149+
packet is dropped with it. Container framing and wrong-length address
150+
containers still reject the whole value.
151+
- `for_request` additionally declines a DNS option this side never solicited.
152+
That is engineering judgement and not a specification requirement: RFC 1661
153+
5.3 permits a Configure-Nak to append options the peer desires that were not
154+
in the Configure-Request, so the unit is kept and only the option is skipped.
155+
Use `IpcpNakCorrelation::expecting` for the RFC-permissive reading, which
156+
accepts both DNS options.
157+
- A peer may serve DNS by either mechanism, so prefer
158+
`PcoAddressConfiguration::dns_server_ipv4_all`. Container addresses come
159+
first, in wire order and with their multiplicity preserved; the IPCP primary
160+
and secondary addresses follow, each appended only if the list does not
161+
already contain it. The container list itself is never deduplicated.
132162
- `PcoRequest::ipv4_link_mtu` emits the zero-length IPv4 Link MTU Request
133163
container `0x0010`, and `PcoAddressConfiguration::ipv4_link_mtu` carries the
134164
two-octet value the network returns under the same identifier. TS 24.008

‎crates/opc-proto-gtpv2c/src/lib.rs‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
//! unaccompanied combination does not compile. [`PcoRequest`] also emits an
3333
//! IPCP (`0x8021`) Configure-Request for the RFC 1877 DNS options, ahead of
3434
//! the containers as TS 24.008 10.5.6.3 positions it, and
35-
//! [`PcoAddressConfiguration`] reads the peer's Configure-Nak answer, and the
35+
//! [`PcoAddressConfiguration::decode_network_contents_correlated`] reads the
36+
//! peer's Configure-Nak answer once [`IpcpNakCorrelation`] correlates it to the
37+
//! Identifier that was sent, as RFC 1661 5.3 requires; a malformed `0x8021`
38+
//! unit is discarded on its own and leaves its sibling containers standing. The
3639
//! IPv4 Link MTU container `0x0010` is carried in both directions.
3740
//! Product code remains responsible for deciding when optional policy-owned
3841
//! values apply and for obtaining them from AAA/HSS or local configuration.
@@ -127,11 +130,11 @@ pub use ie::{
127130
};
128131
pub use message::{Message, OwnedMessage};
129132
pub use pco::{
130-
IpcpDnsRequest, PcoAddressConfiguration, PcoDecodeError, PcoRequest, PcscfAddressRequest,
131-
PcscfRequest, PCO_CONTAINER_DNS_SERVER_IPV4, PCO_CONTAINER_DNS_SERVER_IPV6,
132-
PCO_CONTAINER_IPV4_LINK_MTU, PCO_CONTAINER_P_CSCF_IPV4, PCO_CONTAINER_P_CSCF_IPV6,
133-
PCO_CONTAINER_P_CSCF_RESELECTION_SUPPORT, PCO_HEADER_PPP_FOR_IP_PDN, PCO_MAX_CONTAINERS,
134-
PCO_PROTOCOL_IPCP,
133+
IpcpDnsRequest, IpcpNakCorrelation, PcoAddressConfiguration, PcoDecodeError, PcoDecoded,
134+
PcoIpcpDiscard, PcoIpcpDiscardReason, PcoRequest, PcscfAddressRequest, PcscfRequest,
135+
PCO_CONTAINER_DNS_SERVER_IPV4, PCO_CONTAINER_DNS_SERVER_IPV6, PCO_CONTAINER_IPV4_LINK_MTU,
136+
PCO_CONTAINER_P_CSCF_IPV4, PCO_CONTAINER_P_CSCF_IPV6, PCO_CONTAINER_P_CSCF_RESELECTION_SUPPORT,
137+
PCO_HEADER_PPP_FOR_IP_PDN, PCO_MAX_CONTAINERS, PCO_PROTOCOL_IPCP,
135138
};
136139
#[allow(deprecated)]
137140
pub use s2b::{

0 commit comments

Comments
 (0)