Propagate ECN information on posix sockets to rtc::ReceivedPacket

Two new socket options are introduced OPT_SEND_ECN used for setting ECN bits. OPT_RECV_ECN used for reading the ECN bits.

If ECN bits are set on received IP packets,  ECT(1) and CE is propagated via rtc::ReceivedPacket.

Bug: webrtc:15368
Change-Id: I3ac335007e2f7d30564569bbc80ce47fa541bef1
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/332380
Reviewed-by: Jonas Oreland <jonaso@google.com>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41885}
This commit is contained in:
Per K 2024-03-11 10:21:48 +01:00 committed by WebRTC LUCI CQ
parent 329f0ead43
commit 8df31c915a
14 changed files with 244 additions and 25 deletions

View file

@ -727,7 +727,7 @@ void DtlsTransport::OnDtlsEvent(rtc::StreamInterface* dtls, int sig, int err) {
NotifyPacketReceived(rtc::ReceivedPacket(
rtc::MakeArrayView(buf, read), rtc::SocketAddress(),
webrtc::Timestamp::Micros(rtc::TimeMicros()),
rtc::ReceivedPacket::kDtlsDecrypted));
rtc::EcnMarking::kNotEct, rtc::ReceivedPacket::kDtlsDecrypted));
} else if (ret == rtc::SR_EOS) {
// Remote peer shut down the association with no error.
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport closed by remote";

View file

@ -34,9 +34,9 @@ TEST(PacketTransportInternal,
EXPECT_EQ(packet.decryption_info(),
rtc::ReceivedPacket::kDtlsDecrypted);
});
packet_transport.NotifyPacketReceived(
rtc::ReceivedPacket({}, rtc::SocketAddress(), absl::nullopt,
rtc::ReceivedPacket::kDtlsDecrypted));
packet_transport.NotifyPacketReceived(rtc::ReceivedPacket(
{}, rtc::SocketAddress(), absl::nullopt, rtc::EcnMarking::kNotEct,
rtc::ReceivedPacket::kDtlsDecrypted));
packet_transport.DeregisterReceivedPacketCallback(&receiver);
}

View file

@ -1046,6 +1046,7 @@ rtc_library("threading") {
"../api/task_queue:pending_task_safety_flag",
"../api/units:time_delta",
"../system_wrappers:field_trial",
"./network:ecn_marking",
"synchronization:mutex",
"system:no_unique_address",
"system:rtc_export",
@ -1092,6 +1093,7 @@ rtc_library("socket") {
":macromagic",
":socket_address",
"../api/units:timestamp",
"./network:ecn_marking",
"system:rtc_export",
"third_party/sigslot",
]

View file

@ -145,9 +145,9 @@ void AsyncUDPSocket::OnReadEvent(Socket* socket) {
}
*receive_buffer.arrival_time += *socket_time_offset_;
}
NotifyPacketReceived(ReceivedPacket(receive_buffer.payload,
receive_buffer.source_address,
receive_buffer.arrival_time));
NotifyPacketReceived(
ReceivedPacket(receive_buffer.payload, receive_buffer.source_address,
receive_buffer.arrival_time, receive_buffer.ecn));
}
void AsyncUDPSocket::OnWriteEvent(Socket* socket) {

View file

@ -17,6 +17,11 @@ rtc_library("sent_packet") {
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
rtc_source_set("ecn_marking") {
visibility = [ "*" ]
sources = [ "ecn_marking.h" ]
}
rtc_library("received_packet") {
visibility = [ "*" ]
sources = [
@ -24,6 +29,7 @@ rtc_library("received_packet") {
"received_packet.h",
]
deps = [
":ecn_marking",
"..:socket_address",
"../../api:array_view",
"../../api/units:timestamp",

View file

@ -0,0 +1,42 @@
/*
* 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.
*/
#ifndef RTC_BASE_NETWORK_ECN_MARKING_H_
#define RTC_BASE_NETWORK_ECN_MARKING_H_
namespace rtc {
// TODO(https://bugs.webrtc.org/15368): L4S support is slowly being developed.
// Help is appreciated.
// L4S Explicit Congestion Notification (ECN) .
// https://www.rfc-editor.org/rfc/rfc9331.html ECT stands for ECN-Capable
// Transport and CE stands for Congestion Experienced.
// RFC-3168, Section 5
// +-----+-----+
// | ECN FIELD |
// +-----+-----+
// ECT CE [Obsolete] RFC 2481 names for the ECN bits.
// 0 0 Not-ECT
// 0 1 ECT(1)
// 1 0 ECT(0)
// 1 1 CE
enum class EcnMarking {
kNotEct = 0, // Not ECN-Capable Transport
kEct1 = 1, // ECN-Capable Transport
kEct0 = 2, // Not used by L4s (or webrtc.)
kCe = 3, // Congestion experienced
};
} // namespace rtc
#endif // RTC_BASE_NETWORK_ECN_MARKING_H_

View file

@ -20,15 +20,17 @@ namespace rtc {
ReceivedPacket::ReceivedPacket(rtc::ArrayView<const uint8_t> payload,
const SocketAddress& source_address,
absl::optional<webrtc::Timestamp> arrival_time,
EcnMarking ecn,
DecryptionInfo decryption)
: payload_(payload),
arrival_time_(std::move(arrival_time)),
source_address_(source_address),
ecn_(ecn),
decryption_info_(decryption) {}
ReceivedPacket ReceivedPacket::CopyAndSet(
DecryptionInfo decryption_info) const {
return ReceivedPacket(payload_, source_address_, arrival_time_,
return ReceivedPacket(payload_, source_address_, arrival_time_, ecn_,
decryption_info);
}

View file

@ -15,6 +15,7 @@
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/units/timestamp.h"
#include "rtc_base/network/ecn_marking.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/system/rtc_export.h"
@ -38,6 +39,7 @@ class RTC_EXPORT ReceivedPacket {
ReceivedPacket(rtc::ArrayView<const uint8_t> payload,
const SocketAddress& source_address,
absl::optional<webrtc::Timestamp> arrival_time = absl::nullopt,
EcnMarking ecn = EcnMarking::kNotEct,
DecryptionInfo decryption = kNotDecrypted);
ReceivedPacket CopyAndSet(DecryptionInfo decryption_info) const;
@ -52,6 +54,9 @@ class RTC_EXPORT ReceivedPacket {
return arrival_time_;
}
// L4S Explicit Congestion Notification.
EcnMarking ecn() const { return ecn_; }
const DecryptionInfo& decryption_info() const { return decryption_info_; }
static ReceivedPacket CreateFromLegacy(
@ -73,6 +78,7 @@ class RTC_EXPORT ReceivedPacket {
rtc::ArrayView<const uint8_t> payload_;
absl::optional<webrtc::Timestamp> arrival_time_;
const SocketAddress& source_address_;
EcnMarking ecn_;
DecryptionInfo decryption_info_;
};

View file

@ -48,6 +48,7 @@
#include "rtc_base/event.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/network/ecn_marking.h"
#include "rtc_base/network_monitor.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/time_utils.h"
@ -108,6 +109,33 @@ typedef char* SockOptArg;
#endif
namespace {
// RFC-3168, Section 5. ECN is the two least significant bits.
static constexpr uint8_t kEcnMask = 0x03;
#if defined(WEBRTC_POSIX)
rtc::EcnMarking EcnFromDs(uint8_t ds) {
// RFC-3168, Section 5.
constexpr uint8_t ECN_ECT1 = 0x01;
constexpr uint8_t ECN_ECT0 = 0x02;
constexpr uint8_t ECN_CE = 0x03;
const uint8_t ecn = ds & kEcnMask;
if (ecn == ECN_ECT1) {
return rtc::EcnMarking::kEct1;
}
if (ecn == ECN_ECT0) {
return rtc::EcnMarking::kEct0;
}
if (ecn == ECN_CE) {
return rtc::EcnMarking::kCe;
}
return rtc::EcnMarking::kNotEct;
}
#endif
class ScopedSetTrue {
public:
ScopedSetTrue(bool* value) : value_(value) {
@ -125,6 +153,7 @@ class ScopedSetTrue {
bool IsScmTimeStampExperimentDisabled() {
return webrtc::field_trial::IsDisabled("WebRTC-SCM-Timestamp");
}
} // namespace
namespace rtc {
@ -314,8 +343,19 @@ int PhysicalSocket::GetOption(Option opt, int* value) {
#if defined(WEBRTC_POSIX)
// unshift DSCP value to get six most significant bits of IP DiffServ field
*value >>= 2;
#endif
} else if (opt == OPT_SEND_ECN) {
#if defined(WEBRTC_POSIX)
// Least 2 significant bits.
*value = *value & kEcnMask;
#endif
} else if (opt == OPT_RECV_ECN) {
#if defined(WEBRTC_POSIX)
// Least 2 significant bits.
*value = *value & kEcnMask;
#endif
}
return ret;
}
@ -329,10 +369,13 @@ int PhysicalSocket::SetOption(Option opt, int value) {
value = (value) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
#endif
} else if (opt == OPT_DSCP) {
#if defined(WEBRTC_POSIX)
// shift DSCP value to fit six most significant bits of IP DiffServ field
value <<= 2;
#endif
// IP DiffServ consists of DSCP 6 most significant, ECN 2 least
// significant.
dscp_ = value << 2;
value = dscp_ + (ecn_ & kEcnMask);
} else if (opt == OPT_SEND_ECN) {
ecn_ = value;
value = dscp_ + (ecn_ & kEcnMask);
}
#if defined(WEBRTC_POSIX)
if (sopt == IPV6_TCLASS) {
@ -401,8 +444,8 @@ int PhysicalSocket::SendTo(const void* buffer,
}
int PhysicalSocket::Recv(void* buffer, size_t length, int64_t* timestamp) {
int received =
DoReadFromSocket(buffer, length, /*out_addr*/ nullptr, timestamp);
int received = DoReadFromSocket(buffer, length, /*out_addr*/ nullptr,
timestamp, /*ecn=*/nullptr);
if ((received == 0) && (length != 0)) {
// Note: on graceful shutdown, recv can return 0. In this case, we
// pretend it is blocking, and then signal close, so that simplifying
@ -431,7 +474,7 @@ int PhysicalSocket::RecvFrom(void* buffer,
size_t length,
SocketAddress* out_addr,
int64_t* timestamp) {
int received = DoReadFromSocket(buffer, length, out_addr, timestamp);
int received = DoReadFromSocket(buffer, length, out_addr, timestamp, nullptr);
UpdateLastError();
int error = GetError();
@ -450,9 +493,9 @@ int PhysicalSocket::RecvFrom(ReceiveBuffer& buffer) {
static constexpr int BUF_SIZE = 64 * 1024;
buffer.payload.EnsureCapacity(BUF_SIZE);
int received =
DoReadFromSocket(buffer.payload.data(), buffer.payload.capacity(),
&buffer.source_address, &timestamp);
int received = DoReadFromSocket(
buffer.payload.data(), buffer.payload.capacity(), &buffer.source_address,
&timestamp, ecn_ ? &buffer.ecn : nullptr);
buffer.payload.SetSize(received > 0 ? received : 0);
if (received > 0 && timestamp != -1) {
buffer.arrival_time = webrtc::Timestamp::Micros(timestamp);
@ -472,7 +515,8 @@ int PhysicalSocket::RecvFrom(ReceiveBuffer& buffer) {
int PhysicalSocket::DoReadFromSocket(void* buffer,
size_t length,
SocketAddress* out_addr,
int64_t* timestamp) {
int64_t* timestamp,
EcnMarking* ecn) {
sockaddr_storage addr_storage;
socklen_t addr_len = sizeof(addr_storage);
sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
@ -487,8 +531,10 @@ int PhysicalSocket::DoReadFromSocket(void* buffer,
msg.msg_name = addr;
msg.msg_namelen = addr_len;
}
char control[CMSG_SPACE(sizeof(struct timeval))] = {};
if (timestamp) {
// TODO(bugs.webrtc.org/15368): What size is needed? IPV6_TCLASS is supposed
// to be an int. Why is a larger size needed?
char control[CMSG_SPACE(sizeof(struct timeval) + 5 * sizeof(int))] = {};
if (timestamp || ecn) {
*timestamp = -1;
msg.msg_control = &control;
msg.msg_controllen = sizeof(control);
@ -498,17 +544,23 @@ int PhysicalSocket::DoReadFromSocket(void* buffer,
// An error occured or shut down.
return received;
}
if (timestamp) {
if (timestamp || ecn) {
struct cmsghdr* cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (ecn) {
if ((cmsg->cmsg_type == IPV6_TCLASS &&
cmsg->cmsg_level == IPPROTO_IPV6) ||
(cmsg->cmsg_type == IP_TOS && cmsg->cmsg_level == IPPROTO_IP)) {
*ecn = EcnFromDs(CMSG_DATA(cmsg)[0]);
}
}
if (cmsg->cmsg_level != SOL_SOCKET)
continue;
if (cmsg->cmsg_type == SCM_TIMESTAMP) {
if (timestamp && cmsg->cmsg_type == SCM_TIMESTAMP) {
timeval* ts = reinterpret_cast<timeval*>(CMSG_DATA(cmsg));
*timestamp =
rtc::kNumMicrosecsPerSec * static_cast<int64_t>(ts->tv_sec) +
static_cast<int64_t>(ts->tv_usec);
break;
}
}
}
@ -699,6 +751,34 @@ int PhysicalSocket::TranslateOption(Option opt, int* slevel, int* sopt) {
#else
RTC_LOG(LS_WARNING) << "Socket::OPT_DSCP not supported.";
return -1;
#endif
case OPT_SEND_ECN:
#if defined(WEBRTC_POSIX)
if (family_ == AF_INET6) {
*slevel = IPPROTO_IPV6;
*sopt = IPV6_TCLASS;
} else {
*slevel = IPPROTO_IP;
*sopt = IP_TOS;
}
break;
#else
RTC_LOG(LS_WARNING) << "Socket::OPT_SEND_ESN not supported.";
return -1;
#endif
case OPT_RECV_ECN:
#if defined(WEBRTC_POSIX)
if (family_ == AF_INET6) {
*slevel = IPPROTO_IPV6;
*sopt = IPV6_RECVTCLASS;
} else {
*slevel = IPPROTO_IP;
*sopt = IP_RECVTOS;
}
break;
#else
RTC_LOG(LS_WARNING) << "Socket::OPT_RECV_ECN not supported.";
return -1;
#endif
case OPT_RTP_SENDTIME_EXTN_ID:
return -1; // No logging is necessary as this not a OS socket option.

View file

@ -224,7 +224,8 @@ class PhysicalSocket : public Socket, public sigslot::has_slots<> {
int DoReadFromSocket(void* buffer,
size_t length,
SocketAddress* out_addr,
int64_t* timestamp);
int64_t* timestamp,
EcnMarking* ecn);
void OnResolveResult(const webrtc::AsyncDnsResolverResult& resolver);
@ -246,6 +247,8 @@ class PhysicalSocket : public Socket, public sigslot::has_slots<> {
int error_ RTC_GUARDED_BY(mutex_);
ConnState state_;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_;
uint8_t dscp_ = 0; // 6bit.
uint8_t ecn_ = 0; // 2bits.
#if !defined(NDEBUG)
std::string dbg_addr_;

View file

@ -484,6 +484,19 @@ TEST_F(PhysicalSocketTest, TestSocketRecvTimestampIPv6ScmExperimentDisabled) {
}
#endif
#if !defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
// TODO(bugs.webrtc.org/15368): IpV4 fails on IOS and MAC. IPV6 works.
TEST_F(PhysicalSocketTest, TestSocketSendRecvWithEcnIPv4) {
MAYBE_SKIP_IPV6;
SocketTest::TestSocketSendRecvWithEcnIPV4();
}
#endif
TEST_F(PhysicalSocketTest, TestSocketSendRecvWithEcnIPv6) {
MAYBE_SKIP_IPV6;
SocketTest::TestSocketSendRecvWithEcnIPV6();
}
// Verify that if the socket was unable to be bound to a real network interface
// (not loopback), Bind will return an error.
TEST_F(PhysicalSocketTest,

View file

@ -30,6 +30,7 @@
#include "api/units/timestamp.h"
#include "rtc_base/buffer.h"
#include "rtc_base/network/ecn_marking.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
@ -91,6 +92,7 @@ class RTC_EXPORT Socket {
absl::optional<webrtc::Timestamp> arrival_time;
SocketAddress source_address;
EcnMarking ecn = EcnMarking::kNotEct;
Buffer& payload;
};
virtual ~Socket() {}
@ -144,6 +146,8 @@ class RTC_EXPORT Socket {
OPT_RTP_SENDTIME_EXTN_ID, // This is a non-traditional socket option param.
// This is specific to libjingle and will be used
// if SendTime option is needed at socket level.
OPT_SEND_ECN, // 2-bit ECN
OPT_RECV_ECN
};
virtual int GetOption(Option opt, int* value) = 0;
virtual int SetOption(Option opt, int value) = 0;

View file

@ -234,6 +234,15 @@ void SocketTest::TestUdpSocketRecvTimestampUseRtcEpochIPv6() {
UdpSocketRecvTimestampUseRtcEpoch(kIPv6Loopback);
}
void SocketTest::TestSocketSendRecvWithEcnIPV4() {
SocketSendRecvWithEcn(kIPv4Loopback);
}
void SocketTest::TestSocketSendRecvWithEcnIPV6() {
MAYBE_SKIP_IPV6;
SocketSendRecvWithEcn(kIPv6Loopback);
}
// For unbound sockets, GetLocalAddress / GetRemoteAddress return AF_UNSPEC
// values on Windows, but an empty address of the same family on Linux/MacOS X.
bool IsUnspecOrEmptyIP(const IPAddress& address) {
@ -1079,6 +1088,18 @@ void SocketTest::GetSetOptionsInternal(const IPAddress& loopback) {
ASSERT_NE(-1, socket->SetOption(Socket::OPT_DSCP, desired_dscp));
ASSERT_NE(-1, socket->GetOption(Socket::OPT_DSCP, &current_dscp));
ASSERT_EQ(desired_dscp, current_dscp);
int current_send_esn, desired_send_esn = 1;
ASSERT_NE(-1, socket->GetOption(Socket::OPT_SEND_ECN, &current_send_esn));
ASSERT_NE(-1, socket->SetOption(Socket::OPT_SEND_ECN, desired_send_esn));
ASSERT_NE(-1, socket->GetOption(Socket::OPT_SEND_ECN, &current_send_esn));
ASSERT_EQ(current_send_esn, desired_send_esn);
int current_recv_esn, desired_recv_esn = 1;
ASSERT_NE(-1, socket->GetOption(Socket::OPT_RECV_ECN, &current_recv_esn));
ASSERT_NE(-1, socket->SetOption(Socket::OPT_RECV_ECN, desired_recv_esn));
ASSERT_NE(-1, socket->GetOption(Socket::OPT_RECV_ECN, &current_recv_esn));
ASSERT_EQ(current_recv_esn, desired_recv_esn);
#endif
}
@ -1143,4 +1164,41 @@ void SocketTest::UdpSocketRecvTimestampUseRtcEpoch(const IPAddress& loopback) {
EXPECT_GT(packet_2->packet_time->us(), packet_1->packet_time->us());
EXPECT_NEAR(packet_2->packet_time->us(), rtc::TimeMicros(), 1000'000);
}
void SocketTest::SocketSendRecvWithEcn(const IPAddress& loopback) {
StreamSink sink;
std::unique_ptr<Socket> socket(
socket_factory_->CreateSocket(loopback.family(), SOCK_DGRAM));
EXPECT_EQ(0, socket->Bind(SocketAddress(loopback, 0)));
SocketAddress address = socket->GetLocalAddress();
sink.Monitor(socket.get());
rtc::Buffer buffer;
Socket::ReceiveBuffer receive_buffer(buffer);
socket->SendTo("foo", 3, address);
EXPECT_TRUE_WAIT(sink.Check(socket.get(), SSE_READ), kTimeout);
ASSERT_GT(socket->RecvFrom(receive_buffer), 0);
EXPECT_EQ(receive_buffer.ecn, EcnMarking::kNotEct);
socket->SetOption(Socket::OPT_SEND_ECN, 1); // Ect(1)
socket->SetOption(Socket::OPT_RECV_ECN, 1);
socket->SendTo("bar", 3, address);
EXPECT_TRUE_WAIT(sink.Check(socket.get(), SSE_READ), kTimeout);
ASSERT_GT(socket->RecvFrom(receive_buffer), 0);
EXPECT_EQ(receive_buffer.ecn, EcnMarking::kEct1);
socket->SetOption(Socket::OPT_SEND_ECN, 2); // Ect(0)
socket->SendTo("bar", 3, address);
EXPECT_TRUE_WAIT(sink.Check(socket.get(), SSE_READ), kTimeout);
ASSERT_GT(socket->RecvFrom(receive_buffer), 0);
EXPECT_EQ(receive_buffer.ecn, EcnMarking::kEct0);
socket->SetOption(Socket::OPT_SEND_ECN, 3); // Ce
socket->SendTo("bar", 3, address);
EXPECT_TRUE_WAIT(sink.Check(socket.get(), SSE_READ), kTimeout);
ASSERT_GT(socket->RecvFrom(receive_buffer), 0);
EXPECT_EQ(receive_buffer.ecn, EcnMarking::kCe);
}
} // namespace rtc

View file

@ -64,6 +64,8 @@ class SocketTest : public ::testing::Test {
void TestSocketRecvTimestampIPv6();
void TestUdpSocketRecvTimestampUseRtcEpochIPv4();
void TestUdpSocketRecvTimestampUseRtcEpochIPv6();
void TestSocketSendRecvWithEcnIPV4();
void TestSocketSendRecvWithEcnIPV6();
static const int kTimeout = 5000; // ms
const IPAddress kIPv4Loopback;
@ -95,6 +97,7 @@ class SocketTest : public ::testing::Test {
void GetSetOptionsInternal(const IPAddress& loopback);
void SocketRecvTimestamp(const IPAddress& loopback);
void UdpSocketRecvTimestampUseRtcEpoch(const IPAddress& loopback);
void SocketSendRecvWithEcn(const IPAddress& loopback);
SocketFactory* socket_factory_;
};