webrtc_m130/p2p/dtls/dtls_stun_piggyback_controller_unittest.cc
Philipp Hancke 589acd56d0 dtls-stun piggybacking: make it compatible with DTLS 1.3
DTLS 1.3 encrypts more parts of the handshake so we move from
deep packet inspection to looking at the state of DTLS to
decide whether to intercept the packet.

BUG=webrtc:367395350

Change-Id: Idb1eda0437f24002f48381af5d6a167a4a153381
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/374501
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43794}
2025-01-24 03:11:19 -08:00

296 lines
11 KiB
C++

/*
* Copyright 2024 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 "p2p/dtls/dtls_stun_piggyback_controller.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "api/array_view.h"
#include "api/transport/stun.h"
#include "test/gtest.h"
namespace {
// Extracted from a stock DTLS call using Wireshark.
// Each packet (apart from the last) is truncated to
// the first fragment to keep things short.
// Based on a "server hello done" but with different msg_seq.
const std::vector<uint8_t> dtls_flight1 = {
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x01, // seq=1
0x00, 0x0c, 0x0e, 0x00, 0x00, 0x00, 0x12, 0x34, 0x00, // msg_seq=0x1234
0x00, 0x00, 0x00, 0x00, 0x00};
const std::vector<uint8_t> dtls_flight2 = {
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x02, // seq=2
0x00, 0x0c, 0x0e, 0x00, 0x00, 0x00, 0x43, 0x21, 0x00, // msg_seq=0x4321
0x00, 0x00, 0x00, 0x00, 0x00};
const std::vector<uint8_t> dtls_flight3 = {
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x03, // seq=3
0x00, 0x0c, 0x0e, 0x00, 0x00, 0x00, 0x44, 0x44, 0x00, // msg_seq=0x4444
0x00, 0x00, 0x00, 0x00, 0x00};
const std::vector<uint8_t> dtls_flight4 = {
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x04, // seq=4
0x00, 0x0c, 0x0e, 0x00, 0x00, 0x00, 0x54, 0x86, 0x00, // msg_seq=0x5486
0x00, 0x00, 0x00, 0x00, 0x00};
const std::vector<uint8_t> empty = {};
} // namespace
namespace cricket {
using State = DtlsStunPiggybackController::State;
class DtlsStunPiggybackControllerTest : public ::testing::Test {
protected:
DtlsStunPiggybackControllerTest()
: client_([](rtc::ArrayView<const uint8_t> data) {}),
server_([](rtc::ArrayView<const uint8_t> data) {}) {}
void SendClientToServer(const std::vector<uint8_t> data,
StunMessageType type) {
if (!data.empty()) {
EXPECT_TRUE(client_.MaybeConsumePacket(data));
} else {
client_.ClearCachedPacketForTesting();
}
std::unique_ptr<StunByteStringAttribute> attr_data;
if (client_.GetDataToPiggyback(type)) {
attr_data = std::make_unique<StunByteStringAttribute>(
STUN_ATTR_META_DTLS_IN_STUN, *client_.GetDataToPiggyback(type));
}
std::unique_ptr<StunByteStringAttribute> attr_ack;
if (client_.GetAckToPiggyback(type)) {
attr_ack = std::make_unique<StunByteStringAttribute>(
STUN_ATTR_META_DTLS_IN_STUN_ACK, *client_.GetAckToPiggyback(type));
}
server_.ReportDataPiggybacked(attr_data.get(), attr_ack.get());
}
void SendServerToClient(const std::vector<uint8_t> data,
StunMessageType type) {
if (!data.empty()) {
EXPECT_TRUE(server_.MaybeConsumePacket(data));
} else {
server_.ClearCachedPacketForTesting();
}
std::unique_ptr<StunByteStringAttribute> attr_data;
if (server_.GetDataToPiggyback(type)) {
attr_data = std::make_unique<StunByteStringAttribute>(
STUN_ATTR_META_DTLS_IN_STUN, *server_.GetDataToPiggyback(type));
}
std::unique_ptr<StunByteStringAttribute> attr_ack;
if (server_.GetAckToPiggyback(type)) {
attr_ack = std::make_unique<StunByteStringAttribute>(
STUN_ATTR_META_DTLS_IN_STUN_ACK, *server_.GetAckToPiggyback(type));
}
client_.ReportDataPiggybacked(attr_data.get(), attr_ack.get());
if (data == dtls_flight4) {
// After sending flight 4, the server handshake is complete.
server_.SetDtlsHandshakeComplete(/*is_client=*/false,
/*is_dtls13=*/false);
// When receiving flight 4, client handshake is complete.
client_.SetDtlsHandshakeComplete(/*is_client=*/true, /*is_dtls13=*/false);
}
}
void DisableSupport(DtlsStunPiggybackController& client_or_server) {
ASSERT_EQ(client_or_server.state(), State::TENTATIVE);
client_or_server.ReportDataPiggybacked(nullptr, nullptr);
ASSERT_EQ(client_or_server.state(), State::OFF);
}
DtlsStunPiggybackController client_;
DtlsStunPiggybackController server_;
};
TEST_F(DtlsStunPiggybackControllerTest, BasicHandshake) {
// Flight 1+2
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
EXPECT_EQ(server_.state(), State::CONFIRMED);
SendServerToClient(dtls_flight2, STUN_BINDING_RESPONSE);
EXPECT_EQ(client_.state(), State::CONFIRMED);
// Flight 3+4
SendClientToServer(dtls_flight3, STUN_BINDING_REQUEST);
SendServerToClient(dtls_flight4, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::PENDING);
EXPECT_EQ(client_.state(), State::PENDING);
// Post-handshake ACK
SendServerToClient(empty, STUN_BINDING_REQUEST);
SendClientToServer(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::COMPLETE);
EXPECT_EQ(client_.state(), State::COMPLETE);
}
TEST_F(DtlsStunPiggybackControllerTest, FirstClientPacketLost) {
// Client to server got lost (or arrives late)
// Flight 1
SendServerToClient(empty, STUN_BINDING_REQUEST);
SendClientToServer(dtls_flight1, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::CONFIRMED);
EXPECT_EQ(client_.state(), State::CONFIRMED);
// Flight 2+3
SendServerToClient(dtls_flight2, STUN_BINDING_REQUEST);
SendClientToServer(dtls_flight3, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::CONFIRMED);
EXPECT_EQ(client_.state(), State::CONFIRMED);
// Flight 4
SendServerToClient(dtls_flight4, STUN_BINDING_REQUEST);
SendClientToServer(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::COMPLETE);
EXPECT_EQ(client_.state(), State::PENDING);
// Post-handshake ACK
SendServerToClient(empty, STUN_BINDING_REQUEST);
EXPECT_EQ(client_.state(), State::COMPLETE);
}
TEST_F(DtlsStunPiggybackControllerTest, NotSupportedByServer) {
DisableSupport(server_);
// Flight 1
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
SendServerToClient(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(client_.state(), State::OFF);
}
TEST_F(DtlsStunPiggybackControllerTest, NotSupportedByServerClientReceives) {
DisableSupport(server_);
// Client to server got lost (or arrives late)
SendServerToClient(empty, STUN_BINDING_REQUEST);
EXPECT_EQ(client_.state(), State::OFF);
}
TEST_F(DtlsStunPiggybackControllerTest, NotSupportedByClient) {
DisableSupport(client_);
SendServerToClient(empty, STUN_BINDING_REQUEST);
SendClientToServer(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::OFF);
}
TEST_F(DtlsStunPiggybackControllerTest, SomeRequestsDoNotGoThrough) {
// Client to server got lost (or arrives late)
// Flight 1
SendServerToClient(empty, STUN_BINDING_REQUEST);
SendClientToServer(dtls_flight1, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::CONFIRMED);
EXPECT_EQ(client_.state(), State::CONFIRMED);
// Flight 1+2, server sent request got lost.
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
SendServerToClient(dtls_flight2, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::CONFIRMED);
EXPECT_EQ(client_.state(), State::CONFIRMED);
// Flight 3+4
SendClientToServer(dtls_flight3, STUN_BINDING_REQUEST);
SendServerToClient(dtls_flight4, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::PENDING);
EXPECT_EQ(client_.state(), State::PENDING);
// Post-handshake ACK
SendClientToServer(empty, STUN_BINDING_REQUEST);
SendServerToClient(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::COMPLETE);
EXPECT_EQ(client_.state(), State::COMPLETE);
}
TEST_F(DtlsStunPiggybackControllerTest, LossOnPostHandshakeAck) {
// Flight 1+2
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
EXPECT_EQ(server_.state(), State::CONFIRMED);
SendServerToClient(dtls_flight2, STUN_BINDING_RESPONSE);
EXPECT_EQ(client_.state(), State::CONFIRMED);
// Flight 3+4
SendClientToServer(dtls_flight3, STUN_BINDING_REQUEST);
SendServerToClient(dtls_flight4, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::PENDING);
EXPECT_EQ(client_.state(), State::PENDING);
// Post-handshake ACK. Client to server gets lost
SendServerToClient(empty, STUN_BINDING_REQUEST);
SendClientToServer(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::COMPLETE);
EXPECT_EQ(client_.state(), State::COMPLETE);
}
TEST_F(DtlsStunPiggybackControllerTest,
UnsupportedStateAfterFallbackHandshakeRemainsOff) {
DisableSupport(client_);
DisableSupport(server_);
// Set DTLS complete after normal handshake.
client_.SetDtlsHandshakeComplete(/*is_client=*/true, /*is_dtls13=*/false);
EXPECT_EQ(client_.state(), State::OFF);
server_.SetDtlsHandshakeComplete(/*is_client=*/false, /*is_dtls13=*/false);
EXPECT_EQ(server_.state(), State::OFF);
}
TEST_F(DtlsStunPiggybackControllerTest, BasicHandshakeAckData) {
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_RESPONSE), "");
EXPECT_EQ(client_.GetAckToPiggyback(STUN_BINDING_REQUEST), "");
// Flight 1+2
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
SendServerToClient(dtls_flight2, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_REQUEST),
std::string("\x12\x34", 2));
EXPECT_EQ(client_.GetAckToPiggyback(STUN_BINDING_RESPONSE),
std::string("\x43\x21", 2));
// Flight 3+4
SendClientToServer(dtls_flight3, STUN_BINDING_REQUEST);
SendServerToClient(dtls_flight4, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_RESPONSE),
std::string("\x12\x34\x44\x44", 4));
EXPECT_EQ(client_.GetAckToPiggyback(STUN_BINDING_REQUEST),
std::string("\x43\x21\x54\x86", 4));
// Post-handshake ACK
SendServerToClient(empty, STUN_BINDING_REQUEST);
SendClientToServer(empty, STUN_BINDING_RESPONSE);
EXPECT_EQ(server_.state(), State::COMPLETE);
EXPECT_EQ(client_.state(), State::COMPLETE);
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_RESPONSE), std::nullopt);
EXPECT_EQ(client_.GetAckToPiggyback(STUN_BINDING_REQUEST), std::nullopt);
}
TEST_F(DtlsStunPiggybackControllerTest, AckDataNoDuplicates) {
// Flight 1+2
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_REQUEST),
std::string("\x12\x34", 2));
SendClientToServer(dtls_flight3, STUN_BINDING_REQUEST);
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_REQUEST),
std::string("\x12\x34\x44\x44", 4));
// Receive Flight 1 again, no change expected.
SendClientToServer(dtls_flight1, STUN_BINDING_REQUEST);
EXPECT_EQ(server_.GetAckToPiggyback(STUN_BINDING_REQUEST),
std::string("\x12\x34\x44\x44", 4));
}
} // namespace cricket