From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../dcsctp/socket/transmission_control_block.cc | 333 +++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 third_party/libwebrtc/net/dcsctp/socket/transmission_control_block.cc (limited to 'third_party/libwebrtc/net/dcsctp/socket/transmission_control_block.cc') diff --git a/third_party/libwebrtc/net/dcsctp/socket/transmission_control_block.cc b/third_party/libwebrtc/net/dcsctp/socket/transmission_control_block.cc new file mode 100644 index 0000000000..0621b48e80 --- /dev/null +++ b/third_party/libwebrtc/net/dcsctp/socket/transmission_control_block.cc @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "net/dcsctp/socket/transmission_control_block.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/types/optional.h" +#include "net/dcsctp/packet/chunk/data_chunk.h" +#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h" +#include "net/dcsctp/packet/chunk/idata_chunk.h" +#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h" +#include "net/dcsctp/packet/chunk/reconfig_chunk.h" +#include "net/dcsctp/packet/chunk/sack_chunk.h" +#include "net/dcsctp/packet/sctp_packet.h" +#include "net/dcsctp/public/dcsctp_options.h" +#include "net/dcsctp/public/types.h" +#include "net/dcsctp/rx/data_tracker.h" +#include "net/dcsctp/rx/reassembly_queue.h" +#include "net/dcsctp/socket/capabilities.h" +#include "net/dcsctp/socket/stream_reset_handler.h" +#include "net/dcsctp/timer/timer.h" +#include "net/dcsctp/tx/retransmission_queue.h" +#include "net/dcsctp/tx/retransmission_timeout.h" +#include "rtc_base/logging.h" +#include "rtc_base/strings/string_builder.h" + +namespace dcsctp { + +TransmissionControlBlock::TransmissionControlBlock( + TimerManager& timer_manager, + absl::string_view log_prefix, + const DcSctpOptions& options, + const Capabilities& capabilities, + DcSctpSocketCallbacks& callbacks, + SendQueue& send_queue, + VerificationTag my_verification_tag, + TSN my_initial_tsn, + VerificationTag peer_verification_tag, + TSN peer_initial_tsn, + size_t a_rwnd, + TieTag tie_tag, + PacketSender& packet_sender, + std::function is_connection_established) + : log_prefix_(log_prefix), + options_(options), + timer_manager_(timer_manager), + capabilities_(capabilities), + callbacks_(callbacks), + t3_rtx_(timer_manager_.CreateTimer( + "t3-rtx", + absl::bind_front(&TransmissionControlBlock::OnRtxTimerExpiry, this), + TimerOptions(options.rto_initial, + TimerBackoffAlgorithm::kExponential, + /*max_restarts=*/absl::nullopt, + options.max_timer_backoff_duration.has_value() + ? *options.max_timer_backoff_duration + : DurationMs::InfiniteDuration()))), + delayed_ack_timer_(timer_manager_.CreateTimer( + "delayed-ack", + absl::bind_front(&TransmissionControlBlock::OnDelayedAckTimerExpiry, + this), + TimerOptions(options.delayed_ack_max_timeout, + TimerBackoffAlgorithm::kExponential, + /*max_restarts=*/0, + /*max_backoff_duration=*/DurationMs::InfiniteDuration(), + webrtc::TaskQueueBase::DelayPrecision::kHigh))), + my_verification_tag_(my_verification_tag), + my_initial_tsn_(my_initial_tsn), + peer_verification_tag_(peer_verification_tag), + peer_initial_tsn_(peer_initial_tsn), + tie_tag_(tie_tag), + is_connection_established_(std::move(is_connection_established)), + packet_sender_(packet_sender), + rto_(options), + tx_error_counter_(log_prefix, options), + data_tracker_(log_prefix, delayed_ack_timer_.get(), peer_initial_tsn), + reassembly_queue_(log_prefix, + peer_initial_tsn, + options.max_receiver_window_buffer_size, + capabilities.message_interleaving), + retransmission_queue_( + log_prefix, + &callbacks_, + my_initial_tsn, + a_rwnd, + send_queue, + absl::bind_front(&TransmissionControlBlock::ObserveRTT, this), + [this]() { tx_error_counter_.Clear(); }, + *t3_rtx_, + options, + capabilities.partial_reliability, + capabilities.message_interleaving), + stream_reset_handler_(log_prefix, + this, + &timer_manager, + &data_tracker_, + &reassembly_queue_, + &retransmission_queue_), + heartbeat_handler_(log_prefix, options, this, &timer_manager_) { + send_queue.EnableMessageInterleaving(capabilities.message_interleaving); +} + +void TransmissionControlBlock::ObserveRTT(DurationMs rtt) { + DurationMs prev_rto = rto_.rto(); + rto_.ObserveRTT(rtt); + RTC_DLOG(LS_VERBOSE) << log_prefix_ << "new rtt=" << *rtt + << ", srtt=" << *rto_.srtt() << ", rto=" << *rto_.rto() + << " (" << *prev_rto << ")"; + t3_rtx_->set_duration(rto_.rto()); + + DurationMs delayed_ack_tmo = + std::min(rto_.rto() * 0.5, options_.delayed_ack_max_timeout); + delayed_ack_timer_->set_duration(delayed_ack_tmo); +} + +absl::optional TransmissionControlBlock::OnRtxTimerExpiry() { + TimeMs now = callbacks_.TimeMillis(); + RTC_DLOG(LS_INFO) << log_prefix_ << "Timer " << t3_rtx_->name() + << " has expired"; + if (cookie_echo_chunk_.has_value()) { + // In the COOKIE_ECHO state, let the T1-COOKIE timer trigger + // retransmissions, to avoid having two timers doing that. + RTC_DLOG(LS_VERBOSE) << "Not retransmitting as T1-cookie is active."; + } else { + if (IncrementTxErrorCounter("t3-rtx expired")) { + retransmission_queue_.HandleT3RtxTimerExpiry(); + SendBufferedPackets(now); + } + } + return absl::nullopt; +} + +absl::optional TransmissionControlBlock::OnDelayedAckTimerExpiry() { + data_tracker_.HandleDelayedAckTimerExpiry(); + MaybeSendSack(); + return absl::nullopt; +} + +void TransmissionControlBlock::MaybeSendSack() { + if (data_tracker_.ShouldSendAck(/*also_if_delayed=*/false)) { + SctpPacket::Builder builder = PacketBuilder(); + builder.Add( + data_tracker_.CreateSelectiveAck(reassembly_queue_.remaining_bytes())); + Send(builder); + } +} + +void TransmissionControlBlock::MaybeSendForwardTsn(SctpPacket::Builder& builder, + TimeMs now) { + if (now >= limit_forward_tsn_until_ && + retransmission_queue_.ShouldSendForwardTsn(now)) { + if (capabilities_.message_interleaving) { + builder.Add(retransmission_queue_.CreateIForwardTsn()); + } else { + builder.Add(retransmission_queue_.CreateForwardTsn()); + } + Send(builder); + // https://datatracker.ietf.org/doc/html/rfc3758 + // "IMPLEMENTATION NOTE: An implementation may wish to limit the number of + // duplicate FORWARD TSN chunks it sends by ... waiting a full RTT before + // sending a duplicate FORWARD TSN." + // "Any delay applied to the sending of FORWARD TSN chunk SHOULD NOT exceed + // 200ms and MUST NOT exceed 500ms". + limit_forward_tsn_until_ = now + std::min(DurationMs(200), rto_.srtt()); + } +} + +void TransmissionControlBlock::MaybeSendFastRetransmit() { + if (!retransmission_queue_.has_data_to_be_fast_retransmitted()) { + return; + } + + // https://datatracker.ietf.org/doc/html/rfc4960#section-7.2.4 + // "Determine how many of the earliest (i.e., lowest TSN) DATA chunks marked + // for retransmission will fit into a single packet, subject to constraint of + // the path MTU of the destination transport address to which the packet is + // being sent. Call this value K. Retransmit those K DATA chunks in a single + // packet. When a Fast Retransmit is being performed, the sender SHOULD + // ignore the value of cwnd and SHOULD NOT delay retransmission for this + // single packet." + + SctpPacket::Builder builder(peer_verification_tag_, options_); + auto chunks = retransmission_queue_.GetChunksForFastRetransmit( + builder.bytes_remaining()); + for (auto& [tsn, data] : chunks) { + if (capabilities_.message_interleaving) { + builder.Add(IDataChunk(tsn, std::move(data), false)); + } else { + builder.Add(DataChunk(tsn, std::move(data), false)); + } + } + Send(builder); +} + +void TransmissionControlBlock::SendBufferedPackets(SctpPacket::Builder& builder, + TimeMs now) { + for (int packet_idx = 0; + packet_idx < options_.max_burst && retransmission_queue_.can_send_data(); + ++packet_idx) { + // Only add control chunks to the first packet that is sent, if sending + // multiple packets in one go (as allowed by the congestion window). + if (packet_idx == 0) { + if (cookie_echo_chunk_.has_value()) { + // https://tools.ietf.org/html/rfc4960#section-5.1 + // "The COOKIE ECHO chunk can be bundled with any pending outbound DATA + // chunks, but it MUST be the first chunk in the packet..." + RTC_DCHECK(builder.empty()); + builder.Add(*cookie_echo_chunk_); + } + + // https://tools.ietf.org/html/rfc4960#section-6 + // "Before an endpoint transmits a DATA chunk, if any received DATA + // chunks have not been acknowledged (e.g., due to delayed ack), the + // sender should create a SACK and bundle it with the outbound DATA chunk, + // as long as the size of the final SCTP packet does not exceed the + // current MTU." + if (data_tracker_.ShouldSendAck(/*also_if_delayed=*/true)) { + builder.Add(data_tracker_.CreateSelectiveAck( + reassembly_queue_.remaining_bytes())); + } + MaybeSendForwardTsn(builder, now); + absl::optional reconfig = + stream_reset_handler_.MakeStreamResetRequest(); + if (reconfig.has_value()) { + builder.Add(*reconfig); + } + } + + auto chunks = + retransmission_queue_.GetChunksToSend(now, builder.bytes_remaining()); + for (auto& [tsn, data] : chunks) { + if (capabilities_.message_interleaving) { + builder.Add(IDataChunk(tsn, std::move(data), false)); + } else { + builder.Add(DataChunk(tsn, std::move(data), false)); + } + } + + // https://www.ietf.org/archive/id/draft-tuexen-tsvwg-sctp-zero-checksum-02.html#section-4.2 + // "When an end point sends a packet containing a COOKIE ECHO chunk, it MUST + // include a correct CRC32c checksum in the packet containing the COOKIE + // ECHO chunk." + bool write_checksum = + !capabilities_.zero_checksum || cookie_echo_chunk_.has_value(); + if (!packet_sender_.Send(builder, write_checksum)) { + break; + } + + if (cookie_echo_chunk_.has_value()) { + // https://tools.ietf.org/html/rfc4960#section-5.1 + // "... until the COOKIE ACK is returned the sender MUST NOT send any + // other packets to the peer." + break; + } + } +} + +std::string TransmissionControlBlock::ToString() const { + rtc::StringBuilder sb; + + sb.AppendFormat( + "verification_tag=%08x, last_cumulative_ack=%u, capabilities=", + *peer_verification_tag_, *data_tracker_.last_cumulative_acked_tsn()); + + if (capabilities_.partial_reliability) { + sb << "PR,"; + } + if (capabilities_.message_interleaving) { + sb << "IL,"; + } + if (capabilities_.reconfig) { + sb << "Reconfig,"; + } + if (capabilities_.zero_checksum) { + sb << "ZeroChecksum,"; + } + sb << " max_in=" << capabilities_.negotiated_maximum_incoming_streams; + sb << " max_out=" << capabilities_.negotiated_maximum_outgoing_streams; + + return sb.Release(); +} + +HandoverReadinessStatus TransmissionControlBlock::GetHandoverReadiness() const { + HandoverReadinessStatus status; + status.Add(data_tracker_.GetHandoverReadiness()); + status.Add(stream_reset_handler_.GetHandoverReadiness()); + status.Add(reassembly_queue_.GetHandoverReadiness()); + status.Add(retransmission_queue_.GetHandoverReadiness()); + return status; +} + +void TransmissionControlBlock::AddHandoverState( + DcSctpSocketHandoverState& state) { + state.capabilities.partial_reliability = capabilities_.partial_reliability; + state.capabilities.message_interleaving = capabilities_.message_interleaving; + state.capabilities.reconfig = capabilities_.reconfig; + state.capabilities.zero_checksum = capabilities_.zero_checksum; + state.capabilities.negotiated_maximum_incoming_streams = + capabilities_.negotiated_maximum_incoming_streams; + state.capabilities.negotiated_maximum_outgoing_streams = + capabilities_.negotiated_maximum_outgoing_streams; + + state.my_verification_tag = my_verification_tag().value(); + state.peer_verification_tag = peer_verification_tag().value(); + state.my_initial_tsn = my_initial_tsn().value(); + state.peer_initial_tsn = peer_initial_tsn().value(); + state.tie_tag = tie_tag().value(); + + data_tracker_.AddHandoverState(state); + stream_reset_handler_.AddHandoverState(state); + reassembly_queue_.AddHandoverState(state); + retransmission_queue_.AddHandoverState(state); +} + +void TransmissionControlBlock::RestoreFromState( + const DcSctpSocketHandoverState& state) { + data_tracker_.RestoreFromState(state); + retransmission_queue_.RestoreFromState(state); + reassembly_queue_.RestoreFromState(state); +} +} // namespace dcsctp -- cgit v1.2.3