aboutsummaryrefslogtreecommitdiff
path: root/p2p/base
diff options
context:
space:
mode:
authorJonas Oreland <jonaso@webrtc.org>2019-12-11 11:35:48 +0100
committerCommit Bot <commit-bot@chromium.org>2019-12-11 14:23:30 +0000
commit9a52bd733ca963fb8a9883e5cc3d3974ecccffb8 (patch)
tree51acb1833c34ffded91c5f8872871fecfda19450 /p2p/base
parentc907d4f223ebb49780bdee23840a618604f317c4 (diff)
downloadwebrtc-9a52bd733ca963fb8a9883e5cc3d3974ecccffb8.tar.gz
STUN PING request
This patch introduces a new type of STUN ping, GOOG_PING_REQUEST/RESPONSE which is similar to a STUN_BINDING but does not transmit any values. The Connection class automatically sends these if no STUN attributes has changed since last call to Connection::Ping() if the remote peer has signaled that it supports it. BUG=webrtc:11100 Change-Id: Ib1b590f0b90ca6cb56f2eb07cd62f976e246bc8c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/159961 Commit-Queue: Jonas Oreland <jonaso@webrtc.org> Reviewed-by: Taylor <deadbeef@webrtc.org> Reviewed-by: Björn Terelius <terelius@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30062}
Diffstat (limited to 'p2p/base')
-rw-r--r--p2p/base/connection.cc156
-rw-r--r--p2p/base/connection.h22
-rw-r--r--p2p/base/p2p_transport_channel.cc11
-rw-r--r--p2p/base/p2p_transport_channel_ice_field_trials.h6
-rw-r--r--p2p/base/p2p_transport_channel_unittest.cc34
-rw-r--r--p2p/base/port.cc108
-rw-r--r--p2p/base/port_unittest.cc305
7 files changed, 562 insertions, 80 deletions
diff --git a/p2p/base/connection.cc b/p2p/base/connection.cc
index fbbd85344b..e11b4bcba2 100644
--- a/p2p/base/connection.cc
+++ b/p2p/base/connection.cc
@@ -143,11 +143,15 @@ constexpr int64_t kMinExtraPingDelayMs = 100;
// Default field trials.
const cricket::IceFieldTrials kDefaultFieldTrials;
+constexpr int kSupportGoogPingVersionIndex =
+ static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION);
+
} // namespace
namespace cricket {
-// A ConnectionRequest is a simple STUN ping used to determine writability.
+// A ConnectionRequest is a STUN binding used to determine writability.
ConnectionRequest::ConnectionRequest(Connection* connection)
: StunRequest(new IceMessage()), connection_(connection) {}
@@ -220,10 +224,14 @@ void ConnectionRequest::Prepare(StunMessage* request) {
request->AddAttribute(std::make_unique<StunUInt32Attribute>(
STUN_ATTR_PRIORITY, prflx_priority));
- // Adding Message Integrity attribute.
- request->AddMessageIntegrity(connection_->remote_candidate().password());
- // Adding Fingerprint.
- request->AddFingerprint();
+ if (connection_->ShouldSendGoogPing(request)) {
+ request->SetType(GOOG_PING_REQUEST);
+ request->ClearAttributes();
+ request->AddMessageIntegrity32(connection_->remote_candidate().password());
+ } else {
+ request->AddMessageIntegrity(connection_->remote_candidate().password());
+ request->AddFingerprint();
+ }
}
void ConnectionRequest::OnResponse(StunMessage* response) {
@@ -451,11 +459,11 @@ void Connection::OnReadPacket(const char* data,
rtc::LoggingSeverity sev = (!writable() ? rtc::LS_INFO : rtc::LS_VERBOSE);
switch (msg->type()) {
case STUN_BINDING_REQUEST:
- RTC_LOG_V(sev) << ToString() << ": Received STUN ping, id="
- << rtc::hex_encode(msg->transaction_id());
-
+ RTC_LOG_V(sev) << ToString() << ": Received "
+ << StunMethodToString(msg->type())
+ << ", id=" << rtc::hex_encode(msg->transaction_id());
if (remote_ufrag == remote_candidate_.username()) {
- HandleBindingRequest(msg.get());
+ HandleStunBindingOrGoogPingRequest(msg.get());
} else {
// The packet had the right local username, but the remote username
// was not the right one for the remote address.
@@ -487,7 +495,16 @@ void Connection::OnReadPacket(const char* data,
case STUN_BINDING_INDICATION:
ReceivedPing(msg->transaction_id());
break;
-
+ case GOOG_PING_REQUEST:
+ HandleStunBindingOrGoogPingRequest(msg.get());
+ break;
+ case GOOG_PING_RESPONSE:
+ case GOOG_PING_ERROR_RESPONSE:
+ if (msg->ValidateMessageIntegrity32(data, size,
+ remote_candidate().password())) {
+ requests_.CheckResponse(msg.get());
+ }
+ break;
default:
RTC_NOTREACHED();
break;
@@ -495,7 +512,7 @@ void Connection::OnReadPacket(const char* data,
}
}
-void Connection::HandleBindingRequest(IceMessage* msg) {
+void Connection::HandleStunBindingOrGoogPingRequest(IceMessage* msg) {
// This connection should now be receiving.
ReceivedPing(msg->transaction_id());
if (webrtc::field_trial::IsEnabled("WebRTC-ExtraICEPing") &&
@@ -523,12 +540,14 @@ void Connection::HandleBindingRequest(IceMessage* msg) {
}
const rtc::SocketAddress& remote_addr = remote_candidate_.address();
- const std::string& remote_ufrag = remote_candidate_.username();
- // Check for role conflicts.
- if (!port_->MaybeIceRoleConflict(remote_addr, msg, remote_ufrag)) {
- // Received conflicting role from the peer.
- RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
- return;
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ // Check for role conflicts.
+ const std::string& remote_ufrag = remote_candidate_.username();
+ if (!port_->MaybeIceRoleConflict(remote_addr, msg, remote_ufrag)) {
+ // Received conflicting role from the peer.
+ RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
+ return;
+ }
}
stats_.recv_ping_requests++;
@@ -536,7 +555,12 @@ void Connection::HandleBindingRequest(IceMessage* msg) {
msg->reduced_transaction_id());
// This is a validated stun request from remote peer.
- SendBindingResponse(msg);
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ SendStunBindingResponse(msg);
+ } else {
+ RTC_DCHECK(msg->type() == GOOG_PING_REQUEST);
+ SendGoogPingResponse(msg);
+ }
// If it timed out on writing check, start up again
if (!pruned_ && write_state_ == STATE_WRITE_TIMEOUT) {
@@ -587,12 +611,9 @@ void Connection::HandleBindingRequest(IceMessage* msg) {
}
}
-void Connection::SendBindingResponse(const StunMessage* request) {
+void Connection::SendStunBindingResponse(const StunMessage* request) {
RTC_DCHECK(request->type() == STUN_BINDING_REQUEST);
- // Where I send the response.
- const rtc::SocketAddress& addr = remote_candidate_.address();
-
// Retrieve the username from the request.
const StunByteStringAttribute* username_attr =
request->GetByteString(STUN_ATTR_USERNAME);
@@ -623,10 +644,36 @@ void Connection::SendBindingResponse(const StunMessage* request) {
}
response.AddAttribute(std::make_unique<StunXorAddressAttribute>(
- STUN_ATTR_XOR_MAPPED_ADDRESS, addr));
+ STUN_ATTR_XOR_MAPPED_ADDRESS, remote_candidate_.address()));
+
+ if (field_trials_->announce_goog_ping) {
+ auto list =
+ StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
+ list->AddTypeAtIndex(kSupportGoogPingVersionIndex, kGoogPingVersion);
+ response.AddAttribute(std::move(list));
+ }
+
response.AddMessageIntegrity(local_candidate().password());
response.AddFingerprint();
+ SendResponseMessage(response);
+}
+
+void Connection::SendGoogPingResponse(const StunMessage* request) {
+ RTC_DCHECK(request->type() == GOOG_PING_REQUEST);
+
+ // Fill in the response message.
+ StunMessage response;
+ response.SetType(GOOG_PING_RESPONSE);
+ response.SetTransactionID(request->transaction_id());
+ response.AddMessageIntegrity32(local_candidate().password());
+ SendResponseMessage(response);
+}
+
+void Connection::SendResponseMessage(const StunMessage& response) {
+ // Where I send the response.
+ const rtc::SocketAddress& addr = remote_candidate_.address();
+
// Send the response message.
rtc::ByteBufferWriter buf;
response.Write(&buf);
@@ -635,21 +682,22 @@ void Connection::SendBindingResponse(const StunMessage* request) {
rtc::PacketType::kIceConnectivityCheckResponse;
auto err = port_->SendTo(buf.Data(), buf.Length(), addr, options, false);
if (err < 0) {
- RTC_LOG(LS_ERROR) << ToString()
- << ": Failed to send STUN ping response, to="
- << addr.ToSensitiveString() << ", err=" << err
+ RTC_LOG(LS_ERROR) << ToString() << ": Failed to send "
+ << StunMethodToString(response.type())
+ << ", to=" << addr.ToSensitiveString() << ", err=" << err
<< ", id=" << rtc::hex_encode(response.transaction_id());
} else {
// Log at LS_INFO if we send a stun ping response on an unwritable
// connection.
rtc::LoggingSeverity sev = (!writable()) ? rtc::LS_INFO : rtc::LS_VERBOSE;
- RTC_LOG_V(sev) << ToString() << ": Sent STUN ping response, to="
- << addr.ToSensitiveString()
+ RTC_LOG_V(sev) << ToString() << ": Sent "
+ << StunMethodToString(response.type())
+ << ", to=" << addr.ToSensitiveString()
<< ", id=" << rtc::hex_encode(response.transaction_id());
stats_.sent_ping_responses++;
LogCandidatePairEvent(webrtc::IceCandidatePairEventType::kCheckResponseSent,
- request->reduced_transaction_id());
+ response.reduced_transaction_id());
}
}
@@ -786,7 +834,8 @@ void Connection::ReceivedPing(const absl::optional<std::string>& request_id) {
}
void Connection::HandlePiggybackCheckAcknowledgementIfAny(StunMessage* msg) {
- RTC_DCHECK(msg->type() == STUN_BINDING_REQUEST);
+ RTC_DCHECK(msg->type() == STUN_BINDING_REQUEST ||
+ msg->type() == GOOG_PING_REQUEST);
const StunByteStringAttribute* last_ice_check_received_attr =
msg->GetByteString(STUN_ATTR_LAST_ICE_CHECK_RECEIVED);
if (last_ice_check_received_attr) {
@@ -981,8 +1030,9 @@ void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
if (RTC_LOG_CHECK_LEVEL_V(sev)) {
std::string pings;
PrintPingsSinceLastResponse(&pings, 5);
- RTC_LOG_V(sev) << ToString() << ": Received STUN ping response, id="
- << rtc::hex_encode(request->id())
+ RTC_LOG_V(sev) << ToString() << ": Received "
+ << StunMethodToString(response->type())
+ << ", id=" << rtc::hex_encode(request->id())
<< ", code=0" // Makes logging easier to parse.
", rtt="
<< rtt << ", pings_since_last_response=" << pings;
@@ -1002,17 +1052,33 @@ void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
webrtc::IceCandidatePairEventType::kCheckResponseReceived,
response->reduced_transaction_id());
- MaybeUpdateLocalCandidate(request, response);
+ if (request->msg()->type() == STUN_BINDING_REQUEST) {
+ auto goog_misc = response->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO);
+ if (goog_misc != nullptr &&
+ goog_misc->Size() >= kSupportGoogPingVersionIndex &&
+ goog_misc->GetType(kSupportGoogPingVersionIndex) >= kGoogPingVersion) {
+ // The remote peer has indicated that it supports GOOG_PING.
+ remote_support_goog_ping_ = true;
+ }
+
+ MaybeUpdateLocalCandidate(request, response);
+
+ if (field_trials_->enable_goog_ping && remote_support_goog_ping_) {
+ cached_stun_binding_ = request->msg()->Clone();
+ }
+ }
}
void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
StunMessage* response) {
int error_code = response->GetErrorCodeValue();
- RTC_LOG(LS_WARNING) << ToString() << ": Received STUN error response id="
- << rtc::hex_encode(request->id())
+ RTC_LOG(LS_WARNING) << ToString() << ": Received "
+ << StunMethodToString(response->type())
+ << " id=" << rtc::hex_encode(request->id())
<< " code=" << error_code
<< " rtt=" << request->Elapsed();
+ cached_stun_binding_.reset();
if (error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE ||
error_code == STUN_ERROR_SERVER_ERROR ||
error_code == STUN_ERROR_UNAUTHORIZED) {
@@ -1021,6 +1087,8 @@ void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
// Race failure, retry
} else if (error_code == STUN_ERROR_ROLE_CONFLICT) {
HandleRoleConflictFromPeer();
+ } else if (request->msg()->type() == GOOG_PING_REQUEST) {
+ // Race, retry.
} else {
// This is not a valid connection.
RTC_LOG(LS_ERROR) << ToString()
@@ -1041,8 +1109,9 @@ void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
void Connection::OnConnectionRequestSent(ConnectionRequest* request) {
// Log at LS_INFO if we send a ping on an unwritable connection.
rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
- RTC_LOG_V(sev) << ToString()
- << ": Sent STUN ping, id=" << rtc::hex_encode(request->id())
+ RTC_LOG_V(sev) << ToString() << ": Sent "
+ << StunMethodToString(request->msg()->type())
+ << ", id=" << rtc::hex_encode(request->id())
<< ", use_candidate=" << use_candidate_attr()
<< ", nomination=" << nomination();
stats_.sent_ping_requests_total++;
@@ -1219,6 +1288,19 @@ bool Connection::TooManyOutstandingPings(
return true;
}
+bool Connection::ShouldSendGoogPing(const StunMessage* message) {
+ if (remote_support_goog_ping_ && cached_stun_binding_ &&
+ cached_stun_binding_->EqualAttributes(message, [](int type) {
+ // Ignore these attributes.
+ return type != STUN_ATTR_FINGERPRINT &&
+ type != STUN_ATTR_MESSAGE_INTEGRITY &&
+ type != STUN_ATTR_RETRANSMIT_COUNT;
+ })) {
+ return true;
+ }
+ return false;
+}
+
ProxyConnection::ProxyConnection(Port* port,
size_t index,
const Candidate& remote_candidate)
diff --git a/p2p/base/connection.h b/p2p/base/connection.h
index 39066f406f..9f3ad21125 100644
--- a/p2p/base/connection.h
+++ b/p2p/base/connection.h
@@ -11,6 +11,7 @@
#ifndef P2P_BASE_CONNECTION_H_
#define P2P_BASE_CONNECTION_H_
+#include <memory>
#include <string>
#include <vector>
@@ -31,6 +32,10 @@
namespace cricket {
+// Version number for GOOG_PING, this is added to have the option of
+// adding other flavors in the future.
+constexpr int kGoogPingVersion = 1;
+
// Connection and Port has circular dependencies.
// So we use forward declaration rather than include.
class Port;
@@ -227,7 +232,7 @@ class Connection : public CandidatePairInterface,
void ReceivedPing(
const absl::optional<std::string>& request_id = absl::nullopt);
// Handles the binding request; sends a response if this is a valid request.
- void HandleBindingRequest(IceMessage* msg);
+ void HandleStunBindingOrGoogPingRequest(IceMessage* msg);
// Handles the piggyback acknowledgement of the lastest connectivity check
// that the remote peer has received, if it is indicated in the incoming
// connectivity check from the peer.
@@ -298,7 +303,9 @@ class Connection : public CandidatePairInterface,
return rtt_estimate_;
}
- void SendBindingResponse(const StunMessage* request);
+ void SendStunBindingResponse(const StunMessage* request);
+ void SendGoogPingResponse(const StunMessage* request);
+ void SendResponseMessage(const StunMessage& response);
// An accessor for unit tests.
Port* PortForTest() { return port_; }
@@ -368,6 +375,10 @@ class Connection : public CandidatePairInterface,
void LogCandidatePairEvent(webrtc::IceCandidatePairEventType type,
uint32_t transaction_id);
+ // Check if this IceMessage is identical
+ // to last message ack:ed STUN_BINDING_REQUEST.
+ bool ShouldSendGoogPing(const StunMessage* message);
+
WriteState write_state_;
bool receiving_;
bool connected_;
@@ -424,6 +435,13 @@ class Connection : public CandidatePairInterface,
absl::optional<webrtc::IceCandidatePairDescription> log_description_;
webrtc::IceEventLog* ice_event_log_ = nullptr;
+ // GOOG_PING_REQUEST is sent in place of STUN_BINDING_REQUEST
+ // if configured via field trial, the remote peer supports it (signaled
+ // in STUN_BINDING) and if the last STUN BINDING is identical to the one
+ // that is about to be sent.
+ absl::optional<bool> remote_support_goog_ping_;
+ std::unique_ptr<StunMessage> cached_stun_binding_;
+
const IceFieldTrials* field_trials_;
rtc::EventBasedExponentialMovingAverage rtt_estimate_;
diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc
index c11a6b4efb..781f709f06 100644
--- a/p2p/base/p2p_transport_channel.cc
+++ b/p2p/base/p2p_transport_channel.cc
@@ -641,12 +641,21 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) {
}
webrtc::StructParametersParser::Create(
+ // go/skylift-light
"skip_relay_to_non_relay_connections",
&field_trials_.skip_relay_to_non_relay_connections,
+ // Limiting pings sent.
"max_outstanding_pings", &field_trials_.max_outstanding_pings,
+ // Delay initial selection of connection.
"initial_select_dampening", &field_trials_.initial_select_dampening,
+ // Delay initial selection of connections, that are receiving.
"initial_select_dampening_ping_received",
&field_trials_.initial_select_dampening_ping_received,
+ // Reply that we support goog ping.
+ "announce_goog_ping", &field_trials_.announce_goog_ping,
+ // Use goog ping if remote support it.
+ "enable_goog_ping", &field_trials_.enable_goog_ping,
+ // How fast does a RTT sample decay.
"rtt_estimate_halftime_ms", &field_trials_.rtt_estimate_halftime_ms)
->Parse(webrtc::field_trial::FindFullName("WebRTC-IceFieldTrials"));
@@ -1028,7 +1037,7 @@ void P2PTransportChannel::OnUnknownAddress(PortInterface* port,
: "resurrected")
<< " candidate: " << remote_candidate.ToSensitiveString();
AddConnection(connection);
- connection->HandleBindingRequest(stun_msg);
+ connection->HandleStunBindingOrGoogPingRequest(stun_msg);
// Update the list of connections since we just added another. We do this
// after sending the response since it could (in principle) delete the
diff --git a/p2p/base/p2p_transport_channel_ice_field_trials.h b/p2p/base/p2p_transport_channel_ice_field_trials.h
index e0854a15c7..20836f75b3 100644
--- a/p2p/base/p2p_transport_channel_ice_field_trials.h
+++ b/p2p/base/p2p_transport_channel_ice_field_trials.h
@@ -32,6 +32,12 @@ struct IceFieldTrials {
// give us chance to find a better connection before starting.
absl::optional<int> initial_select_dampening_ping_received;
+ // Announce GOOG_PING support in STUN_BINDING_RESPONSE.
+ bool announce_goog_ping = true;
+
+ // Enable sending GOOG_PING if remote announce it.
+ bool enable_goog_ping = false;
+
// Decay rate for RTT estimate using EventBasedExponentialMovingAverage
// expressed as halving time.
int rtt_estimate_halftime_ms = 500;
diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc
index 23ed57cdd9..c2299ccf84 100644
--- a/p2p/base/p2p_transport_channel_unittest.cc
+++ b/p2p/base/p2p_transport_channel_unittest.cc
@@ -609,7 +609,7 @@ class P2PTransportChannelTestBase : public ::testing::Test,
return CheckConnected(ch1, ch2) && CheckCandidatePair(ch1, ch2, from, to);
}
- void Test(const Result& expected) {
+ virtual void Test(const Result& expected) {
rtc::ScopedFakeClock clock;
int64_t connect_start = rtc::TimeMillis();
int64_t connect_time;
@@ -1195,16 +1195,26 @@ const P2PTransportChannelTest::Result*
LTRT},
};
+class P2PTransportChannelTestWithFieldTrials
+ : public P2PTransportChannelTest,
+ public ::testing::WithParamInterface<std::string> {
+ public:
+ void Test(const Result& expected) override {
+ webrtc::test::ScopedFieldTrials field_trials(GetParam());
+ P2PTransportChannelTest::Test(expected);
+ }
+};
+
// The actual tests that exercise all the various configurations.
// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE
-#define P2P_TEST_DECLARATION(x, y, z) \
- TEST_F(P2PTransportChannelTest, z##Test##x##To##y) { \
- ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
- PORTALLOCATOR_ENABLE_SHARED_SOCKET); \
- if (kMatrix[x][y] != NULL) \
- Test(*kMatrix[x][y]); \
- else \
- RTC_LOG(LS_WARNING) << "Not yet implemented"; \
+#define P2P_TEST_DECLARATION(x, y, z) \
+ TEST_P(P2PTransportChannelTestWithFieldTrials, z##Test##x##To##y) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET); \
+ if (kMatrix[x][y] != NULL) \
+ Test(*kMatrix[x][y]); \
+ else \
+ RTC_LOG(LS_WARNING) << "Not yet implemented"; \
}
#define P2P_TEST(x, y) P2P_TEST_DECLARATION(x, y, /* empty argument */)
@@ -1236,6 +1246,12 @@ P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
P2P_TEST_SET(PROXY_HTTPS)
P2P_TEST_SET(PROXY_SOCKS)
+INSTANTIATE_TEST_SUITE_P(
+ P2PTransportChannelTestWithFieldTrials,
+ P2PTransportChannelTestWithFieldTrials,
+ // Each field-trial is ~144 tests (some return not-yet-implemented).
+ testing::Values("", "WebRTC-IceFieldTrials/enable_goog_ping:true/"));
+
// Test that we restart candidate allocation when local ufrag&pwd changed.
// Standard Ice protocol is used.
TEST_F(P2PTransportChannelTest, HandleUfragPwdChange) {
diff --git a/p2p/base/port.cc b/p2p/base/port.cc
index b92f1226c0..dbc04a484b 100644
--- a/p2p/base/port.cc
+++ b/p2p/base/port.cc
@@ -392,8 +392,8 @@ void Port::OnReadPacket(const char* data,
} else if (!msg) {
// STUN message handled already
} else if (msg->type() == STUN_BINDING_REQUEST) {
- RTC_LOG(LS_INFO) << "Received STUN ping id="
- << rtc::hex_encode(msg->transaction_id())
+ RTC_LOG(LS_INFO) << "Received " << StunMethodToString(msg->type())
+ << " id=" << rtc::hex_encode(msg->transaction_id())
<< " from unknown address " << addr.ToSensitiveString();
// We need to signal an unknown address before we handle any role conflict
// below. Otherwise there would be no candidate pair and TURN entry created
@@ -404,12 +404,20 @@ void Port::OnReadPacket(const char* data,
RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
return;
}
+ } else if (msg->type() == GOOG_PING_REQUEST) {
+ // This is a PING sent to a connection that was destroyed.
+ // Send back that this is the case and a authenticated BINDING
+ // is needed.
+ SendBindingErrorResponse(msg.get(), addr, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
} else {
// NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
// pruned a connection for this port while it had STUN requests in flight,
// because we then get back responses for them, which this code correctly
// does not handle.
- if (msg->type() != STUN_BINDING_RESPONSE) {
+ if (msg->type() != STUN_BINDING_RESPONSE &&
+ msg->type() != GOOG_PING_RESPONSE &&
+ msg->type() != GOOG_PING_ERROR_RESPONSE) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received unexpected STUN message type: "
<< msg->type() << " from unknown address: "
@@ -444,7 +452,11 @@ bool Port::GetStunMessage(const char* data,
// Don't bother parsing the packet if we can tell it's not STUN.
// In ICE mode, all STUN packets will have a valid fingerprint.
- if (!StunMessage::ValidateFingerprint(data, size)) {
+ // Except GOOG_PING_REQUEST/RESPONSE that does not send fingerprint.
+ int types[] = {GOOG_PING_REQUEST, GOOG_PING_RESPONSE,
+ GOOG_PING_ERROR_RESPONSE};
+ if (!StunMessage::IsStunMethod(types, data, size) &&
+ !StunMessage::ValidateFingerprint(data, size)) {
return false;
}
@@ -461,8 +473,9 @@ bool Port::GetStunMessage(const char* data,
// If not present, fail with a 400 Bad Request.
if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) ||
!stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY)) {
- RTC_LOG(LS_ERROR) << ToString()
- << ": Received STUN request without username/M-I from: "
+ RTC_LOG(LS_ERROR) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type())
+ << " without username/M-I from: "
<< addr.ToSensitiveString();
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
STUN_ERROR_REASON_BAD_REQUEST);
@@ -474,9 +487,10 @@ bool Port::GetStunMessage(const char* data,
std::string remote_ufrag;
if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag) ||
local_ufrag != username_fragment()) {
- RTC_LOG(LS_ERROR) << ToString()
- << ": Received STUN request with bad local username "
- << local_ufrag << " from " << addr.ToSensitiveString();
+ RTC_LOG(LS_ERROR) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type())
+ << " with bad local username " << local_ufrag
+ << " from " << addr.ToSensitiveString();
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
STUN_ERROR_REASON_UNAUTHORIZED);
return true;
@@ -484,9 +498,9 @@ bool Port::GetStunMessage(const char* data,
// If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized
if (!stun_msg->ValidateMessageIntegrity(data, size, password_)) {
- RTC_LOG(LS_ERROR) << ToString()
- << ": Received STUN request with bad M-I from "
- << addr.ToSensitiveString()
+ RTC_LOG(LS_ERROR) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type())
+ << " with bad M-I from " << addr.ToSensitiveString()
<< ", password_=" << password_;
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
STUN_ERROR_REASON_UNAUTHORIZED);
@@ -497,30 +511,51 @@ bool Port::GetStunMessage(const char* data,
(stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
- RTC_LOG(LS_ERROR) << ToString()
- << ": Received STUN binding error: class="
- << error_code->eclass()
+ RTC_LOG(LS_ERROR) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type())
+ << ": class=" << error_code->eclass()
<< " number=" << error_code->number() << " reason='"
<< error_code->reason() << "' from "
<< addr.ToSensitiveString();
// Return message to allow error-specific processing
} else {
- RTC_LOG(LS_ERROR)
- << ToString()
- << ": Received STUN binding error without a error code from "
- << addr.ToSensitiveString();
+ RTC_LOG(LS_ERROR) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type())
+ << " without a error code from "
+ << addr.ToSensitiveString();
return true;
}
}
// NOTE: Username should not be used in verifying response messages.
out_username->clear();
} else if (stun_msg->type() == STUN_BINDING_INDICATION) {
- RTC_LOG(LS_VERBOSE) << ToString()
- << ": Received STUN binding indication: from "
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type()) << ": from "
<< addr.ToSensitiveString();
out_username->clear();
// No stun attributes will be verified, if it's stun indication message.
// Returning from end of the this method.
+ } else if (stun_msg->type() == GOOG_PING_REQUEST) {
+ if (!stun_msg->ValidateMessageIntegrity32(data, size, password_)) {
+ RTC_LOG(LS_ERROR) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type())
+ << " with bad M-I from " << addr.ToSensitiveString()
+ << ", password_=" << password_;
+ SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+ return true;
+ }
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type()) << " from "
+ << addr.ToSensitiveString();
+ out_username->clear();
+ } else if (stun_msg->type() == GOOG_PING_RESPONSE ||
+ stun_msg->type() == GOOG_PING_ERROR_RESPONSE) {
+ // note: the MessageIntegrity32 will be verified in Connection.cc
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
+ << StunMethodToString(stun_msg->type()) << " from "
+ << addr.ToSensitiveString();
+ out_username->clear();
} else {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN packet with invalid type ("
@@ -665,11 +700,16 @@ void Port::SendBindingErrorResponse(StunMessage* request,
const rtc::SocketAddress& addr,
int error_code,
const std::string& reason) {
- RTC_DCHECK(request->type() == STUN_BINDING_REQUEST);
+ RTC_DCHECK(request->type() == STUN_BINDING_REQUEST ||
+ request->type() == GOOG_PING_REQUEST);
// Fill in the response message.
StunMessage response;
- response.SetType(STUN_BINDING_ERROR_RESPONSE);
+ if (request->type() == STUN_BINDING_REQUEST) {
+ response.SetType(STUN_BINDING_ERROR_RESPONSE);
+ } else {
+ response.SetType(GOOG_PING_ERROR_RESPONSE);
+ }
response.SetTransactionID(request->transaction_id());
// When doing GICE, we need to write out the error code incorrectly to
@@ -682,9 +722,18 @@ void Port::SendBindingErrorResponse(StunMessage* request,
// Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY,
// because we don't have enough information to determine the shared secret.
if (error_code != STUN_ERROR_BAD_REQUEST &&
- error_code != STUN_ERROR_UNAUTHORIZED)
- response.AddMessageIntegrity(password_);
- response.AddFingerprint();
+ error_code != STUN_ERROR_UNAUTHORIZED &&
+ request->type() != GOOG_PING_REQUEST) {
+ if (request->type() == STUN_BINDING_REQUEST) {
+ response.AddMessageIntegrity(password_);
+ } else {
+ response.AddMessageIntegrity32(password_);
+ }
+ }
+
+ if (request->type() == STUN_BINDING_REQUEST) {
+ response.AddFingerprint();
+ }
// Send the response message.
rtc::ByteBufferWriter buf;
@@ -693,9 +742,10 @@ void Port::SendBindingErrorResponse(StunMessage* request,
options.info_signaled_after_sent.packet_type =
rtc::PacketType::kIceConnectivityCheckResponse;
SendTo(buf.Data(), buf.Length(), addr, options, false);
- RTC_LOG(LS_INFO) << ToString()
- << ": Sending STUN binding error: reason=" << reason
- << " to " << addr.ToSensitiveString();
+ RTC_LOG(LS_INFO) << ToString() << ": Sending STUN "
+ << StunMethodToString(response.type())
+ << ": reason=" << reason << " to "
+ << addr.ToSensitiveString();
}
void Port::KeepAliveUntilPruned() {
diff --git a/p2p/base/port_unittest.cc b/p2p/base/port_unittest.cc
index 55ff5be5ad..9abeb3ada4 100644
--- a/p2p/base/port_unittest.cc
+++ b/p2p/base/port_unittest.cc
@@ -302,7 +302,7 @@ class TestChannel : public sigslot::has_slots<> {
c.set_address(remote_address_);
conn_ = port_->CreateConnection(c, Port::ORIGIN_MESSAGE);
conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
- conn_->SendBindingResponse(remote_request_.get());
+ conn_->SendStunBindingResponse(remote_request_.get());
remote_request_.reset();
}
void Ping() { Ping(0); }
@@ -406,6 +406,8 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
ports_destroyed_(0) {}
protected:
+ std::string password() { return password_; }
+
void TestLocalToLocal() {
auto port1 = CreateUdpPort(kLocalAddr1);
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
@@ -2621,7 +2623,7 @@ TEST_F(PortTest, TestIceLiteConnectivity) {
auto* con = ice_lite_port->CreateConnection(
ice_full_port_ptr->Candidates()[0], cricket::Port::ORIGIN_MESSAGE);
std::unique_ptr<IceMessage> request = CopyStunMessage(*msg);
- con->SendBindingResponse(request.get());
+ con->SendStunBindingResponse(request.get());
// Feeding the respone message from litemode to the full mode connection.
ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->data<char>(),
@@ -2643,6 +2645,305 @@ TEST_F(PortTest, TestIceLiteConnectivity) {
ch1.Stop();
}
+namespace {
+
+// Utility function for testing goog ping.
+absl::optional<int> GetSupportedGoogPingVersion(const StunMessage* response) {
+ auto goog_misc = response->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO);
+ if (goog_misc == nullptr) {
+ return absl::nullopt;
+ }
+
+ if (goog_misc->Size() <
+ static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION)) {
+ return absl::nullopt;
+ }
+
+ return goog_misc->GetType(
+ static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION));
+}
+
+} // namespace
+
+class GoogPingTest
+ : public PortTest,
+ public ::testing::WithParamInterface<std::pair<bool, bool>> {};
+
+// This test verifies the announce/enable on/off behavior
+TEST_P(GoogPingTest, TestGoogPingAnnounceEnable) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = GetParam().first;
+ trials.enable_goog_ping = GetParam().second;
+ RTC_LOG(LS_INFO) << "Testing combination: "
+ << " announce: " << trials.announce_goog_ping
+ << " enable:" << trials.enable_goog_ping;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* request1 = port1->last_stun_msg();
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ con->SendStunBindingResponse(request1);
+
+ // Then check the response matches the settings.
+ const auto* response = port2->last_stun_msg();
+ ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ ASSERT_EQ(trials.announce_goog_ping,
+ GetSupportedGoogPingVersion(response) &&
+ GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
+
+ // Feeding the respone message back.
+ ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
+ port2->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* request2 = port1->last_stun_msg();
+
+ // It should be a GOOG_PING if both of these are TRUE
+ if (trials.announce_goog_ping && trials.enable_goog_ping) {
+ ASSERT_EQ(request2->type(), GOOG_PING_REQUEST);
+ con->SendGoogPingResponse(request2);
+ } else {
+ ASSERT_EQ(request2->type(), STUN_BINDING_REQUEST);
+ con->SendStunBindingResponse(request2);
+ }
+
+ const auto* response2 = port2->last_stun_msg();
+ ASSERT_TRUE(response2 != nullptr);
+
+ // It should be a GOOG_PING_RESPONSE if both of these are TRUE
+ if (trials.announce_goog_ping && trials.enable_goog_ping) {
+ ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
+ } else {
+ ASSERT_EQ(response2->type(), STUN_BINDING_RESPONSE);
+ }
+
+ ch1.Stop();
+}
+
+INSTANTIATE_TEST_SUITE_P(GoogPingTest,
+ GoogPingTest,
+ // test all combinations of <announce, enable> pairs.
+ ::testing::Values(std::make_pair(false, false),
+ std::make_pair(true, false),
+ std::make_pair(false, true),
+ std::make_pair(true, true)));
+
+// This test checks that a change in attributes falls back to STUN_BINDING
+TEST_F(PortTest, TestChangeInAttributeMakesGoogPingFallsbackToStunBinding) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = true;
+ trials.enable_goog_ping = true;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != nullptr);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg = port1->last_stun_msg();
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ // Feed the message into the connection.
+ con->SendStunBindingResponse(msg);
+
+ // The check reply wrt to settings.
+ const auto* response = port2->last_stun_msg();
+ ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ ASSERT_TRUE(GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
+
+ // Feeding the respone message back.
+ ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
+ port2->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg2 = port1->last_stun_msg();
+
+ // It should be a GOOG_PING if both of these are TRUE
+ ASSERT_EQ(msg2->type(), GOOG_PING_REQUEST);
+ con->SendGoogPingResponse(msg2);
+
+ const auto* response2 = port2->last_stun_msg();
+ ASSERT_TRUE(response2 != nullptr);
+
+ // It should be a GOOG_PING_RESPONSE.
+ ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
+
+ // And now the third ping.
+ port1->Reset();
+ port2->Reset();
+
+ // Modify the message to be sent.
+ ch1.conn()->set_use_candidate_attr(!ch1.conn()->use_candidate_attr());
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg3 = port1->last_stun_msg();
+
+ // It should be a STUN_BINDING_REQUEST
+ ASSERT_EQ(msg3->type(), STUN_BINDING_REQUEST);
+
+ ch1.Stop();
+}
+
+// This test that an error response fall back to STUN_BINDING.
+TEST_F(PortTest, TestErrorResponseMakesGoogPingFallBackToStunBinding) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = true;
+ trials.enable_goog_ping = true;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg = port1->last_stun_msg();
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ // Feed the message into the connection.
+ con->SendStunBindingResponse(msg);
+
+ // The check reply wrt to settings.
+ const auto* response = port2->last_stun_msg();
+ ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ ASSERT_TRUE(GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
+
+ // Feeding the respone message back.
+ ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
+ port2->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg2 = port1->last_stun_msg();
+
+ // It should be a GOOG_PING.
+ ASSERT_EQ(msg2->type(), GOOG_PING_REQUEST);
+ con->SendGoogPingResponse(msg2);
+
+ const auto* response2 = port2->last_stun_msg();
+ ASSERT_TRUE(response2 != nullptr);
+
+ // It should be a GOOG_PING_RESPONSE.
+ ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
+
+ // But rather than the RESPONSE...feedback an error.
+ StunMessage error_response;
+ error_response.SetType(GOOG_PING_ERROR_RESPONSE);
+ error_response.SetTransactionID(response2->transaction_id());
+ error_response.AddMessageIntegrity32("rpass");
+ rtc::ByteBufferWriter buf;
+ error_response.Write(&buf);
+
+ ch1.conn()->OnReadPacket(buf.Data(), buf.Length(),
+ /* packet_time_us */ -1);
+
+ // And now the third ping...this should be a binding.
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg3 = port1->last_stun_msg();
+
+ // It should be a STUN_BINDING_REQUEST
+ ASSERT_EQ(msg3->type(), STUN_BINDING_REQUEST);
+
+ ch1.Stop();
+}
+
// This test case verifies that both the controlling port and the controlled
// port will time out after connectivity is lost, if they are not marked as
// "keep alive until pruned."