| /* |
| * Copyright 2004 The WebRTC Project Authors. All rights reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| #include <errno.h> |
| #include <string.h> |
| #include <algorithm> |
| |
| #include "p2p/base/connection.h" |
| #include "p2p/base/relay_port.h" |
| #include "p2p/base/stun.h" |
| #include "p2p/base/stun_request.h" |
| #include "rtc_base/async_packet_socket.h" |
| #include "rtc_base/byte_buffer.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/dscp.h" |
| #include "rtc_base/location.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/message_handler.h" |
| #include "rtc_base/message_queue.h" |
| #include "rtc_base/net_helper.h" |
| #include "rtc_base/proxy_info.h" |
| #include "rtc_base/time_utils.h" |
| |
| namespace cricket { |
| |
| static const int kMessageConnectTimeout = 1; |
| static const int kKeepAliveDelay = 10 * 60 * 1000; |
| static const int kRetryTimeout = 50 * 1000; // ICE says 50 secs |
| // How long to wait for a socket to connect to remote host in milliseconds |
| // before trying another connection. |
| static const int kSoftConnectTimeoutMs = 3 * 1000; |
| |
| // Handles a connection to one address/port/protocol combination for a |
| // particular RelayEntry. |
| class RelayConnection : public sigslot::has_slots<> { |
| public: |
| RelayConnection(const ProtocolAddress* protocol_address, |
| rtc::AsyncPacketSocket* socket, |
| rtc::Thread* thread); |
| ~RelayConnection() override; |
| rtc::AsyncPacketSocket* socket() const { return socket_; } |
| |
| const ProtocolAddress* protocol_address() { return protocol_address_; } |
| |
| rtc::SocketAddress GetAddress() const { return protocol_address_->address; } |
| |
| ProtocolType GetProtocol() const { return protocol_address_->proto; } |
| |
| int SetSocketOption(rtc::Socket::Option opt, int value); |
| |
| // Validates a response to a STUN allocate request. |
| bool CheckResponse(StunMessage* msg); |
| |
| // Sends data to the relay server. |
| int Send(const void* pv, size_t cb, const rtc::PacketOptions& options); |
| |
| // Sends a STUN allocate request message to the relay server. |
| void SendAllocateRequest(RelayEntry* entry, int delay); |
| |
| // Return the latest error generated by the socket. |
| int GetError() { return socket_->GetError(); } |
| |
| // Called on behalf of a StunRequest to write data to the socket. This is |
| // already STUN intended for the server, so no wrapping is necessary. |
| void OnSendPacket(const void* data, size_t size, StunRequest* req); |
| |
| private: |
| rtc::AsyncPacketSocket* socket_; |
| const ProtocolAddress* protocol_address_; |
| StunRequestManager* request_manager_; |
| rtc::DiffServCodePoint dscp_; |
| }; |
| |
| // Manages a number of connections to the relayserver, one for each |
| // available protocol. We aim to use each connection for only a |
| // specific destination address so that we can avoid wrapping every |
| // packet in a STUN send / data indication. |
| class RelayEntry : public rtc::MessageHandler, public sigslot::has_slots<> { |
| public: |
| RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr); |
| ~RelayEntry() override; |
| |
| RelayPort* port() { return port_; } |
| |
| const rtc::SocketAddress& address() const { return ext_addr_; } |
| void set_address(const rtc::SocketAddress& addr) { ext_addr_ = addr; } |
| |
| bool connected() const { return connected_; } |
| bool locked() const { return locked_; } |
| |
| // Returns the last error on the socket of this entry. |
| int GetError(); |
| |
| // Returns the most preferred connection of the given |
| // ones. Connections are rated based on protocol in the order of: |
| // UDP, TCP and SSLTCP, where UDP is the most preferred protocol |
| static RelayConnection* GetBestConnection(RelayConnection* conn1, |
| RelayConnection* conn2); |
| |
| // Sends the STUN requests to the server to initiate this connection. |
| void Connect(); |
| |
| // Called when this entry becomes connected. The address given is the one |
| // exposed to the outside world on the relay server. |
| void OnConnect(const rtc::SocketAddress& mapped_addr, |
| RelayConnection* socket); |
| |
| // Sends a packet to the given destination address using the socket of this |
| // entry. This will wrap the packet in STUN if necessary. |
| int SendTo(const void* data, |
| size_t size, |
| const rtc::SocketAddress& addr, |
| const rtc::PacketOptions& options); |
| |
| // Schedules a keep-alive allocate request. |
| void ScheduleKeepAlive(); |
| |
| void SetServerIndex(size_t sindex) { server_index_ = sindex; } |
| |
| // Sets this option on the socket of each connection. |
| int SetSocketOption(rtc::Socket::Option opt, int value); |
| |
| size_t ServerIndex() const { return server_index_; } |
| |
| // Try a different server address |
| void HandleConnectFailure(rtc::AsyncPacketSocket* socket); |
| |
| // Implementation of the MessageHandler Interface. |
| void OnMessage(rtc::Message* pmsg) override; |
| |
| private: |
| RelayPort* port_; |
| rtc::SocketAddress ext_addr_; |
| size_t server_index_; |
| bool connected_; |
| bool locked_; |
| RelayConnection* current_connection_; |
| |
| // Called when a TCP connection is established or fails |
| void OnSocketConnect(rtc::AsyncPacketSocket* socket); |
| void OnSocketClose(rtc::AsyncPacketSocket* socket, int error); |
| |
| // Called when a packet is received on this socket. |
| void OnReadPacket(rtc::AsyncPacketSocket* socket, |
| const char* data, |
| size_t size, |
| const rtc::SocketAddress& remote_addr, |
| const int64_t& packet_time_us); |
| |
| void OnSentPacket(rtc::AsyncPacketSocket* socket, |
| const rtc::SentPacket& sent_packet); |
| |
| // Called when the socket is currently able to send. |
| void OnReadyToSend(rtc::AsyncPacketSocket* socket); |
| |
| // Sends the given data on the socket to the server with no wrapping. This |
| // returns the number of bytes written or -1 if an error occurred. |
| int SendPacket(const void* data, |
| size_t size, |
| const rtc::PacketOptions& options); |
| }; |
| |
| // Handles an allocate request for a particular RelayEntry. |
| class AllocateRequest : public StunRequest { |
| public: |
| AllocateRequest(RelayEntry* entry, RelayConnection* connection); |
| ~AllocateRequest() override = default; |
| |
| void Prepare(StunMessage* request) override; |
| |
| void OnSent() override; |
| int resend_delay() override; |
| |
| void OnResponse(StunMessage* response) override; |
| void OnErrorResponse(StunMessage* response) override; |
| void OnTimeout() override; |
| |
| private: |
| RelayEntry* entry_; |
| RelayConnection* connection_; |
| int64_t start_time_; |
| }; |
| |
| RelayPort::RelayPort(rtc::Thread* thread, |
| rtc::PacketSocketFactory* factory, |
| rtc::Network* network, |
| uint16_t min_port, |
| uint16_t max_port, |
| const std::string& username, |
| const std::string& password) |
| : Port(thread, |
| RELAY_PORT_TYPE, |
| factory, |
| network, |
| min_port, |
| max_port, |
| username, |
| password), |
| ready_(false), |
| error_(0) { |
| entries_.push_back(new RelayEntry(this, rtc::SocketAddress())); |
| // TODO(?): set local preference value for TCP based candidates. |
| } |
| |
| RelayPort::~RelayPort() { |
| for (size_t i = 0; i < entries_.size(); ++i) |
| delete entries_[i]; |
| thread()->Clear(this); |
| } |
| |
| void RelayPort::AddServerAddress(const ProtocolAddress& addr) { |
| // Since HTTP proxies usually only allow 443, |
| // let's up the priority on PROTO_SSLTCP |
| if (addr.proto == PROTO_SSLTCP && (proxy().type == rtc::PROXY_HTTPS || |
| proxy().type == rtc::PROXY_UNKNOWN)) { |
| server_addr_.push_front(addr); |
| } else { |
| server_addr_.push_back(addr); |
| } |
| } |
| |
| void RelayPort::AddExternalAddress(const ProtocolAddress& addr) { |
| std::string proto_name = ProtoToString(addr.proto); |
| for (std::vector<ProtocolAddress>::iterator it = external_addr_.begin(); |
| it != external_addr_.end(); ++it) { |
| if ((it->address == addr.address) && (it->proto == addr.proto)) { |
| RTC_LOG(INFO) << "Redundant relay address: " << proto_name << " @ " |
| << addr.address.ToSensitiveString(); |
| return; |
| } |
| } |
| external_addr_.push_back(addr); |
| } |
| |
| void RelayPort::SetReady() { |
| if (!ready_) { |
| std::vector<ProtocolAddress>::iterator iter; |
| for (iter = external_addr_.begin(); iter != external_addr_.end(); ++iter) { |
| std::string proto_name = ProtoToString(iter->proto); |
| // In case of Gturn, related address is set to null socket address. |
| // This is due to as mapped address stun attribute is used for allocated |
| // address. |
| AddAddress(iter->address, iter->address, rtc::SocketAddress(), proto_name, |
| proto_name, "", RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY_UDP, |
| 0, "", false); |
| } |
| ready_ = true; |
| SignalPortComplete(this); |
| } |
| } |
| |
| const ProtocolAddress* RelayPort::ServerAddress(size_t index) const { |
| if (index < server_addr_.size()) |
| return &server_addr_[index]; |
| return NULL; |
| } |
| |
| bool RelayPort::HasMagicCookie(const char* data, size_t size) { |
| if (size < 24 + sizeof(TURN_MAGIC_COOKIE_VALUE)) { |
| return false; |
| } else { |
| return memcmp(data + 24, TURN_MAGIC_COOKIE_VALUE, |
| sizeof(TURN_MAGIC_COOKIE_VALUE)) == 0; |
| } |
| } |
| |
| void RelayPort::PrepareAddress() { |
| // We initiate a connect on the first entry. If this completes, it will fill |
| // in the server address as the address of this port. |
| RTC_DCHECK(entries_.size() == 1); |
| entries_[0]->Connect(); |
| ready_ = false; |
| } |
| |
| Connection* RelayPort::CreateConnection(const Candidate& address, |
| CandidateOrigin origin) { |
| // We only create conns to non-udp sockets if they are incoming on this port |
| if ((address.protocol() != UDP_PROTOCOL_NAME) && |
| (origin != ORIGIN_THIS_PORT)) { |
| return 0; |
| } |
| |
| // We don't support loopback on relays |
| if (address.type() == Type()) { |
| return 0; |
| } |
| |
| if (!IsCompatibleAddress(address.address())) { |
| return 0; |
| } |
| |
| size_t index = 0; |
| for (size_t i = 0; i < Candidates().size(); ++i) { |
| const Candidate& local = Candidates()[i]; |
| if (local.protocol() == address.protocol()) { |
| index = i; |
| break; |
| } |
| } |
| |
| Connection* conn = new ProxyConnection(this, index, address); |
| AddOrReplaceConnection(conn); |
| return conn; |
| } |
| |
| int RelayPort::SendTo(const void* data, |
| size_t size, |
| const rtc::SocketAddress& addr, |
| const rtc::PacketOptions& options, |
| bool payload) { |
| // Try to find an entry for this specific address. Note that the first entry |
| // created was not given an address initially, so it can be set to the first |
| // address that comes along. |
| RelayEntry* entry = 0; |
| |
| for (size_t i = 0; i < entries_.size(); ++i) { |
| if (entries_[i]->address().IsNil() && payload) { |
| entry = entries_[i]; |
| entry->set_address(addr); |
| break; |
| } else if (entries_[i]->address() == addr) { |
| entry = entries_[i]; |
| break; |
| } |
| } |
| |
| // If we did not find one, then we make a new one. This will not be useable |
| // until it becomes connected, however. |
| if (!entry && payload) { |
| entry = new RelayEntry(this, addr); |
| if (!entries_.empty()) { |
| entry->SetServerIndex(entries_[0]->ServerIndex()); |
| } |
| entry->Connect(); |
| entries_.push_back(entry); |
| } |
| |
| // If the entry is connected, then we can send on it (though wrapping may |
| // still be necessary). Otherwise, we can't yet use this connection, so we |
| // default to the first one. |
| if (!entry || !entry->connected()) { |
| RTC_DCHECK(!entries_.empty()); |
| entry = entries_[0]; |
| if (!entry->connected()) { |
| error_ = ENOTCONN; |
| return SOCKET_ERROR; |
| } |
| } |
| |
| // Send the actual contents to the server using the usual mechanism. |
| rtc::PacketOptions modified_options(options); |
| CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent); |
| int sent = entry->SendTo(data, size, addr, modified_options); |
| if (sent <= 0) { |
| RTC_DCHECK(sent < 0); |
| error_ = entry->GetError(); |
| return SOCKET_ERROR; |
| } |
| // The caller of the function is expecting the number of user data bytes, |
| // rather than the size of the packet. |
| return static_cast<int>(size); |
| } |
| |
| int RelayPort::SetOption(rtc::Socket::Option opt, int value) { |
| int result = 0; |
| for (size_t i = 0; i < entries_.size(); ++i) { |
| if (entries_[i]->SetSocketOption(opt, value) < 0) { |
| result = -1; |
| error_ = entries_[i]->GetError(); |
| } |
| } |
| options_.push_back(OptionValue(opt, value)); |
| return result; |
| } |
| |
| int RelayPort::GetOption(rtc::Socket::Option opt, int* value) { |
| std::vector<OptionValue>::iterator it; |
| for (it = options_.begin(); it < options_.end(); ++it) { |
| if (it->first == opt) { |
| *value = it->second; |
| return 0; |
| } |
| } |
| return SOCKET_ERROR; |
| } |
| |
| int RelayPort::GetError() { |
| return error_; |
| } |
| |
| bool RelayPort::SupportsProtocol(const std::string& protocol) const { |
| // Relay port may create both TCP and UDP connections. |
| return true; |
| } |
| |
| ProtocolType RelayPort::GetProtocol() const { |
| // We shouldn't be using RelayPort, but we need to provide an implementation |
| // here. |
| return PROTO_UDP; |
| } |
| |
| void RelayPort::OnReadPacket(const char* data, |
| size_t size, |
| const rtc::SocketAddress& remote_addr, |
| ProtocolType proto, |
| int64_t packet_time_us) { |
| if (Connection* conn = GetConnection(remote_addr)) { |
| conn->OnReadPacket(data, size, packet_time_us); |
| } else { |
| Port::OnReadPacket(data, size, remote_addr, proto); |
| } |
| } |
| |
| RelayConnection::RelayConnection(const ProtocolAddress* protocol_address, |
| rtc::AsyncPacketSocket* socket, |
| rtc::Thread* thread) |
| : socket_(socket), |
| protocol_address_(protocol_address), |
| dscp_(rtc::DSCP_NO_CHANGE) { |
| request_manager_ = new StunRequestManager(thread); |
| request_manager_->SignalSendPacket.connect(this, |
| &RelayConnection::OnSendPacket); |
| } |
| |
| RelayConnection::~RelayConnection() { |
| delete request_manager_; |
| delete socket_; |
| } |
| |
| int RelayConnection::SetSocketOption(rtc::Socket::Option opt, int value) { |
| if (opt == rtc::Socket::OPT_DSCP) { |
| dscp_ = static_cast<rtc::DiffServCodePoint>(value); |
| } |
| if (socket_) { |
| return socket_->SetOption(opt, value); |
| } |
| return 0; |
| } |
| |
| bool RelayConnection::CheckResponse(StunMessage* msg) { |
| return request_manager_->CheckResponse(msg); |
| } |
| |
| void RelayConnection::OnSendPacket(const void* data, |
| size_t size, |
| StunRequest* req) { |
| rtc::PacketOptions options(dscp_); |
| int sent = socket_->SendTo(data, size, GetAddress(), options); |
| if (sent <= 0) { |
| RTC_LOG(LS_VERBOSE) << "OnSendPacket: failed sending to " |
| << GetAddress().ToString() |
| << strerror(socket_->GetError()); |
| RTC_DCHECK(sent < 0); |
| } |
| } |
| |
| int RelayConnection::Send(const void* pv, |
| size_t cb, |
| const rtc::PacketOptions& options) { |
| return socket_->SendTo(pv, cb, GetAddress(), options); |
| } |
| |
| void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) { |
| request_manager_->SendDelayed(new AllocateRequest(entry, this), delay); |
| } |
| |
| RelayEntry::RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr) |
| : port_(port), |
| ext_addr_(ext_addr), |
| server_index_(0), |
| connected_(false), |
| locked_(false), |
| current_connection_(NULL) {} |
| |
| RelayEntry::~RelayEntry() { |
| // Remove all RelayConnections and dispose sockets. |
| delete current_connection_; |
| current_connection_ = NULL; |
| } |
| |
| void RelayEntry::Connect() { |
| // If we're already connected, return. |
| if (connected_) |
| return; |
| |
| // If we've exhausted all options, bail out. |
| const ProtocolAddress* ra = port()->ServerAddress(server_index_); |
| if (!ra) { |
| RTC_LOG(LS_WARNING) << "No more relay addresses left to try"; |
| return; |
| } |
| |
| // Remove any previous connection. |
| if (current_connection_) { |
| port()->thread()->Dispose(current_connection_); |
| current_connection_ = NULL; |
| } |
| |
| // Try to set up our new socket. |
| RTC_LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto) |
| << " @ " << ra->address.ToSensitiveString(); |
| |
| rtc::AsyncPacketSocket* socket = NULL; |
| |
| if (ra->proto == PROTO_UDP) { |
| // UDP sockets are simple. |
| socket = port_->socket_factory()->CreateUdpSocket( |
| rtc::SocketAddress(port_->Network()->GetBestIP(), 0), port_->min_port(), |
| port_->max_port()); |
| } else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) { |
| int opts = (ra->proto == PROTO_SSLTCP) |
| ? rtc::PacketSocketFactory::OPT_TLS_FAKE |
| : 0; |
| socket = port_->socket_factory()->CreateClientTcpSocket( |
| rtc::SocketAddress(port_->Network()->GetBestIP(), 0), ra->address, |
| port_->proxy(), port_->user_agent(), opts); |
| } else { |
| RTC_LOG(LS_WARNING) << "Unknown protocol: " << ra->proto; |
| } |
| |
| // If we failed to get a socket, move on to the next protocol. |
| if (!socket) { |
| RTC_LOG(LS_WARNING) << "Socket creation failed"; |
| port()->thread()->Post(RTC_FROM_HERE, this, kMessageConnectTimeout); |
| return; |
| } |
| |
| // Otherwise, create the new connection and configure any socket options. |
| socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket); |
| socket->SignalSentPacket.connect(this, &RelayEntry::OnSentPacket); |
| socket->SignalReadyToSend.connect(this, &RelayEntry::OnReadyToSend); |
| current_connection_ = new RelayConnection(ra, socket, port()->thread()); |
| for (size_t i = 0; i < port_->options().size(); ++i) { |
| current_connection_->SetSocketOption(port_->options()[i].first, |
| port_->options()[i].second); |
| } |
| |
| // If we're trying UDP, start binding requests. |
| // If we're trying TCP, wait for connection with a fixed timeout. |
| if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) { |
| socket->SignalClose.connect(this, &RelayEntry::OnSocketClose); |
| socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect); |
| port()->thread()->PostDelayed(RTC_FROM_HERE, kSoftConnectTimeoutMs, this, |
| kMessageConnectTimeout); |
| } else { |
| current_connection_->SendAllocateRequest(this, 0); |
| } |
| } |
| |
| int RelayEntry::GetError() { |
| if (current_connection_ != NULL) { |
| return current_connection_->GetError(); |
| } |
| return 0; |
| } |
| |
| RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1, |
| RelayConnection* conn2) { |
| return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2; |
| } |
| |
| void RelayEntry::OnConnect(const rtc::SocketAddress& mapped_addr, |
| RelayConnection* connection) { |
| // We are connected, notify our parent. |
| ProtocolType proto = PROTO_UDP; |
| RTC_LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto) << " @ " |
| << mapped_addr.ToSensitiveString(); |
| connected_ = true; |
| |
| port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto)); |
| port_->SetReady(); |
| } |
| |
| int RelayEntry::SendTo(const void* data, |
| size_t size, |
| const rtc::SocketAddress& addr, |
| const rtc::PacketOptions& options) { |
| // If this connection is locked to the address given, then we can send the |
| // packet with no wrapper. |
| if (locked_ && (ext_addr_ == addr)) |
| return SendPacket(data, size, options); |
| |
| // Otherwise, we must wrap the given data in a STUN SEND request so that we |
| // can communicate the destination address to the server. |
| // |
| // Note that we do not use a StunRequest here. This is because there is |
| // likely no reason to resend this packet. If it is late, we just drop it. |
| // The next send to this address will try again. |
| |
| RelayMessage request; |
| request.SetType(STUN_SEND_REQUEST); |
| |
| auto magic_cookie_attr = |
| StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); |
| magic_cookie_attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, |
| sizeof(TURN_MAGIC_COOKIE_VALUE)); |
| request.AddAttribute(std::move(magic_cookie_attr)); |
| |
| auto username_attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME); |
| username_attr->CopyBytes(port_->username_fragment().c_str(), |
| port_->username_fragment().size()); |
| request.AddAttribute(std::move(username_attr)); |
| |
| auto addr_attr = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); |
| addr_attr->SetIP(addr.ipaddr()); |
| addr_attr->SetPort(addr.port()); |
| request.AddAttribute(std::move(addr_attr)); |
| |
| // Attempt to lock |
| if (ext_addr_ == addr) { |
| auto options_attr = StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS); |
| options_attr->SetValue(0x1); |
| request.AddAttribute(std::move(options_attr)); |
| } |
| |
| auto data_attr = StunAttribute::CreateByteString(STUN_ATTR_DATA); |
| data_attr->CopyBytes(data, size); |
| request.AddAttribute(std::move(data_attr)); |
| |
| // TODO(?): compute the HMAC. |
| |
| rtc::ByteBufferWriter buf; |
| request.Write(&buf); |
| |
| return SendPacket(buf.Data(), buf.Length(), options); |
| } |
| |
| void RelayEntry::ScheduleKeepAlive() { |
| if (current_connection_) { |
| current_connection_->SendAllocateRequest(this, kKeepAliveDelay); |
| } |
| } |
| |
| int RelayEntry::SetSocketOption(rtc::Socket::Option opt, int value) { |
| // Set the option on all available sockets. |
| int socket_error = 0; |
| if (current_connection_) { |
| socket_error = current_connection_->SetSocketOption(opt, value); |
| } |
| return socket_error; |
| } |
| |
| void RelayEntry::HandleConnectFailure(rtc::AsyncPacketSocket* socket) { |
| // Make sure it's the current connection that has failed, it might |
| // be an old socked that has not yet been disposed. |
| if (!socket || |
| (current_connection_ && socket == current_connection_->socket())) { |
| if (current_connection_) |
| port()->SignalConnectFailure(current_connection_->protocol_address()); |
| |
| // Try to connect to the next server address. |
| server_index_ += 1; |
| Connect(); |
| } |
| } |
| |
| void RelayEntry::OnMessage(rtc::Message* pmsg) { |
| RTC_DCHECK(pmsg->message_id == kMessageConnectTimeout); |
| if (current_connection_) { |
| const ProtocolAddress* ra = current_connection_->protocol_address(); |
| RTC_LOG(LS_WARNING) << "Relay " << ra->proto << " connection to " |
| << ra->address.ToString() << " timed out"; |
| |
| // Currently we connect to each server address in sequence. If we |
| // have more addresses to try, treat this is an error and move on to |
| // the next address, otherwise give this connection more time and |
| // await the real timeout. |
| // |
| // TODO(?): Connect to servers in parallel to speed up connect time |
| // and to avoid giving up too early. |
| port_->SignalSoftTimeout(ra); |
| HandleConnectFailure(current_connection_->socket()); |
| } else { |
| HandleConnectFailure(NULL); |
| } |
| } |
| |
| void RelayEntry::OnSocketConnect(rtc::AsyncPacketSocket* socket) { |
| RTC_LOG(INFO) << "relay tcp connected to " |
| << socket->GetRemoteAddress().ToSensitiveString(); |
| if (current_connection_ != NULL) { |
| current_connection_->SendAllocateRequest(this, 0); |
| } |
| } |
| |
| void RelayEntry::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) { |
| RTC_LOG_ERR_EX(LERROR, error) << "Relay connection failed: socket closed"; |
| HandleConnectFailure(socket); |
| } |
| |
| void RelayEntry::OnReadPacket(rtc::AsyncPacketSocket* socket, |
| const char* data, |
| size_t size, |
| const rtc::SocketAddress& remote_addr, |
| const int64_t& packet_time_us) { |
| // RTC_DCHECK(remote_addr == port_->server_addr()); |
| // TODO(?): are we worried about this? |
| |
| if (current_connection_ == NULL || socket != current_connection_->socket()) { |
| // This packet comes from an unknown address. |
| RTC_LOG(WARNING) << "Dropping packet: unknown address"; |
| return; |
| } |
| |
| // If the magic cookie is not present, then this is an unwrapped packet sent |
| // by the server, The actual remote address is the one we recorded. |
| if (!port_->HasMagicCookie(data, size)) { |
| if (locked_) { |
| port_->OnReadPacket(data, size, ext_addr_, PROTO_UDP, packet_time_us); |
| } else { |
| RTC_LOG(WARNING) << "Dropping packet: entry not locked"; |
| } |
| return; |
| } |
| |
| rtc::ByteBufferReader buf(data, size); |
| RelayMessage msg; |
| if (!msg.Read(&buf)) { |
| RTC_LOG(INFO) << "Incoming packet was not STUN"; |
| return; |
| } |
| |
| // The incoming packet should be a STUN ALLOCATE response, SEND response, or |
| // DATA indication. |
| if (current_connection_->CheckResponse(&msg)) { |
| return; |
| } else if (msg.type() == STUN_SEND_RESPONSE) { |
| if (const StunUInt32Attribute* options_attr = |
| msg.GetUInt32(STUN_ATTR_OPTIONS)) { |
| if (options_attr->value() & 0x1) { |
| locked_ = true; |
| } |
| } |
| return; |
| } else if (msg.type() != STUN_DATA_INDICATION) { |
| RTC_LOG(INFO) << "Received BAD stun type from server: " << msg.type(); |
| return; |
| } |
| |
| // This must be a data indication. |
| |
| const StunAddressAttribute* addr_attr = |
| msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2); |
| if (!addr_attr) { |
| RTC_LOG(INFO) << "Data indication has no source address"; |
| return; |
| } else if (addr_attr->family() != 1) { |
| RTC_LOG(INFO) << "Source address has bad family"; |
| return; |
| } |
| |
| rtc::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port()); |
| |
| const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA); |
| if (!data_attr) { |
| RTC_LOG(INFO) << "Data indication has no data"; |
| return; |
| } |
| |
| // Process the actual data and remote address in the normal manner. |
| port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2, |
| PROTO_UDP, packet_time_us); |
| } |
| |
| void RelayEntry::OnSentPacket(rtc::AsyncPacketSocket* socket, |
| const rtc::SentPacket& sent_packet) { |
| port_->OnSentPacket(socket, sent_packet); |
| } |
| |
| void RelayEntry::OnReadyToSend(rtc::AsyncPacketSocket* socket) { |
| if (connected()) { |
| port_->OnReadyToSend(); |
| } |
| } |
| |
| int RelayEntry::SendPacket(const void* data, |
| size_t size, |
| const rtc::PacketOptions& options) { |
| int sent = 0; |
| if (current_connection_) { |
| // We are connected, no need to send packets anywere else than to |
| // the current connection. |
| sent = current_connection_->Send(data, size, options); |
| } |
| return sent; |
| } |
| |
| AllocateRequest::AllocateRequest(RelayEntry* entry, RelayConnection* connection) |
| : StunRequest(new RelayMessage()), entry_(entry), connection_(connection) { |
| start_time_ = rtc::TimeMillis(); |
| } |
| |
| void AllocateRequest::Prepare(StunMessage* request) { |
| request->SetType(STUN_ALLOCATE_REQUEST); |
| |
| auto username_attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME); |
| username_attr->CopyBytes(entry_->port()->username_fragment().c_str(), |
| entry_->port()->username_fragment().size()); |
| request->AddAttribute(std::move(username_attr)); |
| } |
| |
| void AllocateRequest::OnSent() { |
| count_ += 1; |
| if (count_ == 5) |
| timeout_ = true; |
| } |
| |
| int AllocateRequest::resend_delay() { |
| if (count_ == 0) { |
| return 0; |
| } |
| return 100 * std::max(1 << (count_ - 1), 2); |
| } |
| |
| void AllocateRequest::OnResponse(StunMessage* response) { |
| const StunAddressAttribute* addr_attr = |
| response->GetAddress(STUN_ATTR_MAPPED_ADDRESS); |
| if (!addr_attr) { |
| RTC_LOG(INFO) << "Allocate response missing mapped address."; |
| } else if (addr_attr->family() != 1) { |
| RTC_LOG(INFO) << "Mapped address has bad family"; |
| } else { |
| rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port()); |
| entry_->OnConnect(addr, connection_); |
| } |
| |
| // We will do a keep-alive regardless of whether this request suceeds. |
| // This should have almost no impact on network usage. |
| entry_->ScheduleKeepAlive(); |
| } |
| |
| void AllocateRequest::OnErrorResponse(StunMessage* response) { |
| const StunErrorCodeAttribute* attr = response->GetErrorCode(); |
| if (!attr) { |
| RTC_LOG(LS_ERROR) << "Missing allocate response error code."; |
| } else { |
| RTC_LOG(INFO) << "Allocate error response: code=" << attr->code() |
| << " reason=" << attr->reason(); |
| } |
| |
| if (rtc::TimeMillis() - start_time_ <= kRetryTimeout) |
| entry_->ScheduleKeepAlive(); |
| } |
| |
| void AllocateRequest::OnTimeout() { |
| RTC_LOG(INFO) << "Allocate request timed out"; |
| entry_->HandleConnectFailure(connection_->socket()); |
| } |
| |
| } // namespace cricket |