diff --git a/include/rtc/configuration.hpp b/include/rtc/configuration.hpp index 0c9693e..d37b663 100644 --- a/include/rtc/configuration.hpp +++ b/include/rtc/configuration.hpp @@ -70,6 +70,7 @@ struct RTC_CPP_EXPORT Configuration { bool enableIceTcp = false; uint16_t portRangeBegin = 1024; uint16_t portRangeEnd = 65535; + std::optional mtu; }; } // namespace rtc diff --git a/include/rtc/include.hpp b/include/rtc/include.hpp index 6b5fedb..cf72f40 100644 --- a/include/rtc/include.hpp +++ b/include/rtc/include.hpp @@ -77,6 +77,8 @@ const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // Max per-channel queue size const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool +const size_t DEFAULT_IPV4_MTU = 1200; // IPv4 safe MTU value recommended by RFC 8261 + // overloaded helper template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; diff --git a/src/dtlssrtptransport.cpp b/src/dtlssrtptransport.cpp index aa02a75..df6292f 100644 --- a/src/dtlssrtptransport.cpp +++ b/src/dtlssrtptransport.cpp @@ -58,10 +58,11 @@ void DtlsSrtpTransport::Cleanup() { srtp_shutdown(); } DtlsSrtpTransport::DtlsSrtpTransport(std::shared_ptr lower, shared_ptr certificate, + std::optional mtu, verifier_callback verifierCallback, message_callback srtpRecvCallback, state_callback stateChangeCallback) - : DtlsTransport(lower, certificate, std::move(verifierCallback), + : DtlsTransport(lower, certificate, mtu, std::move(verifierCallback), std::move(stateChangeCallback)), mSrtpRecvCallback(std::move(srtpRecvCallback)) { // distinct from Transport recv callback diff --git a/src/dtlssrtptransport.hpp b/src/dtlssrtptransport.hpp index 82e586c..e93daca 100644 --- a/src/dtlssrtptransport.hpp +++ b/src/dtlssrtptransport.hpp @@ -39,9 +39,9 @@ public: static void Init(); static void Cleanup(); - DtlsSrtpTransport(std::shared_ptr lower, std::shared_ptr certificate, - verifier_callback verifierCallback, message_callback srtpRecvCallback, - state_callback stateChangeCallback); + DtlsSrtpTransport(std::shared_ptr lower, certificate_ptr certificate, + std::optional mtu, verifier_callback verifierCallback, + message_callback srtpRecvCallback, state_callback stateChangeCallback); ~DtlsSrtpTransport(); bool sendMedia(message_ptr message); diff --git a/src/dtlstransport.cpp b/src/dtlstransport.cpp index d11322f..fc0d7f8 100644 --- a/src/dtlstransport.cpp +++ b/src/dtlstransport.cpp @@ -50,8 +50,9 @@ void DtlsTransport::Init() { void DtlsTransport::Cleanup() { gnutls_global_deinit(); } DtlsTransport::DtlsTransport(shared_ptr lower, certificate_ptr certificate, - verifier_callback verifierCallback, state_callback stateChangeCallback) - : Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate), + std::optional mtu, verifier_callback verifierCallback, + state_callback stateChangeCallback) + : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate), mVerifierCallback(std::move(verifierCallback)), mIsClient(lower->role() == Description::Role::Active), mCurrentDscp(0) { @@ -156,11 +157,15 @@ void DtlsTransport::postHandshake() { } void DtlsTransport::runRecvLoop() { - const size_t maxMtu = 4096; + const size_t bufferSize = 4096; + // Handshake loop try { changeState(State::Connecting); - gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6 + + size_t mtu = mMtu.value_or(DEFAULT_IPV4_MTU + 20) - 8 - 40; // UDP/IPv6 + gnutls_dtls_set_mtu(mSession, static_cast(mtu)); + PLOG_VERBOSE << "SSL MTU set to " << mtu; int ret; do { @@ -174,7 +179,7 @@ void DtlsTransport::runRecvLoop() { // RFC 8261: DTLS MUST support sending messages larger than the current path MTU // See https://tools.ietf.org/html/rfc8261#section-5 - gnutls_dtls_set_mtu(mSession, maxMtu + 1); + gnutls_dtls_set_mtu(mSession, bufferSize + 1); } catch (const std::exception &e) { PLOG_ERROR << "DTLS handshake: " << e.what(); @@ -188,7 +193,6 @@ void DtlsTransport::runRecvLoop() { postHandshake(); changeState(State::Connected); - const size_t bufferSize = maxMtu; char buffer[bufferSize]; while (true) { @@ -314,8 +318,9 @@ void DtlsTransport::Cleanup() { } DtlsTransport::DtlsTransport(shared_ptr lower, shared_ptr certificate, - verifier_callback verifierCallback, state_callback stateChangeCallback) - : Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate), + std::optional mtu, verifier_callback verifierCallback, + state_callback stateChangeCallback) + : Transport(lower, std::move(stateChangeCallback)), mMtu(mtu), mCertificate(certificate), mVerifierCallback(std::move(verifierCallback)), mIsClient(lower->role() == Description::Role::Active), mCurrentDscp(0) { PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)"; @@ -440,16 +445,18 @@ void DtlsTransport::postHandshake() { } void DtlsTransport::runRecvLoop() { - const size_t maxMtu = 4096; + const size_t bufferSize = 4096; try { changeState(State::Connecting); - SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6 + + size_t mtu = mMtu.value_or(DEFAULT_IPV4_MTU + 20) - 8 - 40; // UDP/IPv6 + SSL_set_mtu(mSsl, static_cast(mtu)); + PLOG_VERBOSE << "SSL MTU set to " << mtu; // Initiate the handshake int ret = SSL_do_handshake(mSsl); openssl::check(mSsl, ret, "Handshake failed"); - const size_t bufferSize = maxMtu; byte buffer[bufferSize]; while (mIncomingQueue.running()) { // Process pending messages @@ -466,7 +473,7 @@ void DtlsTransport::runRecvLoop() { if (SSL_is_init_finished(mSsl)) { // RFC 8261: DTLS MUST support sending messages larger than the current path // MTU See https://tools.ietf.org/html/rfc8261#section-5 - SSL_set_mtu(mSsl, maxMtu + 1); + SSL_set_mtu(mSsl, bufferSize + 1); PLOG_INFO << "DTLS handshake finished"; postHandshake(); diff --git a/src/dtlstransport.hpp b/src/dtlstransport.hpp index fa82995..c31f58b 100644 --- a/src/dtlstransport.hpp +++ b/src/dtlstransport.hpp @@ -44,7 +44,8 @@ public: using verifier_callback = std::function; DtlsTransport(std::shared_ptr lower, certificate_ptr certificate, - verifier_callback verifierCallback, state_callback stateChangeCallback); + std::optional mtu, verifier_callback verifierCallback, + state_callback stateChangeCallback); ~DtlsTransport(); virtual void start() override; @@ -57,6 +58,7 @@ protected: virtual void postHandshake(); void runRecvLoop(); + const std::optional mMtu; const certificate_ptr mCertificate; const verifier_callback mVerifierCallback; const bool mIsClient; diff --git a/src/peerconnection.cpp b/src/peerconnection.cpp index e58b8d9..07a719d 100644 --- a/src/peerconnection.cpp +++ b/src/peerconnection.cpp @@ -22,8 +22,8 @@ #include "include.hpp" #include "logcounter.hpp" #include "processor.hpp" -#include "threadpool.hpp" #include "rtp.hpp" +#include "threadpool.hpp" #include "dtlstransport.hpp" #include "icetransport.hpp" @@ -75,6 +75,17 @@ PeerConnection::PeerConnection(const Configuration &config) if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd) throw std::invalid_argument("Invalid port range"); + + if (config.mtu) { + if (*config.mtu < 576) // Min MTU for IPv4 + throw std::invalid_argument("Invalid MTU value"); + + if (*config.mtu > 1500) { // Standard Ethernet + PLOG_WARNING << "MTU set to " << *config.mtu; + } else { + PLOG_VERBOSE << "MTU set to " << *config.mtu; + } + } } PeerConnection::~PeerConnection() { @@ -515,7 +526,7 @@ shared_ptr PeerConnection::initDtlsTransport() { // DTLS-SRTP transport = std::make_shared( - lower, certificate, verifierCallback, + lower, certificate, mConfig.mtu, verifierCallback, weak_bind(&PeerConnection::forwardMedia, this, _1), stateChangeCallback); #else PLOG_WARNING << "Ignoring media support (not compiled with media support)"; @@ -524,8 +535,8 @@ shared_ptr PeerConnection::initDtlsTransport() { if (!transport) { // DTLS only - transport = std::make_shared(lower, certificate, verifierCallback, - stateChangeCallback); + transport = std::make_shared(lower, certificate, mConfig.mtu, + verifierCallback, stateChangeCallback); } std::atomic_store(&mDtlsTransport, transport); @@ -557,7 +568,7 @@ shared_ptr PeerConnection::initSctpTransport() { uint16_t sctpPort = remote->application()->sctpPort().value_or(DEFAULT_SCTP_PORT); auto lower = std::atomic_load(&mDtlsTransport); auto transport = std::make_shared( - lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1), + lower, sctpPort, mConfig.mtu, weak_bind(&PeerConnection::forwardMessage, this, _1), weak_bind(&PeerConnection::forwardBufferedAmount, this, _1, _2), [this, weak_this = weak_from_this()](SctpTransport::State state) { auto shared_this = weak_this.lock(); @@ -663,8 +674,8 @@ void PeerConnection::forwardMessage(message_ptr message) { if (message->type == Message::Control && *message->data() == dataChannelOpenMessage && stream % 2 == remoteParity) { - channel = std::make_shared(shared_from_this(), sctpTransport, - stream); + channel = + std::make_shared(shared_from_this(), sctpTransport, stream); channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this, weak_ptr{channel})); diff --git a/src/sctptransport.cpp b/src/sctptransport.cpp index d7f5fb8..6ac988f 100644 --- a/src/sctptransport.cpp +++ b/src/sctptransport.cpp @@ -17,6 +17,7 @@ */ #include "sctptransport.hpp" +#include "dtlstransport.hpp" #include "logcounter.hpp" #include @@ -25,16 +26,6 @@ #include #include -// RFC 8261 5. DTLS considerations: -// If path MTU discovery is performed by the SCTP layer and IPv4 is used as the network-layer -// protocol, the DTLS implementation SHOULD allow the DTLS user to enforce that the -// corresponding IPv4 packet is sent with the Don't Fragment (DF) bit set. If controlling the DF -// bit is not possible (for example, due to implementation restrictions), a safe value for the -// path MTU has to be used by the SCTP stack. It is RECOMMENDED that the safe value not exceed -// 1200 bytes. -// See https://tools.ietf.org/html/rfc8261#section-5 -#define DEFAULT_MTU 1200 - // The IETF draft says: // SCTP MUST support performing Path MTU discovery without relying on ICMP or ICMPv6 as specified in // [RFC4821] using probing messages specified in [RFC4820]. @@ -60,12 +51,9 @@ #endif */ - using namespace std::chrono_literals; using namespace std::chrono; -using std::shared_ptr; - namespace rtc { static LogCounter COUNTER_UNKNOWN_PPID(plog::warning, @@ -112,7 +100,8 @@ void SctpTransport::Cleanup() { } SctpTransport::SctpTransport(std::shared_ptr lower, uint16_t port, - message_callback recvCallback, amount_callback bufferedAmountCallback, + std::optional mtu, message_callback recvCallback, + amount_callback bufferedAmountCallback, state_callback stateChangeCallback) : Transport(lower, std::move(stateChangeCallback)), mPort(port), mPendingRecvCount(0), mSendQueue(0, message_size_func), mBufferedAmountCallback(std::move(bufferedAmountCallback)) { @@ -180,16 +169,34 @@ SctpTransport::SctpTransport(std::shared_ptr lower, uint16_t port, struct sctp_paddrparams spp = {}; // Enable SCTP heartbeats spp.spp_flags = SPP_HB_ENABLE; + + // RFC 8261 5. DTLS considerations: + // If path MTU discovery is performed by the SCTP layer and IPv4 is used as the network-layer + // protocol, the DTLS implementation SHOULD allow the DTLS user to enforce that the + // corresponding IPv4 packet is sent with the Don't Fragment (DF) bit set. If controlling the DF + // bit is not possible (for example, due to implementation restrictions), a safe value for the + // path MTU has to be used by the SCTP stack. It is RECOMMENDED that the safe value not exceed + // 1200 bytes. + // See https://tools.ietf.org/html/rfc8261#section-5 #if USE_PMTUD - // Enable SCTP path MTU discovery - spp.spp_flags |= SPP_PMTUD_ENABLE; + if (!mtu.has_value()) { #else - // Fall back to a safe MTU value. - spp.spp_flags |= SPP_PMTUD_DISABLE; - // The MTU value provided specifies the space available for chunks in the - // packet, so we also subtract the SCTP header size. - spp.spp_pathmtu = DEFAULT_MTU - 12 - 37 - 8 - 20; // SCTP/DTLS/UDP/IPv4 + if (false) { #endif + // Enable SCTP path MTU discovery + spp.spp_flags |= SPP_PMTUD_ENABLE; + PLOG_VERBOSE << "Path MTU discovery enabled"; + + } else { + // Fall back to a safe MTU value. + spp.spp_flags |= SPP_PMTUD_DISABLE; + // The MTU value provided specifies the space available for chunks in the + // packet, so we also subtract the SCTP header size. + size_t pmtu = mtu.value_or(DEFAULT_IPV4_MTU + 20) - 12 - 37 - 8 - 40; // SCTP/DTLS/UDP/IPv6 + spp.spp_pathmtu = uint32_t(pmtu); + PLOG_VERBOSE << "Path MTU discovery disabled, SCTP MTU set to " << pmtu; + } + if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &spp, sizeof(spp))) throw std::runtime_error("Could not set socket option SCTP_PEER_ADDR_PARAMS, errno=" + std::to_string(errno)); diff --git a/src/sctptransport.hpp b/src/sctptransport.hpp index f715103..2bae169 100644 --- a/src/sctptransport.hpp +++ b/src/sctptransport.hpp @@ -43,8 +43,9 @@ public: using amount_callback = std::function; - SctpTransport(std::shared_ptr lower, uint16_t port, message_callback recvCallback, - amount_callback bufferedAmountCallback, state_callback stateChangeCallback); + SctpTransport(std::shared_ptr lower, uint16_t port, std::optional mtu, + message_callback recvCallback, amount_callback bufferedAmountCallback, + state_callback stateChangeCallback); ~SctpTransport(); void start() override; diff --git a/test/benchmark.cpp b/test/benchmark.cpp index ae8d4a6..fca21f5 100644 --- a/test/benchmark.cpp +++ b/test/benchmark.cpp @@ -40,11 +40,13 @@ size_t benchmark(milliseconds duration) { Configuration config1; // config1.iceServers.emplace_back("stun:stun.l.google.com:19302"); + // config1.mtu = 1500; auto pc1 = std::make_shared(config1); Configuration config2; // config2.iceServers.emplace_back("stun:stun.l.google.com:19302"); + // config2.mtu = 1500; auto pc2 = std::make_shared(config2); diff --git a/test/connectivity.cpp b/test/connectivity.cpp index eb9698a..882e856 100644 --- a/test/connectivity.cpp +++ b/test/connectivity.cpp @@ -36,6 +36,8 @@ void test_connectivity() { // STUN server example (not necessary to connect locally) // Please do not use outside of libdatachannel tests config1.iceServers.emplace_back("stun:stun.ageneau.net:3478"); + // Custom MTU example + config1.mtu = 1500; auto pc1 = std::make_shared(config1); @@ -43,6 +45,8 @@ void test_connectivity() { // STUN server example (not necessary to connect locally) // Please do not use outside of libdatachannel tests config2.iceServers.emplace_back("stun:stun.ageneau.net:3478"); + // Custom MTU example + config2.mtu = 1500; // Port range example config2.portRangeBegin = 5000; config2.portRangeEnd = 6000;