Skip to content

WEBRC congestion control (RFC 3738), unverified: no conformance claimed - #98

Draft
jordijoangimenez wants to merge 6 commits into
feature/wave3-rfc6726-compliance-fixesfrom
feature/webrc-congestion-control
Draft

WEBRC congestion control (RFC 3738), unverified: no conformance claimed#98
jordijoangimenez wants to merge 6 commits into
feature/wave3-rfc6726-compliance-fixesfrom
feature/webrc-congestion-control

Conversation

@jordijoangimenez

Copy link
Copy Markdown
Contributor

Base branch. This pull request is based on feature/wave3-rfc6726-compliance-fixes, the head of
#62, not on development, because it builds on the profile model added there. The diff therefore
shows only this pull request's own six commits. GitHub will retarget it when #62 merges.

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.

Commit What it adds
receiver: join and leave a session's multicast channels at runtime membership as an operation rather than a constructor step, source-specific and any-source, both address families
transmitter: carry more than one channel in a session add_channel() / remove_channel(), channel 0 the base channel
webrc: the sender-side schedule of RFC 3738 the clause 3.1 derived quantities, wave activity and rates, as pure arithmetic
alc: carry Congestion Control Information when a building block supplies it the clause 5.1 short format, CTSI / CN / PSN
transmitter: run the WEBRC sender over the session's channels enable_webrc(), one channel per wave channel, per-channel sequence numbers
webrc: the receiver control loop of RFC 3738 clause 3.2 loss and round-trip estimates, rate equation, target rate, join decision

Why it exists

RFC 3450 clause 2.2, which binds plain ALC:

Implementors of ALC MUST implement a multiple rate feedback-free congestion control building block
that is in accordance to RFC 2357 [12].

RFC 5775 clause 2.2, which binds FLUTE version 2, names the building block:

At a minimum, implementations of ALC MUST support [RFC3738].

Not for the 3GPP profiles, which refuse it

enable_webrc() throws under Profile::Ts26517 and Profile::Ts26346. TS 26.346 clause 7.2.4:

For simplicity of congestion control, FLUTE channelization shall be provided by a single FLUTE
channel with single rate transport.

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:

  • Nothing calls the receiver controller. It answers whether a join is permitted; it does not
    join. Wiring an unverified control loop to real joins would put it in charge of how much bandwidth
    a deployment takes.
  • The sender takes active channels in turn rather than weighting them by the per-slot rates. The
    wave schedule reaches the wire; the wave shape does not. The rate functions exist and are
    tested, and nothing calls them.
  • Slow start is absent. SSR_P stays at infinity; clause 3.2.3.4's adjustment at a loss event is
    not 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::Unprofiled and FLUTE version 2 as non-conformant on RFC 3450 clause 2.2 and
RFC 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.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant