Datagram Transport Integration

- Implement datagram transport adaptor, which wraps datagram transport in DtlsTransportInternal. Datagram adaptor owns both ICE and Datagram Transports.
- Implement setup of datagram transport based on RTCConfiguration flag use_datagram_transport. This is very similar to MediaTransport setup with the exception that we create DTLS datagram adaptor.
- Propagate maximum datagram size to video encoder via MediaTransportConfig.

TODO: Currently this CL can only be tested in downstream projects. Once we add fake datagram transport, we will be able to implement unit tests similar to loopback media transport.

Bug: webrtc:9719
Change-Id: I4fa4a5725598dfee5da4f0f374269a7e289d48ed
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/138100
Commit-Queue: Anton Sukhanov <sukhanov@webrtc.org>
Reviewed-by: Bjorn Mellem <mellem@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28047}
diff --git a/api/media_transport_config.cc b/api/media_transport_config.cc
index 7eb4cd4..99c4140 100644
--- a/api/media_transport_config.cc
+++ b/api/media_transport_config.cc
@@ -10,11 +10,30 @@
 
 #include "api/media_transport_config.h"
 
+#include "rtc_base/checks.h"
+#include "rtc_base/string_utils.h"
+#include "rtc_base/strings/string_builder.h"
+
 namespace webrtc {
 
-std::string MediaTransportConfig::DebugString() const {
-  return (media_transport != nullptr ? "{media_transport: (Transport)}"
-                                     : "{media_transport: null}");
+MediaTransportConfig::MediaTransportConfig(
+    MediaTransportInterface* media_transport)
+    : media_transport(media_transport) {
+  RTC_DCHECK(media_transport != nullptr);
+}
+
+MediaTransportConfig::MediaTransportConfig(size_t rtp_max_packet_size)
+    : rtp_max_packet_size(rtp_max_packet_size) {
+  RTC_DCHECK_GT(rtp_max_packet_size, 0);
+}
+
+std::string MediaTransportConfig::DebugString()
+    const {  // TODO(sukhanov): Add rtp_max_packet_size (requires fixing
+             // audio_send/receive_stream_unittest.cc).
+  rtc::StringBuilder result;
+  result << "{media_transport: "
+         << (media_transport != nullptr ? "(Transport)" : "null") << "}";
+  return result.Release();
 }
 
 }  // namespace webrtc
diff --git a/api/media_transport_config.h b/api/media_transport_config.h
index d5de42a..7c5104b 100644
--- a/api/media_transport_config.h
+++ b/api/media_transport_config.h
@@ -13,28 +13,33 @@
 #include <string>
 #include <utility>
 
+#include "absl/types/optional.h"
+
 namespace webrtc {
 
 class MediaTransportInterface;
 
-// MediaTransportConfig contains meida transport (if provided) and passed from
-// PeerConnection to call obeject and media layers that require access to media
-// transport. In the future we can add other transport (for example, datagram
-// transport) and related configuration.
+// Media transport config is made available to both transport and audio / video
+// layers, but access to individual interfaces should not be open without
+// necessity.
 struct MediaTransportConfig {
   // Default constructor for no-media transport scenarios.
   MediaTransportConfig() = default;
 
-  // TODO(sukhanov): Consider adding RtpTransport* to MediaTransportConfig,
-  // because it's almost always passes along with media_transport.
-  // Does not own media_transport.
-  explicit MediaTransportConfig(MediaTransportInterface* media_transport)
-      : media_transport(media_transport) {}
+  // Constructor for media transport scenarios.
+  // Note that |media_transport| may not be nullptr.
+  explicit MediaTransportConfig(MediaTransportInterface* media_transport);
+
+  // Constructor for datagram transport scenarios.
+  explicit MediaTransportConfig(size_t rtp_max_packet_size);
 
   std::string DebugString() const;
 
   // If provided, all media is sent through media_transport.
   MediaTransportInterface* media_transport = nullptr;
+
+  // If provided, limits RTP packet size (excludes ICE, IP or network overhead).
+  absl::optional<size_t> rtp_max_packet_size;
 };
 
 }  // namespace webrtc
diff --git a/media/base/rtp_data_engine_unittest.cc b/media/base/rtp_data_engine_unittest.cc
index df0f904..cd7d295 100644
--- a/media/base/rtp_data_engine_unittest.cc
+++ b/media/base/rtp_data_engine_unittest.cc
@@ -74,8 +74,7 @@
     cricket::MediaConfig config;
     cricket::RtpDataMediaChannel* channel =
         static_cast<cricket::RtpDataMediaChannel*>(dme->CreateChannel(config));
-    channel->SetInterface(iface_.get(), webrtc::MediaTransportConfig(
-                                            /*media_transport=*/nullptr));
+    channel->SetInterface(iface_.get(), webrtc::MediaTransportConfig());
     channel->SignalDataReceived.connect(receiver_.get(),
                                         &FakeDataReceiver::OnDataReceived);
     return channel;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index eecae16..f95ab95 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -18,6 +18,7 @@
 
 #include "absl/algorithm/container.h"
 #include "absl/strings/match.h"
+#include "api/datagram_transport_interface.h"
 #include "api/video/video_codec_constants.h"
 #include "api/video_codecs/sdp_video_format.h"
 #include "api/video_codecs/video_decoder_factory.h"
@@ -1101,6 +1102,13 @@
   config.rtp.extmap_allow_mixed = ExtmapAllowMixed();
   config.rtcp_report_interval_ms = video_config_.rtcp_report_interval_ms;
 
+  // If sending through Datagram Transport, limit packet size to maximum
+  // packet size supported by datagram_transport.
+  if (media_transport_config().rtp_max_packet_size) {
+    config.rtp.max_packet_size =
+        media_transport_config().rtp_max_packet_size.value();
+  }
+
   WebRtcVideoSendStream* stream = new WebRtcVideoSendStream(
       call_, sp, std::move(config), default_send_options_,
       video_config_.enable_cpu_adaptation, bitrate_config_.max_bitrate_bps,
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index 5db66be..e404896 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -25,6 +25,8 @@
     "base/basic_packet_socket_factory.cc",
     "base/basic_packet_socket_factory.h",
     "base/candidate_pair_interface.h",
+    "base/datagram_dtls_adaptor.cc",
+    "base/datagram_dtls_adaptor.h",
     "base/dtls_transport.cc",
     "base/dtls_transport.h",
     "base/dtls_transport_internal.cc",
diff --git a/p2p/base/datagram_dtls_adaptor.cc b/p2p/base/datagram_dtls_adaptor.cc
new file mode 100644
index 0000000..ecf14b3
--- /dev/null
+++ b/p2p/base/datagram_dtls_adaptor.cc
@@ -0,0 +1,405 @@
+/*
+ *  Copyright 2018 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.
+ */
+
+#include "p2p/base/datagram_dtls_adaptor.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "api/rtc_error.h"
+#include "logging/rtc_event_log/events/rtc_event_dtls_transport_state.h"
+#include "logging/rtc_event_log/events/rtc_event_dtls_writable_state.h"
+#include "logging/rtc_event_log/rtc_event_log.h"
+#include "p2p/base/dtls_transport_internal.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/dscp.h"
+#include "rtc_base/flags.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/message_queue.h"
+#include "rtc_base/rtc_certificate.h"
+#include "rtc_base/ssl_stream_adapter.h"
+#include "rtc_base/stream.h"
+#include "rtc_base/thread.h"
+
+#ifdef BYPASS_DATAGRAM_DTLS_TEST_ONLY
+// Send unencrypted packets directly to ICE, bypassing datagtram
+// transport. Use in tests only.
+constexpr bool kBypassDatagramDtlsTestOnly = true;
+#else
+constexpr bool kBypassDatagramDtlsTestOnly = false;
+#endif
+
+namespace cricket {
+
+DatagramDtlsAdaptor::DatagramDtlsAdaptor(
+    std::unique_ptr<IceTransportInternal> ice_transport,
+    std::unique_ptr<webrtc::DatagramTransportInterface> datagram_transport,
+    const webrtc::CryptoOptions& crypto_options,
+    webrtc::RtcEventLog* event_log)
+    : crypto_options_(crypto_options),
+      ice_transport_(std::move(ice_transport)),
+      datagram_transport_(std::move(datagram_transport)),
+      event_log_(event_log) {
+  RTC_DCHECK(ice_transport_);
+  RTC_DCHECK(datagram_transport_);
+  ConnectToIceTransport();
+}
+
+void DatagramDtlsAdaptor::ConnectToIceTransport() {
+  if (kBypassDatagramDtlsTestOnly) {
+    // In bypass mode we have to subscribe to ICE read and sent events.
+    // Test only case to use ICE directly instead of data transport.
+    ice_transport_->SignalReadPacket.connect(
+        this, &DatagramDtlsAdaptor::OnReadPacket);
+
+    ice_transport_->SignalSentPacket.connect(
+        this, &DatagramDtlsAdaptor::OnSentPacket);
+
+    ice_transport_->SignalWritableState.connect(
+        this, &DatagramDtlsAdaptor::OnWritableState);
+    ice_transport_->SignalReadyToSend.connect(
+        this, &DatagramDtlsAdaptor::OnReadyToSend);
+    ice_transport_->SignalReceivingState.connect(
+        this, &DatagramDtlsAdaptor::OnReceivingState);
+  } else {
+    // Subscribe to Data Transport read packets.
+    datagram_transport_->SetDatagramSink(this);
+    datagram_transport_->SetTransportStateCallback(this);
+
+    // Datagram transport does not propagate network route change.
+    ice_transport_->SignalNetworkRouteChanged.connect(
+        this, &DatagramDtlsAdaptor::OnNetworkRouteChanged);
+  }
+}
+
+DatagramDtlsAdaptor::~DatagramDtlsAdaptor() {
+  // Unsubscribe from Datagram Transport dinks.
+  datagram_transport_->SetDatagramSink(nullptr);
+  datagram_transport_->SetTransportStateCallback(nullptr);
+
+  // Make sure datagram transport is destroyed before ICE.
+  datagram_transport_.reset();
+  ice_transport_.reset();
+}
+
+const webrtc::CryptoOptions& DatagramDtlsAdaptor::crypto_options() const {
+  return crypto_options_;
+}
+
+int DatagramDtlsAdaptor::SendPacket(const char* data,
+                                    size_t len,
+                                    const rtc::PacketOptions& options,
+                                    int flags) {
+  // TODO(sukhanov): Handle options and flags.
+  if (kBypassDatagramDtlsTestOnly) {
+    // In bypass mode sent directly to ICE.
+    return ice_transport_->SendPacket(data, len, options);
+  }
+
+  // Send datagram with id equal to options.packet_id, so we get it back
+  // in DatagramDtlsAdaptor::OnDatagramSent() and propagate notification
+  // up.
+  webrtc::RTCError error = datagram_transport_->SendDatagram(
+      rtc::MakeArrayView(reinterpret_cast<const uint8_t*>(data), len),
+      /*datagram_id=*/options.packet_id);
+
+  return (error.ok() ? len : -1);
+}
+
+void DatagramDtlsAdaptor::OnReadPacket(rtc::PacketTransportInternal* transport,
+                                       const char* data,
+                                       size_t size,
+                                       const int64_t& packet_time_us,
+                                       int flags) {
+  // Only used in bypass mode.
+  RTC_DCHECK(kBypassDatagramDtlsTestOnly);
+
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK_EQ(transport, ice_transport_.get());
+  RTC_DCHECK(flags == 0);
+
+  PropagateReadPacket(
+      rtc::MakeArrayView(reinterpret_cast<const uint8_t*>(data), size),
+      packet_time_us);
+}
+
+void DatagramDtlsAdaptor::OnDatagramReceived(
+    rtc::ArrayView<const uint8_t> data) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK(!kBypassDatagramDtlsTestOnly);
+
+  // TODO(sukhanov): I am not filling out time, but on my video quality
+  // test in WebRTC the time was not set either and higher layers of the stack
+  // overwrite -1 with current current rtc time. Leaveing comment for now to
+  // make sure it works as expected.
+  int64_t packet_time_us = -1;
+
+  PropagateReadPacket(data, packet_time_us);
+}
+
+void DatagramDtlsAdaptor::OnDatagramSent(webrtc::DatagramId datagram_id) {
+  // When we called DatagramTransportInterface::SendDatagram, we passed
+  // packet_id as datagram_id, so we simply need to set it in sent_packet
+  // and propagate notification up the stack.
+
+  // Also see how DatagramDtlsAdaptor::OnSentPacket handles OnSentPacket
+  // notification from ICE in bypass mode.
+  rtc::SentPacket sent_packet(/*packet_id=*/datagram_id, rtc::TimeMillis());
+
+  PropagateOnSentNotification(sent_packet);
+}
+
+void DatagramDtlsAdaptor::OnSentPacket(rtc::PacketTransportInternal* transport,
+                                       const rtc::SentPacket& sent_packet) {
+  // Only used in bypass mode.
+  RTC_DCHECK(kBypassDatagramDtlsTestOnly);
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+
+  PropagateOnSentNotification(sent_packet);
+}
+
+void DatagramDtlsAdaptor::PropagateOnSentNotification(
+    const rtc::SentPacket& sent_packet) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  SignalSentPacket(this, sent_packet);
+}
+
+void DatagramDtlsAdaptor::PropagateReadPacket(
+    rtc::ArrayView<const uint8_t> data,
+    const int64_t& packet_time_us) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  SignalReadPacket(this, reinterpret_cast<const char*>(data.data()),
+                   data.size(), packet_time_us, /*flags=*/0);
+}
+
+int DatagramDtlsAdaptor::component() const {
+  return kDatagramDtlsAdaptorComponent;
+}
+bool DatagramDtlsAdaptor::IsDtlsActive() const {
+  return false;
+}
+bool DatagramDtlsAdaptor::GetDtlsRole(rtc::SSLRole* role) const {
+  return false;
+}
+bool DatagramDtlsAdaptor::SetDtlsRole(rtc::SSLRole role) {
+  return false;
+}
+bool DatagramDtlsAdaptor::GetSrtpCryptoSuite(int* cipher) {
+  return false;
+}
+bool DatagramDtlsAdaptor::GetSslCipherSuite(int* cipher) {
+  return false;
+}
+
+rtc::scoped_refptr<rtc::RTCCertificate>
+DatagramDtlsAdaptor::GetLocalCertificate() const {
+  return nullptr;
+}
+
+bool DatagramDtlsAdaptor::SetLocalCertificate(
+    const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
+  return false;
+}
+
+std::unique_ptr<rtc::SSLCertChain> DatagramDtlsAdaptor::GetRemoteSSLCertChain()
+    const {
+  return nullptr;
+}
+
+bool DatagramDtlsAdaptor::ExportKeyingMaterial(const std::string& label,
+                                               const uint8_t* context,
+                                               size_t context_len,
+                                               bool use_context,
+                                               uint8_t* result,
+                                               size_t result_len) {
+  return false;
+}
+
+bool DatagramDtlsAdaptor::SetRemoteFingerprint(const std::string& digest_alg,
+                                               const uint8_t* digest,
+                                               size_t digest_len) {
+  // TODO(sukhanov): We probably should not called with fingerptints in
+  // datagram scenario, but we may need to change code up the stack before
+  // we can return false or DCHECK.
+  return true;
+}
+
+bool DatagramDtlsAdaptor::SetSslMaxProtocolVersion(
+    rtc::SSLProtocolVersion version) {
+  // TODO(sukhanov): We may be able to return false and/or DCHECK that we
+  // are not called if datagram transport is used, but we need to change
+  // integration before we can do it.
+  return true;
+}
+
+IceTransportInternal* DatagramDtlsAdaptor::ice_transport() {
+  return ice_transport_.get();
+}
+
+webrtc::DatagramTransportInterface* DatagramDtlsAdaptor::datagram_transport() {
+  return datagram_transport_.get();
+}
+
+// Similar implementaton as in p2p/base/dtls_transport.cc.
+void DatagramDtlsAdaptor::OnReadyToSend(
+    rtc::PacketTransportInternal* transport) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  if (writable()) {
+    SignalReadyToSend(this);
+  }
+}
+
+void DatagramDtlsAdaptor::OnWritableState(
+    rtc::PacketTransportInternal* transport) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK(transport == ice_transport_.get());
+  RTC_LOG(LS_VERBOSE) << ": ice_transport writable state changed to "
+                      << ice_transport_->writable();
+
+  if (kBypassDatagramDtlsTestOnly) {
+    // Note: SignalWritableState fired by set_writable.
+    set_writable(ice_transport_->writable());
+    return;
+  }
+
+  switch (dtls_state()) {
+    case DTLS_TRANSPORT_NEW:
+      break;
+    case DTLS_TRANSPORT_CONNECTED:
+      // Note: SignalWritableState fired by set_writable.
+      // Do we also need set_receiving(ice_transport_->receiving()) here now, in
+      // case we lose that signal before "DTLS" connects?
+      // DtlsTransport::OnWritableState does not set_receiving in a similar
+      // case, so leaving it out for the time being, but it would be good to
+      // understand why.
+      set_writable(ice_transport_->writable());
+      break;
+    case DTLS_TRANSPORT_CONNECTING:
+      // Do nothing.
+      break;
+    case DTLS_TRANSPORT_FAILED:
+    case DTLS_TRANSPORT_CLOSED:
+      // Should not happen. Do nothing.
+      break;
+  }
+}
+
+void DatagramDtlsAdaptor::OnStateChanged(webrtc::MediaTransportState state) {
+  // Convert MediaTransportState to DTLS state.
+  switch (state) {
+    case webrtc::MediaTransportState::kPending:
+      set_dtls_state(DTLS_TRANSPORT_CONNECTING);
+      break;
+
+    case webrtc::MediaTransportState::kWritable:
+      // Since we do not set writable state until datagram transport is
+      // connected, we need to call set_writable first.
+      set_writable(ice_transport_->writable());
+      set_dtls_state(DTLS_TRANSPORT_CONNECTED);
+      break;
+
+    case webrtc::MediaTransportState::kClosed:
+      set_dtls_state(DTLS_TRANSPORT_CLOSED);
+      break;
+  }
+}
+
+DtlsTransportState DatagramDtlsAdaptor::dtls_state() const {
+  return dtls_state_;
+}
+
+const std::string& DatagramDtlsAdaptor::transport_name() const {
+  return ice_transport_->transport_name();
+}
+
+bool DatagramDtlsAdaptor::writable() const {
+  // NOTE that even if ice is writable, writable_ maybe false, because we
+  // propagte writable only after DTLS is connect (this is consistent with
+  // implementation in dtls_transport.cc).
+  return writable_;
+}
+
+bool DatagramDtlsAdaptor::receiving() const {
+  return receiving_;
+}
+
+int DatagramDtlsAdaptor::SetOption(rtc::Socket::Option opt, int value) {
+  return ice_transport_->SetOption(opt, value);
+}
+
+int DatagramDtlsAdaptor::GetError() {
+  return ice_transport_->GetError();
+}
+
+void DatagramDtlsAdaptor::OnNetworkRouteChanged(
+    absl::optional<rtc::NetworkRoute> network_route) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  SignalNetworkRouteChanged(network_route);
+}
+
+void DatagramDtlsAdaptor::OnReceivingState(
+    rtc::PacketTransportInternal* transport) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK(transport == ice_transport_.get());
+  RTC_LOG(LS_VERBOSE) << "ice_transport receiving state changed to "
+                      << ice_transport_->receiving();
+
+  if (kBypassDatagramDtlsTestOnly || dtls_state() == DTLS_TRANSPORT_CONNECTED) {
+    // Note: SignalReceivingState fired by set_receiving.
+    set_receiving(ice_transport_->receiving());
+  }
+}
+
+void DatagramDtlsAdaptor::set_receiving(bool receiving) {
+  if (receiving_ == receiving) {
+    return;
+  }
+  receiving_ = receiving;
+  SignalReceivingState(this);
+}
+
+// Similar implementaton as in p2p/base/dtls_transport.cc.
+void DatagramDtlsAdaptor::set_writable(bool writable) {
+  if (writable_ == writable) {
+    return;
+  }
+  if (event_log_) {
+    event_log_->Log(
+        absl::make_unique<webrtc::RtcEventDtlsWritableState>(writable));
+  }
+  RTC_LOG(LS_VERBOSE) << "set_writable to: " << writable;
+  writable_ = writable;
+  if (writable_) {
+    SignalReadyToSend(this);
+  }
+  SignalWritableState(this);
+}
+
+// Similar implementaton as in p2p/base/dtls_transport.cc.
+void DatagramDtlsAdaptor::set_dtls_state(DtlsTransportState state) {
+  if (dtls_state_ == state) {
+    return;
+  }
+  if (event_log_) {
+    event_log_->Log(absl::make_unique<webrtc::RtcEventDtlsTransportState>(
+        ConvertDtlsTransportState(state)));
+  }
+  RTC_LOG(LS_VERBOSE) << "set_dtls_state from:" << dtls_state_ << " to "
+                      << state;
+  dtls_state_ = state;
+  SignalDtlsState(this, state);
+}
+
+}  // namespace cricket
diff --git a/p2p/base/datagram_dtls_adaptor.h b/p2p/base/datagram_dtls_adaptor.h
new file mode 100644
index 0000000..2f6fdc1
--- /dev/null
+++ b/p2p/base/datagram_dtls_adaptor.h
@@ -0,0 +1,154 @@
+/*
+ *  Copyright 2019 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 P2P_BASE_DATAGRAM_DTLS_ADAPTOR_H_
+#define P2P_BASE_DATAGRAM_DTLS_ADAPTOR_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "api/crypto/crypto_options.h"
+#include "api/datagram_transport_interface.h"
+#include "p2p/base/dtls_transport_internal.h"
+#include "p2p/base/ice_transport_internal.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/buffer_queue.h"
+#include "rtc_base/constructor_magic.h"
+#include "rtc_base/ssl_stream_adapter.h"
+#include "rtc_base/stream.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/thread_checker.h"
+
+namespace cricket {
+
+constexpr int kDatagramDtlsAdaptorComponent = -1;
+
+// DTLS wrapper around DatagramTransportInterface.
+// Does not encrypt.
+// Owns Datagram and Ice transports.
+class DatagramDtlsAdaptor : public DtlsTransportInternal,
+                            public webrtc::DatagramSinkInterface,
+                            public webrtc::MediaTransportStateCallback {
+ public:
+  // TODO(sukhanov): Taking crypto options, because DtlsTransportInternal
+  // has a virtual getter crypto_options(). Consider removing getter and
+  // removing crypto_options from DatagramDtlsAdaptor.
+  DatagramDtlsAdaptor(
+      std::unique_ptr<IceTransportInternal> ice_transport,
+      std::unique_ptr<webrtc::DatagramTransportInterface> datagram_transport,
+      const webrtc::CryptoOptions& crypto_options,
+      webrtc::RtcEventLog* event_log);
+
+  ~DatagramDtlsAdaptor() override;
+
+  // Connects to ICE transport callbacks.
+  void ConnectToIceTransport();
+
+  // =====================================================
+  // Overrides for webrtc::DatagramTransportSinkInterface
+  // and MediaTransportStateCallback
+  // =====================================================
+  void OnDatagramReceived(rtc::ArrayView<const uint8_t> data) override;
+
+  void OnDatagramSent(webrtc::DatagramId datagram_id) override;
+
+  void OnStateChanged(webrtc::MediaTransportState state) override;
+
+  // =====================================================
+  // DtlsTransportInternal overrides
+  // =====================================================
+  const webrtc::CryptoOptions& crypto_options() const override;
+  DtlsTransportState dtls_state() const override;
+  int component() const override;
+  bool IsDtlsActive() const override;
+  bool GetDtlsRole(rtc::SSLRole* role) const override;
+  bool SetDtlsRole(rtc::SSLRole role) override;
+  bool GetSrtpCryptoSuite(int* cipher) override;
+  bool GetSslCipherSuite(int* cipher) override;
+  rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const override;
+  bool SetLocalCertificate(
+      const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) override;
+  std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain() const override;
+  bool ExportKeyingMaterial(const std::string& label,
+                            const uint8_t* context,
+                            size_t context_len,
+                            bool use_context,
+                            uint8_t* result,
+                            size_t result_len) override;
+  bool SetRemoteFingerprint(const std::string& digest_alg,
+                            const uint8_t* digest,
+                            size_t digest_len) override;
+  bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) override;
+  IceTransportInternal* ice_transport() override;
+  webrtc::DatagramTransportInterface* datagram_transport() override;
+
+  const std::string& transport_name() const override;
+  bool writable() const override;
+  bool receiving() const override;
+
+ private:
+  void set_receiving(bool receiving);
+  void set_writable(bool writable);
+  void set_dtls_state(DtlsTransportState state);
+
+  // Forwards incoming packet up the stack.
+  void PropagateReadPacket(rtc::ArrayView<const uint8_t> data,
+                           const int64_t& packet_time_us);
+
+  // Signals SentPacket notification.
+  void PropagateOnSentNotification(const rtc::SentPacket& sent_packet);
+
+  // Listens to read packet notifications from ICE (only used in bypass mode).
+  void OnReadPacket(rtc::PacketTransportInternal* transport,
+                    const char* data,
+                    size_t size,
+                    const int64_t& packet_time_us,
+                    int flags);
+
+  void OnReadyToSend(rtc::PacketTransportInternal* transport);
+  void OnWritableState(rtc::PacketTransportInternal* transport);
+  void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> network_route);
+  void OnReceivingState(rtc::PacketTransportInternal* transport);
+
+  int SendPacket(const char* data,
+                 size_t len,
+                 const rtc::PacketOptions& options,
+                 int flags) override;
+  int SetOption(rtc::Socket::Option opt, int value) override;
+  int GetError() override;
+  void OnSentPacket(rtc::PacketTransportInternal* transport,
+                    const rtc::SentPacket& sent_packet);
+
+  rtc::ThreadChecker thread_checker_;
+  webrtc::CryptoOptions crypto_options_;
+  std::unique_ptr<IceTransportInternal> ice_transport_;
+
+  std::unique_ptr<webrtc::DatagramTransportInterface> datagram_transport_;
+
+  // Current ICE writable state. Must be modified by calling set_ice_writable(),
+  // which propagates change notifications.
+  bool writable_ = false;
+
+  // Current receiving state. Must be modified by calling set_receiving(), which
+  // propagates change notifications.
+  bool receiving_ = false;
+
+  // Current DTLS state. Must be modified by calling set_dtls_state(), which
+  // propagates change notifications.
+  DtlsTransportState dtls_state_ = DTLS_TRANSPORT_NEW;
+
+  webrtc::RtcEventLog* const event_log_;
+};
+
+}  // namespace cricket
+
+#endif  // P2P_BASE_DATAGRAM_DTLS_ADAPTOR_H_
diff --git a/p2p/base/dtls_transport.cc b/p2p/base/dtls_transport.cc
index d3db35b..46f0f99 100644
--- a/p2p/base/dtls_transport.cc
+++ b/p2p/base/dtls_transport.cc
@@ -764,24 +764,6 @@
   SignalWritableState(this);
 }
 
-static webrtc::DtlsTransportState ConvertDtlsTransportState(
-    cricket::DtlsTransportState cricket_state) {
-  switch (cricket_state) {
-    case DtlsTransportState::DTLS_TRANSPORT_NEW:
-      return webrtc::DtlsTransportState::kNew;
-    case DtlsTransportState::DTLS_TRANSPORT_CONNECTING:
-      return webrtc::DtlsTransportState::kConnecting;
-    case DtlsTransportState::DTLS_TRANSPORT_CONNECTED:
-      return webrtc::DtlsTransportState::kConnected;
-    case DtlsTransportState::DTLS_TRANSPORT_CLOSED:
-      return webrtc::DtlsTransportState::kClosed;
-    case DtlsTransportState::DTLS_TRANSPORT_FAILED:
-      return webrtc::DtlsTransportState::kFailed;
-  }
-  RTC_NOTREACHED();
-  return webrtc::DtlsTransportState::kNew;
-}
-
 void DtlsTransport::set_dtls_state(DtlsTransportState state) {
   if (dtls_state_ == state) {
     return;
diff --git a/p2p/base/dtls_transport_internal.cc b/p2p/base/dtls_transport_internal.cc
index 6997dbc..dd23b1b 100644
--- a/p2p/base/dtls_transport_internal.cc
+++ b/p2p/base/dtls_transport_internal.cc
@@ -16,4 +16,22 @@
 
 DtlsTransportInternal::~DtlsTransportInternal() = default;
 
+webrtc::DtlsTransportState ConvertDtlsTransportState(
+    cricket::DtlsTransportState cricket_state) {
+  switch (cricket_state) {
+    case DtlsTransportState::DTLS_TRANSPORT_NEW:
+      return webrtc::DtlsTransportState::kNew;
+    case DtlsTransportState::DTLS_TRANSPORT_CONNECTING:
+      return webrtc::DtlsTransportState::kConnecting;
+    case DtlsTransportState::DTLS_TRANSPORT_CONNECTED:
+      return webrtc::DtlsTransportState::kConnected;
+    case DtlsTransportState::DTLS_TRANSPORT_CLOSED:
+      return webrtc::DtlsTransportState::kClosed;
+    case DtlsTransportState::DTLS_TRANSPORT_FAILED:
+      return webrtc::DtlsTransportState::kFailed;
+  }
+  RTC_NOTREACHED();
+  return webrtc::DtlsTransportState::kNew;
+}
+
 }  // namespace cricket
diff --git a/p2p/base/dtls_transport_internal.h b/p2p/base/dtls_transport_internal.h
index b9c399d..16e8b81 100644
--- a/p2p/base/dtls_transport_internal.h
+++ b/p2p/base/dtls_transport_internal.h
@@ -13,10 +13,13 @@
 
 #include <stddef.h>
 #include <stdint.h>
+
 #include <memory>
 #include <string>
 
 #include "api/crypto/crypto_options.h"
+#include "api/datagram_transport_interface.h"
+#include "api/dtls_transport_interface.h"
 #include "api/scoped_refptr.h"
 #include "p2p/base/ice_transport_internal.h"
 #include "p2p/base/packet_transport_internal.h"
@@ -41,6 +44,9 @@
   DTLS_TRANSPORT_FAILED,
 };
 
+webrtc::DtlsTransportState ConvertDtlsTransportState(
+    cricket::DtlsTransportState cricket_state);
+
 enum PacketFlags {
   PF_NORMAL = 0x00,       // A normal packet.
   PF_SRTP_BYPASS = 0x01,  // An encrypted SRTP packet; bypass any additional
@@ -59,6 +65,14 @@
 
   virtual const webrtc::CryptoOptions& crypto_options() const = 0;
 
+  // Returns datagram transport or nullptr if not using datagram transport.
+  // TODO(sukhanov): Make pure virtual.
+  // TODO(sukhanov): Consider moving ownership of datagram transport and ICE
+  // to JsepTransport.
+  virtual webrtc::DatagramTransportInterface* datagram_transport() {
+    return nullptr;
+  }
+
   virtual DtlsTransportState dtls_state() const = 0;
 
   virtual int component() const = 0;
diff --git a/pc/jsep_transport.cc b/pc/jsep_transport.cc
index f82cf2a..26311d1 100644
--- a/pc/jsep_transport.cc
+++ b/pc/jsep_transport.cc
@@ -116,6 +116,7 @@
               : nullptr),
       media_transport_(std::move(media_transport)) {
   RTC_DCHECK(rtp_dtls_transport_);
+  RTC_DCHECK(!datagram_transport() || !media_transport_);
   // Verify the "only one out of these three can be set" invariant.
   if (unencrypted_rtp_transport_) {
     RTC_DCHECK(!sdes_transport);
@@ -135,12 +136,13 @@
 }
 
 JsepTransport::~JsepTransport() {
+  // Disconnect media transport state callbacks and  make sure we delete media
+  // transports before ICE.
   if (media_transport_) {
     media_transport_->SetMediaTransportStateCallback(nullptr);
-
-    // Make sure we delete media transport before ICE.
     media_transport_.reset();
   }
+
   // Clear all DtlsTransports. There may be pointers to these from
   // other places, so we can't assume they'll be deleted by the destructor.
   rtp_dtls_transport_->Clear();
@@ -717,5 +719,4 @@
   }
   SignalMediaTransportStateChanged();
 }
-
 }  // namespace cricket
diff --git a/pc/jsep_transport.h b/pc/jsep_transport.h
index 0f31403..fce21be 100644
--- a/pc/jsep_transport.h
+++ b/pc/jsep_transport.h
@@ -217,6 +217,12 @@
     return media_transport_.get();
   }
 
+  // Returns datagram transport, if available.
+  webrtc::DatagramTransportInterface* datagram_transport() const {
+    rtc::CritScope scope(&accessor_lock_);
+    return rtp_dtls_transport_->internal()->datagram_transport();
+  }
+
   // Returns the latest media transport state.
   webrtc::MediaTransportState media_transport_state() const {
     rtc::CritScope scope(&accessor_lock_);
@@ -332,6 +338,10 @@
 
   // If |media_transport_| is provided, this variable represents the state of
   // media transport.
+  //
+  // NOTE: datagram transport state is handled by DatagramDtlsAdaptor, because
+  // DatagramDtlsAdaptor owns DatagramTransport. This state only represents
+  // media transport.
   webrtc::MediaTransportState media_transport_state_
       RTC_GUARDED_BY(accessor_lock_) = webrtc::MediaTransportState::kPending;
 
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
index fd9551a..55f1d1c 100644
--- a/pc/jsep_transport_controller.cc
+++ b/pc/jsep_transport_controller.cc
@@ -15,6 +15,9 @@
 
 #include "absl/algorithm/container.h"
 #include "absl/memory/memory.h"
+#include "api/datagram_transport_interface.h"
+#include "api/media_transport_interface.h"
+#include "p2p/base/datagram_dtls_adaptor.h"
 #include "p2p/base/ice_transport_internal.h"
 #include "p2p/base/no_op_dtls_transport.h"
 #include "p2p/base/port.h"
@@ -136,12 +139,42 @@
   return jsep_transport->rtp_transport();
 }
 
-MediaTransportInterface* JsepTransportController::GetMediaTransport(
+MediaTransportConfig JsepTransportController::GetMediaTransportConfig(
     const std::string& mid) const {
   auto jsep_transport = GetJsepTransportForMid(mid);
   if (!jsep_transport) {
+    return MediaTransportConfig();
+  }
+
+  MediaTransportInterface* media_transport = nullptr;
+  if (config_.use_media_transport_for_media) {
+    media_transport = jsep_transport->media_transport();
+  }
+
+  DatagramTransportInterface* datagram_transport =
+      jsep_transport->datagram_transport();
+
+  // Media transport and datagram transports can not be used together.
+  RTC_DCHECK(!media_transport || !datagram_transport);
+
+  if (media_transport) {
+    return MediaTransportConfig(media_transport);
+  } else if (datagram_transport) {
+    return MediaTransportConfig(
+        /*rtp_max_packet_size=*/datagram_transport->GetLargestDatagramSize());
+  } else {
+    return MediaTransportConfig();
+  }
+}
+
+MediaTransportInterface*
+JsepTransportController::GetMediaTransportForDataChannel(
+    const std::string& mid) const {
+  auto jsep_transport = GetJsepTransportForMid(mid);
+  if (!jsep_transport || !config_.use_media_transport_for_data_channels) {
     return nullptr;
   }
+
   return jsep_transport->media_transport();
 }
 
@@ -403,7 +436,8 @@
 
 void JsepTransportController::SetMediaTransportSettings(
     bool use_media_transport_for_media,
-    bool use_media_transport_for_data_channels) {
+    bool use_media_transport_for_data_channels,
+    bool use_datagram_transport) {
   RTC_DCHECK(use_media_transport_for_media ==
                  config_.use_media_transport_for_media ||
              jsep_transports_by_name_.empty())
@@ -419,6 +453,7 @@
   config_.use_media_transport_for_media = use_media_transport_for_media;
   config_.use_media_transport_for_data_channels =
       use_media_transport_for_data_channels;
+  config_.use_datagram_transport = use_datagram_transport;
 }
 
 std::unique_ptr<cricket::IceTransportInternal>
@@ -439,16 +474,25 @@
 
 std::unique_ptr<cricket::DtlsTransportInternal>
 JsepTransportController::CreateDtlsTransport(
-    std::unique_ptr<cricket::IceTransportInternal> ice) {
+    std::unique_ptr<cricket::IceTransportInternal> ice,
+    std::unique_ptr<DatagramTransportInterface> datagram_transport) {
   RTC_DCHECK(network_thread_->IsCurrent());
 
   std::unique_ptr<cricket::DtlsTransportInternal> dtls;
-  // If media transport is used for both media and data channels,
-  // then we don't need to create DTLS.
-  // Otherwise, DTLS is still created.
-  if (config_.media_transport_factory &&
-      config_.use_media_transport_for_media &&
-      config_.use_media_transport_for_data_channels) {
+
+  if (datagram_transport) {
+    RTC_DCHECK(config_.use_datagram_transport);
+
+    // Create DTLS wrapper around DatagramTransportInterface.
+    dtls = absl::make_unique<cricket::DatagramDtlsAdaptor>(
+        std::move(ice), std::move(datagram_transport), config_.crypto_options,
+        config_.event_log);
+  } else if (config_.media_transport_factory &&
+             config_.use_media_transport_for_media &&
+             config_.use_media_transport_for_data_channels) {
+    // If media transport is used for both media and data channels,
+    // then we don't need to create DTLS.
+    // Otherwise, DTLS is still created.
     dtls = absl::make_unique<cricket::NoOpDtlsTransport>(
         std::move(ice), config_.crypto_options);
   } else if (config_.external_transport_factory) {
@@ -1024,6 +1068,72 @@
   return media_transport_result.MoveValue();
 }
 
+// TODO(sukhanov): Refactor to avoid code duplication for Media and Datagram
+// transports setup.
+std::unique_ptr<webrtc::DatagramTransportInterface>
+JsepTransportController::MaybeCreateDatagramTransport(
+    const cricket::ContentInfo& content_info,
+    const cricket::SessionDescription& description,
+    bool local) {
+  if (config_.media_transport_factory == nullptr) {
+    return nullptr;
+  }
+
+  if (!config_.use_datagram_transport) {
+    return nullptr;
+  }
+
+  // Caller (offerer) datagram transport.
+  if (local) {
+    if (offer_datagram_transport_) {
+      RTC_LOG(LS_INFO) << "Offered datagram transport has now been activated.";
+      return std::move(offer_datagram_transport_);
+    } else {
+      RTC_LOG(LS_INFO)
+          << "Not returning datagram transport. Either SDES wasn't enabled, or "
+             "datagram transport didn't return an offer earlier.";
+      return nullptr;
+    }
+  }
+
+  // Remote offer. If no x-mt lines, do not create datagram transport.
+  if (description.MediaTransportSettings().empty()) {
+    return nullptr;
+  }
+
+  // When bundle is enabled, two JsepTransports are created, and then
+  // the second transport is destroyed (right away).
+  // For datagram transport, we don't want to create the second
+  // datagram transport in the first place.
+  RTC_LOG(LS_INFO) << "Returning new, client datagram transport.";
+
+  RTC_DCHECK(!local)
+      << "If datagram transport is used, you must call "
+         "GenerateOrGetLastMediaTransportOffer before SetLocalDescription. You "
+         "also must use kRtcpMuxPolicyRequire and kBundlePolicyMaxBundle with "
+         "datagram transport.";
+  MediaTransportSettings settings;
+  settings.is_caller = local;
+  settings.event_log = config_.event_log;
+
+  // Assume there is only one media transport (or if more, use the first one).
+  if (!local && !description.MediaTransportSettings().empty() &&
+      config_.media_transport_factory->GetTransportName() ==
+          description.MediaTransportSettings()[0].transport_name) {
+    settings.remote_transport_parameters =
+        description.MediaTransportSettings()[0].transport_setting;
+  }
+
+  auto datagram_transport_result =
+      config_.media_transport_factory->CreateDatagramTransport(network_thread_,
+                                                               settings);
+
+  // TODO(sukhanov): Proper error handling.
+  RTC_CHECK(datagram_transport_result.ok());
+
+  return datagram_transport_result.MoveValue();
+}
+
 RTCError JsepTransportController::MaybeCreateJsepTransport(
     bool local,
     const cricket::ContentInfo& content_info,
@@ -1052,8 +1162,15 @@
     media_transport->Connect(ice.get());
   }
 
+  std::unique_ptr<DatagramTransportInterface> datagram_transport =
+      MaybeCreateDatagramTransport(content_info, description, local);
+  if (datagram_transport) {
+    datagram_transport_created_once_ = true;
+    datagram_transport->Connect(ice.get());
+  }
+
   std::unique_ptr<cricket::DtlsTransportInternal> rtp_dtls_transport =
-      CreateDtlsTransport(std::move(ice));
+      CreateDtlsTransport(std::move(ice), std::move(datagram_transport));
 
   std::unique_ptr<cricket::DtlsTransportInternal> rtcp_dtls_transport;
   std::unique_ptr<RtpTransport> unencrypted_rtp_transport;
@@ -1064,19 +1181,36 @@
           PeerConnectionInterface::kRtcpMuxPolicyRequire &&
       content_info.type == cricket::MediaProtocolType::kRtp) {
     RTC_DCHECK(media_transport == nullptr);
+    RTC_DCHECK(datagram_transport == nullptr);
     rtcp_dtls_transport = CreateDtlsTransport(
-        CreateIceTransport(content_info.name, /*rtcp=*/true));
+        CreateIceTransport(content_info.name, /*rtcp=*/true),
+        /*datagram_transport=*/nullptr);
   }
 
-  // TODO(sukhanov): Do not create RTP/RTCP transports if media transport is
-  // used, and remove the no-op dtls transport when that's done.
-  if (config_.disable_encryption) {
+  if (datagram_transport) {
+    // TODO(sukhanov): We use unencrypted RTP transport over DatagramTransport,
+    // because MediaTransport encrypts. In the future we may want to
+    // implement our own version of RtpTransport over MediaTransport, because
+    // it will give us more control over things like:
+    // - Fusing
+    // - Rtp header compression
+    // - Handling Rtcp feedback.
+    RTC_LOG(LS_INFO) << "Creating UnencryptedRtpTransport, because datagram "
+                        "transport is used.";
+    RTC_DCHECK(!rtcp_dtls_transport);
+    unencrypted_rtp_transport = CreateUnencryptedRtpTransport(
+        content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
+  } else if (config_.disable_encryption) {
+    RTC_LOG(LS_INFO)
+        << "Creating UnencryptedRtpTransport, becayse encryption is disabled.";
     unencrypted_rtp_transport = CreateUnencryptedRtpTransport(
         content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
   } else if (!content_desc->cryptos().empty()) {
     sdes_transport = CreateSdesTransport(
         content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
+    RTC_LOG(LS_INFO) << "Creating SdesTransport.";
   } else {
+    RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport.";
     dtls_srtp_transport = CreateDtlsSrtpTransport(
         content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
   }
@@ -1087,6 +1221,7 @@
           std::move(sdes_transport), std::move(dtls_srtp_transport),
           std::move(rtp_dtls_transport), std::move(rtcp_dtls_transport),
           std::move(media_transport));
+
   jsep_transport->SignalRtcpMuxActive.connect(
       this, &JsepTransportController::UpdateAggregateStates_n);
   jsep_transport->SignalMediaTransportStateChanged.connect(
@@ -1508,20 +1643,25 @@
 
 absl::optional<cricket::SessionDescription::MediaTransportSetting>
 JsepTransportController::GenerateOrGetLastMediaTransportOffer() {
-  if (media_transport_created_once_) {
+  if (media_transport_created_once_ || datagram_transport_created_once_) {
     RTC_LOG(LS_INFO) << "Not regenerating media transport for the new offer in "
                         "existing session.";
     return media_transport_offer_settings_;
   }
 
   RTC_LOG(LS_INFO) << "Generating media transport offer!";
+
+  absl::optional<std::string> transport_parameters;
+
   // Check that media transport is supposed to be used.
+  // Note that ICE is not available when media transport is created. It will
+  // only be available in 'Connect'. This may be a potential server config, if
+  // we decide to use this peer connection as a caller, not as a callee.
+  // TODO(sukhanov): Avoid code duplication with CreateMedia/MediaTransport.
   if (config_.use_media_transport_for_media ||
       config_.use_media_transport_for_data_channels) {
     RTC_DCHECK(config_.media_transport_factory != nullptr);
-    // ICE is not available when media transport is created. It will only be
-    // available in 'Connect'. This may be a potential server config, if we
-    // decide to use this peer connection as a caller, not as a callee.
+    RTC_DCHECK(!config_.use_datagram_transport);
     webrtc::MediaTransportSettings settings;
     settings.is_caller = true;
     settings.pre_shared_key = rtc::CreateRandomString(32);
@@ -1532,19 +1672,37 @@
 
     if (media_transport_or_error.ok()) {
       offer_media_transport_ = std::move(media_transport_or_error.value());
+      transport_parameters =
+          offer_media_transport_->GetTransportParametersOffer();
     } else {
       RTC_LOG(LS_INFO) << "Unable to create media transport, error="
                        << media_transport_or_error.error().message();
     }
+  } else if (config_.use_datagram_transport) {
+    webrtc::MediaTransportSettings settings;
+    settings.is_caller = true;
+    settings.pre_shared_key = rtc::CreateRandomString(32);
+    settings.event_log = config_.event_log;
+    auto datagram_transport_or_error =
+        config_.media_transport_factory->CreateDatagramTransport(
+            network_thread_, settings);
+
+    if (datagram_transport_or_error.ok()) {
+      offer_datagram_transport_ =
+          std::move(datagram_transport_or_error.value());
+      transport_parameters =
+          offer_datagram_transport_->GetTransportParametersOffer();
+    } else {
+      RTC_LOG(LS_INFO) << "Unable to create media transport, error="
+                       << datagram_transport_or_error.error().message();
+    }
   }
 
-  if (!offer_media_transport_) {
-    RTC_LOG(LS_INFO) << "Media transport doesn't exist";
+  if (!offer_media_transport_ && !offer_datagram_transport_) {
+    RTC_LOG(LS_INFO) << "Media and data transports do not exist";
     return absl::nullopt;
   }
 
-  absl::optional<std::string> transport_parameters =
-      offer_media_transport_->GetTransportParametersOffer();
   if (!transport_parameters) {
     RTC_LOG(LS_INFO) << "Media transport didn't generate the offer";
     // Media transport didn't generate the offer, and is not supposed to be
diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h
index fff08d1..a79817c 100644
--- a/pc/jsep_transport_controller.h
+++ b/pc/jsep_transport_controller.h
@@ -19,6 +19,7 @@
 
 #include "api/candidate.h"
 #include "api/crypto/crypto_options.h"
+#include "api/media_transport_config.h"
 #include "api/media_transport_interface.h"
 #include "api/peer_connection_interface.h"
 #include "logging/rtc_event_log/rtc_event_log.h"
@@ -93,6 +94,9 @@
     // MediaTransportFactory is provided.
     bool use_rtp_media_transport = false;
 
+    // Use encrypted datagram transport to send packets.
+    bool use_datagram_transport = false;
+
     // Optional media transport factory (experimental). If provided it will be
     // used to create media_transport (as long as either
     // |use_media_transport_for_media| or
@@ -133,7 +137,16 @@
   rtc::scoped_refptr<webrtc::DtlsTransport> LookupDtlsTransportByMid(
       const std::string& mid);
 
-  MediaTransportInterface* GetMediaTransport(const std::string& mid) const;
+  MediaTransportConfig GetMediaTransportConfig(const std::string& mid) const;
+
+  MediaTransportInterface* GetMediaTransportForDataChannel(
+      const std::string& mid) const;
+
+  // TODO(sukhanov): Deprecate, return only config.
+  MediaTransportInterface* GetMediaTransport(const std::string& mid) const {
+    return GetMediaTransportConfig(mid).media_transport;
+  }
+
   MediaTransportState GetMediaTransportState(const std::string& mid) const;
 
   /*********************
@@ -190,7 +203,8 @@
   // you did not call 'GetMediaTransport' or 'MaybeCreateJsepTransport'. Once
   // Jsep transport is created, you can't change this setting.
   void SetMediaTransportSettings(bool use_media_transport_for_media,
-                                 bool use_media_transport_for_data_channels);
+                                 bool use_media_transport_for_data_channels,
+                                 bool use_datagram_transport);
 
   // If media transport is present enabled and supported,
   // when this method is called, it creates a media transport and generates its
@@ -308,6 +322,17 @@
       const cricket::ContentInfo& content_info,
       const cricket::SessionDescription& description,
       bool local);
+
+  // Creates datagram transport if config wants to use it, and a=x-mt line is
+  // present for the current media transport. Returned
+  // DatagramTransportInterface is not connected, and must be connected to ICE.
+  // You must call |GenerateOrGetLastMediaTransportOffer| on the caller before
+  // calling MaybeCreateDatagramTransport.
+  std::unique_ptr<webrtc::DatagramTransportInterface>
+  MaybeCreateDatagramTransport(const cricket::ContentInfo& content_info,
+                               const cricket::SessionDescription& description,
+                               bool local);
+
   void MaybeDestroyJsepTransport(const std::string& mid);
   void DestroyAllJsepTransports_n();
 
@@ -320,7 +345,8 @@
       bool local);
 
   std::unique_ptr<cricket::DtlsTransportInternal> CreateDtlsTransport(
-      std::unique_ptr<cricket::IceTransportInternal> ice);
+      std::unique_ptr<cricket::IceTransportInternal> ice,
+      std::unique_ptr<DatagramTransportInterface> datagram_transport);
   std::unique_ptr<cricket::IceTransportInternal> CreateIceTransport(
       const std::string transport_name,
       bool rtcp);
@@ -399,6 +425,22 @@
   absl::optional<cricket::SessionDescription::MediaTransportSetting>
       media_transport_offer_settings_;
 
+  // Early on in the call we don't know if datagram transport is going to be
+  // used, but we need to get the server-supported parameters to add to an SDP.
+  // This server datagram transport will be promoted to the used datagram
+  // transport after the local description is set, and the ownership will be
+  // transferred to the actual JsepTransport. This "offer" datagram transport is
+  // not created if it's done on the party that provides answer. This offer
+  // datagram transport is only created once at the beginning of the connection,
+  // and never again.
+  std::unique_ptr<DatagramTransportInterface> offer_datagram_transport_ =
+      nullptr;
+
+  // Contains the offer of the |offer_datagram_transport_|, in case if it needs
+  // to be repeated.
+  absl::optional<cricket::SessionDescription::MediaTransportSetting>
+      datagram_transport_offer_settings_;
+
   // When the new offer is regenerated (due to upgrade), we don't want to
   // re-create media transport. New streams might be created; but media
   // transport stays the same. This flag prevents re-creation of the transport
@@ -411,6 +453,7 @@
   // recreate the Offer (e.g. after adding streams in Plan B), and so we want to
   // prevent recreation of the media transport when that happens.
   bool media_transport_created_once_ = false;
+  bool datagram_transport_created_once_ = false;
 
   const cricket::SessionDescription* local_desc_ = nullptr;
   const cricket::SessionDescription* remote_desc_ = nullptr;
diff --git a/pc/jsep_transport_controller_unittest.cc b/pc/jsep_transport_controller_unittest.cc
index c0927b9d..346168d 100644
--- a/pc/jsep_transport_controller_unittest.cc
+++ b/pc/jsep_transport_controller_unittest.cc
@@ -442,7 +442,7 @@
                   .ok());
 
   FakeMediaTransport* media_transport = static_cast<FakeMediaTransport*>(
-      transport_controller_->GetMediaTransport(kAudioMid1));
+      transport_controller_->GetMediaTransportForDataChannel(kAudioMid1));
 
   ASSERT_NE(nullptr, media_transport);
 
@@ -451,7 +451,8 @@
   EXPECT_TRUE(media_transport->pre_shared_key().has_value());
 
   // Return nullptr for non-existing mids.
-  EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
+  EXPECT_EQ(nullptr,
+            transport_controller_->GetMediaTransportForDataChannel(kVideoMid2));
 
   EXPECT_EQ(cricket::ICE_CANDIDATE_COMPONENT_RTP,
             transport_controller_->GetDtlsTransport(kAudioMid1)->component())
@@ -563,8 +564,6 @@
   EXPECT_EQ(absl::nullopt, media_transport->settings().pre_shared_key);
   EXPECT_TRUE(media_transport->is_connected());
 
-  EXPECT_EQ("fake-remote-settings",
-            media_transport->remote_transport_parameters());
   // Return nullptr for non-existing mids.
   EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
 
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index ca66a09..14c8683 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -764,6 +764,7 @@
     bool active_reset_srtp_params;
     bool use_media_transport;
     bool use_media_transport_for_data_channels;
+    bool use_datagram_transport;
     absl::optional<CryptoOptions> crypto_options;
     bool offer_extmap_allow_mixed;
   };
@@ -822,6 +823,7 @@
          use_media_transport == o.use_media_transport &&
          use_media_transport_for_data_channels ==
              o.use_media_transport_for_data_channels &&
+         use_datagram_transport == o.use_datagram_transport &&
          crypto_options == o.crypto_options &&
          offer_extmap_allow_mixed == o.offer_extmap_allow_mixed;
 }
@@ -1021,7 +1023,8 @@
 #endif
   config.active_reset_srtp_params = configuration.active_reset_srtp_params;
 
-  if (configuration.use_media_transport ||
+  if (configuration.use_datagram_transport ||
+      configuration.use_media_transport ||
       configuration.use_media_transport_for_data_channels) {
     if (!factory_->media_transport_factory()) {
       RTC_DCHECK(false)
@@ -1051,6 +1054,7 @@
     config.use_media_transport_for_media = configuration.use_media_transport;
     config.use_media_transport_for_data_channels =
         configuration.use_media_transport_for_data_channels;
+    config.use_datagram_transport = configuration.use_datagram_transport;
     config.media_transport_factory = factory_->media_transport_factory();
   }
 
@@ -3412,8 +3416,23 @@
     return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error);
   }
 
+  if (local_description() && configuration.use_datagram_transport !=
+                                 configuration_.use_datagram_transport) {
+    RTC_LOG(LS_ERROR) << "Can't change use_datagram_transport "
+                         "after calling SetLocalDescription.";
+    return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error);
+  }
+
+  if (remote_description() && configuration.use_datagram_transport !=
+                                  configuration_.use_datagram_transport) {
+    RTC_LOG(LS_ERROR) << "Can't change use_datagram_transport "
+                         "after calling SetRemoteDescription.";
+    return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error);
+  }
+
   if (configuration.use_media_transport_for_data_channels ||
-      configuration.use_media_transport) {
+      configuration.use_media_transport ||
+      configuration.use_datagram_transport) {
     RTC_CHECK(configuration.bundle_policy == kBundlePolicyMaxBundle)
         << "Media transport requires MaxBundle policy.";
   }
@@ -3506,7 +3525,8 @@
   transport_controller_->SetIceConfig(ParseIceConfig(modified_config));
   transport_controller_->SetMediaTransportSettings(
       modified_config.use_media_transport,
-      modified_config.use_media_transport_for_data_channels);
+      modified_config.use_media_transport_for_data_channels,
+      modified_config.use_datagram_transport);
 
   if (configuration_.active_reset_srtp_params !=
       modified_config.active_reset_srtp_params) {
@@ -6317,15 +6337,13 @@
 cricket::VoiceChannel* PeerConnection::CreateVoiceChannel(
     const std::string& mid) {
   RtpTransportInternal* rtp_transport = GetRtpTransport(mid);
-  MediaTransportInterface* media_transport = nullptr;
-  if (configuration_.use_media_transport) {
-    media_transport = GetMediaTransport(mid);
-  }
+  MediaTransportConfig media_transport_config =
+      transport_controller_->GetMediaTransportConfig(mid);
 
   cricket::VoiceChannel* voice_channel = channel_manager()->CreateVoiceChannel(
       call_ptr_, configuration_.media_config, rtp_transport,
-      MediaTransportConfig(media_transport), signaling_thread(), mid,
-      SrtpRequired(), GetCryptoOptions(), &ssrc_generator_, audio_options_);
+      media_transport_config, signaling_thread(), mid, SrtpRequired(),
+      GetCryptoOptions(), &ssrc_generator_, audio_options_);
   if (!voice_channel) {
     return nullptr;
   }
@@ -6342,15 +6360,13 @@
 cricket::VideoChannel* PeerConnection::CreateVideoChannel(
     const std::string& mid) {
   RtpTransportInternal* rtp_transport = GetRtpTransport(mid);
-  MediaTransportInterface* media_transport = nullptr;
-  if (configuration_.use_media_transport) {
-    media_transport = GetMediaTransport(mid);
-  }
+  MediaTransportConfig media_transport_config =
+      transport_controller_->GetMediaTransportConfig(mid);
 
   cricket::VideoChannel* video_channel = channel_manager()->CreateVideoChannel(
       call_ptr_, configuration_.media_config, rtp_transport,
-      MediaTransportConfig(media_transport), signaling_thread(), mid,
-      SrtpRequired(), GetCryptoOptions(), &ssrc_generator_, video_options_,
+      media_transport_config, signaling_thread(), mid, SrtpRequired(),
+      GetCryptoOptions(), &ssrc_generator_, video_options_,
       video_bitrate_allocator_factory_.get());
   if (!video_channel) {
     return nullptr;
@@ -6529,7 +6545,8 @@
 
 bool PeerConnection::SetupMediaTransportForDataChannels_n(
     const std::string& mid) {
-  media_transport_ = transport_controller_->GetMediaTransport(mid);
+  media_transport_ =
+      transport_controller_->GetMediaTransportForDataChannel(mid);
   if (!media_transport_) {
     RTC_LOG(LS_ERROR)
         << "Media transport is not available for data channels, mid=" << mid;
@@ -6886,8 +6903,9 @@
 }
 
 bool PeerConnection::SrtpRequired() const {
-  return dtls_enabled_ ||
-         webrtc_session_desc_factory_->SdesPolicy() == cricket::SEC_REQUIRED;
+  return !configuration_.use_datagram_transport &&
+         (dtls_enabled_ ||
+          webrtc_session_desc_factory_->SdesPolicy() == cricket::SEC_REQUIRED);
 }
 
 void PeerConnection::OnTransportControllerGatheringState(
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index d9c625c..7287b7c 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -1079,22 +1079,6 @@
     return rtp_transport;
   }
 
-  // Returns media transport, if PeerConnection was created with configuration
-  // to use media transport. Otherwise returns nullptr.
-  MediaTransportInterface* GetMediaTransport(const std::string& mid)
-      RTC_RUN_ON(signaling_thread()) {
-    auto media_transport = transport_controller_->GetMediaTransport(mid);
-    RTC_DCHECK((configuration_.use_media_transport ||
-                configuration_.use_media_transport_for_data_channels) ==
-               (media_transport != nullptr))
-        << "configuration_.use_media_transport="
-        << configuration_.use_media_transport
-        << ", configuration_.use_media_transport_for_data_channels="
-        << configuration_.use_media_transport_for_data_channels
-        << ", (media_transport != nullptr)=" << (media_transport != nullptr);
-    return media_transport;
-  }
-
   void UpdateNegotiationNeeded();
   bool CheckIfNegotiationIsNeeded();