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