dcsctp: Detect the peer SCTP implementation
It's to be used for clients to record metrics and to e.g. attribute metrics to which SCTP implementation the peer was using. This is not explicitly signaled, so heuristics are used. These are not guaranteed to come to the correct conclusion, and the data is not always available. Note: The behavior of dcSCTP will not change depending on the assumed implementation - only by explicitly signaled capabilities. Bug: webrtc:13216 Change-Id: I2f58054d17d53d947ed5845df7a08f974d42f918 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/233100 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#35103}
This commit is contained in:
parent
11806896bb
commit
f4fa166cc5
@ -206,6 +206,31 @@ struct Metrics {
|
||||
absl::optional<uint32_t> 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<DcSctpSocketHandoverState>
|
||||
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
|
||||
|
||||
|
||||
@ -73,6 +73,8 @@ class MockDcSctpSocket : public DcSctpSocketInterface {
|
||||
GetHandoverStateAndClose,
|
||||
(),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(SctpImplementation, peer_implementation, (), (const));
|
||||
};
|
||||
|
||||
} // namespace dcsctp
|
||||
|
||||
@ -138,6 +138,20 @@ TieTag MakeTieTag(DcSctpSocketCallbacks& cb) {
|
||||
return TieTag(static_cast<uint64_t>(tie_tag_upper) << 32 |
|
||||
static_cast<uint64_t>(tie_tag_lower));
|
||||
}
|
||||
|
||||
SctpImplementation DeterminePeerImplementation(
|
||||
rtc::ArrayView<const uint8_t> cookie) {
|
||||
if (cookie.size() > 8) {
|
||||
absl::string_view magic(reinterpret_cast<const char*>(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<TransmissionControlBlock>(
|
||||
timer_manager_, log_prefix_, options_, capabilities, callbacks_,
|
||||
send_queue_, connect_params_.verification_tag,
|
||||
|
||||
@ -101,7 +101,9 @@ class DcSctpSocket : public DcSctpSocketInterface {
|
||||
Metrics GetMetrics() const override;
|
||||
HandoverReadinessStatus GetHandoverReadiness() const override;
|
||||
absl::optional<DcSctpSocketHandoverState> 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<TransmissionControlBlock> tcb_;
|
||||
|
||||
SctpImplementation peer_implementation_ = SctpImplementation::kUnknown;
|
||||
};
|
||||
} // namespace dcsctp
|
||||
|
||||
|
||||
@ -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<DcSctpSocket>(
|
||||
"A", cb_a_, GetPacketObserver("A"), options_)),
|
||||
sock_z_(std::make_unique<DcSctpSocket>(
|
||||
"Z", cb_z_, GetPacketObserver("Z"), options_)) {}
|
||||
sock_a_(std::make_unique<DcSctpSocket>("A",
|
||||
cb_a_,
|
||||
GetPacketObserver("A"),
|
||||
options_)),
|
||||
sock_z_(std::make_unique<DcSctpSocket>("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
|
||||
|
||||
@ -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<uint8_t> serialized = cookie.Serialize();
|
||||
ASSERT_THAT(serialized, SizeIs(StateCookie::kCookieSize));
|
||||
|
||||
absl::string_view magic(reinterpret_cast<const char*>(serialized.data()), 8);
|
||||
EXPECT_EQ(magic, "dcSCTP00");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace dcsctp
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user