Ljh684/moonnet-lab/src/tcp does not have a README file

    CongestionControl

    pub(open) trait CongestionControl {
    fn window(Self) -> Int
    fn ssthresh(Self) -> Int
    fn name(Self) -> String
    fn phase(Self) -> String
    fn on_ack(Self, acked_bytes : Int, mss : Int, rtt_ps : Int64, now_ps : Int64) -> Unit
    fn on_fast_retransmit(Self, flight_size : Int, mss : Int) -> Unit
    fn on_duplicate_ack(Self, mss : Int) -> Unit
    fn on_recovery_exit(Self, mss : Int) -> Unit
    fn on_partial_ack(Self, mss : Int) -> Unit
    fn on_timeout(Self, flight_size : Int, mss : Int) -> Unit
    }

    How a connection decides how much data it may have in flight.

    The connection owns the mechanics — sequence numbers, timers, retransmission — and asks this object only for a window and tells it about events. That split is what makes Reno, CUBIC and Vegas interchangeable: they differ in how they react to the same four events, not in how the connection works.

    flight_size is how many bytes are sent but not yet acknowledged when the event happens. Losss-based algorithms need it because the amount to cut is a fraction of what is actually outstanding, not a fraction of the window the algorithm previously believed it had.

    Buffered

    type Buffered

    A segment that arrived before its predecessors did.

    Cubic

    pub struct Cubic {
    mss : Int
    cwnd : Int
    ssthresh : Int
    w_max : Int
    cwnd_prior : Int
    epoch_start : Int64
    origin : Int
    k_ps : Int64
    w_est : Double
    counter : Int
    rtt_ps : Int64
    in_recovery : Bool
    fast_convergence : Bool
    }

    CUBIC (RFC 9438).

    Reno's window grows linearly with time, so its rate depends on the round trip time: the same number of round trips buys the same number of segments, and a path with a large bandwidth-delay product needs an impractical number of them before it is full. CUBIC replaces the linear growth with a cubic function of the time since the last congestion event:

    W(t) = C * (t - K)^3 + W_max

    W_max is the window at the previous loss and K is how long the curve takes to climb back to it. Just after a loss the window is far below W_max, so the curve is steep and the connection recovers quickly. As it approaches W_max the curve flattens, so probing for more bandwidth becomes slow and careful. That shape is the whole idea: be aggressive where the path has already proved it can take the traffic, and cautious where it has not.

    Everything inside this algorithm is measured in segments, because that is the unit RFC 9438 is written in and the constant C has the unit segments per second cubed. Bytes appear only at the boundary, where the connection asks for a window.

    Cubic::new

    fn Cubic::new(mss : Int) -> Cubic

    Cubic::with_fast_convergence

    fn Cubic::with_fast_convergence(self : Cubic, enabled : Bool) -> Cubic

    Enable the fast convergence heuristic (RFC 9438 section 4.7). It only helps when several CUBIC flows share a bottleneck.

    Flags

    pub(all) struct Flags {
    syn : Bool
    ack : Bool
    fin : Bool
    rst : Bool
    } derive(Eq,
    Debug
    )

    The control bits a segment carries.

    Flags::ack

    fn Flags::ack() -> Flags

    Flags::data

    fn Flags::data() -> Flags

    A segment with no control bits set: a plain data segment.

    Flags::fin_ack

    fn Flags::fin_ack() -> Flags

    Flags::render

    fn Flags::render(self : Flags) -> String

    Flags::sequence_length

    fn Flags::sequence_length(self : Flags, payload : Int) -> Int

    How many sequence numbers this segment consumes. SYN and FIN each occupy one position in the sequence space even though they carry no payload; forgetting that is the classic off-by-one that makes a handshake appear to work and the first data segment appear to be missing one byte.

    Flags::syn

    fn Flags::syn() -> Flags

    Flags::syn_ack

    fn Flags::syn_ack() -> Flags

    InFlight

    type InFlight

    A segment that has been sent but not yet acknowledged.

    The whole segment is kept, not just its sequence range, because a lost segment has to be sent again and there is nowhere else to get the bytes from. sequence_length differs from payload_length by one for a SYN or a FIN, so acknowledgment accounting can report application bytes without the control bits corrupting the totals.

    LinkPair

    Two connections wired through a pair of one-way links.

    The wiring is the interesting part. A connection cannot be handed a link at construction time, because the link needs the other connection in its delivery callback, and that connection does not exist yet. Instead each connection starts with a no-op sender and is connected to its link afterwards, which keeps the dependency a straight line rather than a cycle and leaves the wiring visible in one place.

    LinkPair::connect

    fn LinkPair::connect(self : LinkPair, sim :
    Sim
    ) -> Unit

    Start the handshake from the client side.

    LinkPair::new

    fn LinkPair::new(sim :
    Sim
    , client_config : TcpConfig, server_config : TcpConfig, client_to_server :
    LinkSpec
    , server_to_client :
    LinkSpec
    , seed : UInt64) -> LinkPair

    LinkPair::new_with_cc

    fn LinkPair::new_with_cc(sim :
    Sim
    , client_config : TcpConfig, server_config : TcpConfig, client_to_server :
    LinkSpec
    , server_to_client :
    LinkSpec
    , seed : UInt64, cc : &CongestionControl) -> LinkPair

    The same wiring, with a chosen congestion control algorithm on the client.

    Only the client is given one because only the client sends bulk data here; the server's algorithm would never be consulted. Handing the same object to both ends would be a mistake anyway: the window and threshold are per connection, so sharing one would let the two ends corrupt each other's state.

    LinkPair::summary

    fn LinkPair::summary(self : LinkPair) -> String

    A one-line-per-end summary.

    NoCongestionControl

    pub struct NoCongestionControl {
    limit : Int
    }

    No congestion control at all: the window is effectively unbounded, so the peer's advertised window is the only limit on the sending rate.

    This is not a straw man. It is the control case that shows what congestion control is buying: the same scenario run with and without it differs only in how the sender behaves when a packet goes missing.

    NoCongestionControl::new

    Reno

    pub struct Reno {
    cwnd : Int
    ssthresh : Int
    in_recovery : Bool
    }

    Classical Reno congestion control (RFC 5681, with the NewReno recovery of RFC 6582).

    Reno makes one judgement: a lost packet means the path is congested, and the response is to halve the sending rate and then creep back up. It has no memory of what the path has delivered before, which is why it gives ground to CUBIC on long fat links and why it is still the reference every other algorithm is measured against.

    Reno::new

    fn Reno::new(mss : Int) -> Reno

    Segment

    pub(all) struct Segment {
    seq : Seq
    ack : Seq
    window : Int
    flags : Flags
    payload : Bytes
    tag : UInt64
    } derive(
    Debug
    )

    A TCP segment.

    window is what the sender of this segment is willing to receive from now on, in bytes: flow control is carried on every segment, including pure ACKs, so a receiver that frees buffer space can always tell the peer about it.

    Segment::label

    fn Segment::label(self : Segment) -> String

    A compact label used in journals, for example SYN+ACK seq=1000 ack=4001.

    Segment::new

    fn Segment::new(seq : Seq, ack : Seq, window : Int, flags : Flags, payload : Bytes, tag : UInt64) -> Segment

    Segment::payload_length

    fn Segment::payload_length(self : Segment) -> Int

    Bytes of payload in this segment.

    Segment::wire_size

    fn Segment::wire_size(self : Segment) -> Int

    The number of bytes this segment occupies on the wire.

    Seq

    pub(all) struct Seq {
    value : UInt
    } derive(Eq,
    Debug
    )

    A 32-bit TCP sequence number.

    Sequence numbers wrap, so they cannot be compared with <. Two numbers on opposite sides of the wrap point are "close" to each other even though their numeric distance is enormous, and an implementation that compares them as plain integers works perfectly until the day it does not. All comparisons go through before, which measures the distance in the wrapping space where half the space is "ahead" and half is "behind".

    Seq::advance

    fn Seq::advance(self : Seq, length : Int) -> Seq

    Advance by length bytes, wrapping like real TCP.

    Seq::after

    fn Seq::after(self : Seq, other : Seq) -> Bool

    True when self comes after other.

    Seq::before

    fn Seq::before(self : Seq, other : Seq) -> Bool

    True when self comes before other in the wrapping sequence space.

    Seq::distance_to

    fn Seq::distance_to(self : Seq, other : Seq) -> Int

    Signed distance from self to other, in bytes.

    Seq::of

    fn Seq::of(value : UInt) -> Seq

    Seq::to_string

    fn Seq::to_string(self : Seq) -> String

    TcpConfig

    pub(all) struct TcpConfig {
    mss : Int
    receive_window : Int
    initial_seq : UInt
    two_msl :
    Time

    rto_initial :
    Time

    rto_min :
    Time

    rto_max :
    Time

    max_retransmits : Int
    } derive(Eq,
    Debug
    )

    Connection parameters. Everything a run depends on is explicit here, so a scenario file can describe a connection completely.

    TcpConfig::new

    fn TcpConfig::new(mss : Int, receive_window : Int, initial_seq : UInt) -> TcpConfig

    TcpConfig::with_rto

    The same configuration with a different retransmission budget, for scenarios that trade realism for a shorter stall.

    TcpConnection

    pub struct TcpConnection {
    config : TcpConfig
    cc : &CongestionControl
    flow : UInt64
    state : TcpState
    emit : (Segment) -> Unit
    snd_una : Seq
    snd_nxt : Seq
    rcv_nxt : Seq
    peer_window : Int
    send_queue : Bytes
    send_offset : Int
    fin_seq : Seq?
    peer_fin : Bool
    inflight : Array[InFlight]
    inflight_head : Int
    reorder : Array[Buffered]
    reorder_bytes : Int
    capture : Bool
    delivered_bytes : Int
    delivered : Bytes
    has_delivered_chunk : Bool
    last_delivery_ps : Int64
    max_delivery_gap_ps : Int64
    segments_sent : Int
    segments_received : Int
    bytes_sent : Int
    bytes_acked : Int
    duplicate_acks : Int
    retransmits : Int
    fast_retransmits : Int
    timeouts : Int
    consecutive_timeouts : Int
    dup_ack_count : Int
    in_recovery : Bool
    recover_until : Seq
    rtt_samples : Int
    srtt_ps : Double
    rttvar_ps : Double
    has_rtt_sample : Bool
    rto :
    Time

    timer_running : Bool
    timer_generation : Int
    tlp_probe_pending : Bool
    tlp_probe_end : Seq?
    tlp_probes : Int
    tlp_timer_running : Bool
    tlp_timer_generation : Int
    }

    One end of a TCP connection.

    The connection is deliberately passive: it never schedules anything for itself. It reacts to segments handed to it, and it hands outgoing segments to whatever emit is set to. That keeps the TCP logic testable without a network, and keeps "when does this happen" in the simulator rather than inside the protocol.

    TcpConnection::bytes_acked

    fn TcpConnection::bytes_acked(self : TcpConnection) -> Int

    Bytes of payload the peer has acknowledged.

    TcpConnection::bytes_sent

    fn TcpConnection::bytes_sent(self : TcpConnection) -> Int

    Bytes of payload this end handed to the network.

    TcpConnection::capture_delivered

    fn TcpConnection::capture_delivered(self : TcpConnection, enabled : Bool) -> Unit

    Ask the connection to retain the byte stream it delivers, so that a test can compare it against what was sent. Off by default; see the field comment.

    TcpConnection::cc_name

    fn TcpConnection::cc_name(self : TcpConnection) -> String

    The name of the congestion control algorithm.

    TcpConnection::cc_phase

    fn TcpConnection::cc_phase(self : TcpConnection) -> String

    Which phase the congestion control algorithm reports.

    TcpConnection::close

    Close the connection. Only the ESTABLISHED and CLOSE-WAIT transitions are modeled; a FIN sent from the middle of the handshake is a case this simulator does not need yet.

    TcpConnection::connect

    Become the active side: send a SYN.

    TcpConnection::cwnd

    fn TcpConnection::cwnd(self : TcpConnection) -> Int

    The congestion window currently in force, in bytes.

    TcpConnection::delivered

    fn TcpConnection::delivered(self : TcpConnection) -> Bytes

    Everything this end has delivered to the application, in order.

    TcpConnection::delivered_bytes

    fn TcpConnection::delivered_bytes(self : TcpConnection) -> Int

    How many bytes have been delivered to the application, in order.

    TcpConnection::duplicate_acks

    fn TcpConnection::duplicate_acks(self : TcpConnection) -> Int

    How many times an acknowledgment arrived that carried no new information.

    TcpConnection::fast_retransmits

    fn TcpConnection::fast_retransmits(self : TcpConnection) -> Int

    Segments sent again because duplicate acknowledgments revealed a hole, without waiting for the timer.

    TcpConnection::in_recovery

    fn TcpConnection::in_recovery(self : TcpConnection) -> Bool

    Whether the connection is currently repairing a hole discovered by duplicate acknowledgments.

    TcpConnection::inflight

    fn TcpConnection::inflight(self : TcpConnection) -> Int

    Segments still waiting for an acknowledgment.

    TcpConnection::listen

    fn TcpConnection::listen(self : TcpConnection) -> Unit

    Become the passive side: wait for someone to connect.

    TcpConnection::max_delivery_gap

    Longest gap between consecutive in-order data deliveries during a traced run.

    TcpConnection::new

    A connection controlled by Reno, which is what a TCP connection normally is.

    TcpConnection::new_with_cc

    fn TcpConnection::new_with_cc(config : TcpConfig, cc : &CongestionControl) -> TcpConnection

    A connection with a specific congestion control algorithm, including NoCongestionControl for experiments that want the peer's window to be the only limit.

    TcpConnection::on_segment

    fn TcpConnection::on_segment(self : TcpConnection, sim :
    Sim
    , segment : Segment) -> Unit

    Handle one received segment.

    TcpConnection::peer_window

    fn TcpConnection::peer_window(self : TcpConnection) -> Int

    The most recent window the peer advertised.

    TcpConnection::rcv_nxt

    fn TcpConnection::rcv_nxt(self : TcpConnection) -> Seq

    TcpConnection::retransmits

    fn TcpConnection::retransmits(self : TcpConnection) -> Int

    Segments sent again because the timer expired.

    TcpConnection::rto

    The retransmission timeout currently in force, including any backoff.

    TcpConnection::rtt_samples

    fn TcpConnection::rtt_samples(self : TcpConnection) -> Int

    How many round-trip samples have been folded into the estimate.

    TcpConnection::segments_received

    fn TcpConnection::segments_received(self : TcpConnection) -> Int

    TcpConnection::segments_sent

    fn TcpConnection::segments_sent(self : TcpConnection) -> Int

    TcpConnection::send

    fn TcpConnection::send(self : TcpConnection, sim :
    Sim
    , data : Bytes) -> Unit

    Queue application data and send as much of it as the peer's window allows.

    TcpConnection::set_emit

    fn TcpConnection::set_emit(self : TcpConnection, emit : (Segment) -> Unit) -> Unit

    Install the outgoing path. Until this is called the connection is a black hole that accepts segments and produces none.

    TcpConnection::set_flow

    fn TcpConnection::set_flow(self : TcpConnection, id : UInt64) -> Unit

    Give this connection an identity, so that several connections sharing a link lose different packets. Flows are numbered from one; zero means "the only connection", and leaves packets judged on their sequence number alone.

    TcpConnection::snd_nxt

    fn TcpConnection::snd_nxt(self : TcpConnection) -> Seq

    TcpConnection::snd_una

    fn TcpConnection::snd_una(self : TcpConnection) -> Seq

    TcpConnection::srtt

    The smoothed round-trip time, once a sample exists.

    TcpConnection::ssthresh

    fn TcpConnection::ssthresh(self : TcpConnection) -> Int

    The slow start threshold of the congestion control algorithm.

    TcpConnection::state

    fn TcpConnection::state(self : TcpConnection) -> TcpState

    The current state.

    TcpConnection::timeouts

    fn TcpConnection::timeouts(self : TcpConnection) -> Int

    How many times the retransmission timer has expired.

    TcpConnection::timer_running

    fn TcpConnection::timer_running(self : TcpConnection) -> Bool

    Whether the retransmission timer is currently armed.

    TcpConnection::tlp_probes

    fn TcpConnection::tlp_probes(self : TcpConnection) -> Int

    How many tail-loss probes this connection has sent.

    TcpState

    pub(all) enum TcpState {
    Closed
    Listen
    SynSent
    SynReceived
    Established
    FinWait1
    FinWait2
    CloseWait
    Closing
    LastAck
    TimeWait
    } derive(Eq,
    Debug
    )

    The connection states of RFC 793.
    impl Show for TcpState

    TcpState::render

    fn TcpState::render(self : TcpState) -> String

    cubic

    fn cubic(mss : Int) -> &CongestionControl

    CUBIC, ready to hand to TcpConnection::new_with_cc.

    header_bytes

    fn header_bytes() -> Int

    A TCP header is 20 bytes when it carries no options. This simulator has no options yet, so the number is exact rather than approximate.

    initial_window

    fn initial_window(mss : Int) -> Int

    The initial window of RFC 6928: at most ten segments, at least two, and at least 14 600 bytes once the segments are large enough to make that the smaller number.

    Ten segments is a deliberate compromise. Two segments take too long to reach a useful rate on a short path, and a larger window would put more than a comfortable burst into a network that has not been measured yet.

    reno

    fn reno(mss : Int) -> &CongestionControl

    Reno, ready to hand to TcpConnection::new_with_cc.

    without_congestion_control

    fn without_congestion_control() -> &CongestionControl

    A control object that imposes no limit, ready to hand to TcpConnection::new_with_cc.