TurnCustomizer - an interface for modifying stun messages sent by TurnPort

This patch adds an interface that allows modification of stun messages
sent by TurnPort. A user can inject a TurnCustomizer on the RTCConfig
and the TurnCustomizer will be invoked by TurnPort before sending
message. This allows user to e.g add custom attributes as described
in rtf5389.

BUG=webrtc:8313

Change-Id: I6f4333e9f8ff7fd20f32677be19285f15e1180b6
Reviewed-on: https://webrtc-review.googlesource.com/7618
Reviewed-by: Guido Urdaneta <guidou@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20233}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index a2c36ac..1c1731e 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -61,6 +61,7 @@
     "rtpsenderinterface.h",
     "statstypes.cc",
     "statstypes.h",
+    "turncustomizer.h",
     "umametrics.h",
     "videosourceproxy.h",
   ]
diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h
index 1039cb5..6369665 100644
--- a/api/peerconnectioninterface.h
+++ b/api/peerconnectioninterface.h
@@ -83,6 +83,7 @@
 #include "api/rtpsenderinterface.h"
 #include "api/stats/rtcstatscollectorcallback.h"
 #include "api/statstypes.h"
+#include "api/turncustomizer.h"
 #include "api/umametrics.h"
 #include "call/callfactoryinterface.h"
 #include "logging/rtc_event_log/rtc_event_log_factory_interface.h"
@@ -474,6 +475,12 @@
     // interval specified in milliseconds by the uniform distribution [a, b].
     rtc::Optional<rtc::IntervalRange> ice_regather_interval_range;
 
+    // Optional TurnCustomizer.
+    // With this class one can modify outgoing TURN messages.
+    // The object passed in must remain valid until PeerConnection::Close() is
+    // called.
+    webrtc::TurnCustomizer* turn_customizer = nullptr;
+
     //
     // Don't forget to update operator== if adding something.
     //
diff --git a/api/turncustomizer.h b/api/turncustomizer.h
new file mode 100644
index 0000000..517abcc
--- /dev/null
+++ b/api/turncustomizer.h
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2017 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 API_TURNCUSTOMIZER_H_
+#define API_TURNCUSTOMIZER_H_
+
+#include <stdlib.h>
+
+namespace cricket {
+class PortInterface;
+class StunMessage;
+}  // namespace cricket
+
+
+namespace webrtc {
+
+class TurnCustomizer {
+ public:
+  // This is called before a TURN message is sent.
+  // This could be used to add implementation specific attributes to a request.
+  virtual void MaybeModifyOutgoingStunMessage(
+      cricket::PortInterface* port,
+      cricket::StunMessage* message) = 0;
+
+  // TURN can send data using channel data messages or Send indication.
+  // This method should return false if |data| should be sent using
+  // a Send indication instead of a ChannelData message, even if a
+  // channel is bound.
+  virtual bool AllowChannelData(cricket::PortInterface* port,
+                                const void* data,
+                                size_t size,
+                                bool payload) = 0;
+
+  virtual ~TurnCustomizer() {}
+};
+
+}  // namespace webrtc
+
+#endif  // API_TURNCUSTOMIZER_H_
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index ec007b0..9f655a7 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -148,6 +148,7 @@
       "base/mockicetransport.h",
       "base/testrelayserver.h",
       "base/teststunserver.h",
+      "base/testturncustomizer.h",
       "base/testturnserver.h",
     ]
     deps = [
diff --git a/p2p/base/port_unittest.cc b/p2p/base/port_unittest.cc
index 36af5de..af853ca 100644
--- a/p2p/base/port_unittest.cc
+++ b/p2p/base/port_unittest.cc
@@ -536,7 +536,8 @@
     return TurnPort::Create(
         &main_, socket_factory, MakeNetwork(addr), 0, 0, username_, password_,
         ProtocolAddress(server_addr, int_proto), kRelayCredentials, 0,
-        std::string(), std::vector<std::string>(), std::vector<std::string>());
+        std::string(), std::vector<std::string>(), std::vector<std::string>(),
+        nullptr);
   }
   RelayPort* CreateGturnPort(const SocketAddress& addr,
                              ProtocolType int_proto, ProtocolType ext_proto) {
diff --git a/p2p/base/portallocator.cc b/p2p/base/portallocator.cc
index 97dedc5..109472b 100644
--- a/p2p/base/portallocator.cc
+++ b/p2p/base/portallocator.cc
@@ -33,7 +33,8 @@
     const ServerAddresses& stun_servers,
     const std::vector<RelayServerConfig>& turn_servers,
     int candidate_pool_size,
-    bool prune_turn_ports) {
+    bool prune_turn_ports,
+    webrtc::TurnCustomizer* turn_customizer) {
   bool ice_servers_changed =
       (stun_servers != stun_servers_ || turn_servers != turn_servers_);
   stun_servers_ = stun_servers;
@@ -62,6 +63,8 @@
     pooled_sessions_.clear();
   }
 
+  turn_customizer_ = turn_customizer;
+
   // If |candidate_pool_size_| is less than the number of pooled sessions, get
   // rid of the extras.
   while (candidate_pool_size_ < static_cast<int>(pooled_sessions_.size())) {
diff --git a/p2p/base/portallocator.h b/p2p/base/portallocator.h
index de8d2d9..5c0d303 100644
--- a/p2p/base/portallocator.h
+++ b/p2p/base/portallocator.h
@@ -25,6 +25,7 @@
 
 namespace webrtc {
 class MetricsObserverInterface;
+class TurnCustomizer;
 }
 
 namespace cricket {
@@ -362,7 +363,8 @@
   bool SetConfiguration(const ServerAddresses& stun_servers,
                         const std::vector<RelayServerConfig>& turn_servers,
                         int candidate_pool_size,
-                        bool prune_turn_ports);
+                        bool prune_turn_ports,
+                        webrtc::TurnCustomizer* turn_customizer = nullptr);
 
   const ServerAddresses& stun_servers() const { return stun_servers_; }
 
@@ -477,6 +479,10 @@
     metrics_observer_ = observer;
   }
 
+  webrtc::TurnCustomizer* turn_customizer() {
+    return turn_customizer_;
+  }
+
  protected:
   virtual PortAllocatorSession* CreateSessionInternal(
       const std::string& content_name,
@@ -512,6 +518,11 @@
   bool prune_turn_ports_ = false;
 
   webrtc::MetricsObserverInterface* metrics_observer_ = nullptr;
+
+  // Customizer for TURN messages.
+  // The instance is owned by application and will be shared among
+  // all TurnPort(s) created.
+  webrtc::TurnCustomizer* turn_customizer_ = nullptr;
 };
 
 }  // namespace cricket
diff --git a/p2p/base/stun.cc b/p2p/base/stun.cc
index 9ac2554..e7d1244 100644
--- a/p2p/base/stun.cc
+++ b/p2p/base/stun.cc
@@ -67,9 +67,18 @@
   return true;
 }
 
+static bool ImplementationDefinedRange(int attr_type)
+{
+  return attr_type >= 0xC000 && attr_type <= 0xFFFF;
+}
+
 void StunMessage::AddAttribute(std::unique_ptr<StunAttribute> attr) {
-  // Fail any attributes that aren't valid for this type of message.
-  RTC_DCHECK_EQ(attr->value_type(), GetAttributeValueType(attr->type()));
+  // Fail any attributes that aren't valid for this type of message,
+  // but allow any type for the range that is "implementation defined"
+  // in the RFC.
+  if (!ImplementationDefinedRange(attr->type())) {
+    RTC_DCHECK_EQ(attr->value_type(), GetAttributeValueType(attr->type()));
+  }
 
   attr->SetOwner(this);
   size_t attr_length = attr->length();
@@ -398,8 +407,16 @@
 
 StunAttribute* StunMessage::CreateAttribute(int type, size_t length) /*const*/ {
   StunAttributeValueType value_type = GetAttributeValueType(type);
-  return StunAttribute::Create(value_type, type, static_cast<uint16_t>(length),
-                               this);
+  if (value_type != STUN_VALUE_UNKNOWN) {
+    return StunAttribute::Create(value_type, type,
+                                 static_cast<uint16_t>(length), this);
+  } else if (ImplementationDefinedRange(type)) {
+    // Read unknown attributes as STUN_VALUE_BYTE_STRING
+    return StunAttribute::Create(STUN_VALUE_BYTE_STRING, type,
+                                 static_cast<uint16_t>(length), this);
+  } else {
+    return NULL;
+  }
 }
 
 const StunAttribute* StunMessage::GetAttribute(int type) const {
diff --git a/p2p/base/stunrequest.cc b/p2p/base/stunrequest.cc
index 8afd1a0..cec9ce3 100644
--- a/p2p/base/stunrequest.cc
+++ b/p2p/base/stunrequest.cc
@@ -207,6 +207,10 @@
   return msg_;
 }
 
+StunMessage* StunRequest::mutable_msg() {
+  return msg_;
+}
+
 int StunRequest::Elapsed() const {
   return static_cast<int>(rtc::TimeMillis() - tstamp_);
 }
diff --git a/p2p/base/stunrequest.h b/p2p/base/stunrequest.h
index 00f317f..e77242e 100644
--- a/p2p/base/stunrequest.h
+++ b/p2p/base/stunrequest.h
@@ -105,6 +105,9 @@
   // Returns a const pointer to |msg_|.
   const StunMessage* msg() const;
 
+  // Returns a mutable pointer to |msg_|.
+  StunMessage* mutable_msg();
+
   // Time elapsed since last send (in ms)
   int Elapsed() const;
 
diff --git a/p2p/base/testturncustomizer.h b/p2p/base/testturncustomizer.h
new file mode 100644
index 0000000..33f48fb
--- /dev/null
+++ b/p2p/base/testturncustomizer.h
@@ -0,0 +1,57 @@
+/*
+ *  Copyright 2017 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_TESTTURNCUSTOMIZER_H_
+#define P2P_BASE_TESTTURNCUSTOMIZER_H_
+
+#include "api/turncustomizer.h"
+#include "rtc_base/ptr_util.h"
+
+namespace cricket {
+
+class TestTurnCustomizer : public webrtc::TurnCustomizer {
+ public:
+  TestTurnCustomizer() {}
+  virtual ~TestTurnCustomizer() {}
+
+  enum TestTurnAttributeExtensions {
+    // Test only attribute
+    STUN_ATTR_COUNTER                     = 0xFF02   // Number
+  };
+
+  void MaybeModifyOutgoingStunMessage(
+      cricket::PortInterface* port,
+      cricket::StunMessage* message) override {
+    modify_cnt_ ++;
+
+    if (add_counter_) {
+      message->AddAttribute(rtc::MakeUnique<cricket::StunUInt32Attribute>(
+          STUN_ATTR_COUNTER, modify_cnt_));
+    }
+    return;
+  }
+
+  bool AllowChannelData(cricket::PortInterface* port,
+                        const void* data,
+                        size_t size,
+                        bool payload) override {
+    allow_channel_data_cnt_++;
+    return allow_channel_data_;
+  }
+
+  bool add_counter_ = false;
+  bool allow_channel_data_ = true;
+  unsigned int modify_cnt_ = 0;
+  unsigned int allow_channel_data_cnt_ = 0;
+};
+
+}  // namespace cricket
+
+#endif  // P2P_BASE_TESTTURNCUSTOMIZER_H_
diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc
index bf1351b..a0b98bb 100644
--- a/p2p/base/turnport.cc
+++ b/p2p/base/turnport.cc
@@ -196,7 +196,8 @@
                    const ProtocolAddress& server_address,
                    const RelayCredentials& credentials,
                    int server_priority,
-                   const std::string& origin)
+                   const std::string& origin,
+                   webrtc::TurnCustomizer* customizer)
     : Port(thread,
            RELAY_PORT_TYPE,
            factory,
@@ -212,7 +213,8 @@
       next_channel_number_(TURN_CHANNEL_NUMBER_START),
       state_(STATE_CONNECTING),
       server_priority_(server_priority),
-      allocate_mismatch_retries_(0) {
+      allocate_mismatch_retries_(0),
+      turn_customizer_(customizer) {
   request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
   request_manager_.set_origin(origin);
 }
@@ -229,7 +231,8 @@
                    int server_priority,
                    const std::string& origin,
                    const std::vector<std::string>& tls_alpn_protocols,
-                   const std::vector<std::string>& tls_elliptic_curves)
+                   const std::vector<std::string>& tls_elliptic_curves,
+                   webrtc::TurnCustomizer* customizer)
     : Port(thread,
            RELAY_PORT_TYPE,
            factory,
@@ -249,7 +252,8 @@
       next_channel_number_(TURN_CHANNEL_NUMBER_START),
       state_(STATE_CONNECTING),
       server_priority_(server_priority),
-      allocate_mismatch_retries_(0) {
+      allocate_mismatch_retries_(0),
+      turn_customizer_(customizer) {
   request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
   request_manager_.set_origin(origin);
 }
@@ -952,6 +956,7 @@
 }
 
 void TurnPort::SendRequest(StunRequest* req, int delay) {
+  TurnCustomizerMaybeModifyOutgoingStunMessage(req->mutable_msg());
   request_manager_.SendDelayed(req, delay);
 }
 
@@ -1142,6 +1147,24 @@
   return url.str();
 }
 
+void TurnPort::TurnCustomizerMaybeModifyOutgoingStunMessage(
+    StunMessage* message) {
+  if (turn_customizer_ == nullptr) {
+    return;
+  }
+
+  turn_customizer_->MaybeModifyOutgoingStunMessage(this, message);
+}
+
+bool TurnPort::TurnCustomizerAllowChannelData(
+    const void* data, size_t size, bool payload) {
+  if (turn_customizer_ == nullptr) {
+    return true;
+  }
+
+  return turn_customizer_->AllowChannelData(this, data, size, payload);
+}
+
 TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
     : StunRequest(new TurnMessage()),
       port_(port) {
@@ -1533,8 +1556,10 @@
 int TurnEntry::Send(const void* data, size_t size, bool payload,
                     const rtc::PacketOptions& options) {
   rtc::ByteBufferWriter buf;
-  if (state_ != STATE_BOUND) {
+  if (state_ != STATE_BOUND ||
+      !port_->TurnCustomizerAllowChannelData(data, size, payload)) {
     // If we haven't bound the channel yet, we have to use a Send Indication.
+    // The turn_customizer_ can also make us use Send Indication.
     TurnMessage msg;
     msg.SetType(TURN_SEND_INDICATION);
     msg.SetTransactionID(
@@ -1543,6 +1568,9 @@
         STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
     msg.AddAttribute(
         rtc::MakeUnique<StunByteStringAttribute>(STUN_ATTR_DATA, data, size));
+
+    port_->TurnCustomizerMaybeModifyOutgoingStunMessage(&msg);
+
     const bool success = msg.Write(&buf);
     RTC_DCHECK(success);
 
diff --git a/p2p/base/turnport.h b/p2p/base/turnport.h
index f891983..1d1ffe2 100644
--- a/p2p/base/turnport.h
+++ b/p2p/base/turnport.h
@@ -26,6 +26,10 @@
 class SignalThread;
 }
 
+namespace webrtc {
+class TurnCustomizer;
+}
+
 namespace cricket {
 
 extern const char TURN_PORT_TYPE[];
@@ -52,9 +56,11 @@
                           const ProtocolAddress& server_address,
                           const RelayCredentials& credentials,
                           int server_priority,
-                          const std::string& origin) {
+                          const std::string& origin,
+                          webrtc::TurnCustomizer* customizer) {
     return new TurnPort(thread, factory, network, socket, username, password,
-                        server_address, credentials, server_priority, origin);
+                        server_address, credentials, server_priority, origin,
+                        customizer);
   }
 
   // Create a TURN port that will use a new socket, bound to |network| and
@@ -71,10 +77,12 @@
                           int server_priority,
                           const std::string& origin,
                           const std::vector<std::string>& tls_alpn_protocols,
-                          const std::vector<std::string>& tls_elliptic_curves) {
+                          const std::vector<std::string>& tls_elliptic_curves,
+                          webrtc::TurnCustomizer* customizer) {
     return new TurnPort(thread, factory, network, min_port, max_port, username,
                         password, server_address, credentials, server_priority,
-                        origin, tls_alpn_protocols, tls_elliptic_curves);
+                        origin, tls_alpn_protocols, tls_elliptic_curves,
+                        customizer);
   }
 
   virtual ~TurnPort();
@@ -184,7 +192,8 @@
            const ProtocolAddress& server_address,
            const RelayCredentials& credentials,
            int server_priority,
-           const std::string& origin);
+           const std::string& origin,
+           webrtc::TurnCustomizer* customizer);
 
   TurnPort(rtc::Thread* thread,
            rtc::PacketSocketFactory* factory,
@@ -198,7 +207,8 @@
            int server_priority,
            const std::string& origin,
            const std::vector<std::string>& tls_alpn_protocols,
-           const std::vector<std::string>& tls_elliptic_curves);
+           const std::vector<std::string>& tls_elliptic_curves,
+           webrtc::TurnCustomizer* customizer);
 
  private:
   enum {
@@ -275,6 +285,10 @@
   // Reconstruct the URL of the server which the candidate is gathered from.
   std::string ReconstructedServerUrl();
 
+  void TurnCustomizerMaybeModifyOutgoingStunMessage(StunMessage* message);
+  bool TurnCustomizerAllowChannelData(const void* data,
+                                      size_t size, bool payload);
+
   ProtocolAddress server_address_;
   TlsCertPolicy tls_cert_policy_ = TlsCertPolicy::TLS_CERT_POLICY_SECURE;
   std::vector<std::string> tls_alpn_protocols_;
@@ -305,6 +319,9 @@
 
   rtc::AsyncInvoker invoker_;
 
+  // Optional TurnCustomizer that can modify outgoing messages.
+  webrtc::TurnCustomizer *turn_customizer_ = nullptr;
+
   friend class TurnEntry;
   friend class TurnAllocateRequest;
   friend class TurnRefreshRequest;
diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc
index 0bc4b31..4abc025 100644
--- a/p2p/base/turnport_unittest.cc
+++ b/p2p/base/turnport_unittest.cc
@@ -18,6 +18,7 @@
 #include "p2p/base/p2pconstants.h"
 #include "p2p/base/portallocator.h"
 #include "p2p/base/tcpport.h"
+#include "p2p/base/testturncustomizer.h"
 #include "p2p/base/testturnserver.h"
 #include "p2p/base/turnport.h"
 #include "p2p/base/udpport.h"
@@ -30,6 +31,7 @@
 #include "rtc_base/gunit.h"
 #include "rtc_base/helpers.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/ptr_util.h"
 #include "rtc_base/socketadapters.h"
 #include "rtc_base/socketaddress.h"
 #include "rtc_base/ssladapter.h"
@@ -264,7 +266,7 @@
     turn_port_.reset(TurnPort::Create(
         &main_, &socket_factory_, network, 0, 0, kIceUfrag1, kIcePwd1,
         server_address, credentials, 0, origin, std::vector<std::string>(),
-        std::vector<std::string>()));
+        std::vector<std::string>(), turn_customizer_.get()));
     // This TURN port will be the controlling.
     turn_port_->SetIceRole(ICEROLE_CONTROLLING);
     ConnectSignals();
@@ -294,7 +296,8 @@
     RelayCredentials credentials(username, password);
     turn_port_.reset(TurnPort::Create(
         &main_, &socket_factory_, MakeNetwork(kLocalAddr1), socket_.get(),
-        kIceUfrag1, kIcePwd1, server_address, credentials, 0, std::string()));
+        kIceUfrag1, kIcePwd1, server_address, credentials, 0, std::string(),
+        nullptr));
     // This TURN port will be the controlling.
     turn_port_->SetIceRole(ICEROLE_CONTROLLING);
     ConnectSignals();
@@ -695,6 +698,7 @@
   std::vector<rtc::Buffer> turn_packets_;
   std::vector<rtc::Buffer> udp_packets_;
   rtc::PacketOptions options;
+  std::unique_ptr<webrtc::TurnCustomizer> turn_customizer_;
 };
 
 TEST_F(TurnPortTest, TestTurnPortType) {
@@ -1458,4 +1462,146 @@
 }
 #endif
 
+class MessageObserver : public StunMessageObserver{
+ public:
+  MessageObserver(unsigned int *message_counter,
+                  unsigned int* channel_data_counter,
+                  unsigned int *attr_counter)
+      : message_counter_(message_counter),
+        channel_data_counter_(channel_data_counter),
+        attr_counter_(attr_counter) {}
+  virtual ~MessageObserver() {}
+  virtual void ReceivedMessage(const TurnMessage* msg) override {
+    if (message_counter_ != nullptr) {
+      (*message_counter_)++;
+    }
+    // Implementation defined attributes are returned as ByteString
+    const StunByteStringAttribute* attr = msg->GetByteString(
+        TestTurnCustomizer::STUN_ATTR_COUNTER);
+    if (attr != nullptr && attr_counter_ != nullptr) {
+      rtc::ByteBufferReader buf(attr->bytes(), attr->length());
+      unsigned int val = ~0u;
+      buf.ReadUInt32(&val);
+      (*attr_counter_)++;
+    }
+  }
+
+  virtual void ReceivedChannelData(const char* data, size_t size) override {
+    if (channel_data_counter_ != nullptr) {
+      (*channel_data_counter_)++;
+    }
+  }
+
+  // Number of TurnMessages observed.
+  unsigned int* message_counter_ = nullptr;
+
+  // Number of channel data observed.
+  unsigned int* channel_data_counter_ = nullptr;
+
+  // Number of TurnMessages that had STUN_ATTR_COUNTER.
+  unsigned int* attr_counter_ = nullptr;
+};
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+// Add customizer and check that it get called.
+TEST_F(TurnPortTest, TestTurnCustomizerCount) {
+  unsigned int observer_message_counter = 0;
+  unsigned int observer_channel_data_counter = 0;
+  unsigned int observer_attr_counter = 0;
+  TestTurnCustomizer* customizer = new TestTurnCustomizer();
+  std::unique_ptr<MessageObserver> validator(new MessageObserver(
+      &observer_message_counter,
+      &observer_channel_data_counter,
+      &observer_attr_counter));
+
+  turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+  turn_customizer_.reset(customizer);
+  turn_server_.server()->SetStunMessageObserver(std::move(validator));
+
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+  TestTurnSendData(PROTO_TLS);
+  EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+
+  // There should have been at least turn_packets_.size() calls to |customizer|.
+  EXPECT_GE(customizer->modify_cnt_ + customizer->allow_channel_data_cnt_,
+            turn_packets_.size());
+
+  // Some channel data should be received.
+  EXPECT_GE(observer_channel_data_counter, 0u);
+
+  // Need to release TURN port before the customizer.
+  turn_port_.reset(nullptr);
+}
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+// Add customizer and check that it can can prevent usage of channel data.
+TEST_F(TurnPortTest, TestTurnCustomizerDisallowChannelData) {
+  unsigned int observer_message_counter = 0;
+  unsigned int observer_channel_data_counter = 0;
+  unsigned int observer_attr_counter = 0;
+  TestTurnCustomizer* customizer = new TestTurnCustomizer();
+  std::unique_ptr<MessageObserver> validator(new MessageObserver(
+      &observer_message_counter,
+      &observer_channel_data_counter,
+      &observer_attr_counter));
+  customizer->allow_channel_data_ = false;
+  turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+  turn_customizer_.reset(customizer);
+  turn_server_.server()->SetStunMessageObserver(std::move(validator));
+
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+  TestTurnSendData(PROTO_TLS);
+  EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+
+  // There should have been at least turn_packets_.size() calls to |customizer|.
+  EXPECT_GE(customizer->modify_cnt_, turn_packets_.size());
+
+  // No channel data should be received.
+  EXPECT_EQ(observer_channel_data_counter, 0u);
+
+  // Need to release TURN port before the customizer.
+  turn_port_.reset(nullptr);
+}
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+// Add customizer and check that it can add attribute to messages.
+TEST_F(TurnPortTest, TestTurnCustomizerAddAttribute) {
+  unsigned int observer_message_counter = 0;
+  unsigned int observer_channel_data_counter = 0;
+  unsigned int observer_attr_counter = 0;
+  TestTurnCustomizer* customizer = new TestTurnCustomizer();
+  std::unique_ptr<MessageObserver> validator(new MessageObserver(
+      &observer_message_counter,
+      &observer_channel_data_counter,
+      &observer_attr_counter));
+  customizer->allow_channel_data_ = false;
+  customizer->add_counter_ = true;
+  turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+  turn_customizer_.reset(customizer);
+  turn_server_.server()->SetStunMessageObserver(std::move(validator));
+
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+  TestTurnSendData(PROTO_TLS);
+  EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+
+  // There should have been at least turn_packets_.size() calls to |customizer|.
+  EXPECT_GE(customizer->modify_cnt_, turn_packets_.size());
+
+  // Everything will be sent as messages since channel data is disallowed.
+  EXPECT_GE(customizer->modify_cnt_, observer_message_counter);
+
+  // All messages should have attribute.
+  EXPECT_EQ(observer_message_counter, observer_attr_counter);
+
+  // At least allow_channel_data_cnt_ messages should have been sent.
+  EXPECT_GE(customizer->modify_cnt_, customizer->allow_channel_data_cnt_);
+  EXPECT_GE(customizer->allow_channel_data_cnt_, 0u);
+
+  // No channel data should be received.
+  EXPECT_EQ(observer_channel_data_counter, 0u);
+
+  // Need to release TURN port before the customizer.
+  turn_port_.reset(nullptr);
+}
+
 }  // namespace cricket
diff --git a/p2p/base/turnserver.cc b/p2p/base/turnserver.cc
index 7e4c436..0f7d815 100644
--- a/p2p/base/turnserver.cc
+++ b/p2p/base/turnserver.cc
@@ -211,6 +211,9 @@
     if (allocation) {
       allocation->HandleChannelData(data, size);
     }
+    if (stun_message_observer_ != nullptr) {
+      stun_message_observer_->ReceivedChannelData(data, size);
+    }
   }
 }
 
@@ -223,6 +226,10 @@
     return;
   }
 
+  if (stun_message_observer_ != nullptr) {
+    stun_message_observer_->ReceivedMessage(&msg);
+  }
+
   // If it's a STUN binding request, handle that specially.
   if (msg.type() == STUN_BINDING_REQUEST) {
     HandleBindingRequest(conn, &msg);
diff --git a/p2p/base/turnserver.h b/p2p/base/turnserver.h
index 1e06a93..f694912 100644
--- a/p2p/base/turnserver.h
+++ b/p2p/base/turnserver.h
@@ -157,6 +157,13 @@
   virtual ~TurnRedirectInterface() {}
 };
 
+class StunMessageObserver {
+ public:
+  virtual void ReceivedMessage(const TurnMessage* msg) = 0;
+  virtual void ReceivedChannelData(const char* data, size_t size) = 0;
+  virtual ~StunMessageObserver() {}
+};
+
 // The core TURN server class. Give it a socket to listen on via
 // AddInternalServerSocket, and a factory to create external sockets via
 // SetExternalSocketFactory, and it's ready to go.
@@ -214,6 +221,11 @@
     return GenerateNonce(timestamp);
   }
 
+  void SetStunMessageObserver(
+      std::unique_ptr<StunMessageObserver> observer) {
+    stun_message_observer_ = std::move(observer);
+  }
+
  private:
   std::string GenerateNonce(int64_t now) const;
   void OnInternalPacket(rtc::AsyncPacketSocket* socket, const char* data,
@@ -296,6 +308,9 @@
   // from this value, and it will be reset to 0 after generating the NONCE.
   int64_t ts_for_next_nonce_ = 0;
 
+  // For testing only. Used to observe STUN messages received.
+  std::unique_ptr<StunMessageObserver> stun_message_observer_;
+
   friend class TurnServerAllocation;
 };
 
diff --git a/p2p/client/basicportallocator.cc b/p2p/client/basicportallocator.cc
index f4c1fbd..4717484 100644
--- a/p2p/client/basicportallocator.cc
+++ b/p2p/client/basicportallocator.cc
@@ -96,11 +96,15 @@
     PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_RELAY;
 
 // BasicPortAllocator
-BasicPortAllocator::BasicPortAllocator(rtc::NetworkManager* network_manager,
-                                       rtc::PacketSocketFactory* socket_factory)
+BasicPortAllocator::BasicPortAllocator(
+    rtc::NetworkManager* network_manager,
+    rtc::PacketSocketFactory* socket_factory,
+    webrtc::TurnCustomizer* customizer)
     : network_manager_(network_manager), socket_factory_(socket_factory) {
   RTC_DCHECK(network_manager_ != nullptr);
   RTC_DCHECK(socket_factory_ != nullptr);
+  SetConfiguration(ServerAddresses(), std::vector<RelayServerConfig>(),
+                   0, false, customizer);
   Construct();
 }
 
@@ -115,7 +119,8 @@
                                        const ServerAddresses& stun_servers)
     : network_manager_(network_manager), socket_factory_(socket_factory) {
   RTC_DCHECK(socket_factory_ != NULL);
-  SetConfiguration(stun_servers, std::vector<RelayServerConfig>(), 0, false);
+  SetConfiguration(stun_servers, std::vector<RelayServerConfig>(), 0, false,
+                   nullptr);
   Construct();
 }
 
@@ -142,7 +147,7 @@
     turn_servers.push_back(config);
   }
 
-  SetConfiguration(stun_servers, turn_servers, 0, false);
+  SetConfiguration(stun_servers, turn_servers, 0, false, nullptr);
   Construct();
 }
 
@@ -188,7 +193,7 @@
   std::vector<RelayServerConfig> new_turn_servers = turn_servers();
   new_turn_servers.push_back(turn_server);
   SetConfiguration(stun_servers(), new_turn_servers, candidate_pool_size(),
-                   prune_turn_ports());
+                   prune_turn_ports(), turn_customizer());
 }
 
 // BasicPortAllocatorSession
@@ -1374,7 +1379,6 @@
       continue;
     }
 
-
     // Shared socket mode must be enabled only for UDP based ports. Hence
     // don't pass shared socket for ports which will create TCP sockets.
     // TODO(mallinath) - Enable shared socket mode for TURN ports. Disabled
@@ -1386,7 +1390,8 @@
                               network_, udp_socket_.get(),
                               session_->username(), session_->password(),
                               *relay_port, config.credentials, config.priority,
-                              session_->allocator()->origin());
+                              session_->allocator()->origin(),
+                              session_->allocator()->turn_customizer());
       turn_ports_.push_back(port);
       // Listen to the port destroyed signal, to allow AllocationSequence to
       // remove entrt from it's map.
@@ -1397,7 +1402,8 @@
           session_->allocator()->min_port(), session_->allocator()->max_port(),
           session_->username(), session_->password(), *relay_port,
           config.credentials, config.priority, session_->allocator()->origin(),
-          config.tls_alpn_protocols, config.tls_elliptic_curves);
+          config.tls_alpn_protocols, config.tls_elliptic_curves,
+          session_->allocator()->turn_customizer());
     }
     RTC_DCHECK(port != NULL);
     port->SetTlsCertPolicy(config.tls_cert_policy);
diff --git a/p2p/client/basicportallocator.h b/p2p/client/basicportallocator.h
index d0c1de1..5ec721b 100644
--- a/p2p/client/basicportallocator.h
+++ b/p2p/client/basicportallocator.h
@@ -15,6 +15,7 @@
 #include <string>
 #include <vector>
 
+#include "api/turncustomizer.h"
 #include "p2p/base/portallocator.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/messagequeue.h"
@@ -26,7 +27,8 @@
 class BasicPortAllocator : public PortAllocator {
  public:
   BasicPortAllocator(rtc::NetworkManager* network_manager,
-                     rtc::PacketSocketFactory* socket_factory);
+                     rtc::PacketSocketFactory* socket_factory,
+                     webrtc::TurnCustomizer* customizer = nullptr);
   explicit BasicPortAllocator(rtc::NetworkManager* network_manager);
   BasicPortAllocator(rtc::NetworkManager* network_manager,
                      rtc::PacketSocketFactory* socket_factory,
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index e8e428d..a201cc7 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -277,6 +277,7 @@
     bool redetermine_role_on_ice_restart;
     rtc::Optional<int> ice_check_min_interval;
     rtc::Optional<rtc::IntervalRange> ice_regather_interval_range;
+    webrtc::TurnCustomizer* turn_customizer;
   };
   static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this),
                 "Did you add something to RTCConfiguration and forget to "
@@ -312,7 +313,8 @@
          enable_ice_renomination == o.enable_ice_renomination &&
          redetermine_role_on_ice_restart == o.redetermine_role_on_ice_restart &&
          ice_check_min_interval == o.ice_check_min_interval &&
-         ice_regather_interval_range == o.ice_regather_interval_range;
+         ice_regather_interval_range == o.ice_regather_interval_range &&
+         turn_customizer == o.turn_customizer;
 }
 
 bool PeerConnectionInterface::RTCConfiguration::operator!=(
@@ -454,6 +456,7 @@
                   << "This shouldn't happen if using PeerConnectionFactory.";
     return false;
   }
+
   if (!observer) {
     // TODO(deadbeef): Why do we do this?
     LOG(LS_ERROR) << "PeerConnection initialized without a "
@@ -1147,6 +1150,7 @@
       configuration.ice_candidate_pool_size;
   modified_config.prune_turn_ports = configuration.prune_turn_ports;
   modified_config.ice_check_min_interval = configuration.ice_check_min_interval;
+  modified_config.turn_customizer = configuration.turn_customizer;
   if (configuration != modified_config) {
     LOG(LS_ERROR) << "Modifying the configuration in an unsupported way.";
     return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error);
@@ -1180,7 +1184,8 @@
           rtc::Bind(&PeerConnection::ReconfigurePortAllocator_n, this,
                     stun_servers, turn_servers, modified_config.type,
                     modified_config.ice_candidate_pool_size,
-                    modified_config.prune_turn_ports))) {
+                    modified_config.prune_turn_ports,
+                    modified_config.turn_customizer))) {
     LOG(LS_ERROR) << "Failed to apply configuration to PortAllocator.";
     return SafeSetError(RTCErrorType::INTERNAL_ERROR, error);
   }
@@ -2520,7 +2525,8 @@
   // properties set above.
   port_allocator_->SetConfiguration(stun_servers, turn_servers,
                                     configuration.ice_candidate_pool_size,
-                                    configuration.prune_turn_ports);
+                                    configuration.prune_turn_ports,
+                                    configuration.turn_customizer);
   return true;
 }
 
@@ -2529,13 +2535,15 @@
     const std::vector<cricket::RelayServerConfig>& turn_servers,
     IceTransportsType type,
     int candidate_pool_size,
-    bool prune_turn_ports) {
+    bool prune_turn_ports,
+    webrtc::TurnCustomizer* turn_customizer) {
   port_allocator_->set_candidate_filter(
       ConvertIceTransportTypeToCandidateFilter(type));
   // Call this last since it may create pooled allocator sessions using the
   // candidate filter set above.
   return port_allocator_->SetConfiguration(
-      stun_servers, turn_servers, candidate_pool_size, prune_turn_ports);
+      stun_servers, turn_servers, candidate_pool_size, prune_turn_ports,
+      turn_customizer);
 }
 
 bool PeerConnection::StartRtcEventLog_w(rtc::PlatformFile file,
diff --git a/pc/peerconnection.h b/pc/peerconnection.h
index 9d49f93..8326ee7 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -17,6 +17,7 @@
 #include <vector>
 
 #include "api/peerconnectioninterface.h"
+#include "api/turncustomizer.h"
 #include "pc/iceserverparsing.h"
 #include "pc/peerconnectionfactory.h"
 #include "pc/rtcstatscollector.h"
@@ -437,7 +438,8 @@
       const std::vector<cricket::RelayServerConfig>& turn_servers,
       IceTransportsType type,
       int candidate_pool_size,
-      bool prune_turn_ports);
+      bool prune_turn_ports,
+      webrtc::TurnCustomizer* turn_customizer);
 
   // Starts recording an RTC event log using the supplied platform file.
   // This function should only be called from the worker thread.
diff --git a/pc/peerconnection_integrationtest.cc b/pc/peerconnection_integrationtest.cc
index e012301..580bfd4 100644
--- a/pc/peerconnection_integrationtest.cc
+++ b/pc/peerconnection_integrationtest.cc
@@ -31,6 +31,7 @@
 #include "p2p/base/p2pconstants.h"
 #include "p2p/base/portinterface.h"
 #include "p2p/base/sessiondescription.h"
+#include "p2p/base/testturncustomizer.h"
 #include "p2p/base/testturnserver.h"
 #include "p2p/client/basicportallocator.h"
 #include "pc/dtmfsender.h"
@@ -2992,6 +2993,7 @@
   cricket::TestTurnServer turn_server_2(network_thread(),
                                         turn_server_2_internal_address,
                                         turn_server_2_external_address);
+
   // Bypass permission check on received packets so media can be sent before
   // the candidate is signaled.
   turn_server_1.set_enable_permission_checks(false);
@@ -3038,6 +3040,71 @@
   delete SetCalleePcWrapperAndReturnCurrent(nullptr);
 }
 
+// Verify that a TurnCustomizer passed in through RTCConfiguration
+// is actually used by the underlying TURN candidate pair.
+// Note that turnport_unittest.cc contains more detailed, lower-level tests.
+TEST_F(PeerConnectionIntegrationTest,           \
+       TurnCustomizerUsedForTurnConnections) {
+  static const rtc::SocketAddress turn_server_1_internal_address{"88.88.88.0",
+                                                                 3478};
+  static const rtc::SocketAddress turn_server_1_external_address{"88.88.88.1",
+                                                                 0};
+  static const rtc::SocketAddress turn_server_2_internal_address{"99.99.99.0",
+                                                                 3478};
+  static const rtc::SocketAddress turn_server_2_external_address{"99.99.99.1",
+                                                                 0};
+  cricket::TestTurnServer turn_server_1(network_thread(),
+                                        turn_server_1_internal_address,
+                                        turn_server_1_external_address);
+  cricket::TestTurnServer turn_server_2(network_thread(),
+                                        turn_server_2_internal_address,
+                                        turn_server_2_external_address);
+
+  PeerConnectionInterface::RTCConfiguration client_1_config;
+  webrtc::PeerConnectionInterface::IceServer ice_server_1;
+  ice_server_1.urls.push_back("turn:88.88.88.0:3478");
+  ice_server_1.username = "test";
+  ice_server_1.password = "test";
+  client_1_config.servers.push_back(ice_server_1);
+  client_1_config.type = webrtc::PeerConnectionInterface::kRelay;
+  auto customizer1 = rtc::MakeUnique<cricket::TestTurnCustomizer>();
+  client_1_config.turn_customizer = customizer1.get();
+
+  PeerConnectionInterface::RTCConfiguration client_2_config;
+  webrtc::PeerConnectionInterface::IceServer ice_server_2;
+  ice_server_2.urls.push_back("turn:99.99.99.0:3478");
+  ice_server_2.username = "test";
+  ice_server_2.password = "test";
+  client_2_config.servers.push_back(ice_server_2);
+  client_2_config.type = webrtc::PeerConnectionInterface::kRelay;
+  auto customizer2 = rtc::MakeUnique<cricket::TestTurnCustomizer>();
+  client_2_config.turn_customizer = customizer2.get();
+
+  ASSERT_TRUE(
+      CreatePeerConnectionWrappersWithConfig(client_1_config, client_2_config));
+  ConnectFakeSignaling();
+
+  // Set "offer to receive audio/video" without adding any tracks, so we just
+  // set up ICE/DTLS with no media.
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_audio = 1;
+  options.offer_to_receive_video = 1;
+  caller()->SetOfferAnswerOptions(options);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(DtlsConnected(), kDefaultTimeout);
+
+  EXPECT_GT(customizer1->allow_channel_data_cnt_, 0u);
+  EXPECT_GT(customizer1->modify_cnt_, 0u);
+
+  EXPECT_GT(customizer2->allow_channel_data_cnt_, 0u);
+  EXPECT_GT(customizer2->modify_cnt_, 0u);
+
+  // Need to free the clients here since they're using things we created on
+  // the stack.
+  delete SetCallerPcWrapperAndReturnCurrent(nullptr);
+  delete SetCalleePcWrapperAndReturnCurrent(nullptr);
+}
+
 // Test that audio and video flow end-to-end when codec names don't use the
 // expected casing, given that they're supposed to be case insensitive. To test
 // this, all but one codec is removed from each media description, and its
diff --git a/pc/peerconnectionfactory.cc b/pc/peerconnectionfactory.cc
index d3c624d..20edaa6 100644
--- a/pc/peerconnectionfactory.cc
+++ b/pc/peerconnectionfactory.cc
@@ -17,6 +17,7 @@
 #include "api/mediastreamtrackproxy.h"
 #include "api/peerconnectionfactoryproxy.h"
 #include "api/peerconnectionproxy.h"
+#include "api/turncustomizer.h"
 #include "api/videosourceproxy.h"
 #include "logging/rtc_event_log/rtc_event_log.h"
 #include "rtc_base/bind.h"
@@ -236,7 +237,8 @@
 
   if (!allocator) {
     allocator.reset(new cricket::BasicPortAllocator(
-        default_network_manager_.get(), default_socket_factory_.get()));
+        default_network_manager_.get(), default_socket_factory_.get(),
+        configuration.turn_customizer));
   }
   network_thread_->Invoke<void>(
       RTC_FROM_HERE, rtc::Bind(&cricket::PortAllocator::SetNetworkIgnoreMask,
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn
index 322104b..5b7ffb7 100644
--- a/sdk/android/BUILD.gn
+++ b/sdk/android/BUILD.gn
@@ -249,6 +249,7 @@
     "src/jni/pc/sdpobserver_jni.h",
     "src/jni/pc/statsobserver_jni.cc",
     "src/jni/pc/statsobserver_jni.h",
+    "src/jni/pc/turncustomizer_jni.cc",
   ]
 
   configs += [ ":libjingle_peerconnection_jni_warnings_config" ]
@@ -420,6 +421,7 @@
     "api/org/webrtc/StatsReport.java",
     "api/org/webrtc/SurfaceTextureHelper.java",
     "api/org/webrtc/SurfaceViewRenderer.java",
+    "api/org/webrtc/TurnCustomizer.java",
     "api/org/webrtc/VideoCapturer.java",
     "api/org/webrtc/VideoCodecInfo.java",
     "api/org/webrtc/VideoCodecStatus.java",
diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java
index 9ddfdfd..881255d 100644
--- a/sdk/android/api/org/webrtc/PeerConnection.java
+++ b/sdk/android/api/org/webrtc/PeerConnection.java
@@ -304,6 +304,9 @@
     public int maxIPv6Networks;
     public IntervalRange iceRegatherIntervalRange;
 
+    // This is an optional wrapper for the C++ webrtc::TurnCustomizer.
+    public TurnCustomizer turnCustomizer;
+
     // TODO(deadbeef): Instead of duplicating the defaults here, we should do
     // something to pick up the defaults from C++. The Objective-C equivalent
     // of RTCConfiguration does that.
diff --git a/sdk/android/api/org/webrtc/TurnCustomizer.java b/sdk/android/api/org/webrtc/TurnCustomizer.java
new file mode 100644
index 0000000..7c68bd1
--- /dev/null
+++ b/sdk/android/api/org/webrtc/TurnCustomizer.java
@@ -0,0 +1,27 @@
+/*
+ *  Copyright 2017 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.
+ */
+
+package org.webrtc;
+
+/** Java wrapper for a C++ TurnCustomizer. */
+public class TurnCustomizer {
+  final long nativeTurnCustomizer;
+
+  public TurnCustomizer(long nativeTurnCustomizer) {
+    this.nativeTurnCustomizer = nativeTurnCustomizer;
+  }
+
+  public void dispose() {
+    nativeFreeTurnCustomizer(nativeTurnCustomizer);
+  }
+
+  private static native void nativeFreeTurnCustomizer(
+      long nativeTurnCustomizer);
+}
diff --git a/sdk/android/src/jni/pc/java_native_conversion.cc b/sdk/android/src/jni/pc/java_native_conversion.cc
index e500f34..3caa438 100644
--- a/sdk/android/src/jni/pc/java_native_conversion.cc
+++ b/sdk/android/src/jni/pc/java_native_conversion.cc
@@ -478,6 +478,15 @@
   jmethodID get_max_id =
       GetMethodID(jni, j_interval_range_class, "getMax", "()I");
 
+  jfieldID j_turn_customizer_type_id = GetFieldID(
+      jni, j_rtc_config_class, "turnCustomizer", "Lorg/webrtc/TurnCustomizer;");
+  jobject j_turn_customizer =
+      GetNullableObjectField(jni, j_rtc_config, j_turn_customizer_type_id);
+
+  jclass j_turn_customizer_class = jni->FindClass("org/webrtc/TurnCustomizer");
+  jfieldID j_native_turn_customizer_id =
+      GetFieldID(jni, j_turn_customizer_class, "nativeTurnCustomizer", "J");
+
   rtc_config->type = JavaToNativeIceTransportsType(jni, j_ice_transports_type);
   rtc_config->bundle_policy = JavaToNativeBundlePolicy(jni, j_bundle_policy);
   rtc_config->rtcp_mux_policy =
@@ -522,6 +531,11 @@
     int max = jni->CallIntMethod(j_ice_regather_interval_range, get_max_id);
     rtc_config->ice_regather_interval_range.emplace(min, max);
   }
+
+  if (!IsNull(jni, j_turn_customizer)) {
+    rtc_config->turn_customizer = reinterpret_cast<webrtc::TurnCustomizer*>(
+        GetLongField(jni, j_turn_customizer, j_native_turn_customizer_id));
+  }
 }
 
 void JavaToNativeRtpParameters(JNIEnv* jni,
diff --git a/sdk/android/src/jni/pc/turncustomizer_jni.cc b/sdk/android/src/jni/pc/turncustomizer_jni.cc
new file mode 100644
index 0000000..f1c33db
--- /dev/null
+++ b/sdk/android/src/jni/pc/turncustomizer_jni.cc
@@ -0,0 +1,26 @@
+/*
+ *  Copyright 2017 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 "api/turncustomizer.h"
+#include "sdk/android/src/jni/jni_helpers.h"
+
+namespace webrtc {
+namespace jni {
+
+JNI_FUNCTION_DECLARATION(void,
+                         TurnCustomizer_nativeFreeTurnCustomizer,
+                         JNIEnv* jni,
+                         jclass,
+                         jlong j_turn_customizer_pointer) {
+  delete reinterpret_cast<TurnCustomizer*>(j_turn_customizer_pointer);
+}
+
+}  // namespace jni
+}  // namespace webrtc