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}
This commit is contained in:
Jonas Oreland 2019-12-11 11:35:48 +01:00 committed by Commit Bot
parent c907d4f223
commit 9a52bd733c
9 changed files with 623 additions and 83 deletions

View File

@ -1115,6 +1115,55 @@ bool StunUInt16ListAttribute::Write(ByteBufferWriter* buf) const {
return true;
}
std::string StunMethodToString(int msg_type) {
switch (msg_type) {
case STUN_BINDING_REQUEST:
return "STUN BINDING request";
case STUN_BINDING_INDICATION:
return "STUN BINDING indication";
case STUN_BINDING_RESPONSE:
return "STUN BINDING response";
case STUN_BINDING_ERROR_RESPONSE:
return "STUN BINDING error response";
case GOOG_PING_REQUEST:
return "GOOG PING request";
case GOOG_PING_RESPONSE:
return "GOOG PING response";
case GOOG_PING_ERROR_RESPONSE:
return "GOOG PING error response";
case STUN_ALLOCATE_REQUEST:
return "TURN ALLOCATE request";
case STUN_ALLOCATE_RESPONSE:
return "TURN ALLOCATE response";
case STUN_ALLOCATE_ERROR_RESPONSE:
return "TURN ALLOCATE error response";
case TURN_REFRESH_REQUEST:
return "TURN REFRESH request";
case TURN_REFRESH_RESPONSE:
return "TURN REFRESH response";
case TURN_REFRESH_ERROR_RESPONSE:
return "TURN REFRESH error response";
case TURN_SEND_INDICATION:
return "TURN SEND INDICATION";
case TURN_DATA_INDICATION:
return "TURN DATA INDICATION";
case TURN_CREATE_PERMISSION_REQUEST:
return "TURN CREATE PERMISSION request";
case TURN_CREATE_PERMISSION_RESPONSE:
return "TURN CREATE PERMISSION response";
case TURN_CREATE_PERMISSION_ERROR_RESPONSE:
return "TURN CREATE PERMISSION error response";
case TURN_CHANNEL_BIND_REQUEST:
return "TURN CHANNEL BIND request";
case TURN_CHANNEL_BIND_RESPONSE:
return "TURN CHANNEL BIND response";
case TURN_CHANNEL_BIND_ERROR_RESPONSE:
return "TURN CHANNEL BIND error response";
default:
return "UNKNOWN<" + std::to_string(msg_type) + ">";
}
}
int GetStunSuccessResponseType(int req_type) {
return IsStunRequestType(req_type) ? (req_type | 0x100) : -1;
}

View File

@ -34,7 +34,9 @@ enum StunMessageType {
STUN_BINDING_RESPONSE = 0x0101,
STUN_BINDING_ERROR_RESPONSE = 0x0111,
// Method 0x80
// Method 0x80, GOOG-PING is a variant of STUN BINDING
// that is sent instead of a STUN BINDING if the binding
// was identical to the one before.
GOOG_PING_REQUEST = 0x200,
GOOG_PING_RESPONSE = 0x300,
GOOG_PING_ERROR_RESPONSE = 0x310,
@ -513,6 +515,9 @@ class StunUInt16ListAttribute : public StunAttribute {
std::vector<uint16_t>* attr_types_;
};
// Return a string e.g "STUN BINDING request".
std::string StunMethodToString(int msg_type);
// Returns the (successful) response type for the given request type.
// Returns -1 if |request_type| is not a valid request type.
int GetStunSuccessResponseType(int request_type);
@ -669,11 +674,15 @@ enum IceAttributeType {
// When adding new attributes to STUN_ATTR_GOOG_MISC_INFO
// (which is a list of uint16_t), append the indices of these attributes below
// and do NOT change the exisiting indices. The indices of attributes must be
// and do NOT change the existing indices. The indices of attributes must be
// consistent with those used in ConnectionRequest::Prepare when forming a STUN
// message for the ICE connectivity check, and they are used when parsing a
// received STUN message.
enum class IceGoogMiscInfoAttributeIndex {};
enum class IceGoogMiscInfoBindingRequestAttributeIndex {};
enum class IceGoogMiscInfoBindingResponseAttributeIndex {
SUPPORT_GOOG_PING_VERSION = 0,
};
// RFC 5245-defined errors.
enum IceErrorCode {

View File

@ -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)

View File

@ -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_;

View File

@ -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

View File

@ -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;

View File

@ -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) {

View File

@ -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() {

View File

@ -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."