RFC 9187
Sequence Number Extension for Windowed Protocols
RFC 9187 ๐: Sequence Number Extension for Windowed Protocols
okay bestie, let's get into RFC 9187 ๐ It's titled Sequence Number Extension for Windowed Protocols, and it's an Independent Submission by J. Touch of Independent Consultant. The category is Informational and the date is January 2022. Its ISSN is 2070-1721. The header doesn't list anything it obsoletes or updates ๐ฆ
Abstract โจ
Sliding window protocols use finite sequence numbers to figure out where each segment goes and what order it's in ๐ฆ Those numbers wrap around and get reused while the protocol is running. This document shows how the endpoints can extend the size of those numbers, so the wrap-and-reuse thing stops mattering, and it doesn't need any extra info in the packet header ๐ธ The extended numbers can then go into encryption and authentication algorithms, so the input bit patterns never repeat over the whole life of a connection ๐
Status of This Memo
this one's not an Internet Standards Track spec, it's just published for information ๐
It's an independent contribution to the RFC Series, separate from any other RFC stream. The RFC Editor picked to publish it at their discretion, and they make zero claims about whether it's worth implementing or deploying ๐ Anything the RFC Editor approves for publication isn't eligible for any level of Internet Standard. (Check Section 2 of RFC 7841 for the details.)
For the current status, any errata, and how to send feedback, head to https://www.rfc-editor.org/info/rfc9187 ๐
Copyright Notice
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved ๐
This document is subject to BCP 78 and to the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info), as they stand on the publication date. Read those carefully, because they spell out your rights and restrictions for this document ๐
Table of Contents
1. Introduction
2. Background
3. Related Discussion
4. Using SNE in Protocol Design
5. Example Code
6. Validation Suite
7. Security Considerations
8. IANA Considerations
9. Informative References
Acknowledgments
Author's Address
1. Introduction ๐
okay so protocols use sequence numbers to keep things in order, and in sliding window systems they also control how much unacknowledged stuff can be floating around at once ๐ช These numbers are finite, so they wrap around during long connections and end up reusing old values ๐
It can be super useful for protocols to track that wrap-around in a separate counter. Then the sequence number and the counter together make a number space that is effectively equivalent and never has to wrap ๐ง This idea was introduced as "Sequence Number Extension" in the TCP Authentication Option (TCP-AO) [RFC5925]. The example there was meant to introduce the concept, but the pseudocode it gives isn't complete ๐
This document brings the formal requirements for Sequence Number Extension (SNE), a code example, and a check sequence that you can use to validate this implementation and any alternate ones ๐ฆ Sequence numbers show up in lots of protocols to support loss detection, reordering, flow control, and congestion control โจ But because the size of a sequence number field is limited, those capabilities can only be supported in certain ways ๐
Under certain conditions, both endpoints of a protocol can track sequence number rollover and effectively extend the sequence number space, without changing the sequence number field used in messages ๐ Those conditions assume that received sequence numbers never vary by more than half the size of the field's space. In other words, they never hop forward or backward by more than half that space ๐ This constraint is typical for sliding window protocols, TCP being a prime example. Still, even though both ends can track rollover unambiguously, it can get surprisingly complex, so this document gives examples and test cases to make that process easier ๐ซถ
This document is for protocol designers who want larger sequence numbers at the endpoints without having to extend the sequence number field in messages. Authentication protocols are a good example, such as TCP-AO [RFC5925] ๐ Using extended sequence numbers should be part of a protocol specification, so that both endpoints can make sure they meet the requirements needed to use them in both places ๐
Here's the roadmap ๐บ๏ธ The rest of this document explains how SNE can be supported, and gives pseudocode showing how received messages can unambiguously work out the right extension value, as long as reordering is constrained. Section 2 gives background on the concept. Section 3 covers currently known uses of SNE. Section 4 explains how SNE is used in protocol design and how it differs from in-band use of sequence numbers. Section 5 provides a framework for testing SNE implementations, including example code for the SNE function, and Section 6 provides a sequence that the code can use for validation. Section 7 wraps up with a discussion of security issues ๐
2. Background ๐ธ
Protocols use sequence numbers to keep messages in order ๐ฌ The transmitter usually increments them either once per message or by the length of the message. The receiver uses them to reorder messages and to detect gaps that point to inferred loss ๐
Sequence numbers are represented inside those messages (for example, in the headers) as values from a finite, unsigned number space ๐งฎ That space is usually represented as a fixed-length bit string, with values ranging from 0..(2^N)-1, inclusive.
Using finite representations has consequences for how these values get used at both the transmitter and the receiver โ ๏ธ Without extra constraints, once the number space "wraps around", the receiver couldn't tell apart two uses of the same value ๐ญ
So extra constraints are needed. Transmitters are typically required to limit reuse until they can assume receivers will successfully tell the two uses of a value apart ๐ซถ The receiver always reads the values it sees on the assumption that successive values never differ by just under half the number space. The receiver can't detect an error in that sequence, but it will read numbers incorrectly if reordering breaks this constraint ๐
The constraint says "forward" values advance by less than half the sequence number space. That way, receivers never see a series of values that breaks the rule ๐ฆ
We define a sequence space like this:
- An unsigned integer within the range of
0..(2^N)-1, meaning N bits. - An operation that increments values in that space by K, where
K < 2^(N-1), meaning less than half the range. Only the transmitter uses this operation ๐ค - An operation that compares two values in that space to determine their order, for example where
X < Ymeans that X comes before Y ๐ข
We assume both sides begin with the same initial value, which can be anywhere in the space ๐ That value is either assumed (for example, 0) before the protocol starts, or coordinated before other messages get exchanged, like TCP Initial Sequence Numbers (ISNs) [RFC0793]. The receiver is assumed to always receive values that are always within (2^N)-1, but successive received values never jump forward or backward by more than 2^(N-1)-1, meaning just under half the range ๐
No other operations are supported. The transmitter isn't allowed to "back up", so values are always used in "increment" order ๐ซ The receiver can't experience loss or gaps larger than 2^(N-1)-1, which is usually enforced either by assumption or by explicit endpoint coordination ๐ค
An SNE is a separate number space that can be combined with the sequence number to make a larger number space that doesn't need to wrap around during a connection ๐
On the transmit side, SNE is pretty straightforward. You increment a local counter each time the sequence number increment wraps around, or you keep a larger local sequence number whose least-significant part is the message sequence number, and whose most-significant part can be treated as the SNE ๐ง The transmitter usually doesn't need to maintain an SNE except when it's used in local computations, like Message Authentication Codes (MACs) in TCP-AO [RFC5925] ๐
The goal of this document is to show that SNE can be done on the receiver side without sending any extra info in messages โจ It defines the stateful function compute_sne() like this:
SNE = compute_sne(seqno)
The compute_sne() function takes the sequence number seen in a received message and works out the matching SNE ๐ง The function keeps persistent local state that tracks the largest SNE and seqno combination received so far. Put SNE and seqno together and you get the equivalent larger sequence number space that can avoid wrap-around ๐
Note that the function defined here can take any series of seqno values and compute their correct SNE, as long as the series never "jumps" more than half the number space "backward" from the largest value seen "forward" ๐
3. Related Discussion ๐
The DNS uses sequence numbers to figure out when a Start of Authority (SOA) serial number is more recent than an earlier one, even when the sequence space wraps [RFC1034][RFC1035] ๐ Using wrapped sequence numbers for sliding windows in network protocols was first described as a sequence number space in [IEN74] ๐ฐ๏ธ
A more recent discussion calls this "serial number arithmetic" and defines a comparison operator that it says was missing from IEN-74 [RFC1982] ๐งฎ That document defines two operations: addition (presumably shifting the window forward) and comparison (defining the order of two values). Addition is limited in that document to values within the range of 0..windowsize/2-1. Comparison is defined there by a set of equations, but that document doesn't give a way for a receiver to compute the correct equivalent SNE. That gap is especially true once you account for sequence number reordering, which this document demonstrates ๐ฅ
4. Using SNE in Protocol Design โจ
okay so, like I said in the intro, message sequence numbers are what make reordering, loss detection, flow control, and congestion control possible ๐ They also help tell apart messages that would otherwise look identical, especially when they keep repeating as part of a sequence or stream ๐
Here's the big thing: the size of the sequence number field in transferred messages decides how much reordering and how many gaps a protocol can handle, and that's capped at half the space of that field ๐ฆ For example, an 8-bit field can reorder and detect losses of anything smaller than 2^7, which is 127 messages. So when we use sequence numbers for reordering, loss detection, flow control, and congestion control, the field size is literally the limit of what those can do ๐
Sequence numbers can also tell messages apart, and this is where it gets messy ๐ If they repeat for messages that are otherwise identical, that can be a problem. Protocols are fine with the repetition because both endpoints know when the sequence number space rolls over ๐ Sometimes it's useful to track that rollover and use the rollover count as an extension to the sequence number, e.g., to tell authentication MACs apart ๐ง This SNE is never sent in any message. The existing sequence number rules keep both ends in sync, for new messages and reordered ones too โจ
The constraints for using SNE were already covered as background in Section 2. The transmitter must never send messages out of sequence beyond half the range of the sequence number field used in messages ๐ซถ The receiver relies on that assumption to figure out whether a received number belongs to a pre-wrap sequence or a post-wrap sequence. ngl this is a big ask, because a receiver can't enforce or detect on its own if the transmitter breaks it ๐ญ So it depends on explicit coordination, like the exchange of acknowledgements, to keep this property true ๐ธ
SNEs are for when it's helpful for both ends to know, without any doubt, whether the sequence number in a message has wrapped, and whether a received message is pre-wrap or post-wrap for each wrap. Both endpoints can use this to make sure every message in an arbitrarily long sequence can be told apart, e.g., for unique MACs ๐
SNE does not extend the actual sequence space of a protocol, so it doesn't extend its tolerance to reordering or gaps either ๐ It also can't improve the dynamic range for flow control or congestion control. There are other somewhat related methods that can, though, like window scaling [RFC7323] (which increases range at the expense of granularity) ๐ฆ
SNE isn't needed if messages are already unique across the whole transfer sequence, e.g., because the sequence number field never wraps around, or because other fields provide that disambiguation, like timestamps ๐
5. Example Code ๐งโ๐ป
The C code below is a verified example of SNE going from 16 to 32 bits ๐ฅ It includes both the framework used for validation and the compute_sne() function. The function is the part you can actually use operationally.
A correct test will print "OK" for each test. An incorrect test will print "ERROR" where that applies ๐
<CODE BEGINS> file "compute_sne.c"
#include <stdio.h>
#include <sys/param.h>
#define distance(x,y) (((x)<(y))?((y)-(x)):((x)-(y)))
#define SNEDEBUG 1
// This is the core code, stand-alone, to compute SNE from seqno
// >> replace this function with your own code to test alternates
unsigned long compute_sne(unsigned long seqno) {
// INPUT: 32-bit unsigned sequence number (low bits)
// OUTPUT: 32-bit unsigned SNE (high bits)
// variables used in this code example to compute SNE:
static unsigned long
RCV_SNE = 0; // high-watermark SNE
static int
RCV_SNE_FLAG = 1; // set during first half rollover
// (prevents re-rollover)
static unsigned long
RCV_PREV_SEQ = 0; // high-watermark SEQ
unsigned long
holdSNE; // temp copy of output
holdSNE = RCV_SNE; // use current SNE to start
if (distance(seqno,RCV_PREV_SEQ) < 0x80000000) {
// both in same SNE range?
if ((seqno >= 0x80000000) && (RCV_PREV_SEQ < 0x80000000)) {
// jumps fwd over N/2?
RCV_SNE_FLAG = 0; // reset wrap increment flag
}
RCV_PREV_SEQ = MAX(seqno,RCV_PREV_SEQ);
// move prev forward if needed
} else {
// both in diff SNE ranges
if (seqno < 0x80000000) {
// jumps forward over zero?
RCV_PREV_SEQ = seqno; // update prev
if (RCV_SNE_FLAG == 0) {
// first jump over zero? (wrap)
RCV_SNE_FLAG = 1;
// set flag so we increment once
RCV_SNE = RCV_SNE + 1;
// increment window
holdSNE = RCV_SNE;
// use updated SNE value
}
} else {
// jump backward over zero
holdSNE = RCV_SNE - 1;
// use pre-rollover SNE value
}
}
#ifdef SNEDEBUG
fprintf(stderr,"state RCV_SNE_FLAG = %1d\n",
RCV_SNE_FLAG);
fprintf(stderr,"state RCV_SNE = %08lx\n", RCV_SNE);
fprintf(stderr,"state RCV_PREV_SEQ = %08lx\n", RCV_PREV_SEQ);
#endif
return holdSNE;
}
int main() {
// variables used as input and output:
unsigned long SEG_SEQ; // input - received SEQ
unsigned long SNE; // output - SNE corresponding
// to received SEQ
// variables used to validate the computed SNE:
unsigned long SEG_HIGH; // input - xmitter side SNE
// -> SNE should match this value
unsigned long long BIG_PREV; // prev 64-bit total seqno
unsigned long long BIG_THIS = 0; // current 64-bit total seqno
// -> THIS, PREV should never jump
// more than half the SEQ space
char *prompt = "Input hex numbers only (0x is optional)\n\n")
"\tHex input\n"
"\t(2 hex numbers separated by whitespace,"
"each with 8 or fewer digits)";
fprintf(stderr,"%s\n",prompt);
while (scanf("%lx %lx",&SEG_HIGH,&SEG_SEQ) == 2) {
BIG_PREV = BIG_THIS;
BIG_THIS = (((unsigned long long)SEG_HIGH) << 32)
| ((unsigned long long)SEG_SEQ);
// given SEG_SEQ, compute SNE
SNE = compute_sne(SEG_SEQ);
fprintf(stderr," SEG_SEQ = %08lx\n", SEG_SEQ);
fprintf(stderr," SNE = %08lx\n", SNE);
fprintf(stderr," SEG_HIGH = %08lx %s\n",SEG_HIGH,
(SEG_HIGH == SNE)? " - OK" : " - ERROR !!!!!!!");
fprintf(stderr,"\t\tthe jump was %16llx %s %s\n",
distance(BIG_PREV,BIG_THIS),
((BIG_PREV < BIG_THIS)?"+":"-"),
(((distance(BIG_PREV,BIG_THIS)) > 0x7FFFFFFF)
? "ILLEGAL JUMP" : "."));
fprintf(stderr,"\n");
fprintf(stderr,"\n");
fprintf(stderr,"%s\n",prompt);
}
}
<CODE ENDS>
6. Validation Suite ๐งช
These numbers are used to validate SNE variants, and they're listed in the order they could legitimately be received ๐ Each line is one 64-bit number, written as two 32-bit hexadecimal numbers with a space in between. They're formatted for use with the example code in Section 5.
A correctly working extended sequence number system should be able to receive the least-significant half (the right side) and compute the correct most-significant half (the left side) ๐ฏ This suite specifically checks both forward and backward jumps in received values that represent legitimate reordering.
00000000 00000000
00000000 30000000
00000000 90000000
00000000 70000000
00000000 a0000000
00000001 00000001
00000000 e0000000
00000001 00000000
00000001 7fffffff
00000001 00000000
00000001 50000000
00000001 80000000
00000001 00000001
00000001 40000000
00000001 90000000
00000001 b0000000
00000002 0fffffff
00000002 20000000
00000002 90000000
00000002 70000000
00000002 A0000000
00000003 00004000
00000002 D0000000
00000003 20000000
00000003 90000000
00000003 70000000
00000003 A0000000
00000004 00004000
00000003 D0000000
7. Security Considerations ๐
Okay, this section is serious bestie ๐ญ Sequence numbers and their extensions can be super useful in a bunch of security contexts. BUT the extension part (the most-significant half) is determined by the sequence values you already exchanged (the least-significant half). So the extension should not be considered as adding entropy for message authentication or encryption. Don't count on it for that ๐จ
8. IANA Considerations ๐ท
This document has no IANA actions. Nothing to register, nothing to update, just vibes โจ
9. Informative References ๐
- [IEN74] Plummmer, W., "Sequence Number Arithmetic", IEN-74, September 1978.
- [RFC0793] Postel, J., "Transmission Control Protocol", STD 7, RFC 793, DOI 10.17487/RFC0793, September 1981, <https://www.rfc-editor.org/info/rfc793>.
- [RFC1034] Mockapetris, P., "Domain names - concepts and facilities", STD 13, RFC 1034, DOI 10.17487/RFC1034, November 1987, <https://www.rfc-editor.org/info/rfc1034>.
- [RFC1035] Mockapetris, P., "Domain names - implementation and specification", STD 13, RFC 1035, DOI 10.17487/RFC1035, November 1987, <https://www.rfc-editor.org/info/rfc1035>.
- [RFC1982] Elz, R. and R. Bush, "Serial Number Arithmetic", RFC 1982, DOI 10.17487/RFC1982, August 1996, <https://www.rfc-editor.org/info/rfc1982>.
- [RFC5925] Touch, J., Mankin, A., and R. Bonica, "The TCP Authentication Option", RFC 5925, DOI 10.17487/RFC5925, June 2010, <https://www.rfc-editor.org/info/rfc5925>.
- [RFC7323] Borman, D., Braden, B., Jacobson, V., and R. Scheffenegger, Ed., "TCP Extensions for High Performance", RFC 7323, DOI 10.17487/RFC7323, September 2014, <https://www.rfc-editor.org/info/rfc7323>.
Acknowledgments ๐ซถ
The need for this document was first noted by Juhamatti Kuusisaari in April 2020, during discussions of the pseudocode in RFC 5925 ๐
Author's Address ๐๏ธ
Joe Touch Manhattan Beach, CA 90266 United States of America
Phone: +1 (310) 560-0334 Email: touch@strayalpha.com
๐ฆ where this page comes from
- ietf: rfc9187 2022-01
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms