From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/media/webrtc/transport/nr_socket_prsock.cpp | 1785 +++++++++++++++++++++++ 1 file changed, 1785 insertions(+) create mode 100644 dom/media/webrtc/transport/nr_socket_prsock.cpp (limited to 'dom/media/webrtc/transport/nr_socket_prsock.cpp') diff --git a/dom/media/webrtc/transport/nr_socket_prsock.cpp b/dom/media/webrtc/transport/nr_socket_prsock.cpp new file mode 100644 index 0000000000..3c99a2d0c8 --- /dev/null +++ b/dom/media/webrtc/transport/nr_socket_prsock.cpp @@ -0,0 +1,1785 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* +Modified version of nr_socket_local, adapted for NSPR +*/ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +Original code from nICEr and nrappkit. + +nICEr copyright: + +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* 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. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"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 COPYRIGHT +OWNER OR CONTRIBUTORS 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. + + +nrappkit copyright: + + Copyright (C) 2001-2003, Network Resonance, Inc. + Copyright (C) 2006, Network Resonance, Inc. + All Rights Reserved + + 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. Neither the name of Network Resonance, Inc. nor the name of any + contributors to this software may be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``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 COPYRIGHT OWNER OR CONTRIBUTORS 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. + + + ekr@rtfm.com Thu Dec 20 20:14:49 2001 +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "nspr.h" +#include "prerror.h" +#include "prio.h" +#include "prnetdb.h" + +#include "mozilla/net/DNS.h" +#include "nsCOMPtr.h" +#include "nsASocketHandler.h" +#include "nsISocketTransportService.h" +#include "nsNetCID.h" +#include "nsISupportsImpl.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" +#include "runnable_utils.h" +#include "mozilla/SyncRunnable.h" +#include "nsTArray.h" +#include "nsISocketFilter.h" +#include "nsDebug.h" +#include "nsNetUtil.h" + +#ifdef XP_WIN +# include "mozilla/WindowsVersion.h" +#endif + +#if defined(MOZILLA_INTERNAL_API) +// csi_platform.h deep in nrappkit defines LOG_INFO and LOG_WARNING +# ifdef LOG_INFO +# define LOG_TEMP_INFO LOG_INFO +# undef LOG_INFO +# endif +# ifdef LOG_WARNING +# define LOG_TEMP_WARNING LOG_WARNING +# undef LOG_WARNING +# endif +# if defined(LOG_DEBUG) +# define LOG_TEMP_DEBUG LOG_DEBUG +# undef LOG_DEBUG +# endif +# undef strlcpy + +# include "mozilla/dom/network/UDPSocketChild.h" + +# ifdef LOG_TEMP_INFO +# define LOG_INFO LOG_TEMP_INFO +# endif +# ifdef LOG_TEMP_WARNING +# define LOG_WARNING LOG_TEMP_WARNING +# endif + +# ifdef LOG_TEMP_DEBUG +# define LOG_DEBUG LOG_TEMP_DEBUG +# endif +# ifdef XP_WIN +# ifdef LOG_DEBUG +# undef LOG_DEBUG +# endif +// cloned from csi_platform.h. Win32 doesn't like how we hide symbols +# define LOG_DEBUG 7 +# endif +#endif + +extern "C" { +#include "nr_api.h" +#include "async_wait.h" +#include "nr_socket.h" +#include "nr_socket_local.h" +#include "stun_hint.h" +} +#include "nr_socket_prsock.h" +#include "simpletokenbucket.h" +#include "test_nr_socket.h" +#include "nr_socket_tcp.h" +#include "nr_socket_proxy_config.h" + +// Implement the nsISupports ref counting +namespace mozilla { + +#if defined(MOZILLA_INTERNAL_API) +class SingletonThreadHolder final { + private: + ~SingletonThreadHolder() { + r_log(LOG_GENERIC, LOG_DEBUG, "Deleting SingletonThreadHolder"); + if (mThread) { + // Likely a connection is somehow being held in CC or GC + NS_WARNING( + "SingletonThreads should be Released and shut down before exit!"); + mThread->Shutdown(); + mThread = nullptr; + } + } + + DISALLOW_COPY_ASSIGN(SingletonThreadHolder); + + public: + // Must be threadsafe for StaticRefPtr/ClearOnShutdown + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SingletonThreadHolder) + + explicit SingletonThreadHolder(const nsACString& aName) : mName(aName) { + mParentThread = NS_GetCurrentThread(); + } + + nsIThread* GetThread() { return mThread; } + + /* + * Keep track of how many instances are using a SingletonThreadHolder. + * When no one is using it, shut it down + */ + void AddUse() { + MOZ_ASSERT(mParentThread == NS_GetCurrentThread()); + MOZ_ASSERT(int32_t(mUseCount) >= 0, "illegal refcnt"); + nsrefcnt count = ++mUseCount; + if (count == 1) { + // idle -> in-use + nsresult rv = NS_NewNamedThread(mName, getter_AddRefs(mThread)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mThread, + "Should successfully create mtransport I/O thread"); + r_log(LOG_GENERIC, LOG_DEBUG, "Created wrapped SingletonThread %p", + mThread.get()); + } + r_log(LOG_GENERIC, LOG_DEBUG, "AddUse_i: %lu", (unsigned long)count); + } + + void ReleaseUse() { + MOZ_ASSERT(mParentThread == NS_GetCurrentThread()); + nsrefcnt count = --mUseCount; + MOZ_ASSERT(int32_t(mUseCount) >= 0, "illegal refcnt"); + if (mThread && count == 0) { + // in-use -> idle -- no one forcing it to remain instantiated + r_log(LOG_GENERIC, LOG_DEBUG, "Shutting down wrapped SingletonThread %p", + mThread.get()); + mThread->AsyncShutdown(); + mThread = nullptr; + // It'd be nice to use a timer instead... But be careful of + // xpcom-shutdown-threads in that case + } + r_log(LOG_GENERIC, LOG_DEBUG, "ReleaseUse_i: %lu", (unsigned long)count); + } + + private: + nsCString mName; + nsAutoRefCnt mUseCount; + nsCOMPtr mParentThread; + nsCOMPtr mThread; +}; + +static StaticRefPtr sThread; + +static void ClearSingletonOnShutdown() { + // We expect everybody to have done ReleaseUse() at the latest during + // xpcom-shutdown-threads. So we need to live longer than that. + ClearOnShutdown(&sThread, ShutdownPhase::XPCOMShutdownFinal); +} +#endif + +static nsIThread* GetIOThreadAndAddUse_s() { + // Always runs on STS thread! +#if defined(MOZILLA_INTERNAL_API) + // We need to safely release this on shutdown to avoid leaks + if (!sThread) { + sThread = new SingletonThreadHolder("mtransport"_ns); + NS_DispatchToMainThread(mozilla::WrapRunnableNM(&ClearSingletonOnShutdown)); + } + // Mark that we're using the shared thread and need it to stick around + sThread->AddUse(); + return sThread->GetThread(); +#else + static nsCOMPtr sThread; + if (!sThread) { + (void)NS_NewNamedThread("mtransport", getter_AddRefs(sThread)); + } + return sThread; +#endif +} + +NrSocketIpc::NrSocketIpc(nsIEventTarget* aThread) : io_thread_(aThread) {} + +static TimeStamp nr_socket_short_term_violation_time; +static TimeStamp nr_socket_long_term_violation_time; + +TimeStamp NrSocketBase::short_term_violation_time() { + return nr_socket_short_term_violation_time; +} + +TimeStamp NrSocketBase::long_term_violation_time() { + return nr_socket_long_term_violation_time; +} + +// NrSocketBase implementation +// async_event APIs +int NrSocketBase::async_wait(int how, NR_async_cb cb, void* cb_arg, + char* function, int line) { + uint16_t flag; + + switch (how) { + case NR_ASYNC_WAIT_READ: + flag = PR_POLL_READ; + break; + case NR_ASYNC_WAIT_WRITE: + flag = PR_POLL_WRITE; + break; + default: + return R_BAD_ARGS; + } + + cbs_[how] = cb; + cb_args_[how] = cb_arg; + poll_flags_ |= flag; + + return 0; +} + +int NrSocketBase::cancel(int how) { + uint16_t flag; + + switch (how) { + case NR_ASYNC_WAIT_READ: + flag = PR_POLL_READ; + break; + case NR_ASYNC_WAIT_WRITE: + flag = PR_POLL_WRITE; + break; + default: + return R_BAD_ARGS; + } + + poll_flags_ &= ~flag; + + return 0; +} + +void NrSocketBase::fire_callback(int how) { + // This can't happen unless we are armed because we only set + // the flags if we are armed + MOZ_ASSERT(cbs_[how]); + + // Now cancel so that we need to be re-armed. Note that + // the re-arming probably happens in the callback we are + // about to fire. + cancel(how); + + cbs_[how](this, how, cb_args_[how]); +} + +// NrSocket implementation +NS_IMPL_QUERY_INTERFACE0(NrSocket) + +// The nsASocket callbacks +void NrSocket::OnSocketReady(PRFileDesc* fd, int16_t outflags) { + if (outflags & PR_POLL_READ & poll_flags()) fire_callback(NR_ASYNC_WAIT_READ); + if (outflags & PR_POLL_WRITE & poll_flags()) + fire_callback(NR_ASYNC_WAIT_WRITE); + if (outflags & (PR_POLL_ERR | PR_POLL_NVAL | PR_POLL_HUP)) + // TODO: Bug 946423: how do we notify the upper layers about this? + close(); +} + +void NrSocket::OnSocketDetached(PRFileDesc* fd) { + r_log(LOG_GENERIC, LOG_DEBUG, "Socket %p detached", fd); +} + +void NrSocket::IsLocal(bool* aIsLocal) { + // TODO(jesup): better check? Does it matter? (likely no) + *aIsLocal = false; +} + +// async_event APIs +int NrSocket::async_wait(int how, NR_async_cb cb, void* cb_arg, char* function, + int line) { + int r = NrSocketBase::async_wait(how, cb, cb_arg, function, line); + + if (!r) { + mPollFlags = poll_flags(); + } + + return r; +} + +int NrSocket::cancel(int how) { + int r = NrSocketBase::cancel(how); + + if (!r) { + mPollFlags = poll_flags(); + } + + return r; +} + +// Helper functions for addresses +static int nr_transport_addr_to_praddr(const nr_transport_addr* addr, + PRNetAddr* naddr) { + int _status; + + memset(naddr, 0, sizeof(*naddr)); + + switch (addr->protocol) { + case IPPROTO_TCP: + break; + case IPPROTO_UDP: + break; + default: + ABORT(R_BAD_ARGS); + } + + switch (addr->ip_version) { + case NR_IPV4: + naddr->inet.family = PR_AF_INET; + naddr->inet.port = addr->u.addr4.sin_port; + naddr->inet.ip = addr->u.addr4.sin_addr.s_addr; + break; + case NR_IPV6: + naddr->ipv6.family = PR_AF_INET6; + naddr->ipv6.port = addr->u.addr6.sin6_port; + naddr->ipv6.flowinfo = addr->u.addr6.sin6_flowinfo; + memcpy(&naddr->ipv6.ip, &addr->u.addr6.sin6_addr, sizeof(in6_addr)); + naddr->ipv6.scope_id = addr->u.addr6.sin6_scope_id; + break; + default: + ABORT(R_BAD_ARGS); + } + + _status = 0; +abort: + return (_status); +} + +// XXX schien@mozilla.com: copy from PRNetAddrToNetAddr, +// should be removed after fix the link error in signaling_unittests +static int praddr_to_netaddr(const PRNetAddr* prAddr, net::NetAddr* addr) { + int _status; + + switch (prAddr->raw.family) { + case PR_AF_INET: + addr->inet.family = AF_INET; + addr->inet.port = prAddr->inet.port; + addr->inet.ip = prAddr->inet.ip; + break; + case PR_AF_INET6: + addr->inet6.family = AF_INET6; + addr->inet6.port = prAddr->ipv6.port; + addr->inet6.flowinfo = prAddr->ipv6.flowinfo; + memcpy(&addr->inet6.ip, &prAddr->ipv6.ip, sizeof(addr->inet6.ip.u8)); + addr->inet6.scope_id = prAddr->ipv6.scope_id; + break; + default: + MOZ_ASSERT(false); + ABORT(R_BAD_ARGS); + } + + _status = 0; +abort: + return (_status); +} + +static int nr_transport_addr_to_netaddr(const nr_transport_addr* addr, + net::NetAddr* naddr) { + int r, _status; + PRNetAddr praddr; + + if ((r = nr_transport_addr_to_praddr(addr, &praddr))) { + ABORT(r); + } + + if ((r = praddr_to_netaddr(&praddr, naddr))) { + ABORT(r); + } + + _status = 0; +abort: + return (_status); +} + +int nr_netaddr_to_transport_addr(const net::NetAddr* netaddr, + nr_transport_addr* addr, int protocol) { + int _status; + int r; + + switch (netaddr->raw.family) { + case AF_INET: + if ((r = nr_ip4_port_to_transport_addr(ntohl(netaddr->inet.ip), + ntohs(netaddr->inet.port), + protocol, addr))) + ABORT(r); + break; + case AF_INET6: + if ((r = nr_ip6_port_to_transport_addr((in6_addr*)&netaddr->inet6.ip.u8, + ntohs(netaddr->inet6.port), + protocol, addr))) + ABORT(r); + break; + default: + MOZ_ASSERT(false); + ABORT(R_BAD_ARGS); + } + _status = 0; +abort: + return (_status); +} + +int nr_praddr_to_transport_addr(const PRNetAddr* praddr, + nr_transport_addr* addr, int protocol, + int keep) { + int _status; + int r; + struct sockaddr_in ip4; + struct sockaddr_in6 ip6; + + switch (praddr->raw.family) { + case PR_AF_INET: + ip4.sin_family = PF_INET; + ip4.sin_addr.s_addr = praddr->inet.ip; + ip4.sin_port = praddr->inet.port; + if ((r = nr_sockaddr_to_transport_addr((sockaddr*)&ip4, protocol, keep, + addr))) + ABORT(r); + break; + case PR_AF_INET6: + ip6.sin6_family = PF_INET6; + ip6.sin6_port = praddr->ipv6.port; + ip6.sin6_flowinfo = praddr->ipv6.flowinfo; + memcpy(&ip6.sin6_addr, &praddr->ipv6.ip, sizeof(in6_addr)); + ip6.sin6_scope_id = praddr->ipv6.scope_id; + if ((r = nr_sockaddr_to_transport_addr((sockaddr*)&ip6, protocol, keep, + addr))) + ABORT(r); + break; + default: + MOZ_ASSERT(false); + ABORT(R_BAD_ARGS); + } + + _status = 0; +abort: + return (_status); +} + +/* + * nr_transport_addr_get_addrstring_and_port + * convert nr_transport_addr to IP address string and port number + */ +int nr_transport_addr_get_addrstring_and_port(const nr_transport_addr* addr, + nsACString* host, int32_t* port) { + int r, _status; + char addr_string[64]; + + // We cannot directly use |nr_transport_addr.as_string| because it contains + // more than ip address, therefore, we need to explicity convert it + // from |nr_transport_addr_get_addrstring|. + if ((r = nr_transport_addr_get_addrstring(addr, addr_string, + sizeof(addr_string)))) { + ABORT(r); + } + + if ((r = nr_transport_addr_get_port(addr, port))) { + ABORT(r); + } + + *host = addr_string; + + _status = 0; +abort: + return (_status); +} + +// nr_socket APIs (as member functions) +int NrSocket::create(nr_transport_addr* addr) { + int r, _status; + + PRStatus status; + PRNetAddr naddr; + + nsresult rv; + nsCOMPtr stservice = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (!NS_SUCCEEDED(rv)) { + ABORT(R_INTERNAL); + } + + if ((r = nr_transport_addr_to_praddr(addr, &naddr))) ABORT(r); + + switch (addr->protocol) { + case IPPROTO_UDP: + if (!(fd_ = PR_OpenUDPSocket(naddr.raw.family))) { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't create UDP socket, " + "family=%d, err=%d", + naddr.raw.family, PR_GetError()); + ABORT(R_INTERNAL); + } +#ifdef XP_WIN + if (!mozilla::IsWin8OrLater()) { + // Increase default send and receive buffer sizes on <= Win7 to be able + // to receive and send an unpaced HD (>= 720p = 1280x720 - I Frame ~ 21K + // size) stream without losing packets. Manual testing showed that 100K + // buffer size was not enough and the packet loss dis-appeared with 256K + // buffer size. See bug 1252769 for future improvements of this. + PRSize min_buffer_size = 256 * 1024; + PRSocketOptionData opt_rcvbuf; + opt_rcvbuf.option = PR_SockOpt_RecvBufferSize; + if ((status = PR_GetSocketOption(fd_, &opt_rcvbuf)) == PR_SUCCESS) { + if (opt_rcvbuf.value.recv_buffer_size < min_buffer_size) { + opt_rcvbuf.value.recv_buffer_size = min_buffer_size; + if ((status = PR_SetSocketOption(fd_, &opt_rcvbuf)) != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't set socket receive buffer size: %d", status); + } + } else { + r_log(LOG_GENERIC, LOG_INFO, + "Socket receive buffer size is already: %d", + opt_rcvbuf.value.recv_buffer_size); + } + } else { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't get socket receive buffer size: %d", status); + } + PRSocketOptionData opt_sndbuf; + opt_sndbuf.option = PR_SockOpt_SendBufferSize; + if ((status = PR_GetSocketOption(fd_, &opt_sndbuf)) == PR_SUCCESS) { + if (opt_sndbuf.value.recv_buffer_size < min_buffer_size) { + opt_sndbuf.value.recv_buffer_size = min_buffer_size; + if ((status = PR_SetSocketOption(fd_, &opt_sndbuf)) != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't set socket send buffer size: %d", status); + } + } else { + r_log(LOG_GENERIC, LOG_INFO, + "Socket send buffer size is already: %d", + opt_sndbuf.value.recv_buffer_size); + } + } else { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't get socket send buffer size: %d", status); + } + } +#endif + break; + case IPPROTO_TCP: + // TODO: Rewrite this to use WebrtcTcpSocket. + // Also use the same logic for TLS. + if (my_addr_.fqdn[0] != '\0') ABORT(R_INTERNAL); + + if (!(fd_ = PR_OpenTCPSocket(naddr.raw.family))) { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't create TCP socket, " + "family=%d, err=%d", + naddr.raw.family, PR_GetError()); + ABORT(R_INTERNAL); + } + // Set ReuseAddr for TCP sockets to enable having several + // sockets bound to same local IP and port + PRSocketOptionData opt_reuseaddr; + opt_reuseaddr.option = PR_SockOpt_Reuseaddr; + opt_reuseaddr.value.reuse_addr = PR_TRUE; + status = PR_SetSocketOption(fd_, &opt_reuseaddr); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't set reuse addr socket option: %d", status); + ABORT(R_INTERNAL); + } + // And also set ReusePort for platforms supporting this socket option + PRSocketOptionData opt_reuseport; + opt_reuseport.option = PR_SockOpt_Reuseport; + opt_reuseport.value.reuse_port = PR_TRUE; + status = PR_SetSocketOption(fd_, &opt_reuseport); + if (status != PR_SUCCESS) { + if (PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) { + r_log(LOG_GENERIC, LOG_CRIT, + "Couldn't set reuse port socket option: %d", status); + ABORT(R_INTERNAL); + } + } + // Try to speedup packet delivery by disabling TCP Nagle + PRSocketOptionData opt_nodelay; + opt_nodelay.option = PR_SockOpt_NoDelay; + opt_nodelay.value.no_delay = PR_TRUE; + status = PR_SetSocketOption(fd_, &opt_nodelay); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_WARNING, + "Couldn't set Nodelay socket option: %d", status); + } + break; + default: + ABORT(R_INTERNAL); + } + + status = PR_Bind(fd_, &naddr); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, "Couldn't bind socket to address %s", + addr->as_string); + ABORT(R_INTERNAL); + } + + r_log(LOG_GENERIC, LOG_DEBUG, "Creating socket %p with addr %s", fd_, + addr->as_string); + nr_transport_addr_copy(&my_addr_, addr); + + /* If we have a wildcard port, patch up the addr */ + if (nr_transport_addr_is_wildcard(addr)) { + status = PR_GetSockName(fd_, &naddr); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket"); + ABORT(R_INTERNAL); + } + + if ((r = nr_praddr_to_transport_addr(&naddr, &my_addr_, addr->protocol, 1))) + ABORT(r); + } + + // Set nonblocking + PRSocketOptionData opt_nonblock; + opt_nonblock.option = PR_SockOpt_Nonblocking; + opt_nonblock.value.non_blocking = PR_TRUE; + status = PR_SetSocketOption(fd_, &opt_nonblock); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, "Couldn't make socket nonblocking"); + ABORT(R_INTERNAL); + } + + // Remember our thread. + ststhread_ = do_QueryInterface(stservice, &rv); + if (!NS_SUCCEEDED(rv)) ABORT(R_INTERNAL); + + // Finally, register with the STS + rv = stservice->AttachSocket(fd_, this); + if (!NS_SUCCEEDED(rv)) { + r_log(LOG_GENERIC, LOG_CRIT, "Couldn't attach socket to STS, rv=%u", + static_cast(rv)); + ABORT(R_INTERNAL); + } + + _status = 0; + +abort: + return (_status); +} + +static int ShouldDrop(size_t len) { + // Global rate limiting for stun requests, to mitigate the ice hammer DoS + // (see http://tools.ietf.org/html/draft-thomson-mmusic-ice-webrtc) + + // Tolerate rate of 8k/sec, for one second. + static SimpleTokenBucket burst(16384 * 1, 16384); + // Tolerate rate of 7.2k/sec over twenty seconds. + static SimpleTokenBucket sustained(7372 * 20, 7372); + + // Check number of tokens in each bucket. + if (burst.getTokens(UINT32_MAX) < len) { + r_log(LOG_GENERIC, LOG_ERR, + "Short term global rate limit for STUN requests exceeded."); +#ifdef MOZILLA_INTERNAL_API + nr_socket_short_term_violation_time = TimeStamp::Now(); +#endif + +// Bug 1013007 +#if !EARLY_BETA_OR_EARLIER + return R_WOULDBLOCK; +#else + MOZ_ASSERT(false, + "Short term global rate limit for STUN requests exceeded. Go " + "bug bcampen@mozilla.com if you weren't intentionally " + "spamming ICE candidates, or don't know what that means."); +#endif + } + + if (sustained.getTokens(UINT32_MAX) < len) { + r_log(LOG_GENERIC, LOG_ERR, + "Long term global rate limit for STUN requests exceeded."); +#ifdef MOZILLA_INTERNAL_API + nr_socket_long_term_violation_time = TimeStamp::Now(); +#endif +// Bug 1013007 +#if !EARLY_BETA_OR_EARLIER + return R_WOULDBLOCK; +#else + MOZ_ASSERT(false, + "Long term global rate limit for STUN requests exceeded. Go " + "bug bcampen@mozilla.com if you weren't intentionally " + "spamming ICE candidates, or don't know what that means."); +#endif + } + + // Take len tokens from both buckets. + // (not threadsafe, but no problem since this is only called from STS) + burst.getTokens(len); + sustained.getTokens(len); + return 0; +} + +// This should be called on the STS thread. +int NrSocket::sendto(const void* msg, size_t len, int flags, + const nr_transport_addr* to) { + ASSERT_ON_THREAD(ststhread_); + int r, _status; + PRNetAddr naddr; + int32_t status; + + if ((r = nr_transport_addr_to_praddr(to, &naddr))) ABORT(r); + + if (fd_ == nullptr) ABORT(R_EOD); + + if (nr_is_stun_request_message((UCHAR*)msg, len) && ShouldDrop(len)) { + ABORT(R_WOULDBLOCK); + } + + // TODO: Convert flags? + status = PR_SendTo(fd_, msg, len, flags, &naddr, PR_INTERVAL_NO_WAIT); + if (status < 0 || (size_t)status != len) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); + + r_log(LOG_GENERIC, LOG_INFO, "Error in sendto %s: %d", to->as_string, + PR_GetError()); + ABORT(R_IO_ERROR); + } + + _status = 0; +abort: + return (_status); +} + +int NrSocket::recvfrom(void* buf, size_t maxlen, size_t* len, int flags, + nr_transport_addr* from) { + ASSERT_ON_THREAD(ststhread_); + int r, _status; + PRNetAddr nfrom; + int32_t status; + + status = PR_RecvFrom(fd_, buf, maxlen, flags, &nfrom, PR_INTERVAL_NO_WAIT); + if (status <= 0) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); + r_log(LOG_GENERIC, LOG_INFO, "Error in recvfrom: %d", (int)PR_GetError()); + ABORT(R_IO_ERROR); + } + *len = status; + + if ((r = nr_praddr_to_transport_addr(&nfrom, from, my_addr_.protocol, 0))) + ABORT(r); + + // r_log(LOG_GENERIC,LOG_DEBUG,"Read %d bytes from %s",*len,addr->as_string); + + _status = 0; +abort: + return (_status); +} + +int NrSocket::getaddr(nr_transport_addr* addrp) { + ASSERT_ON_THREAD(ststhread_); + return nr_transport_addr_copy(addrp, &my_addr_); +} + +// Close the socket so that the STS will detach and then kill it +void NrSocket::close() { + ASSERT_ON_THREAD(ststhread_); + mCondition = NS_BASE_STREAM_CLOSED; + cancel(NR_ASYNC_WAIT_READ); + cancel(NR_ASYNC_WAIT_WRITE); +} + +int NrSocket::connect(const nr_transport_addr* addr) { + ASSERT_ON_THREAD(ststhread_); + int r, _status; + PRNetAddr naddr; + int32_t connect_status, getsockname_status; + + if ((r = nr_transport_addr_to_praddr(addr, &naddr))) ABORT(r); + + if (!fd_) ABORT(R_EOD); + + // Note: this just means we tried to connect, not that we + // are actually live. + connect_invoked_ = true; + connect_status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT); + if (connect_status != PR_SUCCESS) { + if (PR_GetError() != PR_IN_PROGRESS_ERROR) { + r_log(LOG_GENERIC, LOG_CRIT, "PR_Connect failed: %d", PR_GetError()); + ABORT(R_IO_ERROR); + } + } + + // If our local address is wildcard, then fill in the + // address now. + if (nr_transport_addr_is_wildcard(&my_addr_)) { + getsockname_status = PR_GetSockName(fd_, &naddr); + if (getsockname_status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket"); + ABORT(R_INTERNAL); + } + + if ((r = nr_praddr_to_transport_addr(&naddr, &my_addr_, addr->protocol, 1))) + ABORT(r); + } + + // Now return the WOULDBLOCK if needed. + if (connect_status != PR_SUCCESS) { + ABORT(R_WOULDBLOCK); + } + + _status = 0; +abort: + return (_status); +} + +int NrSocket::write(const void* msg, size_t len, size_t* written) { + ASSERT_ON_THREAD(ststhread_); + int _status; + int32_t status; + + if (!connect_invoked_) ABORT(R_FAILED); + + status = PR_Write(fd_, msg, len); + if (status < 0) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); + r_log(LOG_GENERIC, LOG_INFO, "Error in write"); + ABORT(R_IO_ERROR); + } + + *written = status; + + _status = 0; +abort: + return _status; +} + +int NrSocket::read(void* buf, size_t maxlen, size_t* len) { + ASSERT_ON_THREAD(ststhread_); + int _status; + int32_t status; + + if (!connect_invoked_) ABORT(R_FAILED); + + status = PR_Read(fd_, buf, maxlen); + if (status < 0) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); + r_log(LOG_GENERIC, LOG_INFO, "Error in read"); + ABORT(R_IO_ERROR); + } + if (status == 0) ABORT(R_EOD); + + *len = (size_t)status; // Guaranteed to be > 0 + _status = 0; +abort: + return (_status); +} + +int NrSocket::listen(int backlog) { + ASSERT_ON_THREAD(ststhread_); + int32_t status; + int _status; + + assert(fd_); + status = PR_Listen(fd_, backlog); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, "%s: PR_GetError() == %d", __FUNCTION__, + PR_GetError()); + ABORT(R_IO_ERROR); + } + + _status = 0; +abort: + return (_status); +} + +int NrSocket::accept(nr_transport_addr* addrp, nr_socket** sockp) { + ASSERT_ON_THREAD(ststhread_); + int _status, r; + PRStatus status; + PRFileDesc* prfd; + PRNetAddr nfrom; + NrSocket* sock = nullptr; + nsresult rv; + PRSocketOptionData opt_nonblock, opt_nodelay; + nsCOMPtr stservice = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + ABORT(R_INTERNAL); + } + + if (!fd_) ABORT(R_EOD); + + prfd = PR_Accept(fd_, &nfrom, PR_INTERVAL_NO_WAIT); + + if (!prfd) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK); + + ABORT(R_IO_ERROR); + } + + sock = new NrSocket(); + + sock->fd_ = prfd; + nr_transport_addr_copy(&sock->my_addr_, &my_addr_); + + if ((r = nr_praddr_to_transport_addr(&nfrom, addrp, my_addr_.protocol, 0))) + ABORT(r); + + // Set nonblocking + opt_nonblock.option = PR_SockOpt_Nonblocking; + opt_nonblock.value.non_blocking = PR_TRUE; + status = PR_SetSocketOption(prfd, &opt_nonblock); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_CRIT, + "Failed to make accepted socket nonblocking: %d", status); + ABORT(R_INTERNAL); + } + // Disable TCP Nagle + opt_nodelay.option = PR_SockOpt_NoDelay; + opt_nodelay.value.no_delay = PR_TRUE; + status = PR_SetSocketOption(prfd, &opt_nodelay); + if (status != PR_SUCCESS) { + r_log(LOG_GENERIC, LOG_WARNING, + "Failed to set Nodelay on accepted socket: %d", status); + } + + // Should fail only with OOM + if ((r = nr_socket_create_int(static_cast(sock), sock->vtbl(), sockp))) + ABORT(r); + + // Remember our thread. + sock->ststhread_ = do_QueryInterface(stservice, &rv); + if (NS_FAILED(rv)) ABORT(R_INTERNAL); + + // Finally, register with the STS + rv = stservice->AttachSocket(prfd, sock); + if (NS_FAILED(rv)) { + ABORT(R_INTERNAL); + } + + sock->connect_invoked_ = true; + + // Add a reference so that we can delete it in destroy() + sock->AddRef(); + _status = 0; +abort: + if (_status) { + delete sock; + } + + return (_status); +} + +NS_IMPL_ISUPPORTS(NrUdpSocketIpcProxy, nsIUDPSocketInternal) + +nsresult NrUdpSocketIpcProxy::Init(const RefPtr& socket) { + nsresult rv; + sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to get STS thread"); + return rv; + } + + socket_ = socket; + return NS_OK; +} + +NrUdpSocketIpcProxy::~NrUdpSocketIpcProxy() { + // Send our ref to STS to be released + RUN_ON_THREAD(sts_thread_, mozilla::WrapRelease(socket_.forget()), + NS_DISPATCH_NORMAL); +} + +// IUDPSocketInternal interfaces +// callback while error happened in UDP socket operation +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerError(const nsACString& message, + const nsACString& filename, + uint32_t line_number) { + return socket_->CallListenerError(message, filename, line_number); +} + +// callback while receiving UDP packet +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerReceivedData( + const nsACString& host, uint16_t port, const nsTArray& data) { + return socket_->CallListenerReceivedData(host, port, data); +} + +// callback while UDP socket is opened +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerOpened() { + return socket_->CallListenerOpened(); +} + +// callback while UDP socket is connected +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerConnected() { + return socket_->CallListenerConnected(); +} + +// callback while UDP socket is closed +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerClosed() { + return socket_->CallListenerClosed(); +} + +// NrUdpSocketIpc Implementation +NrUdpSocketIpc::NrUdpSocketIpc() + : NrSocketIpc(GetIOThreadAndAddUse_s()), + monitor_("NrUdpSocketIpc"), + err_(false), + state_(NR_INIT) {} + +NrUdpSocketIpc::~NrUdpSocketIpc() = default; + +void NrUdpSocketIpc::Destroy() { +#if defined(MOZILLA_INTERNAL_API) + // destroy_i also dispatches back to STS to call ReleaseUse, to avoid shutting + // down the IO thread before close() runs. + // We use a NonOwning runnable because our refcount has already gone to 0. + io_thread_->Dispatch(NewNonOwningRunnableMethod( + "NrUdpSocketIpc::Destroy", this, &NrUdpSocketIpc::destroy_i)); +#endif +} + +// IUDPSocketInternal interfaces +// callback while error happened in UDP socket operation +NS_IMETHODIMP NrUdpSocketIpc::CallListenerError(const nsACString& message, + const nsACString& filename, + uint32_t line_number) { + ASSERT_ON_THREAD(io_thread_); + + r_log(LOG_GENERIC, LOG_ERR, "UDP socket error:%s at %s:%d this=%p", + message.BeginReading(), filename.BeginReading(), line_number, + (void*)this); + + ReentrantMonitorAutoEnter mon(monitor_); + err_ = true; + monitor_.NotifyAll(); + + return NS_OK; +} + +// callback while receiving UDP packet +NS_IMETHODIMP NrUdpSocketIpc::CallListenerReceivedData( + const nsACString& host, uint16_t port, const nsTArray& data) { + ASSERT_ON_THREAD(io_thread_); + + PRNetAddr addr; + memset(&addr, 0, sizeof(addr)); + + { + ReentrantMonitorAutoEnter mon(monitor_); + + if (PR_SUCCESS != PR_StringToNetAddr(host.BeginReading(), &addr)) { + err_ = true; + MOZ_ASSERT(false, "Failed to convert remote host to PRNetAddr"); + return NS_OK; + } + + // Use PR_IpAddrNull to avoid address being reset to 0. + if (PR_SUCCESS != + PR_SetNetAddr(PR_IpAddrNull, addr.raw.family, port, &addr)) { + err_ = true; + MOZ_ASSERT(false, "Failed to set port in PRNetAddr"); + return NS_OK; + } + } + + auto buf = MakeUnique(); + buf->Copy(data.Elements(), data.Length()); + RefPtr msg(new nr_udp_message(addr, std::move(buf))); + + RUN_ON_THREAD(sts_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::recv_callback_s, msg), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult NrUdpSocketIpc::SetAddress() { + uint16_t port = socket_child_->LocalPort(); + + nsAutoCString address(socket_child_->LocalAddress()); + + PRNetAddr praddr; + if (PR_SUCCESS != PR_InitializeNetAddr(PR_IpAddrAny, port, &praddr)) { + err_ = true; + MOZ_ASSERT(false, "Failed to set port in PRNetAddr"); + return NS_OK; + } + + if (PR_SUCCESS != PR_StringToNetAddr(address.BeginReading(), &praddr)) { + err_ = true; + MOZ_ASSERT(false, "Failed to convert local host to PRNetAddr"); + return NS_OK; + } + + nr_transport_addr expected_addr; + if (nr_transport_addr_copy(&expected_addr, &my_addr_)) { + err_ = true; + MOZ_ASSERT(false, "Failed to copy my_addr_"); + } + + if (nr_praddr_to_transport_addr(&praddr, &my_addr_, IPPROTO_UDP, 1)) { + err_ = true; + MOZ_ASSERT(false, "Failed to copy local host to my_addr_"); + } + + if (!nr_transport_addr_is_wildcard(&expected_addr) && + nr_transport_addr_cmp(&expected_addr, &my_addr_, + NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) { + err_ = true; + MOZ_ASSERT(false, "Address of opened socket is not expected"); + } + + return NS_OK; +} + +// callback while UDP socket is opened +NS_IMETHODIMP NrUdpSocketIpc::CallListenerOpened() { + ASSERT_ON_THREAD(io_thread_); + ReentrantMonitorAutoEnter mon(monitor_); + + r_log(LOG_GENERIC, LOG_DEBUG, "UDP socket opened this=%p", (void*)this); + nsresult rv = SetAddress(); + if (NS_FAILED(rv)) { + return rv; + } + + mon.NotifyAll(); + + return NS_OK; +} + +// callback while UDP socket is connected +NS_IMETHODIMP NrUdpSocketIpc::CallListenerConnected() { + ASSERT_ON_THREAD(io_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + + r_log(LOG_GENERIC, LOG_DEBUG, "UDP socket connected this=%p", (void*)this); + MOZ_ASSERT(state_ == NR_CONNECTED); + + nsresult rv = SetAddress(); + if (NS_FAILED(rv)) { + mon.NotifyAll(); + return rv; + } + + r_log(LOG_GENERIC, LOG_INFO, "Exit UDP socket connected"); + mon.NotifyAll(); + + return NS_OK; +} + +// callback while UDP socket is closed +NS_IMETHODIMP NrUdpSocketIpc::CallListenerClosed() { + ASSERT_ON_THREAD(io_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + + r_log(LOG_GENERIC, LOG_DEBUG, "UDP socket closed this=%p", (void*)this); + MOZ_ASSERT(state_ == NR_CONNECTED || state_ == NR_CLOSING); + state_ = NR_CLOSED; + + return NS_OK; +} + +// +// NrSocketBase methods. +// +int NrUdpSocketIpc::create(nr_transport_addr* addr) { + ASSERT_ON_THREAD(sts_thread_); + + int r, _status; + nsresult rv; + int32_t port; + nsCString host; + + ReentrantMonitorAutoEnter mon(monitor_); + + if (state_ != NR_INIT) { + ABORT(R_INTERNAL); + } + + sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to get STS thread"); + ABORT(R_INTERNAL); + } + + if ((r = nr_transport_addr_get_addrstring_and_port(addr, &host, &port))) { + ABORT(r); + } + + // wildcard address will be resolved at NrUdpSocketIpc::CallListenerVoid + if ((r = nr_transport_addr_copy(&my_addr_, addr))) { + ABORT(r); + } + + state_ = NR_CONNECTING; + + MOZ_ASSERT(io_thread_); + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::create_i, host, + static_cast(port)), + NS_DISPATCH_NORMAL); + + // Wait until socket creation complete. + mon.Wait(); + + if (err_) { + close(); + ABORT(R_INTERNAL); + } + + state_ = NR_CONNECTED; + + _status = 0; +abort: + return (_status); +} + +int NrUdpSocketIpc::sendto(const void* msg, size_t len, int flags, + const nr_transport_addr* to) { + ASSERT_ON_THREAD(sts_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + + // If send err happened before, simply return the error. + if (err_) { + return R_IO_ERROR; + } + + if (state_ != NR_CONNECTED) { + return R_INTERNAL; + } + + int r; + net::NetAddr addr; + if ((r = nr_transport_addr_to_netaddr(to, &addr))) { + return r; + } + + if (nr_is_stun_request_message((UCHAR*)msg, len) && ShouldDrop(len)) { + return R_WOULDBLOCK; + } + + UniquePtr buf(new MediaPacket); + buf->Copy(static_cast(msg), len); + + RUN_ON_THREAD( + io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::sendto_i, addr, std::move(buf)), + NS_DISPATCH_NORMAL); + return 0; +} + +void NrUdpSocketIpc::close() { + r_log(LOG_GENERIC, LOG_DEBUG, "NrUdpSocketIpc::close()"); + + ASSERT_ON_THREAD(sts_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + state_ = NR_CLOSING; + + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::close_i), + NS_DISPATCH_NORMAL); + + // remove all enqueued messages + std::queue> empty; + std::swap(received_msgs_, empty); +} + +int NrUdpSocketIpc::recvfrom(void* buf, size_t maxlen, size_t* len, int flags, + nr_transport_addr* from) { + ASSERT_ON_THREAD(sts_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + + int r, _status; + uint32_t consumed_len; + + *len = 0; + + if (state_ != NR_CONNECTED) { + ABORT(R_INTERNAL); + } + + if (received_msgs_.empty()) { + ABORT(R_WOULDBLOCK); + } + + { + RefPtr msg(received_msgs_.front()); + + received_msgs_.pop(); + + if ((r = nr_praddr_to_transport_addr(&msg->from, from, IPPROTO_UDP, 0))) { + err_ = true; + MOZ_ASSERT(false, "Get bogus address for received UDP packet"); + ABORT(r); + } + + consumed_len = std::min(maxlen, msg->data->len()); + if (consumed_len < msg->data->len()) { + r_log(LOG_GENERIC, LOG_DEBUG, + "Partial received UDP packet will be discard"); + } + + memcpy(buf, msg->data->data(), consumed_len); + *len = consumed_len; + } + + _status = 0; +abort: + return (_status); +} + +int NrUdpSocketIpc::getaddr(nr_transport_addr* addrp) { + ASSERT_ON_THREAD(sts_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + + return nr_transport_addr_copy(addrp, &my_addr_); +} + +int NrUdpSocketIpc::connect(const nr_transport_addr* addr) { + int r, _status; + int32_t port; + nsCString host; + + ReentrantMonitorAutoEnter mon(monitor_); + r_log(LOG_GENERIC, LOG_DEBUG, "NrUdpSocketIpc::connect(%s) this=%p", + addr->as_string, (void*)this); + + if ((r = nr_transport_addr_get_addrstring_and_port(addr, &host, &port))) { + ABORT(r); + } + + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::connect_i, host, + static_cast(port)), + NS_DISPATCH_NORMAL); + + // Wait until connect() completes. + mon.Wait(); + + r_log(LOG_GENERIC, LOG_DEBUG, + "NrUdpSocketIpc::connect this=%p completed err_ = %s", (void*)this, + err_ ? "true" : "false"); + + if (err_) { + ABORT(R_INTERNAL); + } + + _status = 0; +abort: + return _status; +} + +int NrUdpSocketIpc::write(const void* msg, size_t len, size_t* written) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +int NrUdpSocketIpc::read(void* buf, size_t maxlen, size_t* len) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +int NrUdpSocketIpc::listen(int backlog) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +int NrUdpSocketIpc::accept(nr_transport_addr* addrp, nr_socket** sockp) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +// IO thread executors +void NrUdpSocketIpc::create_i(const nsACString& host, const uint16_t port) { + ASSERT_ON_THREAD(io_thread_); + + uint32_t minBuffSize = 0; + RefPtr socketChild = new dom::UDPSocketChild(); + + // This can spin the event loop; don't do that with the monitor held + socketChild->SetBackgroundSpinsEvents(); + + ReentrantMonitorAutoEnter mon(monitor_); + if (!socket_child_) { + socket_child_ = socketChild; + socket_child_->SetFilterName( + nsCString(NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX)); + } else { + socketChild = nullptr; + } + + RefPtr proxy(new NrUdpSocketIpcProxy); + nsresult rv = proxy->Init(this); + if (NS_FAILED(rv)) { + err_ = true; + mon.NotifyAll(); + return; + } + +#ifdef XP_WIN + if (!mozilla::IsWin8OrLater()) { + // Increase default receive and send buffer size on <= Win7 to be able to + // receive and send an unpaced HD (>= 720p = 1280x720 - I Frame ~ 21K size) + // stream without losing packets. + // Manual testing showed that 100K buffer size was not enough and the + // packet loss dis-appeared with 256K buffer size. + // See bug 1252769 for future improvements of this. + minBuffSize = 256 * 1024; + } +#endif + // XXX bug 1126232 - don't use null Principal! + if (NS_FAILED(socket_child_->Bind(proxy, nullptr, host, port, + /* addressReuse = */ false, + /* loopback = */ false, + /* recv buffer size */ minBuffSize, + /* send buffer size */ minBuffSize))) { + err_ = true; + MOZ_ASSERT(false, "Failed to create UDP socket"); + mon.NotifyAll(); + return; + } +} + +void NrUdpSocketIpc::connect_i(const nsACString& host, const uint16_t port) { + ASSERT_ON_THREAD(io_thread_); + nsresult rv; + ReentrantMonitorAutoEnter mon(monitor_); + + RefPtr proxy(new NrUdpSocketIpcProxy); + rv = proxy->Init(this); + if (NS_FAILED(rv)) { + err_ = true; + mon.NotifyAll(); + return; + } + + socket_child_->Connect(proxy, host, port); +} + +void NrUdpSocketIpc::sendto_i(const net::NetAddr& addr, + UniquePtr buf) { + ASSERT_ON_THREAD(io_thread_); + + ReentrantMonitorAutoEnter mon(monitor_); + + if (!socket_child_) { + MOZ_ASSERT(false); + err_ = true; + return; + } + if (NS_FAILED( + socket_child_->SendWithAddress(&addr, buf->data(), buf->len()))) { + err_ = true; + } +} + +void NrUdpSocketIpc::close_i() { + ASSERT_ON_THREAD(io_thread_); + + if (socket_child_) { + socket_child_->Close(); + socket_child_ = nullptr; + } +} + +#if defined(MOZILLA_INTERNAL_API) + +static void ReleaseIOThread_s() { sThread->ReleaseUse(); } + +void NrUdpSocketIpc::destroy_i() { + close_i(); + + RUN_ON_THREAD(sts_thread_, WrapRunnableNM(&ReleaseIOThread_s), + NS_DISPATCH_NORMAL); +} +#endif + +void NrUdpSocketIpc::recv_callback_s(RefPtr msg) { + ASSERT_ON_THREAD(sts_thread_); + + { + ReentrantMonitorAutoEnter mon(monitor_); + if (state_ != NR_CONNECTED) { + return; + } + } + + // enqueue received message + received_msgs_.push(msg); + + if ((poll_flags() & PR_POLL_READ)) { + fire_callback(NR_ASYNC_WAIT_READ); + } +} + +} // namespace mozilla + +using namespace mozilla; + +// Bridge to the nr_socket interface +static int nr_socket_local_destroy(void** objp); +static int nr_socket_local_sendto(void* obj, const void* msg, size_t len, + int flags, const nr_transport_addr* to); +static int nr_socket_local_recvfrom(void* obj, void* restrict buf, + size_t maxlen, size_t* len, int flags, + nr_transport_addr* from); +static int nr_socket_local_getfd(void* obj, NR_SOCKET* fd); +static int nr_socket_local_getaddr(void* obj, nr_transport_addr* addrp); +static int nr_socket_local_close(void* obj); +static int nr_socket_local_connect(void* obj, const nr_transport_addr* addr); +static int nr_socket_local_write(void* obj, const void* msg, size_t len, + size_t* written); +static int nr_socket_local_read(void* obj, void* restrict buf, size_t maxlen, + size_t* len); +static int nr_socket_local_listen(void* obj, int backlog); +static int nr_socket_local_accept(void* obj, nr_transport_addr* addrp, + nr_socket** sockp); + +static nr_socket_vtbl nr_socket_local_vtbl = {2, + nr_socket_local_destroy, + nr_socket_local_sendto, + nr_socket_local_recvfrom, + nr_socket_local_getfd, + nr_socket_local_getaddr, + nr_socket_local_connect, + nr_socket_local_write, + nr_socket_local_read, + nr_socket_local_close, + nr_socket_local_listen, + nr_socket_local_accept}; + +/* static */ +int NrSocketBase::CreateSocket( + nr_transport_addr* addr, RefPtr* sock, + const std::shared_ptr& config) { + int r, _status; + + if (IsForbiddenAddress(addr)) { + ABORT(R_REJECTED); + } + + if (config && config->GetForceProxy() && addr->protocol == IPPROTO_UDP) { + ABORT(R_REJECTED); + } + + // create IPC bridge for content process + if (XRE_IsParentProcess()) { + // TODO: Make NrTcpSocket work on the parent process + *sock = new NrSocket(); + } else if (XRE_IsSocketProcess()) { + if (addr->protocol == IPPROTO_TCP) { + *sock = new NrTcpSocket(config); + } else { + *sock = new NrSocket(); + } + } else { + if (addr->protocol == IPPROTO_TCP) { + *sock = new NrTcpSocket(config); + } else { + *sock = new NrUdpSocketIpc(); + } + } + + r = (*sock)->create(addr); + if (r) ABORT(r); + + _status = 0; +abort: + if (_status) { + *sock = nullptr; + } + return _status; +} + +// static +bool NrSocketBase::IsForbiddenAddress(nr_transport_addr* addr) { + int r, port; + + r = nr_transport_addr_get_port(addr, &port); + if (r) { + return true; + } + + // allow auto assigned ports + if (port != 0) { + // Don't need to check an override scheme + nsresult rv = NS_CheckPortSafety(port, nullptr); + if (NS_FAILED(rv)) { + return true; + } + } + + return false; +} + +static int nr_socket_local_destroy(void** objp) { + if (!objp || !*objp) return 0; + + NrSocketBase* sock = static_cast(*objp); + *objp = nullptr; + + sock->close(); // Signal STS that we want not to listen + sock->Release(); // Decrement the ref count + + return 0; +} + +static int nr_socket_local_sendto(void* obj, const void* msg, size_t len, + int flags, const nr_transport_addr* addr) { + NrSocketBase* sock = static_cast(obj); + + return sock->sendto(msg, len, flags, addr); +} + +static int nr_socket_local_recvfrom(void* obj, void* restrict buf, + size_t maxlen, size_t* len, int flags, + nr_transport_addr* addr) { + NrSocketBase* sock = static_cast(obj); + + return sock->recvfrom(buf, maxlen, len, flags, addr); +} + +static int nr_socket_local_getfd(void* obj, NR_SOCKET* fd) { + NrSocketBase* sock = static_cast(obj); + + *fd = sock; + + return 0; +} + +static int nr_socket_local_getaddr(void* obj, nr_transport_addr* addrp) { + NrSocketBase* sock = static_cast(obj); + + return sock->getaddr(addrp); +} + +static int nr_socket_local_close(void* obj) { + NrSocketBase* sock = static_cast(obj); + + sock->close(); + + return 0; +} + +static int nr_socket_local_write(void* obj, const void* msg, size_t len, + size_t* written) { + NrSocketBase* sock = static_cast(obj); + + return sock->write(msg, len, written); +} + +static int nr_socket_local_read(void* obj, void* restrict buf, size_t maxlen, + size_t* len) { + NrSocketBase* sock = static_cast(obj); + + return sock->read(buf, maxlen, len); +} + +static int nr_socket_local_connect(void* obj, const nr_transport_addr* addr) { + NrSocketBase* sock = static_cast(obj); + + return sock->connect(addr); +} + +static int nr_socket_local_listen(void* obj, int backlog) { + NrSocketBase* sock = static_cast(obj); + + return sock->listen(backlog); +} + +static int nr_socket_local_accept(void* obj, nr_transport_addr* addrp, + nr_socket** sockp) { + NrSocketBase* sock = static_cast(obj); + + return sock->accept(addrp, sockp); +} + +// Implement async api +int NR_async_wait(NR_SOCKET sock, int how, NR_async_cb cb, void* cb_arg, + char* function, int line) { + NrSocketBase* s = static_cast(sock); + + return s->async_wait(how, cb, cb_arg, function, line); +} + +int NR_async_cancel(NR_SOCKET sock, int how) { + NrSocketBase* s = static_cast(sock); + + return s->cancel(how); +} + +nr_socket_vtbl* NrSocketBase::vtbl() { return &nr_socket_local_vtbl; } -- cgit v1.2.3