Add TURN_LOGGING_ID

This patch adds a new (optional) attribute to TURN_ALLOCATE_REQUEST,
TURN_LOGGING_ID (0xFF05).

The attribute is put into the comprehension-optional range
so that a TURN server should ignore it if it doesn't know if.
https://tools.ietf.org/html/rfc5389#section-18.2

The intended usage of this attribute is to correlate client and
backend logs.

Bug: webrtc:10897
Change-Id: I51fdbe15f9025e817cd91ee8e2c3355133212daa
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/149829
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28966}
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index 4ade0b3..2d5e314 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -650,6 +650,12 @@
     // passed.
     bool offer_extmap_allow_mixed = false;
 
+    // TURN logging identifier.
+    // This identifier is added to a TURN allocation
+    // and it intended to be used to be able to match client side
+    // logs with TURN server logs. It will not be added if it's an empty string.
+    std::string turn_logging_id;
+
     //
     // Don't forget to update operator== if adding something.
     //
diff --git a/p2p/base/port_allocator.h b/p2p/base/port_allocator.h
index c0b0e60..f29877c 100644
--- a/p2p/base/port_allocator.h
+++ b/p2p/base/port_allocator.h
@@ -182,6 +182,7 @@
   std::vector<std::string> tls_alpn_protocols;
   std::vector<std::string> tls_elliptic_curves;
   rtc::SSLCertificateVerifier* tls_cert_verifier = nullptr;
+  std::string turn_logging_id;
 };
 
 class RTC_EXPORT PortAllocatorSession : public sigslot::has_slots<> {
diff --git a/p2p/base/turn_port.cc b/p2p/base/turn_port.cc
index f910497..7845b6a 100644
--- a/p2p/base/turn_port.cc
+++ b/p2p/base/turn_port.cc
@@ -33,7 +33,11 @@
 // TODO(juberti): Move to stun.h when relay messages have been renamed.
 static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST;
 
+// Attributes in comprehension-optional range,
+// ignored by TURN server that doesn't know about them.
+// https://tools.ietf.org/html/rfc5389#section-18.2
 static const int STUN_ATTR_MULTI_MAPPING = 0xff04;
+const int STUN_ATTR_TURN_LOGGING_ID = 0xff05;
 
 // TODO(juberti): Extract to turnmessage.h
 static const int TURN_DEFAULT_PORT = 3478;
@@ -314,6 +318,10 @@
   tls_cert_policy_ = tls_cert_policy;
 }
 
+void TurnPort::SetTurnLoggingId(const std::string& turn_logging_id) {
+  turn_logging_id_ = turn_logging_id;
+}
+
 std::vector<std::string> TurnPort::GetTlsAlpnProtocols() const {
   return tls_alpn_protocols_;
 }
@@ -1313,6 +1321,13 @@
   return turn_customizer_->AllowChannelData(this, data, size, payload);
 }
 
+void TurnPort::MaybeAddTurnLoggingId(StunMessage* msg) {
+  if (!turn_logging_id_.empty()) {
+    msg->AddAttribute(absl::make_unique<StunByteStringAttribute>(
+        STUN_ATTR_TURN_LOGGING_ID, turn_logging_id_));
+  }
+}
+
 TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
     : StunRequest(new TurnMessage()), port_(port) {}
 
@@ -1326,6 +1341,7 @@
   if (!port_->hash().empty()) {
     port_->AddRequestAuthInfo(request);
   }
+  port_->MaybeAddTurnLoggingId(request);
   port_->TurnCustomizerMaybeModifyOutgoingStunMessage(request);
 }
 
diff --git a/p2p/base/turn_port.h b/p2p/base/turn_port.h
index e6dab6e..8247dbc 100644
--- a/p2p/base/turn_port.h
+++ b/p2p/base/turn_port.h
@@ -33,6 +33,7 @@
 
 namespace cricket {
 
+extern const int STUN_ATTR_TURN_LOGGING_ID;
 extern const char TURN_PORT_TYPE[];
 class TurnAllocateRequest;
 class TurnEntry;
@@ -148,6 +149,8 @@
   virtual TlsCertPolicy GetTlsCertPolicy() const;
   virtual void SetTlsCertPolicy(TlsCertPolicy tls_cert_policy);
 
+  void SetTurnLoggingId(const std::string& turn_logging_id);
+
   virtual std::vector<std::string> GetTlsAlpnProtocols() const;
   virtual std::vector<std::string> GetTlsEllipticCurves() const;
 
@@ -347,6 +350,8 @@
   // Reconstruct the URL of the server which the candidate is gathered from.
   std::string ReconstructedServerUrl(bool use_hostname);
 
+  void MaybeAddTurnLoggingId(StunMessage* message);
+
   void TurnCustomizerMaybeModifyOutgoingStunMessage(StunMessage* message);
   bool TurnCustomizerAllowChannelData(const void* data,
                                       size_t size,
@@ -388,6 +393,14 @@
   // must outlive the TurnPort's lifetime.
   webrtc::TurnCustomizer* turn_customizer_ = nullptr;
 
+  // Optional TurnLoggingId.
+  // An identifier set by application that is added to TURN_ALLOCATE_REQUEST
+  // and can be used to match client/backend logs.
+  // TODO(jonaso): This should really be initialized in constructor,
+  // but that is currently so terrible. Fix once constructor is changed
+  // to be more easy to work with.
+  std::string turn_logging_id_;
+
   friend class TurnEntry;
   friend class TurnAllocateRequest;
   friend class TurnRefreshRequest;
diff --git a/p2p/base/turn_port_unittest.cc b/p2p/base/turn_port_unittest.cc
index b51a126..73dadb6 100644
--- a/p2p/base/turn_port_unittest.cc
+++ b/p2p/base/turn_port_unittest.cc
@@ -16,6 +16,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/memory/memory.h"
 #include "absl/types/optional.h"
 #include "api/units/time_delta.h"
 #include "p2p/base/basic_packet_socket_factory.h"
@@ -810,6 +811,54 @@
   EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
 }
 
+class TurnLoggingIdValidator : public StunMessageObserver {
+ public:
+  explicit TurnLoggingIdValidator(const char* expect_val)
+      : expect_val_(expect_val) {}
+  ~TurnLoggingIdValidator() {}
+  void ReceivedMessage(const TurnMessage* msg) override {
+    if (msg->type() == cricket::STUN_ALLOCATE_REQUEST) {
+      const StunByteStringAttribute* attr =
+          msg->GetByteString(cricket::STUN_ATTR_TURN_LOGGING_ID);
+      if (expect_val_) {
+        ASSERT_NE(nullptr, attr);
+        ASSERT_EQ(expect_val_, attr->GetString());
+      } else {
+        EXPECT_EQ(nullptr, attr);
+      }
+    }
+  }
+  void ReceivedChannelData(const char* data, size_t size) override {}
+
+ private:
+  const char* expect_val_;
+};
+
+TEST_F(TurnPortTest, TestTurnAllocateWithLoggingId) {
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  turn_port_->SetTurnLoggingId("KESO");
+  turn_server_.server()->SetStunMessageObserver(
+      absl::make_unique<TurnLoggingIdValidator>("KESO"));
+  turn_port_->PrepareAddress();
+  EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 2, fake_clock_);
+  ASSERT_EQ(1U, turn_port_->Candidates().size());
+  EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+            turn_port_->Candidates()[0].address().ipaddr());
+  EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+TEST_F(TurnPortTest, TestTurnAllocateWithoutLoggingId) {
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  turn_server_.server()->SetStunMessageObserver(
+      absl::make_unique<TurnLoggingIdValidator>(nullptr));
+  turn_port_->PrepareAddress();
+  EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 2, fake_clock_);
+  ASSERT_EQ(1U, turn_port_->Candidates().size());
+  EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+            turn_port_->Candidates()[0].address().ipaddr());
+  EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
 // Test bad credentials.
 TEST_F(TurnPortTest, TestTurnBadCredentials) {
   CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr);
diff --git a/p2p/client/turn_port_factory.cc b/p2p/client/turn_port_factory.cc
index 934c019..de4b9e6 100644
--- a/p2p/client/turn_port_factory.cc
+++ b/p2p/client/turn_port_factory.cc
@@ -29,6 +29,7 @@
       args.config->credentials, args.config->priority, args.origin,
       args.turn_customizer);
   port->SetTlsCertPolicy(args.config->tls_cert_policy);
+  port->SetTurnLoggingId(args.config->turn_logging_id);
   return std::move(port);
 }
 
@@ -42,6 +43,7 @@
       args.config->tls_alpn_protocols, args.config->tls_elliptic_curves,
       args.turn_customizer, args.config->tls_cert_verifier);
   port->SetTlsCertPolicy(args.config->tls_cert_policy);
+  port->SetTurnLoggingId(args.config->turn_logging_id);
   return std::move(port);
 }
 
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 0f7970c..9271559 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -809,6 +809,7 @@
     absl::optional<bool> use_datagram_transport_for_data_channels;
     absl::optional<CryptoOptions> crypto_options;
     bool offer_extmap_allow_mixed;
+    std::string turn_logging_id;
   };
   static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this),
                 "Did you add something to RTCConfiguration and forget to "
@@ -871,7 +872,8 @@
          use_datagram_transport_for_data_channels ==
              o.use_datagram_transport_for_data_channels &&
          crypto_options == o.crypto_options &&
-         offer_extmap_allow_mixed == o.offer_extmap_allow_mixed;
+         offer_extmap_allow_mixed == o.offer_extmap_allow_mixed &&
+         turn_logging_id == o.turn_logging_id;
 }
 
 bool PeerConnectionInterface::RTCConfiguration::operator!=(
@@ -1023,6 +1025,11 @@
     return false;
   }
 
+  // Add the turn logging id to all turn servers
+  for (cricket::RelayServerConfig& turn_server : turn_servers) {
+    turn_server.turn_logging_id = configuration.turn_logging_id;
+  }
+
   // The port allocator lives on the network thread and should be initialized
   // there.
   const auto pa_result =
@@ -3625,6 +3632,7 @@
   modified_config.use_datagram_transport = configuration.use_datagram_transport;
   modified_config.use_datagram_transport_for_data_channels =
       configuration.use_datagram_transport_for_data_channels;
+  modified_config.turn_logging_id = configuration.turn_logging_id;
   if (configuration != modified_config) {
     LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION,
                          "Modifying the configuration in an unsupported way.");
@@ -3651,6 +3659,11 @@
   if (parse_error != RTCErrorType::NONE) {
     return RTCError(parse_error);
   }
+  // Add the turn logging id to all turn servers
+  for (cricket::RelayServerConfig& turn_server : turn_servers) {
+    turn_server.turn_logging_id = configuration.turn_logging_id;
+  }
+
   // Note if STUN or TURN servers were supplied.
   if (!stun_servers.empty()) {
     NoteUsageEvent(UsageEvent::STUN_SERVER_ADDED);