WEBRC congestion control (RFC 3738), unverified: no conformance claimed - #98
Draft
jordijoangimenez wants to merge 6 commits into
Draft
Conversation
Problem The receiver joined one multicast group in its constructor and held it for the life of the session, with the join logic inline there and no way to reach it again. A multiple rate congestion control building block works by moving a receiver between a session's channels, so that shape makes one impossible to add. [code-derived] Basis RFC 5775 clause 2.1, on the session shape the building block assumes: "An ALC session comprises multiple channels originating at a single sender" RFC 3450 clause 2.2 requires such a building block for plain ALC, and RFC 5775 clause 2.2 names RFC 3738 for version 2. Neither is implemented here and neither is claimed. This is the prerequisite they both need, and it is useful on its own for a session announced on more than one address. Raised by reading the authority during this work Change Receiver owns the socket, so the membership belongs there. The constructor's inline join, which handled source-specific and any-source multicast over both families, is extracted into set_group_membership(group, join) and now serves leaving as well: the source-specific paths gain their IP_DROP_SOURCE_MEMBERSHIP and MCAST_LEAVE_SOURCE_GROUP counterparts, the any-source paths their leave_group(). join_channel() and leave_channel() expose it, keeping the set of joined groups so a redundant call is a no-op that reports no change rather than a socket error. The interface, and the source where the session is source-specific, are retained from construction so a later join uses the same ones. No behaviour changes for a session that never calls the new methods: the constructor takes the same path it did, through the extracted function. Verification T1: 66 cases pass, 3 new, covering the constructed group being recorded as joined, a further channel joined and left, and redundant calls reporting no change. The existing suite, including the end-to-end multicast transfers, passes unchanged, which is what shows the extraction did not alter the constructor's behaviour. Not in this change Nothing calls these. No congestion control building block, no WEBRC, no CCI content: the field is still sent as zeros, which is correct while no building block defines it. Nothing here claims conformance to RFC 3450 clause 2.2 or RFC 5775 clause 2.2.
Problem Transmitter held one socket and one destination for the life of the session, so a session could only ever have one channel. A multiple rate congestion control building block sends to several channels at different rates and lets each receiver choose how many it joins, which that shape makes impossible. [code-derived] Basis RFC 5775 clause 2.1, on the session shape such a building block assumes: "An ALC session comprises multiple channels originating at a single sender" RFC 3450 clause 2.2 requires such a building block for plain ALC and RFC 5775 clause 2.2 names RFC 3738 for version 2. Neither is implemented here and neither is claimed. This is the sending half of the prerequisite they need; the receiving half landed in the previous commit. Raised by reading the authority during this work Change add_channel() and remove_channel(), with channel 0 the one given at construction, which is WEBRC's base channel and cannot be removed: a session with no channels is not a session. An added channel takes the same socket options and source-address binding as the first, so every channel leaves from the announced source and behaves alike. Nothing sends on an added channel. The send path is untouched and still uses channel 0, so a session that never calls these behaves exactly as before, which the unchanged end-to-end transfers demonstrate. Verification T1: 69 cases pass, 3 new, covering a session starting with one channel, channels added and removed with the indices behaving as documented, and the constructed channel and a nonexistent one both refusing removal. Not in this change No sending on added channels, no rate per channel, no time slots, no CCI content: the field is still zeros, which is correct while no building block defines it. Nothing claims conformance to RFC 3450 clause 2.2 or RFC 5775 clause 2.2.
Problem Congestion control is the one mandatory obligation the library does not meet for a session outside the 3GPP profiles. Nothing of the building block existed, so there was nothing for a sender to drive channels from. [source-derived] Basis RFC 5775 clause 2.2: "At a minimum, implementations of ALC MUST support [RFC3738]." RFC 3450 clause 2.2 imposes the same obligation without naming a building block for plain ALC. RFC 3738 clause 3.1.1 gives the inputs and the quantities derived from them, and clause 3.1.2 the wave shape: "wave channel i is active during time slots i-N+1 modulo T, i-N+2 modulo T, ..., i and is quiescent for time slots i+1 modulo T, i+2 modulo T, ..., i+Q modulo T." Not needed by a 3GPP profile: TS 26.346 clause 7.2.4 excludes congestion control for MBMS download, and clause 7.2.7 fixes the CCI at a 32-bit zero. Raised by reading the authority during this work Change A new Webrc namespace holding the sender's schedule and nothing else: the derived quantities SR_P, BCR_b, L, Q and T from clause 3.1.1, which channels are active in a given time slot, and the rate of the base channel and of a wave within a slot. Pure arithmetic with no I/O, so it is testable against the clause's own formulas without a running session. Inputs are validated rather than trusted. P at exactly 1 makes L undefined through log(P), and a cycle longer than 255 slots cannot be expressed in the 8-bit channel number of the short-format Congestion Control Information, since the base channel takes the value T. Both are refused. Verification T1: 86 cases pass, 17 new. The derived quantities are checked against the clause's formulas recomputed independently in the test, including the recommended TSD of 10 and QD of 300 giving Q=30 and T=34; the wave schedule is checked for its own N slots, for wrapping around the cycle, and for exactly N wave channels being active in every slot of a full cycle; the rates for the endpoints the clause states, for continuity across a slot boundary and for strict decrease within a slot. Not in this change Nothing sends. No packet carries this: the Congestion Control Information field is still zeros. The receiver half of RFC 3738, clause 3.2, is absent entirely. Nothing here claims conformance to RFC 3738, RFC 3450 clause 2.2 or RFC 5775 clause 2.2, and the deviation the clause permits at the start of a wave to hold the aggregate rate constant is not implemented.
…ies it Problem The Congestion Control Information field was always sent as zeros, with no way for a congestion control building block to put anything in it. RFC 3738's channels cannot be told apart by a receiver without it. [code-derived] Basis RFC 3738 clause 5.1 gives the short format as three fields totalling 32 bits, which is the width the LCT header's C=0 already selects, so carrying it costs nothing the header did not spend: "CTSI indicates the index of the current time slot." "CN is the channel number that this packet belongs to." "The PSN of each packet is scoped by its CN value." Zeros remain correct where no building block is running, and are required of a 3GPP session. TS 26.346 V18.2.0 clause 7.2.7: "-The length of the CCI (Congestion Control Identifier) field shall be 32 bits and it is assigned a value of zero (C=0)." Raised by reading the authority during this work Change AlcPacket takes an optional CongestionControlInfo and writes CTSI, channel number and a network-order packet sequence number into the field when given one. Omitted, nothing is written and the already-zeroed buffer stands, so every existing caller including every 3GPP session sends exactly what it sent before. Verification T1: 89 cases pass, 3 new, covering the field left zero when absent, each byte in the order the clause lays out when present, and the 2^16-1 sequence number the clause reserves for the last packet of a wave. Not in this change Nothing supplies one. The transmitter does not yet drive channels from the schedule, so no packet the library sends carries a non-zero field. No conformance to RFC 3738 is claimed.
Problem The WEBRC schedule existed but nothing drove it: every packet still went to the session's single destination with a zero Congestion Control Information field, so a receiver had no way to tell one channel of a session from another. [code-derived] Basis RFC 5775 clause 2.2: "At a minimum, implementations of ALC MUST support [RFC3738]." RFC 3738 clause 5.1 on what each packet carries: "CTSI indicates the index of the current time slot." and "The Current Time Slot Index increases by one modulo T each TSD seconds at the sender". Clause 3.1.2 on the channel numbering: "Recall that CN = T for the base channel and CN = 0,1,...,T-1 for the wave channels." Refused under a 3GPP profile, which excludes congestion control altogether. TS 26.346 V18.2.0 clause 7.2.4: "For simplicity of congestion control, FLUTE channelization shall be provided by a single FLUTE channel with single rate transport." Raised by reading the authority during this work Change enable_webrc() takes the parameters and one address per wave channel, opens a channel for each, and starts the time slot clock. The session's own destination becomes the base channel and takes CN = T. Each packet then goes to one of the channels active in the current slot and carries the Congestion Control Information naming it, with a per-channel sequence number counting consecutively modulo 2^16. The number of addresses must match the T the parameters derive, and a mismatch is refused at setup: a session sending waves to the wrong number of channels is not the session its description announces. Verification T1: 94 cases pass, 5 new, covering refusal under both 3GPP profiles, one channel opened per wave channel with the base channel making T+1, refusal of the wrong number of addresses, the base channel taking CN = T while added channel i is wave channel i-1, and no Congestion Control Information before the building block is enabled. Not in this change Channels active in a slot are taken in turn rather than weighted by the per-slot rates of clause 3.1.2, so the wave shape is not yet reproduced on the wire; the rate functions exist and are tested but nothing calls them. The deviation clause 3.1.2 permits at the start of a wave is not implemented. The receiver half, clause 3.2, is absent. No conformance to RFC 3738, RFC 3450 clause 2.2 or RFC 5775 clause 2.2 is claimed, and none should be until the wave shape and a receiver exist and have been tested against another implementation.
Problem
The sender half of the building block ran, but nothing on the receive side decided which channels
to be joined to, which is where a multiple rate congestion control building block does its
work. [code-derived]
Basis
RFC 3738 clause 3.2.2.1 gives the loss estimate as two filters over packet and loss counts, with
the update rules stated as formulas over W, X, Y and Z and the recommended smoothing constants
Nu = 0.3 and Delta = 0.3.
RFC 3738 clause 3.2.2.2 gives the round-trip estimate: "ARTT is updated to
max{P*ARTT,(1-Rho)*ARTT+Rho*MRTT}", with Alpha recommended at 0.25.
RFC 3738 clause 3.2.2.3: "REQN = 1/(ARTT*sqrt{LOSSP}(0.816 + 7.35*LOSSP*(1+32*LOSSP^2)))"
RFC 3738 clause 3.2.2.7: "TRATE = min{max{SSR_P, REQN}, MRR_P}. When SSR_P = infinity, TRATE is
computed as TRATE = min{4*TRR_P, MRR_P}."
RFC 3738 clause 3.2.3.6 gives the join decision, including "If NWC = N the receiver MUST not
join." and the rate inequality against ARR_P.
Raised by
reading the authority during this work
Change
Webrc::ReceiverController, holding the loss and round-trip estimates, the rate equation, the
target rate and the join decision with the mandatory refusals of clause 3.2.3.6.
It performs no I/O and joins nothing. It is fed events and answers whether a join is permitted,
and the caller acts or does not. That is deliberate rather than unfinished: a rate control loop
cannot be validated against itself, and wiring it to Receiver's joins would put an unverified
control loop in charge of how much bandwidth a deployment takes.
Verification
T1: 105 cases pass, 11 new. The loss estimate is checked for staying bounded without loss and
rising with frequent loss events; the rate equation against the clause's formula recomputed in the
test; the base channel setting the round trip directly and the floor holding against a negative
measurement, which the clause says can occur; the target rate during start-up and its cap; and
each mandatory refusal of clause 3.2.3.6 separately, each shown to change the answer.
Not in this change
Nothing calls it. The optional checks of clause 3.2.3.6, on RR_P against its maximum since the
last join and on TRR_P being greatly below ARR_P, are not applied, and the constant-aggregate-rate
variant of the inequality is not distinguished, the stricter form being used. Slow start keeps
SSR_P at infinity: clause 3.2.3.4's adjustment at a loss event is absent. The reception rates of
clause 3.2.2.5 are supplied by the caller rather than measured here.
No conformance to RFC 3738, RFC 3450 clause 2.2 or RFC 5775 clause 2.2 is claimed, and none should
be until this has been tested against another implementation on a real network.
jordijoangimenez
marked this pull request as draft
August 23, 2026 07:31
This was referenced Aug 23, 2026
FLUTE version 2 (RFC 6726) signalling, off by default: step one of three, no conformance claimed
#65
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of #62, where these commits originally landed. They were separated because they are a
self-contained subsystem, they close no issue, they touch nothing either 3GPP profile uses, and they
are the only part of that work that is unverified. Keeping them there would have forced a
reviewer to accept an unverified congestion controller alongside conformance fixes that are ready.
Dependencies and issues
Depends on: #62, and is based on its branch, so #62 merges first.
Paired with: #61, #64, #65 and #68, independent of this one, either order.
Closes on merge: nothing. No issue tracks this; it is the last mandatory obligation of RFC 3450
clause 2.2 rather than a reported defect.
What this is
The WEBRC congestion control building block, RFC 3738, in outline. Six commits, 12 files,
roughly 1,400 added lines of which about a third are tests.
receiver: join and leave a session's multicast channels at runtimetransmitter: carry more than one channel in a sessionadd_channel()/remove_channel(), channel 0 the base channelwebrc: the sender-side schedule of RFC 3738alc: carry Congestion Control Information when a building block supplies ittransmitter: run the WEBRC sender over the session's channelsenable_webrc(), one channel per wave channel, per-channel sequence numberswebrc: the receiver control loop of RFC 3738 clause 3.2Why it exists
RFC 3450 clause 2.2, which binds plain ALC:
RFC 5775 clause 2.2, which binds FLUTE version 2, names the building block:
Not for the 3GPP profiles, which refuse it
enable_webrc()throws underProfile::Ts26517andProfile::Ts26346. TS 26.346 clause 7.2.4:Clause 7.2.7 fixes the CCI at a 32-bit zero besides. So this changes nothing for MBSTF, rt-mbms-mw,
or any other 3GPP consumer: their sessions behave exactly as before, on one channel with a zero CCI,
which is what those clauses require.
No conformance is claimed, and none should be
Three gaps, each recorded in the commit that left it:
join. Wiring an unverified control loop to real joins would put it in charge of how much bandwidth
a deployment takes.
wave schedule reaches the wire; the wave shape does not. The rate functions exist and are
tested, and nothing calls them.
SSR_Pstays at infinity; clause 3.2.3.4's adjustment at a loss event isnot implemented.
Beyond those, the reason to withhold a claim is structural: a rate-control loop cannot be validated
against itself. Every test here checks a formula against the clause that states it, which catches
transcription errors and nothing else. Whether the loop converges, backs off, or oscillates against a
real peer on a real network is untested and untestable in this repository. RFC 3738 is also
Experimental, and wrong congestion control degrades other people's networks, not only the sender's.
Treat
Profile::Unprofiledand FLUTE version 2 as non-conformant on RFC 3450 clause 2.2 andRFC 5775 clause 2.2 until this has been tested against a second implementation.
What would close it
Weight the sender by the per-slot rates; implement slow start; wire the receiver controller to real
joins; then test against another implementation. All four, in that order.
Verification
T1: 105 cases pass on this branch, 47 of them new. Each formula is checked against the clause that
states it, including the derived quantities of clause 3.1.1 recomputed independently in the test,
exactly N wave channels active in every slot of a full cycle, the rate equation of clause 3.2.2.3,
and each mandatory refusal of clause 3.2.3.6 shown separately to change the answer.
What that verification does not cover: any running session, any interaction with a real peer, and
the control loop's behaviour over time. See above.
Change type
New feature, not for 3GPP MBS or MBMS use, and not yet fit to enable in a deployment.