/* * libjingle * Copyright 2004--2005, 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/rawtransportchannel.h" #include #include #include "talk/base/common.h" #include "talk/p2p/base/constants.h" #include "talk/p2p/base/portallocator.h" #include "talk/p2p/base/portinterface.h" #include "talk/p2p/base/rawtransport.h" #include "talk/p2p/base/relayport.h" #include "talk/p2p/base/sessionmanager.h" #include "talk/p2p/base/stunport.h" #include "talk/xmllite/qname.h" #include "talk/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #if defined(FEATURE_ENABLE_PSTN) namespace { const uint32 MSG_DESTROY_UNUSED_PORTS = 1; } // namespace namespace cricket { RawTransportChannel::RawTransportChannel(const std::string& content_name, int component, RawTransport* transport, talk_base::Thread *worker_thread, PortAllocator *allocator) : TransportChannelImpl(content_name, component), raw_transport_(transport), allocator_(allocator), allocator_session_(NULL), stun_port_(NULL), relay_port_(NULL), port_(NULL), use_relay_(false) { if (worker_thread == NULL) worker_thread_ = raw_transport_->worker_thread(); else worker_thread_ = worker_thread; } RawTransportChannel::~RawTransportChannel() { delete allocator_session_; } int RawTransportChannel::SendPacket(const char *data, size_t size, const talk_base::PacketOptions& options, int flags) { if (port_ == NULL) return -1; if (remote_address_.IsNil()) return -1; if (flags != 0) return -1; return port_->SendTo(data, size, remote_address_, options, true); } int RawTransportChannel::SetOption(talk_base::Socket::Option opt, int value) { // TODO: allow these to be set before we have a port if (port_ == NULL) return -1; return port_->SetOption(opt, value); } int RawTransportChannel::GetError() { return (port_ != NULL) ? port_->GetError() : 0; } void RawTransportChannel::Connect() { // Create an allocator that only returns stun and relay ports. // Use empty string for ufrag and pwd here. There won't be any STUN or relay // interactions when using RawTC. // TODO: Change raw to only use local udp ports. allocator_session_ = allocator_->CreateSession( SessionId(), content_name(), component(), "", ""); uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP; #if !defined(FEATURE_ENABLE_STUN_CLASSIFICATION) flags |= PORTALLOCATOR_DISABLE_RELAY; #endif allocator_session_->set_flags(flags); allocator_session_->SignalPortReady.connect( this, &RawTransportChannel::OnPortReady); allocator_session_->SignalCandidatesReady.connect( this, &RawTransportChannel::OnCandidatesReady); // The initial ports will include stun. allocator_session_->StartGettingPorts(); } void RawTransportChannel::Reset() { set_readable(false); set_writable(false); delete allocator_session_; allocator_session_ = NULL; stun_port_ = NULL; relay_port_ = NULL; port_ = NULL; remote_address_ = talk_base::SocketAddress(); } void RawTransportChannel::OnCandidate(const Candidate& candidate) { remote_address_ = candidate.address(); ASSERT(!remote_address_.IsNil()); set_readable(true); // We can write once we have a port and a remote address. if (port_ != NULL) SetWritable(); } void RawTransportChannel::OnRemoteAddress( const talk_base::SocketAddress& remote_address) { remote_address_ = remote_address; set_readable(true); if (port_ != NULL) SetWritable(); } // Note about stun classification // Code to classify our NAT type and use the relay port if we are behind an // asymmetric NAT is under a FEATURE_ENABLE_STUN_CLASSIFICATION #define. // To turn this one we will have to enable a second stun address and make sure // that the relay server works for raw UDP. // // Another option is to classify the NAT type early and not offer the raw // transport type at all if we can't support it. void RawTransportChannel::OnPortReady( PortAllocatorSession* session, PortInterface* port) { ASSERT(session == allocator_session_); if (port->Type() == STUN_PORT_TYPE) { stun_port_ = static_cast(port); } else if (port->Type() == RELAY_PORT_TYPE) { relay_port_ = static_cast(port); } else { ASSERT(false); } } void RawTransportChannel::OnCandidatesReady( PortAllocatorSession *session, const std::vector& candidates) { ASSERT(session == allocator_session_); ASSERT(candidates.size() >= 1); // The most recent candidate is the one we haven't seen yet. Candidate c = candidates[candidates.size() - 1]; if (c.type() == STUN_PORT_TYPE) { ASSERT(stun_port_ != NULL); #if defined(FEATURE_ENABLE_STUN_CLASSIFICATION) // We need to wait until we have two addresses. if (stun_port_->candidates().size() < 2) return; // This is the second address. If these addresses are the same, then we // are not behind a symmetric NAT. Hence, a stun port should be sufficient. if (stun_port_->candidates()[0].address() == stun_port_->candidates()[1].address()) { SetPort(stun_port_); return; } // We will need to use relay. use_relay_ = true; // If we already have a relay address, we're good. Otherwise, we will need // to wait until one arrives. if (relay_port_->candidates().size() > 0) SetPort(relay_port_); #else // defined(FEATURE_ENABLE_STUN_CLASSIFICATION) // Always use the stun port. We don't classify right now so just assume it // will work fine. SetPort(stun_port_); #endif } else if (c.type() == RELAY_PORT_TYPE) { if (use_relay_) SetPort(relay_port_); } else { ASSERT(false); } } void RawTransportChannel::SetPort(PortInterface* port) { ASSERT(port_ == NULL); port_ = port; // We don't need any ports other than the one we picked. allocator_session_->StopGettingPorts(); worker_thread_->Post( this, MSG_DESTROY_UNUSED_PORTS, NULL); // Send a message to the other client containing our address. ASSERT(port_->Candidates().size() >= 1); ASSERT(port_->Candidates()[0].protocol() == "udp"); SignalCandidateReady(this, port_->Candidates()[0]); // Read all packets from this port. port_->EnablePortPackets(); port_->SignalReadPacket.connect(this, &RawTransportChannel::OnReadPacket); // We can write once we have a port and a remote address. if (!remote_address_.IsAny()) SetWritable(); } void RawTransportChannel::SetWritable() { ASSERT(port_ != NULL); ASSERT(!remote_address_.IsAny()); set_writable(true); Candidate remote_candidate; remote_candidate.set_address(remote_address_); SignalRouteChange(this, remote_candidate); } void RawTransportChannel::OnReadPacket( PortInterface* port, const char* data, size_t size, const talk_base::SocketAddress& addr) { ASSERT(port_ == port); SignalReadPacket(this, data, size, talk_base::CreatePacketTime(0), 0); } void RawTransportChannel::OnMessage(talk_base::Message* msg) { ASSERT(msg->message_id == MSG_DESTROY_UNUSED_PORTS); ASSERT(port_ != NULL); if (port_ != stun_port_) { stun_port_->Destroy(); stun_port_ = NULL; } if (port_ != relay_port_ && relay_port_ != NULL) { relay_port_->Destroy(); relay_port_ = NULL; } } } // namespace cricket #endif // defined(FEATURE_ENABLE_PSTN)