diff --git a/src/ice_socket.h b/src/ice_socket.h index 69d85a44d08667424f0285c7419839e11a7e32da..a35fb64269631f68116a4ad5621abddf79f17a00 100644 --- a/src/ice_socket.h +++ b/src/ice_socket.h @@ -23,9 +23,9 @@ #include <memory> #include <functional> -#if defined(_MSC_VER) -#include <BaseTsd.h> -using ssize_t = SSIZE_T; +#if defined(_MSC_VER) +#include <BaseTsd.h> +using ssize_t = SSIZE_T; #endif namespace ring { @@ -49,6 +49,7 @@ class IceSocket ssize_t getNextPacketSize() const; ssize_t waitForData(unsigned int timeout); void setOnRecv(IceRecvCb cb); + uint16_t getTransportOverhead(); }; }; diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp index dc359963e1dd392476cab1f69b9842fdc464f11d..489d47e85f4ebfad0e85495801d43152bc3a81ab 100644 --- a/src/ice_transport.cpp +++ b/src/ice_transport.cpp @@ -44,6 +44,8 @@ namespace ring { static constexpr unsigned STUN_MAX_PACKET_SIZE {8192}; +static constexpr uint16_t IPV6_HEADER_SIZE = 40; // Size in bytes of IPV6 packet header +static constexpr uint16_t IPV4_HEADER_SIZE = 20; // Size in bytes of IPV4 packet header // TODO: C++14 ? remove me and use std::min template< class T > @@ -1061,4 +1063,9 @@ IceSocket::setOnRecv(IceRecvCb cb) return ice_transport_->setOnRecv(compId_, cb); } +uint16_t +IceSocket::getTransportOverhead(){ + return (ice_transport_->getRemoteAddress(compId_).getFamily() == AF_INET) ? IPV4_HEADER_SIZE : IPV6_HEADER_SIZE; +} + } // namespace ring diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp index 1bbbccc9549d0ce72b0bec57248f622a6c42f32a..f2238758d5bf9afe7a3fc33d8affcee8a043e223 100644 --- a/src/media/audio/audio_rtp_session.cpp +++ b/src/media/audio/audio_rtp_session.cpp @@ -49,7 +49,8 @@ class AudioSender { const MediaDescription& args, SocketPair& socketPair, const uint16_t seqVal, - bool muteState); + bool muteState, + const uint16_t mtu); ~AudioSender(); void setMuted(bool isMuted); @@ -71,6 +72,7 @@ class AudioSender { AudioBuffer resampledData_; const uint16_t seqVal_; bool muteState_ = false; + uint16_t mtu_; using seconds = std::chrono::duration<double, std::ratio<1>>; const seconds secondsPerPacket_ {0.02}; // 20 ms @@ -85,12 +87,14 @@ AudioSender::AudioSender(const std::string& id, const MediaDescription& args, SocketPair& socketPair, const uint16_t seqVal, - bool muteState) : + bool muteState, + const uint16_t mtu) : id_(id), dest_(dest), args_(args), seqVal_(seqVal), muteState_(muteState), + mtu_(mtu), loop_([&] { return setup(socketPair); }, std::bind(&AudioSender::process, this), std::bind(&AudioSender::cleanup, this)) @@ -107,7 +111,7 @@ bool AudioSender::setup(SocketPair& socketPair) { audioEncoder_.reset(new MediaEncoder); - muxContext_.reset(socketPair.createIOContext()); + muxContext_.reset(socketPair.createIOContext(mtu_)); try { /* Encoder setup */ @@ -199,7 +203,8 @@ class AudioReceiveThread public: AudioReceiveThread(const std::string &id, const AudioFormat& format, - const std::string& sdp); + const std::string& sdp, + const uint16_t mtu); ~AudioReceiveThread(); void addIOContext(SocketPair &socketPair); void startLoop(); @@ -230,6 +235,8 @@ class AudioReceiveThread std::shared_ptr<RingBuffer> ringbuffer_; + uint16_t mtu_; + ThreadLoop loop_; bool setup(); void process(); @@ -238,10 +245,12 @@ class AudioReceiveThread AudioReceiveThread::AudioReceiveThread(const std::string& id, const AudioFormat& format, - const std::string& sdp) + const std::string& sdp, + const uint16_t mtu) : id_(id) , format_(format) , stream_(sdp) + , mtu_(mtu) , sdpContext_(new MediaIOHandle(sdp.size(), false, &readFunction, 0, 0, this)) , loop_(std::bind(&AudioReceiveThread::setup, this), @@ -345,7 +354,7 @@ AudioReceiveThread::interruptCb(void* data) void AudioReceiveThread::addIOContext(SocketPair& socketPair) { - demuxContext_.reset(socketPair.createIOContext()); + demuxContext_.reset(socketPair.createIOContext(mtu_)); } void @@ -391,7 +400,7 @@ AudioRtpSession::startSender() sender_.reset(); socketPair_->stopSendOp(false); sender_.reset(new AudioSender(callID_, getRemoteRtpUri(), send_, - *socketPair_, initSeqVal_, muteState_)); + *socketPair_, initSeqVal_, muteState_, mtu_)); } catch (const MediaEncoderException &e) { RING_ERR("%s", e.what()); send_.enabled = false; @@ -423,7 +432,8 @@ AudioRtpSession::startReceiver() auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(receive_.codec); receiveThread_.reset(new AudioReceiveThread(callID_, accountAudioCodec->audioformat, - receive_.receiving_sdp)); + receive_.receiving_sdp, + mtu_)); receiveThread_->addIOContext(*socketPair_); receiveThread_->startLoop(); } diff --git a/src/media/rtp_session.h b/src/media/rtp_session.h index 0dc4e5ecaef06a2a5d46b6ef3aa6f4c26e017d6b..6206181e0abfe142f7fb8bed1ab5b277581a3eac 100644 --- a/src/media/rtp_session.h +++ b/src/media/rtp_session.h @@ -51,6 +51,8 @@ public: bool isSending() const noexcept { return send_.enabled; } bool isReceiving() const noexcept { return receive_.enabled; } + void setMtu(uint16_t mtu) { mtu_ = mtu; } + protected: std::recursive_mutex mutex_; std::unique_ptr<SocketPair> socketPair_; @@ -59,6 +61,8 @@ protected: MediaDescription send_; MediaDescription receive_; + uint16_t mtu_; + std::string getRemoteRtpUri() const { return "rtp://" + send_.addr.toString(true); } diff --git a/src/media/socket_pair.cpp b/src/media/socket_pair.cpp index 398bee84f66fde6863434f48a1b6ae1eeb30d165..0b7045aca93be2125c667c1501b8695a36ffa752 100644 --- a/src/media/socket_pair.cpp +++ b/src/media/socket_pair.cpp @@ -60,6 +60,8 @@ namespace ring { static constexpr int NET_POLL_TIMEOUT = 100; /* poll() timeout in ms */ static constexpr int RTP_MAX_PACKET_LENGTH = 2048; +static constexpr auto UDP_HEADER_SIZE = 8; +static constexpr auto SRTP_OVERHEAD = 10; enum class DataType : unsigned { RTP=1<<0, RTCP=1<<1 }; @@ -190,10 +192,6 @@ udp_socket_create(sockaddr_storage* addr, socklen_t* addr_len, int local_port) return udp_fd; } -// Maximal size allowed for a RTP packet, this value of 1232 bytes is an IPv6 minimum (1280 - 40 IPv6 header - 8 UDP header). -static const size_t RTP_BUFFER_SIZE = 1232; -static const size_t SRTP_BUFFER_SIZE = RTP_BUFFER_SIZE - 10; - SocketPair::SocketPair(const char *uri, int localPort) : rtp_sock_() , rtcp_sock_() @@ -334,9 +332,11 @@ SocketPair::openSockets(const char* uri, int local_rtp_port) } MediaIOHandle* -SocketPair::createIOContext() +SocketPair::createIOContext(const uint16_t mtu) { - return new MediaIOHandle(srtpContext_ ? SRTP_BUFFER_SIZE : RTP_BUFFER_SIZE, true, + auto ip_header_size = rtp_sock_->getTransportOverhead(); + return new MediaIOHandle( mtu - (srtpContext_ ? SRTP_OVERHEAD : 0) - UDP_HEADER_SIZE - ip_header_size, + true, [](void* sp, uint8_t* buf, int len){ return static_cast<SocketPair*>(sp)->readCallback(buf, len); }, [](void* sp, uint8_t* buf, int len){ return static_cast<SocketPair*>(sp)->writeCallback(buf, len); }, 0, reinterpret_cast<void*>(this)); diff --git a/src/media/socket_pair.h b/src/media/socket_pair.h index 4fc447be7d42c5a9742e754d2ff62e7deb7789ab..95fc503473a72e7058179a6a4138a8fd1a4beada 100644 --- a/src/media/socket_pair.h +++ b/src/media/socket_pair.h @@ -80,7 +80,7 @@ class SocketPair { void interrupt(); - MediaIOHandle* createIOContext(); + MediaIOHandle* createIOContext(const uint16_t mtu); void openSockets(const char* uri, int localPort); void closeSockets(); diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp index 3f8c91c9ca08622c30277d852e0d6fdc336d60bf..5318c0b8fb2ae81d7e07c11849c3ac64d03d5c91 100644 --- a/src/media/video/video_receive_thread.cpp +++ b/src/media/video/video_receive_thread.cpp @@ -38,13 +38,15 @@ using std::string; VideoReceiveThread::VideoReceiveThread(const std::string& id, const std::string &sdp, - const bool isReset) : + const bool isReset, + uint16_t mtu) : VideoGenerator::VideoGenerator() , args_() , dstWidth_(0) , dstHeight_(0) , id_(id) , stream_(sdp) + , mtu_(mtu) , sdpContext_(stream_.str().size(), false, &readFunction, 0, 0, this) , sink_ {Manager::instance().createSinkClient(id)} , restartDecoder_(false) @@ -162,7 +164,7 @@ int VideoReceiveThread::readFunction(void *opaque, uint8_t *buf, int buf_size) void VideoReceiveThread::addIOContext(SocketPair &socketPair) { - demuxContext_.reset(socketPair.createIOContext()); + demuxContext_.reset(socketPair.createIOContext(mtu_)); } bool VideoReceiveThread::decodeFrame() diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h index 515c486bd7258bee1f87903a0e049d0b9a4b65c1..5b2253023a53fb494338f7c8dd9a278cbb8a3827 100644 --- a/src/media/video/video_receive_thread.h +++ b/src/media/video/video_receive_thread.h @@ -47,7 +47,7 @@ class SinkClient; class VideoReceiveThread : public VideoGenerator { public: - VideoReceiveThread(const std::string &id, const std::string &sdp, const bool isReset); + VideoReceiveThread(const std::string &id, const std::string &sdp, const bool isReset, uint16_t mtu); ~VideoReceiveThread(); void startLoop(); @@ -80,6 +80,8 @@ private: std::shared_ptr<SinkClient> sink_; std::atomic_bool restartDecoder_; bool isReset_; + uint16_t mtu_; + void (*requestKeyFrameCallback_)(const std::string &); void openDecoder(); bool decodeFrame(); diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index 265006ed4db0bba452d1c6ac21205d2057d48d68..c031540a8fa6d92913cb9bc53b38f2def190c908 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -102,7 +102,7 @@ void VideoRtpSession::startSender() sender_.reset(); socketPair_->stopSendOp(false); sender_.reset(new VideoSender(getRemoteRtpUri(), localVideoParams_, - send_, *socketPair_, initSeqVal_)); + send_, *socketPair_, initSeqVal_, mtu_)); } catch (const MediaEncoderException &e) { RING_ERR("%s", e.what()); send_.enabled = false; @@ -138,8 +138,9 @@ void VideoRtpSession::startReceiver() isReset = true; } receiveThread_.reset( - new VideoReceiveThread(callID_, receive_.receiving_sdp, isReset) + new VideoReceiveThread(callID_, receive_.receiving_sdp, isReset, mtu_) ); + /* ebail: keyframe requests can lead to timeout if they are not answered. * we decided so to disable them for the moment receiveThread_->setRequestKeyFrameCallback(&SIPVoIPLink::enqueueKeyframeRequest); diff --git a/src/media/video/video_sender.cpp b/src/media/video/video_sender.cpp index d9531c01fbdd1aa5ae307cee591c716935cded29..c8246c29f4ab941510fc73fcf8eb241dd2982e8a 100644 --- a/src/media/video/video_sender.cpp +++ b/src/media/video/video_sender.cpp @@ -37,8 +37,9 @@ using std::string; VideoSender::VideoSender(const std::string& dest, const DeviceParams& dev, const MediaDescription& args, SocketPair& socketPair, - const uint16_t seqVal) - : muxContext_(socketPair.createIOContext()) + const uint16_t seqVal, + uint16_t mtu) + : muxContext_(socketPair.createIOContext(mtu)) , videoEncoder_(new MediaEncoder) { videoEncoder_->setDeviceOptions(dev); diff --git a/src/media/video/video_sender.h b/src/media/video/video_sender.h index 4bb2285016a04fd887ab255568ae053d909b37d2..6852b3452df0cd639d00719276060f6d78af053b 100644 --- a/src/media/video/video_sender.h +++ b/src/media/video/video_sender.h @@ -46,7 +46,8 @@ public: const DeviceParams& dev, const MediaDescription& args, SocketPair& socketPair, - const uint16_t seqVal); + const uint16_t seqVal, + uint16_t mtu); ~VideoSender(); diff --git a/src/ringdht/sips_transport_ice.cpp b/src/ringdht/sips_transport_ice.cpp index f4488de4fe4ed11dc45ea6f182a1264ee8b0bc76..c56ddda5ebe2458b67b9afa65dd0bf5be75c8641 100644 --- a/src/ringdht/sips_transport_ice.cpp +++ b/src/ringdht/sips_transport_ice.cpp @@ -694,4 +694,10 @@ SipsIceTransport::send(pjsip_tx_data* tdata, const pj_sockaddr_t* rem_addr, return PJ_EPENDING; } +uint16_t +SipsIceTransport::getTlsSessionMtu() +{ + return tls_->getMtu(); +} + }} // namespace ring::tls diff --git a/src/ringdht/sips_transport_ice.h b/src/ringdht/sips_transport_ice.h index d0e243cf95996e521a65f2790cc6f5eedc5b7253..48cbcb3e02538eff095f54ed5462ed529784b5e5 100644 --- a/src/ringdht/sips_transport_ice.h +++ b/src/ringdht/sips_transport_ice.h @@ -74,6 +74,9 @@ struct SipsIceTransport IpAddr getLocalAddress() const { return local_; } IpAddr getRemoteAddress() const { return remote_; } + // uses the tls_ uniquepointer internal gnutls_session_t, to call its method to get its MTU + uint16_t getTlsSessionMtu(); + private: NON_COPYABLE(SipsIceTransport); diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp index b4f1dcf3cef91811e51c9ebc01f04f9cef7d735a..526638a2854a2dd7471d1306bd78f04588e63b5d 100644 --- a/src/security/tls_session.cpp +++ b/src/security/tls_session.cpp @@ -30,22 +30,38 @@ #include "noncopyable.h" #include "compiler_intrinsics.h" +#include <gnutls/gnutls.h> #include <gnutls/dtls.h> #include <gnutls/abstract.h> #include <algorithm> #include <cstring> // std::memset +#include <cstdlib> +#include <unistd.h> + namespace ring { namespace tls { static constexpr const char* TLS_CERT_PRIORITY_STRING {"SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"}; static constexpr const char* TLS_FULL_PRIORITY_STRING {"SECURE192:-KX-ALL:+ANON-ECDH:+ANON-DH:+SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"}; static constexpr int DTLS_MTU {1232}; // (1280 from IPv6 minimum MTU - 40 IPv6 header - 8 UDP header) +static constexpr uint16_t MIN_MTU {512}; +static constexpr uint16_t INPUT_BUFFER_SIZE {16*1024}; // to be coherent with the maximum size advised in path mtu discovery static constexpr std::size_t INPUT_MAX_SIZE {1000}; // Maximum packet to store before dropping (pkt size = DTLS_MTU) static constexpr ssize_t FLOOD_THRESHOLD {4*1024}; static constexpr auto FLOOD_PAUSE = std::chrono::milliseconds(100); // Time to wait after an invalid cookie packet (anti flood attack) static constexpr auto DTLS_RETRANSMIT_TIMEOUT = std::chrono::milliseconds(1000); // Delay between two handshake request on DTLS static constexpr auto COOKIE_TIMEOUT = std::chrono::seconds(10); // Time to wait for a cookie packet from client +static constexpr uint8_t UDP_HEADER_SIZE = 8; // Size in bytes of UDP packet header +static constexpr uint8_t HEARTBEAT_RETRIES = 1; // Number of tries at each heartbeat ping send (1 + 1 if error) +static constexpr auto HEARTBEAT_RETRANS_TIMEOUT = std::chrono::milliseconds(700); // gnutls heartbeat retransmission timeout for each ping (in milliseconds) +static constexpr auto HEARTBEAT_TOTAL_TIMEOUT = HEARTBEAT_RETRANS_TIMEOUT * HEARTBEAT_RETRIES; // gnutls heartbeat time limit for heartbeat procedure (in milliseconds) + +// mtus array to test, do not add mtu over the interface MTU, this will result in false result due to packet fragmentation. +// also do not set over 16000 this will result in a gnutls error (unexpected packet size) +// neither under MIN_MTU because it makes no sense and could result in underflow of certain variables. +// Put mtus values in ascending order in the array to avoid sorting +static constexpr std::array<uint16_t, MTUS_TO_TEST> mtus = {MIN_MTU, 800, 1280, 1500}; // Helper to cast any duration into an integer number of milliseconds template <class Rep, class Period> @@ -209,6 +225,9 @@ TlsSessionState TlsSession::setupClient() { auto ret = gnutls_init(&session_, GNUTLS_CLIENT | GNUTLS_DATAGRAM); + RING_WARN("[TLS] set heartbeat reception for retrocompatibility check on server"); + gnutls_heartbeat_enable(session_,GNUTLS_HB_PEER_ALLOWED_TO_SEND); + if (ret != GNUTLS_E_SUCCESS) { RING_ERR("[TLS] session init failed: %s", gnutls_strerror(ret)); return TlsSessionState::SHUTDOWN; @@ -529,6 +548,7 @@ TlsSession::setup() fsmHandlers_[TlsSessionState::SETUP] = [this](TlsSessionState s){ return handleStateSetup(s); }; fsmHandlers_[TlsSessionState::COOKIE] = [this](TlsSessionState s){ return handleStateCookie(s); }; fsmHandlers_[TlsSessionState::HANDSHAKE] = [this](TlsSessionState s){ return handleStateHandshake(s); }; + fsmHandlers_[TlsSessionState::MTU_DISCOVERY] = [this](TlsSessionState s){ return handleStateMtuDiscovery(s); }; fsmHandlers_[TlsSessionState::ESTABLISHED] = [this](TlsSessionState s){ return handleStateEstablished(s); }; fsmHandlers_[TlsSessionState::SHUTDOWN] = [this](TlsSessionState s){ return handleStateShutdown(s); }; @@ -636,6 +656,9 @@ TlsSession::handleStateCookie(TlsSessionState state) RING_DBG("[TLS] cookie ok"); ret = gnutls_init(&session_, GNUTLS_SERVER | GNUTLS_DATAGRAM); + RING_WARN("[TLS] set heartbeat reception"); + gnutls_heartbeat_enable(session_,GNUTLS_HB_PEER_ALLOWED_TO_SEND); + if (ret != GNUTLS_E_SUCCESS) { RING_ERR("[TLS] session init failed: %s", gnutls_strerror(ret)); return TlsSessionState::SHUTDOWN; @@ -716,10 +739,103 @@ TlsSession::handleStateHandshake(TlsSessionState state) callbacks_.onCertificatesUpdate(local, remote, remote_count); } + return TlsSessionState::MTU_DISCOVERY; +} + +TlsSessionState +TlsSession::handleStateMtuDiscovery(TlsSessionState state) +{ + //set dtls mtu to be over each and every mtus tested + gnutls_dtls_set_mtu(session_, mtus.back()); + // retrocompatibility check + if(gnutls_heartbeat_allowed(session_, GNUTLS_HB_LOCAL_ALLOWED_TO_SEND) == 1) { + if (!isServer()){ + RING_WARN("[TLS] HEARTBEAT PATH MTU DISCOVERY"); + pathMtuHeartbeat(); + pmtudOver_ = true; + RING_WARN("[TLS] HEARTBEAT PATH MTU DISCOVERY OVER"); + } + } else { + RING_ERR("[TLS] PEER HEARTBEAT DISABLED: setting minimal value to MTU @%d for retrocompatibility", DTLS_MTU); + gnutls_dtls_set_mtu(session_, DTLS_MTU); + pmtudOver_ = true; + } maxPayload_ = gnutls_dtls_get_data_mtu(session_); + if (pmtudOver_) + RING_WARN("[TLS] maxPayload for dtls : %d B", getMaxPayload()); + return TlsSessionState::ESTABLISHED; } +/* + * Path MTU discovery heuristic + * heuristic description: + * The two members of the current tls connection will exchange dtls heartbeat messages + * of increasing size until the heartbeat times out which will be considered as a packet + * drop from the network due to the size of the packet. (one retry to test for a buffer issue) + * when timeout happens or all the values have been tested, the mtu will be returned. + * In case of unexpected error the first (and minimal) value of the mtu array + */ +void +TlsSession::pathMtuHeartbeat() +{ + int errno_send = 1; // non zero initialisation + auto tls_overhead = gnutls_record_overhead_size(session_); + RING_WARN("[TLS] tls session overhead : %d", tls_overhead); + transportOverhead_ = socket_->getTransportOverhead(); + gnutls_heartbeat_set_timeouts(session_, HEARTBEAT_RETRANS_TIMEOUT.count(), HEARTBEAT_TOTAL_TIMEOUT.count()); + RING_DBG("[TLS] Heartbeat PMTUD : retransmission timeout set to: %d ms", HEARTBEAT_RETRANS_TIMEOUT); + + // server side: managing pong in state established + // client side: managing ping on heartbeat + uint16_t bytesToSend; + mtuProbe_ = mtus.cbegin(); + RING_DBG("[TLS] Heartbeat PMTUD : client side"); + + while (mtuProbe_ != mtus.cend()){ + bytesToSend = (*mtuProbe_) - 3 - tls_overhead - UDP_HEADER_SIZE - transportOverhead_; + do { + RING_DBG("[TLS] Heartbeat PMTUD : ping with mtu %d and effective payload %d", *mtuProbe_, bytesToSend); + errno_send = gnutls_heartbeat_ping(session_, bytesToSend, HEARTBEAT_RETRIES, GNUTLS_HEARTBEAT_WAIT); + RING_DBG("[TLS] Heartbeat PMTUD : ping sequence over with errno %d: %s", errno_send, + gnutls_strerror(errno_send)); + } + while (errno_send == GNUTLS_E_AGAIN || errno_send == GNUTLS_E_INTERRUPTED); + + if (errno_send == GNUTLS_E_SUCCESS) { + ++mtuProbe_; + } else if (errno_send == GNUTLS_E_TIMEDOUT){ // timeout is considered as a packet loss, then the good mtu is the precedent. + if (mtuProbe_ == mtus.cbegin()) { + RING_WARN("[TLS] Heartbeat PMTUD : no response on first ping, setting minimal MTU value @%d", MIN_MTU); + gnutls_dtls_set_mtu(session_, MIN_MTU - UDP_HEADER_SIZE - transportOverhead_); + + } else { + --mtuProbe_; + RING_DBG("[TLS] Heartbeat PMTUD : timed out: Path MTU found @ %d", *mtuProbe_); + gnutls_dtls_set_mtu(session_, *mtuProbe_ - UDP_HEADER_SIZE - transportOverhead_); + } + return; + } else { + RING_WARN("[TLS] Heartbeat PMTUD : client ping failed: error %d: %s", errno_send, + gnutls_strerror(errno_send)); + if (mtuProbe_ != mtus.begin()) + --mtuProbe_; + gnutls_dtls_set_mtu(session_, *mtuProbe_ - UDP_HEADER_SIZE - transportOverhead_); + return; + } + } + + + if (errno_send == GNUTLS_E_SUCCESS) { + RING_WARN("[TLS] Heartbeat PMTUD completed : reached test value %d", mtus.back()); + --mtuProbe_; // for loop over, setting mtu to last valid mtu + } + + gnutls_dtls_set_mtu(session_, *mtuProbe_ - UDP_HEADER_SIZE - transportOverhead_); + RING_WARN("[TLS] Heartbeat PMTUD : new mtu set to %d", *mtuProbe_); +} + + TlsSessionState TlsSession::handleStateEstablished(TlsSessionState state) { @@ -732,12 +848,39 @@ TlsSession::handleStateEstablished(TlsSessionState state) // Handle RX data from network if (!rxQueue_.empty()) { - std::vector<uint8_t> buf(8*1024); + std::vector<uint8_t> buf(INPUT_BUFFER_SIZE); unsigned char sequence[8]; lk.unlock(); auto ret = gnutls_record_recv_seq(session_, buf.data(), buf.size(), sequence); - if (ret > 0) { + if (ret > 0 && pmtudOver_) { + buf.resize(ret); + // TODO: handle sequence re-order + if (callbacks_.onRxData) + callbacks_.onRxData(std::move(buf)); + return state; + } else if (ret == GNUTLS_E_HEARTBEAT_PING_RECEIVED) { + + RING_DBG("[TLS] Heartbeat PMTUD : ping received sending pong"); + auto errno_send = gnutls_heartbeat_pong(session_, 0); + + if (errno_send != GNUTLS_E_SUCCESS){ + RING_WARN("[TLS] Heartbeat PMTUD : failed on pong with error %d: %s", errno_send, + gnutls_strerror(errno_send)); + } else { + ++hbPingRecved_; + } + + } else if (ret > 0 && pmtudOver_ == false){ + if (hbPingRecved_ > 0){ + gnutls_dtls_set_mtu(session_, mtus[hbPingRecved_ - 1] - UDP_HEADER_SIZE - transportOverhead_); + maxPayload_ = gnutls_dtls_get_data_mtu(session_); + } else { + gnutls_dtls_set_mtu(session_, MIN_MTU - UDP_HEADER_SIZE - transportOverhead_); + maxPayload_ = gnutls_dtls_get_data_mtu(session_); + } + RING_WARN("[TLS] maxPayload for dtls : %d B", getMaxPayload()); + pmtudOver_ = true; buf.resize(ret); // TODO: handle sequence re-order if (callbacks_.onRxData) @@ -819,4 +962,10 @@ DhParams::generate() return params; } +uint16_t +TlsSession::getMtu() +{ + return gnutls_dtls_get_mtu(session_); +} + }} // namespace ring::tls diff --git a/src/security/tls_session.h b/src/security/tls_session.h index 745f0291248678687358dac4d2d2fa5d5ed4db32..c66c903706bdffcbf9afcce178fe5873d6b343fc 100644 --- a/src/security/tls_session.h +++ b/src/security/tls_session.h @@ -39,6 +39,8 @@ #include <vector> #include <map> #include <atomic> +#include <iterator> +#include <array> namespace ring { class IceTransport; @@ -52,10 +54,13 @@ struct PrivateKey; namespace ring { namespace tls { +static constexpr uint8_t MTUS_TO_TEST = 4; //number of mtus to test in path mtu discovery. + enum class TlsSessionState { SETUP, COOKIE, // server only HANDSHAKE, + MTU_DISCOVERY, ESTABLISHED, SHUTDOWN }; @@ -171,6 +176,8 @@ public: ssize_t send(const void* data, std::size_t size); ssize_t send(const std::vector<uint8_t>& data); + uint16_t getMtu(); + private: using clock = std::chrono::steady_clock; using StateHandler = std::function<TlsSessionState(TlsSessionState state)>; @@ -186,6 +193,7 @@ private: TlsSessionState handleStateSetup(TlsSessionState state); TlsSessionState handleStateCookie(TlsSessionState state); TlsSessionState handleStateHandshake(TlsSessionState state); + TlsSessionState handleStateMtuDiscovery(TlsSessionState state); TlsSessionState handleStateEstablished(TlsSessionState state); TlsSessionState handleStateShutdown(TlsSessionState state); std::map<TlsSessionState, StateHandler> fsmHandlers_ {}; @@ -235,6 +243,13 @@ private: bool setup(); void process(); void cleanup(); + + // Path mtu discovery + std::array<uint16_t, MTUS_TO_TEST>::const_iterator mtuProbe_; + unsigned hbPingRecved_ {0}; + bool pmtudOver_ {false}; + uint8_t transportOverhead_; + void pathMtuHeartbeat(); }; }} // namespace ring::tls diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index b81b9fb3b5f8edead082d23c75f51f6b0419aff6..7bec8a9e922b37701f6c14751e9a1a6042c9c5b7 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -856,11 +856,15 @@ SIPCall::startAllMedia() continue; } + auto new_mtu = transport_->getTlsMtu(); + avformatrtp_->setMtu(new_mtu); + #ifdef RING_VIDEO if (local.type == MEDIA_VIDEO) videortp_->switchInput(videoInput_); -#endif + videortp_->setMtu(new_mtu); +#endif rtp->updateMedia(remote, local); // Not restarting media loop on hold as it's a huge waste of CPU ressources diff --git a/src/sip/siptransport.cpp b/src/sip/siptransport.cpp index 3e2ca56863047de22ac17639bd0d14be8906e28c..ee54f1107737a90feb6b937ef72576f9a358702d 100644 --- a/src/sip/siptransport.cpp +++ b/src/sip/siptransport.cpp @@ -173,6 +173,12 @@ SipTransport::removeStateListener(uintptr_t lid) return false; } +uint16_t +SipTransport::getTlsMtu(){ + auto tls_tr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(transport_.get())->self; + return tls_tr->getTlsSessionMtu(); +} + SipTransportBroker::SipTransportBroker(pjsip_endpoint *endpt, pj_caching_pool& cp, pj_pool_t& pool) : cp_(cp), pool_(pool), endpt_(endpt) diff --git a/src/sip/siptransport.h b/src/sip/siptransport.h index 0605719a2c82a5c469c51e581e0088fe4e3f60e7..0e4919e16edaf50e35eb532e1a29d62f4955496c 100644 --- a/src/sip/siptransport.h +++ b/src/sip/siptransport.h @@ -138,6 +138,8 @@ class SipTransport /** Only makes sense for connection-oriented transports */ bool isConnected() const noexcept { return connected_; } + uint16_t getTlsMtu(); + private: NON_COPYABLE(SipTransport);