diff --git a/net/dcsctp/public/dcsctp_socket.h b/net/dcsctp/public/dcsctp_socket.h index 583d037019..2b56094ac1 100644 --- a/net/dcsctp/public/dcsctp_socket.h +++ b/net/dcsctp/public/dcsctp_socket.h @@ -206,6 +206,31 @@ struct Metrics { absl::optional peer_rwnd_bytes = absl::nullopt; }; +// Represent known SCTP implementations. +enum class SctpImplementation { + // There is not enough information toto determine any SCTP implementation. + kUnknown, + // This implementation. + kDcsctp, + // https://github.com/sctplab/usrsctp. + kUsrSctp, + // Any other implementation. + kOther, +}; + +inline constexpr absl::string_view ToString(SctpImplementation implementation) { + switch (implementation) { + case SctpImplementation::kUnknown: + return "unknown"; + case SctpImplementation::kDcsctp: + return "dcsctp"; + case SctpImplementation::kUsrSctp: + return "usrsctp"; + case SctpImplementation::kOther: + return "other"; + } +} + // Callbacks that the DcSctpSocket will call synchronously to the owning // client. It is allowed to call back into the library from callbacks that start // with "On". It has been explicitly documented when it's not allowed to call @@ -440,6 +465,15 @@ class DcSctpSocketInterface { // called on success. virtual absl::optional GetHandoverStateAndClose() = 0; + + // Returns the detected SCTP implementation of the peer. As this is not + // explicitly signalled during the connection establishment, heuristics is + // used to analyze e.g. the state cookie in the INIT-ACK chunk. + // + // If this method is called too early (before + // `DcSctpSocketCallbacks::OnConnected` has triggered), this will likely + // return `SctpImplementation::kUnknown`. + virtual SctpImplementation peer_implementation() const = 0; }; } // namespace dcsctp diff --git a/net/dcsctp/public/mock_dcsctp_socket.h b/net/dcsctp/public/mock_dcsctp_socket.h index eb1e8ccec9..d207899a18 100644 --- a/net/dcsctp/public/mock_dcsctp_socket.h +++ b/net/dcsctp/public/mock_dcsctp_socket.h @@ -73,6 +73,8 @@ class MockDcSctpSocket : public DcSctpSocketInterface { GetHandoverStateAndClose, (), (override)); + + MOCK_METHOD(SctpImplementation, peer_implementation, (), (const)); }; } // namespace dcsctp diff --git a/net/dcsctp/socket/dcsctp_socket.cc b/net/dcsctp/socket/dcsctp_socket.cc index 7910a3953f..a1cc12d1e1 100644 --- a/net/dcsctp/socket/dcsctp_socket.cc +++ b/net/dcsctp/socket/dcsctp_socket.cc @@ -138,6 +138,20 @@ TieTag MakeTieTag(DcSctpSocketCallbacks& cb) { return TieTag(static_cast(tie_tag_upper) << 32 | static_cast(tie_tag_lower)); } + +SctpImplementation DeterminePeerImplementation( + rtc::ArrayView cookie) { + if (cookie.size() > 8) { + absl::string_view magic(reinterpret_cast(cookie.data()), 8); + if (magic == "dcSCTP00") { + return SctpImplementation::kDcsctp; + } + if (magic == "KAME-BSD") { + return SctpImplementation::kUsrSctp; + } + } + return SctpImplementation::kOther; +} } // namespace DcSctpSocket::DcSctpSocket(absl::string_view log_prefix, @@ -1149,6 +1163,8 @@ void DcSctpSocket::HandleInitAck( Capabilities capabilities = GetCapabilities(options_, chunk->parameters()); t1_init_->Stop(); + peer_implementation_ = DeterminePeerImplementation(cookie->data()); + tcb_ = std::make_unique( timer_manager_, log_prefix_, options_, capabilities, callbacks_, send_queue_, connect_params_.verification_tag, diff --git a/net/dcsctp/socket/dcsctp_socket.h b/net/dcsctp/socket/dcsctp_socket.h index 508a8a6aad..c249864182 100644 --- a/net/dcsctp/socket/dcsctp_socket.h +++ b/net/dcsctp/socket/dcsctp_socket.h @@ -101,7 +101,9 @@ class DcSctpSocket : public DcSctpSocketInterface { Metrics GetMetrics() const override; HandoverReadinessStatus GetHandoverReadiness() const override; absl::optional GetHandoverStateAndClose() override; - + SctpImplementation peer_implementation() const override { + return peer_implementation_; + } // Returns this socket's verification tag, or zero if not yet connected. VerificationTag verification_tag() const { return tcb_ != nullptr ? tcb_->my_verification_tag() : VerificationTag(0); @@ -276,6 +278,8 @@ class DcSctpSocket : public DcSctpSocketInterface { State state_ = State::kClosed; // If the connection is established, contains a transmission control block. std::unique_ptr tcb_; + + SctpImplementation peer_implementation_ = SctpImplementation::kUnknown; }; } // namespace dcsctp diff --git a/net/dcsctp/socket/dcsctp_socket_test.cc b/net/dcsctp/socket/dcsctp_socket_test.cc index 71eefd486a..66876e4e25 100644 --- a/net/dcsctp/socket/dcsctp_socket_test.cc +++ b/net/dcsctp/socket/dcsctp_socket_test.cc @@ -255,10 +255,14 @@ class DcSctpSocketTest : public testing::Test { : options_(MakeOptionsForTest(enable_message_interleaving)), cb_a_("A"), cb_z_("Z"), - sock_a_(std::make_unique( - "A", cb_a_, GetPacketObserver("A"), options_)), - sock_z_(std::make_unique( - "Z", cb_z_, GetPacketObserver("Z"), options_)) {} + sock_a_(std::make_unique("A", + cb_a_, + GetPacketObserver("A"), + options_)), + sock_z_(std::make_unique("Z", + cb_z_, + GetPacketObserver("Z"), + options_)) {} void AdvanceTime(DurationMs duration) { cb_a_.AdvanceTime(duration); @@ -2050,5 +2054,26 @@ TEST_F(DcSctpSocketTest, SendMessagesAfterHandover) { EXPECT_THAT(msg->payload(), testing::ElementsAre(1, 2, 3)); } +TEST_F(DcSctpSocketTest, CanDetectDcsctpImplementation) { + ConnectSockets(); + + EXPECT_EQ(sock_a_->peer_implementation(), SctpImplementation::kDcsctp); + + // As A initiated the connection establishment, Z will not receive enough + // information to know about A's implementation + EXPECT_EQ(sock_z_->peer_implementation(), SctpImplementation::kUnknown); +} + +TEST_F(DcSctpSocketTest, BothCanDetectDcsctpImplementation) { + EXPECT_CALL(cb_a_, OnConnected).Times(1); + EXPECT_CALL(cb_z_, OnConnected).Times(1); + sock_a_->Connect(); + sock_z_->Connect(); + + ExchangeMessages(*sock_a_, cb_a_, *sock_z_, cb_z_); + + EXPECT_EQ(sock_a_->peer_implementation(), SctpImplementation::kDcsctp); + EXPECT_EQ(sock_z_->peer_implementation(), SctpImplementation::kDcsctp); +} } // namespace } // namespace dcsctp diff --git a/net/dcsctp/socket/state_cookie_test.cc b/net/dcsctp/socket/state_cookie_test.cc index eab41a7a56..870620ba14 100644 --- a/net/dcsctp/socket/state_cookie_test.cc +++ b/net/dcsctp/socket/state_cookie_test.cc @@ -36,5 +36,18 @@ TEST(StateCookieTest, SerializeAndDeserialize) { EXPECT_TRUE(deserialized.capabilities().reconfig); } +TEST(StateCookieTest, ValidateMagicValue) { + Capabilities capabilities = {/*partial_reliability=*/true, + /*message_interleaving=*/false, + /*reconfig=*/true}; + StateCookie cookie(VerificationTag(123), TSN(456), + /*a_rwnd=*/789, TieTag(101112), capabilities); + std::vector serialized = cookie.Serialize(); + ASSERT_THAT(serialized, SizeIs(StateCookie::kCookieSize)); + + absl::string_view magic(reinterpret_cast(serialized.data()), 8); + EXPECT_EQ(magic, "dcSCTP00"); +} + } // namespace } // namespace dcsctp