// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/renderer_host/p2p/socket_host.h" #include "base/metrics/histogram.h" #include "base/sys_byteorder.h" #include "content/browser/renderer_host/p2p/socket_host_tcp.h" #include "content/browser/renderer_host/p2p/socket_host_tcp_server.h" #include "content/browser/renderer_host/p2p/socket_host_udp.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/public/browser/browser_thread.h" #include "third_party/webrtc/media/base/rtputils.h" #include "third_party/webrtc/media/base/turnutils.h" namespace { const uint32_t kStunMagicCookie = 0x2112A442; const size_t kMinRtcpHeaderLength = 8; const size_t kDtlsRecordHeaderLength = 13; bool IsDtlsPacket(const char* data, size_t length) { const uint8_t* u = reinterpret_cast(data); return (length >= kDtlsRecordHeaderLength && (u[0] > 19 && u[0] < 64)); } bool IsRtcpPacket(const char* data, size_t length) { if (length < kMinRtcpHeaderLength) { return false; } int type = (static_cast(data[1]) & 0x7F); return (type >= 64 && type < 96); } } // namespace namespace content { P2PSocketHost::P2PSocketHost(IPC::Sender* message_sender, int socket_id, ProtocolType protocol_type) : message_sender_(message_sender), id_(socket_id), state_(STATE_UNINITIALIZED), dump_incoming_rtp_packet_(false), dump_outgoing_rtp_packet_(false), protocol_type_(protocol_type), send_packets_delayed_total_(0), send_packets_total_(0), send_bytes_delayed_max_(0), send_bytes_delayed_cur_(0), weak_ptr_factory_(this) { } P2PSocketHost::~P2PSocketHost() { if (protocol_type_ == P2PSocketHost::UDP) { UMA_HISTOGRAM_COUNTS_10000("WebRTC.SystemMaxConsecutiveBytesDelayed_UDP", send_bytes_delayed_max_); } else { UMA_HISTOGRAM_COUNTS_10000("WebRTC.SystemMaxConsecutiveBytesDelayed_TCP", send_bytes_delayed_max_); } if (send_packets_total_ > 0) { int delay_rate = (send_packets_delayed_total_ * 100) / send_packets_total_; if (protocol_type_ == P2PSocketHost::UDP) { UMA_HISTOGRAM_PERCENTAGE("WebRTC.SystemPercentPacketsDelayed_UDP", delay_rate); } else { UMA_HISTOGRAM_PERCENTAGE("WebRTC.SystemPercentPacketsDelayed_TCP", delay_rate); } } } // Verifies that the packet |data| has a valid STUN header. // static bool P2PSocketHost::GetStunPacketType( const char* data, int data_size, StunMessageType* type) { if (data_size < kStunHeaderSize) { return false; } uint32_t cookie = base::NetToHost32(*reinterpret_cast(data + 4)); if (cookie != kStunMagicCookie) { return false; } uint16_t length = base::NetToHost16(*reinterpret_cast(data + 2)); if (length != data_size - kStunHeaderSize) { return false; } int message_type = base::NetToHost16(*reinterpret_cast(data)); // Verify that the type is known: switch (message_type) { case STUN_BINDING_REQUEST: case STUN_BINDING_RESPONSE: case STUN_BINDING_ERROR_RESPONSE: case STUN_SHARED_SECRET_REQUEST: case STUN_SHARED_SECRET_RESPONSE: case STUN_SHARED_SECRET_ERROR_RESPONSE: case STUN_ALLOCATE_REQUEST: case STUN_ALLOCATE_RESPONSE: case STUN_ALLOCATE_ERROR_RESPONSE: case STUN_SEND_REQUEST: case STUN_SEND_RESPONSE: case STUN_SEND_ERROR_RESPONSE: case STUN_DATA_INDICATION: *type = static_cast(message_type); return true; default: return false; } } // static bool P2PSocketHost::IsRequestOrResponse(StunMessageType type) { return type == STUN_BINDING_REQUEST || type == STUN_BINDING_RESPONSE || type == STUN_ALLOCATE_REQUEST || type == STUN_ALLOCATE_RESPONSE; } // static P2PSocketHost* P2PSocketHost::Create(IPC::Sender* message_sender, int socket_id, P2PSocketType type, net::URLRequestContextGetter* url_context, P2PMessageThrottler* throttler) { switch (type) { case P2P_SOCKET_UDP: return new P2PSocketHostUdp(message_sender, socket_id, throttler); case P2P_SOCKET_TCP_SERVER: return new P2PSocketHostTcpServer( message_sender, socket_id, P2P_SOCKET_TCP_CLIENT); case P2P_SOCKET_STUN_TCP_SERVER: return new P2PSocketHostTcpServer( message_sender, socket_id, P2P_SOCKET_STUN_TCP_CLIENT); case P2P_SOCKET_TCP_CLIENT: case P2P_SOCKET_SSLTCP_CLIENT: case P2P_SOCKET_TLS_CLIENT: return new P2PSocketHostTcp(message_sender, socket_id, type, url_context); case P2P_SOCKET_STUN_TCP_CLIENT: case P2P_SOCKET_STUN_SSLTCP_CLIENT: case P2P_SOCKET_STUN_TLS_CLIENT: return new P2PSocketHostStunTcp( message_sender, socket_id, type, url_context); } NOTREACHED(); return NULL; } void P2PSocketHost::StartRtpDump( bool incoming, bool outgoing, const RenderProcessHost::WebRtcRtpPacketCallback& packet_callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK(!packet_callback.is_null()); DCHECK(incoming || outgoing); if (incoming) { dump_incoming_rtp_packet_ = true; } if (outgoing) { dump_outgoing_rtp_packet_ = true; } packet_dump_callback_ = packet_callback; } void P2PSocketHost::StopRtpDump(bool incoming, bool outgoing) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK(incoming || outgoing); if (incoming) { dump_incoming_rtp_packet_ = false; } if (outgoing) { dump_outgoing_rtp_packet_ = false; } if (!dump_incoming_rtp_packet_ && !dump_outgoing_rtp_packet_) { packet_dump_callback_.Reset(); } } void P2PSocketHost::DumpRtpPacket(const char* packet, size_t length, bool incoming) { if (IsDtlsPacket(packet, length) || IsRtcpPacket(packet, length)) { return; } size_t rtp_packet_pos = 0; size_t rtp_packet_length = length; if (!cricket::UnwrapTurnPacket(reinterpret_cast(packet), length, &rtp_packet_pos, &rtp_packet_length)) { return; } packet += rtp_packet_pos; size_t header_length = 0; bool valid = cricket::ValidateRtpHeader(reinterpret_cast(packet), rtp_packet_length, &header_length); if (!valid) { NOTREACHED(); return; } scoped_ptr header_buffer(new uint8_t[header_length]); memcpy(header_buffer.get(), packet, header_length); // Posts to the IO thread as the data members should be accessed on the IO // thread only. BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&P2PSocketHost::DumpRtpPacketOnIOThread, weak_ptr_factory_.GetWeakPtr(), base::Passed(&header_buffer), header_length, rtp_packet_length, incoming)); } void P2PSocketHost::DumpRtpPacketOnIOThread(scoped_ptr packet_header, size_t header_length, size_t packet_length, bool incoming) { DCHECK_CURRENTLY_ON(BrowserThread::IO); if ((incoming && !dump_incoming_rtp_packet_) || (!incoming && !dump_outgoing_rtp_packet_) || packet_dump_callback_.is_null()) { return; } // |packet_dump_callback_| must be called on the UI thread. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(packet_dump_callback_, base::Passed(&packet_header), header_length, packet_length, incoming)); } void P2PSocketHost::IncrementDelayedPackets() { send_packets_delayed_total_++; } void P2PSocketHost::IncrementTotalSentPackets() { send_packets_total_++; } void P2PSocketHost::IncrementDelayedBytes(uint32_t size) { send_bytes_delayed_cur_ += size; if (send_bytes_delayed_cur_ > send_bytes_delayed_max_) { send_bytes_delayed_max_ = send_bytes_delayed_cur_; } } void P2PSocketHost::DecrementDelayedBytes(uint32_t size) { send_bytes_delayed_cur_ -= size; DCHECK_GE(send_bytes_delayed_cur_, 0); } } // namespace content