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);