dcsctp: Resend SHUTDOWN ACK on timer expiration If the T2-shutdown timer expires while the receiver is in the SHUTDOWN-ACK-SENT state, it MUST resend the SHUTDOWN ACK chunk. Previously, OnShutdownTimerExpiry would always call SendShutdown(), meaning that if the timer expired in the SHUTDOWN-ACK-SENT state, it would incorrectly send a SHUTDOWN chunk instead of a SHUTDOWN ACK. This CL fixes this by checking the socket state in OnShutdownTimerExpiry. Bug: webrtc:500087337 Change-Id: I603c415a162bf7d194ef06a9c5e74dbf3b0dafe8 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/461920 Commit-Queue: Victor Boivie <boivie@webrtc.org> Reviewed-by: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47337}
diff --git a/net/dcsctp/socket/dcsctp_socket.cc b/net/dcsctp/socket/dcsctp_socket.cc index 0bcdbd5..cc883ba 100644 --- a/net/dcsctp/socket/dcsctp_socket.cc +++ b/net/dcsctp/socket/dcsctp_socket.cc
@@ -1036,6 +1036,8 @@ } TimeDelta DcSctpSocket::OnShutdownTimerExpiry() { + RTC_DCHECK(state_ == State::kShutdownSent || + state_ == State::kShutdownAckSent); RTC_DLOG(LS_VERBOSE) << log_prefix() << "Timer " << t2_shutdown_->name() << " has expired: " << t2_shutdown_->expiration_count() << "/" @@ -1058,10 +1060,19 @@ return TimeDelta::Zero(); } - // https://tools.ietf.org/html/rfc4960#section-9.2 - // "If the timer expires, the endpoint must resend the SHUTDOWN with the - // updated last sequential TSN received from its peer." - SendShutdown(); + if (state_ == State::kShutdownAckSent) { + // https://tools.ietf.org/html/rfc4960#section-9.2 + // "... and start a T2-shutdown timer of its own, entering the + // SHUTDOWN-ACK-SENT state. If the timer expires, the endpoint must + // resend the SHUTDOWN ACK." + SendShutdownAck(); + } else { + // https://tools.ietf.org/html/rfc4960#section-9.2 + // "It shall then start the T2-shutdown timer and enter the SHUTDOWN-SENT + // state. If the timer expires, the endpoint must resend the SHUTDOWN + // with the updated last sequential TSN received from its peer." + SendShutdown(); + } RTC_DCHECK(IsConsistent()); return tcb_->current_rto(); }
diff --git a/net/dcsctp/socket/dcsctp_socket_test.cc b/net/dcsctp/socket/dcsctp_socket_test.cc index fcab267..7d50182 100644 --- a/net/dcsctp/socket/dcsctp_socket_test.cc +++ b/net/dcsctp/socket/dcsctp_socket_test.cc
@@ -40,6 +40,7 @@ #include "net/dcsctp/packet/chunk/reconfig_chunk.h" #include "net/dcsctp/packet/chunk/sack_chunk.h" #include "net/dcsctp/packet/chunk/shutdown_chunk.h" +#include "net/dcsctp/packet/chunk/shutdown_ack_chunk.h" #include "net/dcsctp/packet/data.h" #include "net/dcsctp/packet/error_cause/unrecognized_chunk_type_cause.h" #include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h" @@ -776,6 +777,31 @@ EXPECT_TRUE(a.cb.ConsumeSentPacket().empty()); } +TEST(DcSctpSocketTest, T2ShutdownTimerExpiresResendsShutdownAck) { + SocketUnderTest a("A"); + SocketUnderTest z("Z"); + + ConnectSockets(a, z); + + z.socket.Shutdown(); + + // A receives SHUTDOWN, sends SHUTDOWN_ACK. + // A is now in SHUTDOWN-ACK-SENT state. + a.socket.ReceivePacket(z.cb.ConsumeSentPacket()); + + // Drop the SHUTDOWN_ACK sent by A. + EXPECT_THAT(a.cb.ConsumeSentPacket(), + HasChunks(ElementsAre(IsChunkType(ShutdownAckChunk::kType)))); + + EXPECT_EQ(a.socket.state(), SocketState::kShuttingDown); + EXPECT_EQ(z.socket.state(), SocketState::kShuttingDown); + + // Advance time to trigger timer expiry, which makes A resend SHUTDOWN_ACK. + AdvanceTime(a, z, a.options.rto_initial.ToTimeDelta()); + EXPECT_THAT(a.cb.ConsumeSentPacket(), + HasChunks(ElementsAre(IsChunkType(ShutdownAckChunk::kType)))); +} + TEST(DcSctpSocketTest, EstablishConnectionWhileSendingData) { SocketUnderTest a("A"); SocketUnderTest z("Z");