28100cb388
BUG=N/A TBR=niklas.enbom@webrtc.org Review URL: https://webrtc-codereview.appspot.com/29829004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7472 4adac7df-926f-26a2-2b94-8c16560cd09d
978 lines
32 KiB
C++
978 lines
32 KiB
C++
/*
|
|
* libjingle
|
|
* Copyright 2004, Google Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "talk/p2p/base/transport.h"
|
|
|
|
#include "talk/p2p/base/candidate.h"
|
|
#include "talk/p2p/base/constants.h"
|
|
#include "talk/p2p/base/parsing.h"
|
|
#include "talk/p2p/base/port.h"
|
|
#include "talk/p2p/base/sessionmanager.h"
|
|
#include "talk/p2p/base/transportchannelimpl.h"
|
|
#include "webrtc/libjingle/xmllite/xmlelement.h"
|
|
#include "talk/xmpp/constants.h"
|
|
#include "webrtc/base/bind.h"
|
|
#include "webrtc/base/common.h"
|
|
#include "webrtc/base/logging.h"
|
|
|
|
namespace cricket {
|
|
|
|
using rtc::Bind;
|
|
|
|
enum {
|
|
MSG_ONSIGNALINGREADY = 1,
|
|
MSG_ONREMOTECANDIDATE,
|
|
MSG_READSTATE,
|
|
MSG_WRITESTATE,
|
|
MSG_REQUESTSIGNALING,
|
|
MSG_CANDIDATEREADY,
|
|
MSG_ROUTECHANGE,
|
|
MSG_CONNECTING,
|
|
MSG_CANDIDATEALLOCATIONCOMPLETE,
|
|
MSG_ROLECONFLICT,
|
|
MSG_COMPLETED,
|
|
MSG_FAILED,
|
|
};
|
|
|
|
struct ChannelParams : public rtc::MessageData {
|
|
ChannelParams() : channel(NULL), candidate(NULL) {}
|
|
explicit ChannelParams(int component)
|
|
: component(component), channel(NULL), candidate(NULL) {}
|
|
explicit ChannelParams(Candidate* candidate)
|
|
: channel(NULL), candidate(candidate) {
|
|
}
|
|
|
|
~ChannelParams() {
|
|
delete candidate;
|
|
}
|
|
|
|
std::string name;
|
|
int component;
|
|
TransportChannelImpl* channel;
|
|
Candidate* candidate;
|
|
};
|
|
|
|
static std::string IceProtoToString(TransportProtocol proto) {
|
|
std::string proto_str;
|
|
switch (proto) {
|
|
case ICEPROTO_GOOGLE:
|
|
proto_str = "gice";
|
|
break;
|
|
case ICEPROTO_HYBRID:
|
|
proto_str = "hybrid";
|
|
break;
|
|
case ICEPROTO_RFC5245:
|
|
proto_str = "ice";
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
return proto_str;
|
|
}
|
|
|
|
static bool VerifyIceParams(const TransportDescription& desc) {
|
|
// For legacy protocols.
|
|
if (desc.ice_ufrag.empty() && desc.ice_pwd.empty())
|
|
return true;
|
|
|
|
if (desc.ice_ufrag.length() < ICE_UFRAG_MIN_LENGTH ||
|
|
desc.ice_ufrag.length() > ICE_UFRAG_MAX_LENGTH) {
|
|
return false;
|
|
}
|
|
if (desc.ice_pwd.length() < ICE_PWD_MIN_LENGTH ||
|
|
desc.ice_pwd.length() > ICE_PWD_MAX_LENGTH) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BadTransportDescription(const std::string& desc, std::string* err_desc) {
|
|
if (err_desc) {
|
|
*err_desc = desc;
|
|
}
|
|
LOG(LS_ERROR) << desc;
|
|
return false;
|
|
}
|
|
|
|
bool IceCredentialsChanged(const std::string& old_ufrag,
|
|
const std::string& old_pwd,
|
|
const std::string& new_ufrag,
|
|
const std::string& new_pwd) {
|
|
// TODO(jiayl): The standard (RFC 5245 Section 9.1.1.1) says that ICE should
|
|
// restart when both the ufrag and password are changed, but we do restart
|
|
// when either ufrag or passwrod is changed to keep compatible with GICE. We
|
|
// should clean this up when GICE is no longer used.
|
|
return (old_ufrag != new_ufrag) || (old_pwd != new_pwd);
|
|
}
|
|
|
|
static bool IceCredentialsChanged(const TransportDescription& old_desc,
|
|
const TransportDescription& new_desc) {
|
|
return IceCredentialsChanged(old_desc.ice_ufrag, old_desc.ice_pwd,
|
|
new_desc.ice_ufrag, new_desc.ice_pwd);
|
|
}
|
|
|
|
Transport::Transport(rtc::Thread* signaling_thread,
|
|
rtc::Thread* worker_thread,
|
|
const std::string& content_name,
|
|
const std::string& type,
|
|
PortAllocator* allocator)
|
|
: signaling_thread_(signaling_thread),
|
|
worker_thread_(worker_thread),
|
|
content_name_(content_name),
|
|
type_(type),
|
|
allocator_(allocator),
|
|
destroyed_(false),
|
|
readable_(TRANSPORT_STATE_NONE),
|
|
writable_(TRANSPORT_STATE_NONE),
|
|
was_writable_(false),
|
|
connect_requested_(false),
|
|
ice_role_(ICEROLE_UNKNOWN),
|
|
tiebreaker_(0),
|
|
protocol_(ICEPROTO_HYBRID),
|
|
remote_ice_mode_(ICEMODE_FULL) {
|
|
}
|
|
|
|
Transport::~Transport() {
|
|
ASSERT(signaling_thread_->IsCurrent());
|
|
ASSERT(destroyed_);
|
|
}
|
|
|
|
void Transport::SetIceRole(IceRole role) {
|
|
worker_thread_->Invoke<void>(Bind(&Transport::SetIceRole_w, this, role));
|
|
}
|
|
|
|
void Transport::SetIdentity(rtc::SSLIdentity* identity) {
|
|
worker_thread_->Invoke<void>(Bind(&Transport::SetIdentity_w, this, identity));
|
|
}
|
|
|
|
bool Transport::GetIdentity(rtc::SSLIdentity** identity) {
|
|
// The identity is set on the worker thread, so for safety it must also be
|
|
// acquired on the worker thread.
|
|
return worker_thread_->Invoke<bool>(
|
|
Bind(&Transport::GetIdentity_w, this, identity));
|
|
}
|
|
|
|
bool Transport::GetRemoteCertificate(rtc::SSLCertificate** cert) {
|
|
// Channels can be deleted on the worker thread, so for safety the remote
|
|
// certificate is acquired on the worker thread.
|
|
return worker_thread_->Invoke<bool>(
|
|
Bind(&Transport::GetRemoteCertificate_w, this, cert));
|
|
}
|
|
|
|
bool Transport::GetRemoteCertificate_w(rtc::SSLCertificate** cert) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
if (channels_.empty())
|
|
return false;
|
|
|
|
ChannelMap::iterator iter = channels_.begin();
|
|
return iter->second->GetRemoteCertificate(cert);
|
|
}
|
|
|
|
bool Transport::SetLocalTransportDescription(
|
|
const TransportDescription& description,
|
|
ContentAction action,
|
|
std::string* error_desc) {
|
|
return worker_thread_->Invoke<bool>(Bind(
|
|
&Transport::SetLocalTransportDescription_w, this,
|
|
description, action, error_desc));
|
|
}
|
|
|
|
bool Transport::SetRemoteTransportDescription(
|
|
const TransportDescription& description,
|
|
ContentAction action,
|
|
std::string* error_desc) {
|
|
return worker_thread_->Invoke<bool>(Bind(
|
|
&Transport::SetRemoteTransportDescription_w, this,
|
|
description, action, error_desc));
|
|
}
|
|
|
|
TransportChannelImpl* Transport::CreateChannel(int component) {
|
|
return worker_thread_->Invoke<TransportChannelImpl*>(Bind(
|
|
&Transport::CreateChannel_w, this, component));
|
|
}
|
|
|
|
TransportChannelImpl* Transport::CreateChannel_w(int component) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
TransportChannelImpl *impl;
|
|
rtc::CritScope cs(&crit_);
|
|
|
|
// Create the entry if it does not exist.
|
|
bool impl_exists = false;
|
|
if (channels_.find(component) == channels_.end()) {
|
|
impl = CreateTransportChannel(component);
|
|
channels_[component] = ChannelMapEntry(impl);
|
|
} else {
|
|
impl = channels_[component].get();
|
|
impl_exists = true;
|
|
}
|
|
|
|
// Increase the ref count.
|
|
channels_[component].AddRef();
|
|
destroyed_ = false;
|
|
|
|
if (impl_exists) {
|
|
// If this is an existing channel, we should just return it without
|
|
// connecting to all the signal again.
|
|
return impl;
|
|
}
|
|
|
|
// Push down our transport state to the new channel.
|
|
impl->SetIceRole(ice_role_);
|
|
impl->SetIceTiebreaker(tiebreaker_);
|
|
// TODO(ronghuawu): Change CreateChannel_w to be able to return error since
|
|
// below Apply**Description_w calls can fail.
|
|
if (local_description_)
|
|
ApplyLocalTransportDescription_w(impl, NULL);
|
|
if (remote_description_)
|
|
ApplyRemoteTransportDescription_w(impl, NULL);
|
|
if (local_description_ && remote_description_)
|
|
ApplyNegotiatedTransportDescription_w(impl, NULL);
|
|
|
|
impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState);
|
|
impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState);
|
|
impl->SignalRequestSignaling.connect(
|
|
this, &Transport::OnChannelRequestSignaling);
|
|
impl->SignalCandidateReady.connect(this, &Transport::OnChannelCandidateReady);
|
|
impl->SignalRouteChange.connect(this, &Transport::OnChannelRouteChange);
|
|
impl->SignalCandidatesAllocationDone.connect(
|
|
this, &Transport::OnChannelCandidatesAllocationDone);
|
|
impl->SignalRoleConflict.connect(this, &Transport::OnRoleConflict);
|
|
impl->SignalConnectionRemoved.connect(
|
|
this, &Transport::OnChannelConnectionRemoved);
|
|
|
|
if (connect_requested_) {
|
|
impl->Connect();
|
|
if (channels_.size() == 1) {
|
|
// If this is the first channel, then indicate that we have started
|
|
// connecting.
|
|
signaling_thread()->Post(this, MSG_CONNECTING, NULL);
|
|
}
|
|
}
|
|
return impl;
|
|
}
|
|
|
|
TransportChannelImpl* Transport::GetChannel(int component) {
|
|
rtc::CritScope cs(&crit_);
|
|
ChannelMap::iterator iter = channels_.find(component);
|
|
return (iter != channels_.end()) ? iter->second.get() : NULL;
|
|
}
|
|
|
|
bool Transport::HasChannels() {
|
|
rtc::CritScope cs(&crit_);
|
|
return !channels_.empty();
|
|
}
|
|
|
|
void Transport::DestroyChannel(int component) {
|
|
worker_thread_->Invoke<void>(Bind(
|
|
&Transport::DestroyChannel_w, this, component));
|
|
}
|
|
|
|
void Transport::DestroyChannel_w(int component) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
|
|
TransportChannelImpl* impl = NULL;
|
|
{
|
|
rtc::CritScope cs(&crit_);
|
|
ChannelMap::iterator iter = channels_.find(component);
|
|
if (iter == channels_.end())
|
|
return;
|
|
|
|
iter->second.DecRef();
|
|
if (!iter->second.ref()) {
|
|
impl = iter->second.get();
|
|
channels_.erase(iter);
|
|
}
|
|
}
|
|
|
|
if (connect_requested_ && channels_.empty()) {
|
|
// We're no longer attempting to connect.
|
|
signaling_thread()->Post(this, MSG_CONNECTING, NULL);
|
|
}
|
|
|
|
if (impl) {
|
|
// Check in case the deleted channel was the only non-writable channel.
|
|
OnChannelWritableState(impl);
|
|
DestroyTransportChannel(impl);
|
|
}
|
|
}
|
|
|
|
void Transport::ConnectChannels() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
worker_thread_->Invoke<void>(Bind(&Transport::ConnectChannels_w, this));
|
|
}
|
|
|
|
void Transport::ConnectChannels_w() {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
if (connect_requested_ || channels_.empty())
|
|
return;
|
|
connect_requested_ = true;
|
|
signaling_thread()->Post(
|
|
this, MSG_CANDIDATEREADY, NULL);
|
|
|
|
if (!local_description_) {
|
|
// TOOD(mallinath) : TransportDescription(TD) shouldn't be generated here.
|
|
// As Transport must know TD is offer or answer and cricket::Transport
|
|
// doesn't have the capability to decide it. This should be set by the
|
|
// Session.
|
|
// Session must generate local TD before remote candidates pushed when
|
|
// initiate request initiated by the remote.
|
|
LOG(LS_INFO) << "Transport::ConnectChannels_w: No local description has "
|
|
<< "been set. Will generate one.";
|
|
TransportDescription desc(NS_GINGLE_P2P, std::vector<std::string>(),
|
|
rtc::CreateRandomString(ICE_UFRAG_LENGTH),
|
|
rtc::CreateRandomString(ICE_PWD_LENGTH),
|
|
ICEMODE_FULL, CONNECTIONROLE_NONE, NULL,
|
|
Candidates());
|
|
SetLocalTransportDescription_w(desc, CA_OFFER, NULL);
|
|
}
|
|
|
|
CallChannels_w(&TransportChannelImpl::Connect);
|
|
if (!channels_.empty()) {
|
|
signaling_thread()->Post(this, MSG_CONNECTING, NULL);
|
|
}
|
|
}
|
|
|
|
void Transport::OnConnecting_s() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
SignalConnecting(this);
|
|
}
|
|
|
|
void Transport::DestroyAllChannels() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
worker_thread_->Invoke<void>(
|
|
Bind(&Transport::DestroyAllChannels_w, this));
|
|
worker_thread()->Clear(this);
|
|
signaling_thread()->Clear(this);
|
|
destroyed_ = true;
|
|
}
|
|
|
|
void Transport::DestroyAllChannels_w() {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
std::vector<TransportChannelImpl*> impls;
|
|
{
|
|
rtc::CritScope cs(&crit_);
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end();
|
|
++iter) {
|
|
iter->second.DecRef();
|
|
if (!iter->second.ref())
|
|
impls.push_back(iter->second.get());
|
|
}
|
|
}
|
|
channels_.clear();
|
|
|
|
|
|
for (size_t i = 0; i < impls.size(); ++i)
|
|
DestroyTransportChannel(impls[i]);
|
|
}
|
|
|
|
void Transport::ResetChannels() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
worker_thread_->Invoke<void>(Bind(&Transport::ResetChannels_w, this));
|
|
}
|
|
|
|
void Transport::ResetChannels_w() {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
|
|
// We are no longer attempting to connect
|
|
connect_requested_ = false;
|
|
|
|
// Clear out the old messages, they aren't relevant
|
|
rtc::CritScope cs(&crit_);
|
|
ready_candidates_.clear();
|
|
|
|
// Reset all of the channels
|
|
CallChannels_w(&TransportChannelImpl::Reset);
|
|
}
|
|
|
|
void Transport::OnSignalingReady() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
if (destroyed_) return;
|
|
|
|
worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL);
|
|
|
|
// Notify the subclass.
|
|
OnTransportSignalingReady();
|
|
}
|
|
|
|
void Transport::CallChannels_w(TransportChannelFunc func) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
rtc::CritScope cs(&crit_);
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end();
|
|
++iter) {
|
|
((iter->second.get())->*func)();
|
|
}
|
|
}
|
|
|
|
bool Transport::VerifyCandidate(const Candidate& cand, std::string* error) {
|
|
// No address zero.
|
|
if (cand.address().IsNil() || cand.address().IsAny()) {
|
|
*error = "candidate has address of zero";
|
|
return false;
|
|
}
|
|
|
|
// Disallow all ports below 1024, except for 80 and 443 on public addresses.
|
|
int port = cand.address().port();
|
|
if (cand.protocol() == TCP_PROTOCOL_NAME &&
|
|
(cand.tcptype() == TCPTYPE_ACTIVE_STR || port == 0)) {
|
|
// Expected for active-only candidates per
|
|
// http://tools.ietf.org/html/rfc6544#section-4.5 so no error.
|
|
// Libjingle clients emit port 0, in "active" mode.
|
|
return true;
|
|
}
|
|
if (port < 1024) {
|
|
if ((port != 80) && (port != 443)) {
|
|
*error = "candidate has port below 1024, but not 80 or 443";
|
|
return false;
|
|
}
|
|
|
|
if (cand.address().IsPrivateIP()) {
|
|
*error = "candidate has port of 80 or 443 with private IP address";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Transport::GetStats(TransportStats* stats) {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
return worker_thread_->Invoke<bool>(Bind(
|
|
&Transport::GetStats_w, this, stats));
|
|
}
|
|
|
|
bool Transport::GetStats_w(TransportStats* stats) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
stats->content_name = content_name();
|
|
stats->channel_stats.clear();
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end();
|
|
++iter) {
|
|
TransportChannelStats substats;
|
|
substats.component = iter->second->component();
|
|
if (!iter->second->GetStats(&substats.connection_infos)) {
|
|
return false;
|
|
}
|
|
stats->channel_stats.push_back(substats);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Transport::GetSslRole(rtc::SSLRole* ssl_role) const {
|
|
return worker_thread_->Invoke<bool>(Bind(
|
|
&Transport::GetSslRole_w, this, ssl_role));
|
|
}
|
|
|
|
void Transport::OnRemoteCandidates(const std::vector<Candidate>& candidates) {
|
|
for (std::vector<Candidate>::const_iterator iter = candidates.begin();
|
|
iter != candidates.end();
|
|
++iter) {
|
|
OnRemoteCandidate(*iter);
|
|
}
|
|
}
|
|
|
|
void Transport::OnRemoteCandidate(const Candidate& candidate) {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
if (destroyed_) return;
|
|
|
|
if (!HasChannel(candidate.component())) {
|
|
LOG(LS_WARNING) << "Ignoring candidate for unknown component "
|
|
<< candidate.component();
|
|
return;
|
|
}
|
|
|
|
ChannelParams* params = new ChannelParams(new Candidate(candidate));
|
|
worker_thread()->Post(this, MSG_ONREMOTECANDIDATE, params);
|
|
}
|
|
|
|
void Transport::OnRemoteCandidate_w(const Candidate& candidate) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
ChannelMap::iterator iter = channels_.find(candidate.component());
|
|
// It's ok for a channel to go away while this message is in transit.
|
|
if (iter != channels_.end()) {
|
|
iter->second->OnCandidate(candidate);
|
|
}
|
|
}
|
|
|
|
void Transport::OnChannelReadableState(TransportChannel* channel) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
signaling_thread()->Post(this, MSG_READSTATE, NULL);
|
|
}
|
|
|
|
void Transport::OnChannelReadableState_s() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
TransportState readable = GetTransportState_s(true);
|
|
if (readable_ != readable) {
|
|
readable_ = readable;
|
|
SignalReadableState(this);
|
|
}
|
|
}
|
|
|
|
void Transport::OnChannelWritableState(TransportChannel* channel) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
signaling_thread()->Post(this, MSG_WRITESTATE, NULL);
|
|
|
|
MaybeCompleted_w();
|
|
}
|
|
|
|
void Transport::OnChannelWritableState_s() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
TransportState writable = GetTransportState_s(false);
|
|
if (writable_ != writable) {
|
|
was_writable_ = (writable_ == TRANSPORT_STATE_ALL);
|
|
writable_ = writable;
|
|
SignalWritableState(this);
|
|
}
|
|
}
|
|
|
|
TransportState Transport::GetTransportState_s(bool read) {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
rtc::CritScope cs(&crit_);
|
|
bool any = false;
|
|
bool all = !channels_.empty();
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end();
|
|
++iter) {
|
|
bool b = (read ? iter->second->readable() :
|
|
iter->second->writable());
|
|
any = any || b;
|
|
all = all && b;
|
|
}
|
|
if (all) {
|
|
return TRANSPORT_STATE_ALL;
|
|
} else if (any) {
|
|
return TRANSPORT_STATE_SOME;
|
|
} else {
|
|
return TRANSPORT_STATE_NONE;
|
|
}
|
|
}
|
|
|
|
void Transport::OnChannelRequestSignaling(TransportChannelImpl* channel) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
ChannelParams* params = new ChannelParams(channel->component());
|
|
signaling_thread()->Post(this, MSG_REQUESTSIGNALING, params);
|
|
}
|
|
|
|
void Transport::OnChannelRequestSignaling_s(int component) {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
LOG(LS_INFO) << "Transport: " << content_name_ << ", allocating candidates";
|
|
// Resetting ICE state for the channel.
|
|
{
|
|
rtc::CritScope cs(&crit_);
|
|
ChannelMap::iterator iter = channels_.find(component);
|
|
if (iter != channels_.end())
|
|
iter->second.set_candidates_allocated(false);
|
|
}
|
|
SignalRequestSignaling(this);
|
|
}
|
|
|
|
void Transport::OnChannelCandidateReady(TransportChannelImpl* channel,
|
|
const Candidate& candidate) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
rtc::CritScope cs(&crit_);
|
|
ready_candidates_.push_back(candidate);
|
|
|
|
// We hold any messages until the client lets us connect.
|
|
if (connect_requested_) {
|
|
signaling_thread()->Post(
|
|
this, MSG_CANDIDATEREADY, NULL);
|
|
}
|
|
}
|
|
|
|
void Transport::OnChannelCandidateReady_s() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
ASSERT(connect_requested_);
|
|
|
|
std::vector<Candidate> candidates;
|
|
{
|
|
rtc::CritScope cs(&crit_);
|
|
candidates.swap(ready_candidates_);
|
|
}
|
|
|
|
// we do the deleting of Candidate* here to keep the new above and
|
|
// delete below close to each other
|
|
if (!candidates.empty()) {
|
|
SignalCandidatesReady(this, candidates);
|
|
}
|
|
}
|
|
|
|
void Transport::OnChannelRouteChange(TransportChannel* channel,
|
|
const Candidate& remote_candidate) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
ChannelParams* params = new ChannelParams(new Candidate(remote_candidate));
|
|
params->channel = static_cast<cricket::TransportChannelImpl*>(channel);
|
|
signaling_thread()->Post(this, MSG_ROUTECHANGE, params);
|
|
}
|
|
|
|
void Transport::OnChannelRouteChange_s(const TransportChannel* channel,
|
|
const Candidate& remote_candidate) {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
SignalRouteChange(this, remote_candidate.component(), remote_candidate);
|
|
}
|
|
|
|
void Transport::OnChannelCandidatesAllocationDone(
|
|
TransportChannelImpl* channel) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
rtc::CritScope cs(&crit_);
|
|
ChannelMap::iterator iter = channels_.find(channel->component());
|
|
ASSERT(iter != channels_.end());
|
|
LOG(LS_INFO) << "Transport: " << content_name_ << ", component "
|
|
<< channel->component() << " allocation complete";
|
|
iter->second.set_candidates_allocated(true);
|
|
|
|
// If all channels belonging to this Transport got signal, then
|
|
// forward this signal to upper layer.
|
|
// Can this signal arrive before all transport channels are created?
|
|
for (iter = channels_.begin(); iter != channels_.end(); ++iter) {
|
|
if (!iter->second.candidates_allocated())
|
|
return;
|
|
}
|
|
signaling_thread_->Post(this, MSG_CANDIDATEALLOCATIONCOMPLETE);
|
|
|
|
MaybeCompleted_w();
|
|
}
|
|
|
|
void Transport::OnChannelCandidatesAllocationDone_s() {
|
|
ASSERT(signaling_thread()->IsCurrent());
|
|
LOG(LS_INFO) << "Transport: " << content_name_ << " allocation complete";
|
|
SignalCandidatesAllocationDone(this);
|
|
}
|
|
|
|
void Transport::OnRoleConflict(TransportChannelImpl* channel) {
|
|
signaling_thread_->Post(this, MSG_ROLECONFLICT);
|
|
}
|
|
|
|
void Transport::OnChannelConnectionRemoved(TransportChannelImpl* channel) {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
MaybeCompleted_w();
|
|
|
|
// Check if the state is now Failed.
|
|
// Failed is only available in the Controlling ICE role.
|
|
if (channel->GetIceRole() != ICEROLE_CONTROLLING) {
|
|
return;
|
|
}
|
|
|
|
ChannelMap::iterator iter = channels_.find(channel->component());
|
|
ASSERT(iter != channels_.end());
|
|
// Failed can only occur after candidate allocation has stopped.
|
|
if (!iter->second.candidates_allocated()) {
|
|
return;
|
|
}
|
|
|
|
size_t connections = channel->GetConnectionCount();
|
|
if (connections == 0) {
|
|
// A Transport has failed if any of its channels have no remaining
|
|
// connections.
|
|
signaling_thread_->Post(this, MSG_FAILED);
|
|
}
|
|
}
|
|
|
|
void Transport::MaybeCompleted_w() {
|
|
ASSERT(worker_thread()->IsCurrent());
|
|
|
|
// A Transport's ICE process is completed if all of its channels are writable,
|
|
// have finished allocating candidates, and have pruned all but one of their
|
|
// connections.
|
|
ChannelMap::const_iterator iter;
|
|
for (iter = channels_.begin(); iter != channels_.end(); ++iter) {
|
|
const TransportChannelImpl* channel = iter->second.get();
|
|
if (!(channel->writable() &&
|
|
channel->GetConnectionCount() == 1 &&
|
|
channel->GetIceRole() == ICEROLE_CONTROLLING &&
|
|
iter->second.candidates_allocated())) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
signaling_thread_->Post(this, MSG_COMPLETED);
|
|
}
|
|
|
|
void Transport::SetIceRole_w(IceRole role) {
|
|
rtc::CritScope cs(&crit_);
|
|
ice_role_ = role;
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end(); ++iter) {
|
|
iter->second->SetIceRole(ice_role_);
|
|
}
|
|
}
|
|
|
|
void Transport::SetRemoteIceMode_w(IceMode mode) {
|
|
rtc::CritScope cs(&crit_);
|
|
remote_ice_mode_ = mode;
|
|
// Shouldn't channels be created after this method executed?
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end(); ++iter) {
|
|
iter->second->SetRemoteIceMode(remote_ice_mode_);
|
|
}
|
|
}
|
|
|
|
bool Transport::SetLocalTransportDescription_w(
|
|
const TransportDescription& desc,
|
|
ContentAction action,
|
|
std::string* error_desc) {
|
|
bool ret = true;
|
|
rtc::CritScope cs(&crit_);
|
|
|
|
if (!VerifyIceParams(desc)) {
|
|
return BadTransportDescription("Invalid ice-ufrag or ice-pwd length",
|
|
error_desc);
|
|
}
|
|
|
|
if (local_description_ && IceCredentialsChanged(*local_description_, desc)) {
|
|
IceRole new_ice_role = (action == CA_OFFER) ? ICEROLE_CONTROLLING
|
|
: ICEROLE_CONTROLLED;
|
|
|
|
// It must be called before ApplyLocalTransportDescription_w, which may
|
|
// trigger an ICE restart and depends on the new ICE role.
|
|
SetIceRole_w(new_ice_role);
|
|
}
|
|
|
|
local_description_.reset(new TransportDescription(desc));
|
|
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end(); ++iter) {
|
|
ret &= ApplyLocalTransportDescription_w(iter->second.get(), error_desc);
|
|
}
|
|
if (!ret)
|
|
return false;
|
|
|
|
// If PRANSWER/ANSWER is set, we should decide transport protocol type.
|
|
if (action == CA_PRANSWER || action == CA_ANSWER) {
|
|
ret &= NegotiateTransportDescription_w(action, error_desc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool Transport::SetRemoteTransportDescription_w(
|
|
const TransportDescription& desc,
|
|
ContentAction action,
|
|
std::string* error_desc) {
|
|
bool ret = true;
|
|
rtc::CritScope cs(&crit_);
|
|
|
|
if (!VerifyIceParams(desc)) {
|
|
return BadTransportDescription("Invalid ice-ufrag or ice-pwd length",
|
|
error_desc);
|
|
}
|
|
|
|
remote_description_.reset(new TransportDescription(desc));
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end(); ++iter) {
|
|
ret &= ApplyRemoteTransportDescription_w(iter->second.get(), error_desc);
|
|
}
|
|
|
|
// If PRANSWER/ANSWER is set, we should decide transport protocol type.
|
|
if (action == CA_PRANSWER || action == CA_ANSWER) {
|
|
ret = NegotiateTransportDescription_w(CA_OFFER, error_desc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool Transport::ApplyLocalTransportDescription_w(TransportChannelImpl* ch,
|
|
std::string* error_desc) {
|
|
// If existing protocol_type is HYBRID, we may have not chosen the final
|
|
// protocol type, so update the channel protocol type from the
|
|
// local description. Otherwise, skip updating the protocol type.
|
|
// We check for HYBRID to avoid accidental changes; in the case of a
|
|
// session renegotiation, the new offer will have the google-ice ICE option,
|
|
// so we need to make sure we don't switch back from ICE mode to HYBRID
|
|
// when this happens.
|
|
// There are some other ways we could have solved this, but this is the
|
|
// simplest. The ultimate solution will be to get rid of GICE altogether.
|
|
IceProtocolType protocol_type;
|
|
if (ch->GetIceProtocolType(&protocol_type) &&
|
|
protocol_type == ICEPROTO_HYBRID) {
|
|
ch->SetIceProtocolType(
|
|
TransportProtocolFromDescription(local_description()));
|
|
}
|
|
ch->SetIceCredentials(local_description_->ice_ufrag,
|
|
local_description_->ice_pwd);
|
|
return true;
|
|
}
|
|
|
|
bool Transport::ApplyRemoteTransportDescription_w(TransportChannelImpl* ch,
|
|
std::string* error_desc) {
|
|
ch->SetRemoteIceCredentials(remote_description_->ice_ufrag,
|
|
remote_description_->ice_pwd);
|
|
return true;
|
|
}
|
|
|
|
bool Transport::ApplyNegotiatedTransportDescription_w(
|
|
TransportChannelImpl* channel, std::string* error_desc) {
|
|
channel->SetIceProtocolType(protocol_);
|
|
channel->SetRemoteIceMode(remote_ice_mode_);
|
|
return true;
|
|
}
|
|
|
|
bool Transport::NegotiateTransportDescription_w(ContentAction local_role,
|
|
std::string* error_desc) {
|
|
// TODO(ekr@rtfm.com): This is ICE-specific stuff. Refactor into
|
|
// P2PTransport.
|
|
const TransportDescription* offer;
|
|
const TransportDescription* answer;
|
|
|
|
if (local_role == CA_OFFER) {
|
|
offer = local_description_.get();
|
|
answer = remote_description_.get();
|
|
} else {
|
|
offer = remote_description_.get();
|
|
answer = local_description_.get();
|
|
}
|
|
|
|
TransportProtocol offer_proto = TransportProtocolFromDescription(offer);
|
|
TransportProtocol answer_proto = TransportProtocolFromDescription(answer);
|
|
|
|
// If offered protocol is gice/ice, then we expect to receive matching
|
|
// protocol in answer, anything else is treated as an error.
|
|
// HYBRID is not an option when offered specific protocol.
|
|
// If offered protocol is HYBRID and answered protocol is HYBRID then
|
|
// gice is preferred protocol.
|
|
// TODO(mallinath) - Answer from local or remote should't have both ice
|
|
// and gice support. It should always pick which protocol it wants to use.
|
|
// Once WebRTC stops supporting gice (for backward compatibility), HYBRID in
|
|
// answer must be treated as error.
|
|
if ((offer_proto == ICEPROTO_GOOGLE || offer_proto == ICEPROTO_RFC5245) &&
|
|
(offer_proto != answer_proto)) {
|
|
std::ostringstream desc;
|
|
desc << "Offer and answer protocol mismatch: "
|
|
<< IceProtoToString(offer_proto)
|
|
<< " vs "
|
|
<< IceProtoToString(answer_proto);
|
|
return BadTransportDescription(desc.str(), error_desc);
|
|
}
|
|
protocol_ = answer_proto == ICEPROTO_HYBRID ? ICEPROTO_GOOGLE : answer_proto;
|
|
|
|
// If transport is in ICEROLE_CONTROLLED and remote end point supports only
|
|
// ice_lite, this local end point should take CONTROLLING role.
|
|
if (ice_role_ == ICEROLE_CONTROLLED &&
|
|
remote_description_->ice_mode == ICEMODE_LITE) {
|
|
SetIceRole_w(ICEROLE_CONTROLLING);
|
|
}
|
|
|
|
// Update remote ice_mode to all existing channels.
|
|
remote_ice_mode_ = remote_description_->ice_mode;
|
|
|
|
// Now that we have negotiated everything, push it downward.
|
|
// Note that we cache the result so that if we have race conditions
|
|
// between future SetRemote/SetLocal invocations and new channel
|
|
// creation, we have the negotiation state saved until a new
|
|
// negotiation happens.
|
|
for (ChannelMap::iterator iter = channels_.begin();
|
|
iter != channels_.end();
|
|
++iter) {
|
|
if (!ApplyNegotiatedTransportDescription_w(iter->second.get(), error_desc))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Transport::OnMessage(rtc::Message* msg) {
|
|
switch (msg->message_id) {
|
|
case MSG_ONSIGNALINGREADY:
|
|
CallChannels_w(&TransportChannelImpl::OnSignalingReady);
|
|
break;
|
|
case MSG_ONREMOTECANDIDATE: {
|
|
ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
|
|
OnRemoteCandidate_w(*params->candidate);
|
|
delete params;
|
|
}
|
|
break;
|
|
case MSG_CONNECTING:
|
|
OnConnecting_s();
|
|
break;
|
|
case MSG_READSTATE:
|
|
OnChannelReadableState_s();
|
|
break;
|
|
case MSG_WRITESTATE:
|
|
OnChannelWritableState_s();
|
|
break;
|
|
case MSG_REQUESTSIGNALING: {
|
|
ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
|
|
OnChannelRequestSignaling_s(params->component);
|
|
delete params;
|
|
}
|
|
break;
|
|
case MSG_CANDIDATEREADY:
|
|
OnChannelCandidateReady_s();
|
|
break;
|
|
case MSG_ROUTECHANGE: {
|
|
ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
|
|
OnChannelRouteChange_s(params->channel, *params->candidate);
|
|
delete params;
|
|
}
|
|
break;
|
|
case MSG_CANDIDATEALLOCATIONCOMPLETE:
|
|
OnChannelCandidatesAllocationDone_s();
|
|
break;
|
|
case MSG_ROLECONFLICT:
|
|
SignalRoleConflict();
|
|
break;
|
|
case MSG_COMPLETED:
|
|
SignalCompleted(this);
|
|
break;
|
|
case MSG_FAILED:
|
|
SignalFailed(this);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool TransportParser::ParseAddress(const buzz::XmlElement* elem,
|
|
const buzz::QName& address_name,
|
|
const buzz::QName& port_name,
|
|
rtc::SocketAddress* address,
|
|
ParseError* error) {
|
|
if (!elem->HasAttr(address_name))
|
|
return BadParse("address does not have " + address_name.LocalPart(), error);
|
|
if (!elem->HasAttr(port_name))
|
|
return BadParse("address does not have " + port_name.LocalPart(), error);
|
|
|
|
address->SetIP(elem->Attr(address_name));
|
|
std::istringstream ist(elem->Attr(port_name));
|
|
int port = 0;
|
|
ist >> port;
|
|
address->SetPort(port);
|
|
|
|
return true;
|
|
}
|
|
|
|
// We're GICE if the namespace is NS_GOOGLE_P2P, or if NS_JINGLE_ICE_UDP is
|
|
// used and the GICE ice-option is set.
|
|
TransportProtocol TransportProtocolFromDescription(
|
|
const TransportDescription* desc) {
|
|
ASSERT(desc != NULL);
|
|
if (desc->transport_type == NS_JINGLE_ICE_UDP) {
|
|
return (desc->HasOption(ICE_OPTION_GICE)) ?
|
|
ICEPROTO_HYBRID : ICEPROTO_RFC5245;
|
|
}
|
|
return ICEPROTO_GOOGLE;
|
|
}
|
|
|
|
} // namespace cricket
|