Browse Source

Added optional MTU setting in configuration

Paul-Louis Ageneau 4 years ago
parent
commit
6ef8f1e1a7

+ 1 - 0
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<size_t> mtu;
 };
 
 } // namespace rtc

+ 2 - 0
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 <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
 template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

+ 2 - 1
src/dtlssrtptransport.cpp

@@ -58,10 +58,11 @@ void DtlsSrtpTransport::Cleanup() { srtp_shutdown(); }
 
 DtlsSrtpTransport::DtlsSrtpTransport(std::shared_ptr<IceTransport> lower,
                                      shared_ptr<Certificate> certificate,
+                                     std::optional<size_t> 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
 

+ 3 - 3
src/dtlssrtptransport.hpp

@@ -39,9 +39,9 @@ public:
 	static void Init();
 	static void Cleanup();
 
-	DtlsSrtpTransport(std::shared_ptr<IceTransport> lower, std::shared_ptr<Certificate> certificate,
-	                  verifier_callback verifierCallback, message_callback srtpRecvCallback,
-	                  state_callback stateChangeCallback);
+	DtlsSrtpTransport(std::shared_ptr<IceTransport> lower, certificate_ptr certificate,
+	                  std::optional<size_t> mtu, verifier_callback verifierCallback,
+	                  message_callback srtpRecvCallback, state_callback stateChangeCallback);
 	~DtlsSrtpTransport();
 
 	bool sendMedia(message_ptr message);

+ 19 - 12
src/dtlstransport.cpp

@@ -50,8 +50,9 @@ void DtlsTransport::Init() {
 void DtlsTransport::Cleanup() { gnutls_global_deinit(); }
 
 DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
-                             verifier_callback verifierCallback, state_callback stateChangeCallback)
-    : Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate),
+                             std::optional<size_t> 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<unsigned int>(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<IceTransport> lower, shared_ptr<Certificate> certificate,
-                             verifier_callback verifierCallback, state_callback stateChangeCallback)
-    : Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate),
+                             std::optional<size_t> 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<unsigned int>(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();

+ 3 - 1
src/dtlstransport.hpp

@@ -44,7 +44,8 @@ public:
 	using verifier_callback = std::function<bool(const std::string &fingerprint)>;
 
 	DtlsTransport(std::shared_ptr<IceTransport> lower, certificate_ptr certificate,
-	              verifier_callback verifierCallback, state_callback stateChangeCallback);
+	              std::optional<size_t> 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<size_t> mMtu;
 	const certificate_ptr mCertificate;
 	const verifier_callback mVerifierCallback;
 	const bool mIsClient;

+ 18 - 7
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<DtlsTransport> PeerConnection::initDtlsTransport() {
 
 			// DTLS-SRTP
 			transport = std::make_shared<DtlsSrtpTransport>(
-			    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<DtlsTransport> PeerConnection::initDtlsTransport() {
 
 		if (!transport) {
 			// DTLS only
-			transport = std::make_shared<DtlsTransport>(lower, certificate, verifierCallback,
-			                                            stateChangeCallback);
+			transport = std::make_shared<DtlsTransport>(lower, certificate, mConfig.mtu,
+			                                            verifierCallback, stateChangeCallback);
 		}
 
 		std::atomic_store(&mDtlsTransport, transport);
@@ -557,7 +568,7 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
 		uint16_t sctpPort = remote->application()->sctpPort().value_or(DEFAULT_SCTP_PORT);
 		auto lower = std::atomic_load(&mDtlsTransport);
 		auto transport = std::make_shared<SctpTransport>(
-		    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<NegotiatedDataChannel>(shared_from_this(), sctpTransport,
-			                                                  stream);
+			channel =
+			    std::make_shared<NegotiatedDataChannel>(shared_from_this(), sctpTransport, stream);
 			channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
 			                          weak_ptr<DataChannel>{channel}));
 

+ 28 - 21
src/sctptransport.cpp

@@ -17,6 +17,7 @@
  */
 
 #include "sctptransport.hpp"
+#include "dtlstransport.hpp"
 #include "logcounter.hpp"
 
 #include <chrono>
@@ -25,16 +26,6 @@
 #include <thread>
 #include <vector>
 
-// 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<Transport> lower, uint16_t port,
-                             message_callback recvCallback, amount_callback bufferedAmountCallback,
+                             std::optional<size_t> 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<Transport> 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));

+ 3 - 2
src/sctptransport.hpp

@@ -43,8 +43,9 @@ public:
 
 	using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
 
-	SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, message_callback recvCallback,
-	              amount_callback bufferedAmountCallback, state_callback stateChangeCallback);
+	SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, std::optional<size_t> mtu,
+	              message_callback recvCallback, amount_callback bufferedAmountCallback,
+	              state_callback stateChangeCallback);
 	~SctpTransport();
 
 	void start() override;

+ 2 - 0
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<PeerConnection>(config1);
 
 	Configuration config2;
 	// config2.iceServers.emplace_back("stun:stun.l.google.com:19302");
+	// config2.mtu = 1500;
 
 	auto pc2 = std::make_shared<PeerConnection>(config2);
 

+ 4 - 0
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<PeerConnection>(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;