michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: /* michael@0: Modified version of nr_socket_local, adapted for NSPR michael@0: */ michael@0: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: Original code from nICEr and nrappkit. michael@0: michael@0: nICEr copyright: michael@0: michael@0: Copyright (c) 2007, Adobe Systems, Incorporated michael@0: All rights reserved. michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions are michael@0: met: michael@0: michael@0: * Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: michael@0: * Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: michael@0: * Neither the name of Adobe Systems, Network Resonance nor the names of its michael@0: contributors may be used to endorse or promote products derived from michael@0: this software without specific prior written permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: nrappkit copyright: michael@0: michael@0: Copyright (C) 2001-2003, Network Resonance, Inc. michael@0: Copyright (C) 2006, Network Resonance, Inc. michael@0: All Rights Reserved michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions michael@0: are met: michael@0: michael@0: 1. Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: 2. Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: 3. Neither the name of Network Resonance, Inc. nor the name of any michael@0: contributors to this software may be used to endorse or promote michael@0: products derived from this software without specific prior written michael@0: permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' michael@0: AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE michael@0: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE michael@0: ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE michael@0: LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR michael@0: CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF michael@0: SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS michael@0: INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN michael@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) michael@0: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE michael@0: POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: ekr@rtfm.com Thu Dec 20 20:14:49 2001 michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "nspr.h" michael@0: #include "prerror.h" michael@0: #include "prio.h" michael@0: #include "prnetdb.h" michael@0: michael@0: #include "mozilla/net/DNS.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsASocketHandler.h" michael@0: #include "nsISocketTransportService.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "runnable_utils.h" michael@0: michael@0: extern "C" { michael@0: #include "nr_api.h" michael@0: #include "async_wait.h" michael@0: #include "nr_socket.h" michael@0: #include "nr_socket_local.h" michael@0: #include "stun_hint.h" michael@0: } michael@0: #include "nr_socket_prsock.h" michael@0: #include "simpletokenbucket.h" michael@0: michael@0: // Implement the nsISupports ref counting michael@0: namespace mozilla { michael@0: michael@0: // NrSocketBase implementation michael@0: // async_event APIs michael@0: int NrSocketBase::async_wait(int how, NR_async_cb cb, void *cb_arg, michael@0: char *function, int line) { michael@0: uint16_t flag; michael@0: michael@0: switch (how) { michael@0: case NR_ASYNC_WAIT_READ: michael@0: flag = PR_POLL_READ; michael@0: break; michael@0: case NR_ASYNC_WAIT_WRITE: michael@0: flag = PR_POLL_WRITE; michael@0: break; michael@0: default: michael@0: return R_BAD_ARGS; michael@0: } michael@0: michael@0: cbs_[how] = cb; michael@0: cb_args_[how] = cb_arg; michael@0: poll_flags_ |= flag; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int NrSocketBase::cancel(int how) { michael@0: uint16_t flag; michael@0: michael@0: switch (how) { michael@0: case NR_ASYNC_WAIT_READ: michael@0: flag = PR_POLL_READ; michael@0: break; michael@0: case NR_ASYNC_WAIT_WRITE: michael@0: flag = PR_POLL_WRITE; michael@0: break; michael@0: default: michael@0: return R_BAD_ARGS; michael@0: } michael@0: michael@0: poll_flags_ &= ~flag; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: void NrSocketBase::fire_callback(int how) { michael@0: // This can't happen unless we are armed because we only set michael@0: // the flags if we are armed michael@0: MOZ_ASSERT(cbs_[how]); michael@0: michael@0: // Now cancel so that we need to be re-armed. Note that michael@0: // the re-arming probably happens in the callback we are michael@0: // about to fire. michael@0: cancel(how); michael@0: michael@0: cbs_[how](this, how, cb_args_[how]); michael@0: } michael@0: michael@0: // NrSocket implementation michael@0: NS_IMPL_ISUPPORTS0(NrSocket) michael@0: michael@0: michael@0: // The nsASocket callbacks michael@0: void NrSocket::OnSocketReady(PRFileDesc *fd, int16_t outflags) { michael@0: if (outflags & PR_POLL_READ) michael@0: fire_callback(NR_ASYNC_WAIT_READ); michael@0: if (outflags & PR_POLL_WRITE) michael@0: fire_callback(NR_ASYNC_WAIT_WRITE); michael@0: } michael@0: michael@0: void NrSocket::OnSocketDetached(PRFileDesc *fd) { michael@0: ; // TODO: Log? michael@0: } michael@0: michael@0: void NrSocket::IsLocal(bool *aIsLocal) { michael@0: // TODO(jesup): better check? Does it matter? (likely no) michael@0: *aIsLocal = false; michael@0: } michael@0: michael@0: // async_event APIs michael@0: int NrSocket::async_wait(int how, NR_async_cb cb, void *cb_arg, michael@0: char *function, int line) { michael@0: int r = NrSocketBase::async_wait(how, cb, cb_arg, function, line); michael@0: michael@0: if (!r) { michael@0: mPollFlags = poll_flags(); michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: int NrSocket::cancel(int how) { michael@0: int r = NrSocketBase::cancel(how); michael@0: michael@0: if (!r) { michael@0: mPollFlags = poll_flags(); michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: // Helper functions for addresses michael@0: static int nr_transport_addr_to_praddr(nr_transport_addr *addr, michael@0: PRNetAddr *naddr) michael@0: { michael@0: int _status; michael@0: michael@0: memset(naddr, 0, sizeof(*naddr)); michael@0: michael@0: switch(addr->protocol){ michael@0: case IPPROTO_TCP: michael@0: break; michael@0: case IPPROTO_UDP: michael@0: break; michael@0: default: michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: michael@0: switch(addr->ip_version){ michael@0: case NR_IPV4: michael@0: naddr->inet.family = PR_AF_INET; michael@0: naddr->inet.port = addr->u.addr4.sin_port; michael@0: naddr->inet.ip = addr->u.addr4.sin_addr.s_addr; michael@0: break; michael@0: case NR_IPV6: michael@0: #if 0 michael@0: naddr->ipv6.family = PR_AF_INET6; michael@0: naddr->ipv6.port = addr->u.addr6.sin6_port; michael@0: #ifdef LINUX michael@0: memcpy(naddr->ipv6.ip._S6_un._S6_u8, michael@0: &addr->u.addr6.sin6_addr.__in6_u.__u6_addr8, 16); michael@0: #else michael@0: memcpy(naddr->ipv6.ip._S6_un._S6_u8, michael@0: &addr->u.addr6.sin6_addr.__u6_addr.__u6_addr8, 16); michael@0: #endif michael@0: #else michael@0: // TODO: make IPv6 work michael@0: ABORT(R_INTERNAL); michael@0: #endif michael@0: break; michael@0: default: michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: //XXX schien@mozilla.com: copy from PRNetAddrToNetAddr, michael@0: // should be removed after fix the link error in signaling_unittests michael@0: static int praddr_to_netaddr(const PRNetAddr *prAddr, net::NetAddr *addr) michael@0: { michael@0: int _status; michael@0: michael@0: switch (prAddr->raw.family) { michael@0: case PR_AF_INET: michael@0: addr->inet.family = AF_INET; michael@0: addr->inet.port = prAddr->inet.port; michael@0: addr->inet.ip = prAddr->inet.ip; michael@0: break; michael@0: case PR_AF_INET6: michael@0: addr->inet6.family = AF_INET6; michael@0: addr->inet6.port = prAddr->ipv6.port; michael@0: addr->inet6.flowinfo = prAddr->ipv6.flowinfo; michael@0: memcpy(&addr->inet6.ip, &prAddr->ipv6.ip, sizeof(addr->inet6.ip.u8)); michael@0: addr->inet6.scope_id = prAddr->ipv6.scope_id; michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false); michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: static int nr_transport_addr_to_netaddr(nr_transport_addr *addr, michael@0: net::NetAddr *naddr) michael@0: { michael@0: int r, _status; michael@0: PRNetAddr praddr; michael@0: michael@0: if((r = nr_transport_addr_to_praddr(addr, &praddr))) { michael@0: ABORT(r); michael@0: } michael@0: michael@0: if((r = praddr_to_netaddr(&praddr, naddr))) { michael@0: ABORT(r); michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int nr_netaddr_to_transport_addr(const net::NetAddr *netaddr, michael@0: nr_transport_addr *addr, int protocol) michael@0: { michael@0: int _status; michael@0: int r; michael@0: michael@0: switch(netaddr->raw.family) { michael@0: case AF_INET: michael@0: if ((r = nr_ip4_port_to_transport_addr(ntohl(netaddr->inet.ip), michael@0: ntohs(netaddr->inet.port), michael@0: protocol, addr))) michael@0: ABORT(r); michael@0: break; michael@0: case AF_INET6: michael@0: ABORT(R_BAD_ARGS); michael@0: default: michael@0: MOZ_ASSERT(false); michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int nr_praddr_to_transport_addr(const PRNetAddr *praddr, michael@0: nr_transport_addr *addr, int protocol, michael@0: int keep) michael@0: { michael@0: int _status; michael@0: int r; michael@0: struct sockaddr_in ip4; michael@0: michael@0: switch(praddr->raw.family) { michael@0: case PR_AF_INET: michael@0: ip4.sin_family = PF_INET; michael@0: ip4.sin_addr.s_addr = praddr->inet.ip; michael@0: ip4.sin_port = praddr->inet.port; michael@0: if ((r = nr_sockaddr_to_transport_addr((sockaddr *)&ip4, michael@0: sizeof(ip4), michael@0: protocol, keep, michael@0: addr))) michael@0: ABORT(r); michael@0: break; michael@0: case PR_AF_INET6: michael@0: #if 0 michael@0: r = nr_sockaddr_to_transport_addr((sockaddr *)&praddr->raw, michael@0: sizeof(struct sockaddr_in6),IPPROTO_UDP,keep,addr); michael@0: break; michael@0: #endif michael@0: ABORT(R_BAD_ARGS); michael@0: default: michael@0: MOZ_ASSERT(false); michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: /* michael@0: * nr_transport_addr_get_addrstring_and_port michael@0: * convert nr_transport_addr to IP address string and port number michael@0: */ michael@0: int nr_transport_addr_get_addrstring_and_port(nr_transport_addr *addr, michael@0: nsACString *host, int32_t *port) { michael@0: int r, _status; michael@0: char addr_string[64]; michael@0: michael@0: // We cannot directly use |nr_transport_addr.as_string| because it contains michael@0: // more than ip address, therefore, we need to explicity convert it michael@0: // from |nr_transport_addr_get_addrstring|. michael@0: if ((r=nr_transport_addr_get_addrstring(addr, addr_string, sizeof(addr_string)))) { michael@0: ABORT(r); michael@0: } michael@0: michael@0: if ((r=nr_transport_addr_get_port(addr, port))) { michael@0: ABORT(r); michael@0: } michael@0: michael@0: *host = addr_string; michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: // nr_socket APIs (as member functions) michael@0: int NrSocket::create(nr_transport_addr *addr) { michael@0: int r,_status; michael@0: michael@0: PRStatus status; michael@0: PRNetAddr naddr; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr stservice = michael@0: do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: if (!NS_SUCCEEDED(rv)) { michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: if((r=nr_transport_addr_to_praddr(addr, &naddr))) michael@0: ABORT(r); michael@0: michael@0: switch (addr->protocol) { michael@0: case IPPROTO_UDP: michael@0: if (!(fd_ = PR_NewUDPSocket())) { michael@0: r_log(LOG_GENERIC,LOG_CRIT,"Couldn't create socket"); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: break; michael@0: case IPPROTO_TCP: michael@0: if (!(fd_ = PR_NewTCPSocket())) { michael@0: r_log(LOG_GENERIC,LOG_CRIT,"Couldn't create socket"); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: break; michael@0: default: michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: status = PR_Bind(fd_, &naddr); michael@0: if (status != PR_SUCCESS) { michael@0: r_log(LOG_GENERIC,LOG_CRIT,"Couldn't bind socket to address %s", michael@0: addr->as_string); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: r_log(LOG_GENERIC,LOG_DEBUG,"Creating socket %p with addr %s", michael@0: fd_, addr->as_string); michael@0: nr_transport_addr_copy(&my_addr_,addr); michael@0: michael@0: /* If we have a wildcard port, patch up the addr */ michael@0: if(nr_transport_addr_is_wildcard(addr)){ michael@0: status = PR_GetSockName(fd_, &naddr); michael@0: if (status != PR_SUCCESS){ michael@0: r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket"); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: if((r=nr_praddr_to_transport_addr(&naddr,&my_addr_,addr->protocol,1))) michael@0: ABORT(r); michael@0: } michael@0: michael@0: michael@0: // Set nonblocking michael@0: PRSocketOptionData option; michael@0: option.option = PR_SockOpt_Nonblocking; michael@0: option.value.non_blocking = PR_TRUE; michael@0: status = PR_SetSocketOption(fd_, &option); michael@0: if (status != PR_SUCCESS) { michael@0: r_log(LOG_GENERIC, LOG_CRIT, "Couldn't make socket nonblocking"); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: // Remember our thread. michael@0: ststhread_ = do_QueryInterface(stservice, &rv); michael@0: if (!NS_SUCCEEDED(rv)) michael@0: ABORT(R_INTERNAL); michael@0: michael@0: // Finally, register with the STS michael@0: rv = stservice->AttachSocket(fd_, this); michael@0: if (!NS_SUCCEEDED(rv)) { michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: _status = 0; michael@0: michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: // This should be called on the STS thread. michael@0: int NrSocket::sendto(const void *msg, size_t len, michael@0: int flags, nr_transport_addr *to) { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: int r,_status; michael@0: PRNetAddr naddr; michael@0: int32_t status; michael@0: michael@0: if ((r=nr_transport_addr_to_praddr(to, &naddr))) michael@0: ABORT(r); michael@0: michael@0: if(fd_==nullptr) michael@0: ABORT(R_EOD); michael@0: michael@0: if (nr_is_stun_request_message((UCHAR*)msg, len)) { michael@0: // Global rate limiting for stun requests, to mitigate the ice hammer DoS michael@0: // (see http://tools.ietf.org/html/draft-thomson-mmusic-ice-webrtc) michael@0: michael@0: // Tolerate rate of 8k/sec, for one second. michael@0: static SimpleTokenBucket burst(8192*1, 8192); michael@0: // Tolerate rate of 3.6k/sec over twenty seconds. michael@0: static SimpleTokenBucket sustained(3686*20, 3686); michael@0: michael@0: // Check number of tokens in each bucket. michael@0: if (burst.getTokens(UINT32_MAX) < len || michael@0: sustained.getTokens(UINT32_MAX) < len) { michael@0: r_log(LOG_GENERIC, LOG_ERR, michael@0: "Global rate limit for STUN requests exceeded."); michael@0: MOZ_ASSERT(false, michael@0: "Global rate limit for STUN requests exceeded. Go bug " michael@0: "bcampen@mozilla.com if you weren't intentionally spamming " michael@0: "ICE candidates, or don't know what that means."); michael@0: ABORT(R_WOULDBLOCK); michael@0: } michael@0: michael@0: // Take len tokens from both buckets. michael@0: // (not threadsafe, but no problem since this is only called from STS) michael@0: burst.getTokens(len); michael@0: sustained.getTokens(len); michael@0: } michael@0: michael@0: // TODO: Convert flags? michael@0: status = PR_SendTo(fd_, msg, len, flags, &naddr, PR_INTERVAL_NO_WAIT); michael@0: if (status < 0 || (size_t)status != len) { michael@0: if (PR_GetError() == PR_WOULD_BLOCK_ERROR) michael@0: ABORT(R_WOULDBLOCK); michael@0: michael@0: r_log(LOG_GENERIC, LOG_INFO, "Error in sendto %s", to->as_string); michael@0: ABORT(R_IO_ERROR); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int NrSocket::recvfrom(void * buf, size_t maxlen, michael@0: size_t *len, int flags, michael@0: nr_transport_addr *from) { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: int r,_status; michael@0: PRNetAddr nfrom; michael@0: int32_t status; michael@0: michael@0: status = PR_RecvFrom(fd_, buf, maxlen, flags, &nfrom, PR_INTERVAL_NO_WAIT); michael@0: if (status <= 0) { michael@0: r_log(LOG_GENERIC,LOG_ERR,"Error in recvfrom"); michael@0: ABORT(R_IO_ERROR); michael@0: } michael@0: *len=status; michael@0: michael@0: if((r=nr_praddr_to_transport_addr(&nfrom,from,my_addr_.protocol,0))) michael@0: ABORT(r); michael@0: michael@0: //r_log(LOG_GENERIC,LOG_DEBUG,"Read %d bytes from %s",*len,addr->as_string); michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int NrSocket::getaddr(nr_transport_addr *addrp) { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: return nr_transport_addr_copy(addrp, &my_addr_); michael@0: } michael@0: michael@0: // Close the socket so that the STS will detach and then kill it michael@0: void NrSocket::close() { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: mCondition = NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: michael@0: int NrSocket::connect(nr_transport_addr *addr) { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: int r,_status; michael@0: PRNetAddr naddr; michael@0: int32_t status; michael@0: michael@0: if ((r=nr_transport_addr_to_praddr(addr, &naddr))) michael@0: ABORT(r); michael@0: michael@0: if(!fd_) michael@0: ABORT(R_EOD); michael@0: michael@0: // Note: this just means we tried to connect, not that we michael@0: // are actually live. michael@0: connect_invoked_ = true; michael@0: status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT); michael@0: michael@0: if (status != PR_SUCCESS) { michael@0: if (PR_GetError() == PR_IN_PROGRESS_ERROR) michael@0: ABORT(R_WOULDBLOCK); michael@0: michael@0: ABORT(R_IO_ERROR); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: michael@0: int NrSocket::write(const void *msg, size_t len, size_t *written) { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: int _status; michael@0: int32_t status; michael@0: michael@0: if (!connect_invoked_) michael@0: ABORT(R_FAILED); michael@0: michael@0: status = PR_Write(fd_, msg, len); michael@0: if (status < 0) { michael@0: if (PR_GetError() == PR_WOULD_BLOCK_ERROR) michael@0: ABORT(R_WOULDBLOCK); michael@0: ABORT(R_IO_ERROR); michael@0: } michael@0: michael@0: *written = status; michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: int NrSocket::read(void* buf, size_t maxlen, size_t *len) { michael@0: ASSERT_ON_THREAD(ststhread_); michael@0: int _status; michael@0: int32_t status; michael@0: michael@0: if (!connect_invoked_) michael@0: ABORT(R_FAILED); michael@0: michael@0: status = PR_Read(fd_, buf, maxlen); michael@0: if (status < 0) { michael@0: if (PR_GetError() == PR_WOULD_BLOCK_ERROR) michael@0: ABORT(R_WOULDBLOCK); michael@0: ABORT(R_IO_ERROR); michael@0: } michael@0: if (status == 0) michael@0: ABORT(R_EOD); michael@0: michael@0: *len = (size_t)status; // Guaranteed to be > 0 michael@0: _status = 0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: // NrSocketIpc Implementation michael@0: NS_IMPL_ISUPPORTS(NrSocketIpc, nsIUDPSocketInternal) michael@0: michael@0: NrSocketIpc::NrSocketIpc(const nsCOMPtr &main_thread) michael@0: : err_(false), michael@0: state_(NR_INIT), michael@0: main_thread_(main_thread), michael@0: monitor_("NrSocketIpc") { michael@0: } michael@0: michael@0: // IUDPSocketInternal interfaces michael@0: // callback while error happened in UDP socket operation michael@0: NS_IMETHODIMP NrSocketIpc::CallListenerError(const nsACString &type, michael@0: const nsACString &message, michael@0: const nsACString &filename, michael@0: uint32_t line_number, michael@0: uint32_t column_number) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: MOZ_ASSERT(type.EqualsLiteral("onerror")); michael@0: michael@0: r_log(LOG_GENERIC, LOG_ERR, "UDP socket error:%s at %s:%d:%d", michael@0: message.BeginReading(), filename.BeginReading(), michael@0: line_number, column_number); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: err_ = true; michael@0: monitor_.NotifyAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // callback while receiving UDP packet michael@0: NS_IMETHODIMP NrSocketIpc::CallListenerReceivedData(const nsACString &type, michael@0: const nsACString &host, michael@0: uint16_t port, uint8_t *data, michael@0: uint32_t data_length) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: MOZ_ASSERT(type.EqualsLiteral("ondata")); michael@0: michael@0: PRNetAddr addr; michael@0: memset(&addr, 0, sizeof(addr)); michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: if (PR_SUCCESS != PR_StringToNetAddr(host.BeginReading(), &addr)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to convert remote host to PRNetAddr"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Use PR_IpAddrNull to avoid address being reset to 0. michael@0: if (PR_SUCCESS != PR_SetNetAddr(PR_IpAddrNull, addr.raw.family, port, &addr)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to set port in PRNetAddr"); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsAutoPtr buf(new DataBuffer(data, data_length)); michael@0: RefPtr msg(new nr_udp_message(addr, buf)); michael@0: michael@0: RUN_ON_THREAD(sts_thread_, michael@0: mozilla::WrapRunnable(nsRefPtr(this), michael@0: &NrSocketIpc::recv_callback_s, michael@0: msg), michael@0: NS_DISPATCH_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // callback while UDP socket is opened or closed michael@0: NS_IMETHODIMP NrSocketIpc::CallListenerVoid(const nsACString &type) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: if (type.EqualsLiteral("onopen")) { michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: uint16_t port; michael@0: if (NS_FAILED(socket_child_->GetLocalPort(&port))) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to get local port"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString address; michael@0: if(NS_FAILED(socket_child_->GetLocalAddress(address))) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to get local address"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: PRNetAddr praddr; michael@0: if (PR_SUCCESS != PR_InitializeNetAddr(PR_IpAddrAny, port, &praddr)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to set port in PRNetAddr"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (PR_SUCCESS != PR_StringToNetAddr(address.BeginReading(), &praddr)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to convert local host to PRNetAddr"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nr_transport_addr expected_addr; michael@0: if(nr_transport_addr_copy(&expected_addr, &my_addr_)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to copy my_addr_"); michael@0: } michael@0: michael@0: if (nr_praddr_to_transport_addr(&praddr, &my_addr_, IPPROTO_UDP, 1)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to copy local host to my_addr_"); michael@0: } michael@0: michael@0: if (nr_transport_addr_cmp(&expected_addr, &my_addr_, michael@0: NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Address of opened socket is not expected"); michael@0: } michael@0: michael@0: mon.NotifyAll(); michael@0: } else if (type.EqualsLiteral("onclose")) { michael@0: // Already handled in UpdateReadyState, nothing to do here michael@0: } else { michael@0: MOZ_ASSERT(false, "Received unexpected event"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // callback while UDP packet is sent michael@0: NS_IMETHODIMP NrSocketIpc::CallListenerSent(const nsACString &type, michael@0: nsresult result) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: MOZ_ASSERT(type.EqualsLiteral("onsent")); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: err_ = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // callback for state update after every socket operation michael@0: NS_IMETHODIMP NrSocketIpc::UpdateReadyState(const nsACString &readyState) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: if (readyState.EqualsLiteral("closed")) { michael@0: MOZ_ASSERT(state_ == NR_CONNECTED || state_ == NR_CLOSING); michael@0: state_ = NR_CLOSED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nr_socket public APIs michael@0: int NrSocketIpc::create(nr_transport_addr *addr) { michael@0: ASSERT_ON_THREAD(sts_thread_); michael@0: michael@0: int r, _status; michael@0: nsresult rv; michael@0: int32_t port; michael@0: nsCString host; michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: if (state_ != NR_INIT) { michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_ASSERT(false, "Failed to get STS thread"); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: if ((r=nr_transport_addr_get_addrstring_and_port(addr, &host, &port))) { michael@0: ABORT(r); michael@0: } michael@0: michael@0: // wildcard address will be resolved at NrSocketIpc::CallListenerVoid michael@0: if ((r=nr_transport_addr_copy(&my_addr_, addr))) { michael@0: ABORT(r); michael@0: } michael@0: michael@0: state_ = NR_CONNECTING; michael@0: michael@0: RUN_ON_THREAD(main_thread_, michael@0: mozilla::WrapRunnable(nsRefPtr(this), michael@0: &NrSocketIpc::create_m, michael@0: host, static_cast(port)), michael@0: NS_DISPATCH_NORMAL); michael@0: michael@0: // Wait until socket creation complete. michael@0: mon.Wait(); michael@0: michael@0: if (err_) { michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: state_ = NR_CONNECTED; michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int NrSocketIpc::sendto(const void *msg, size_t len, int flags, michael@0: nr_transport_addr *to) { michael@0: ASSERT_ON_THREAD(sts_thread_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: //If send err happened before, simply return the error. michael@0: if (err_) { michael@0: return R_IO_ERROR; michael@0: } michael@0: michael@0: if (!socket_child_) { michael@0: return R_EOD; michael@0: } michael@0: michael@0: if (state_ != NR_CONNECTED) { michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: int r; michael@0: net::NetAddr addr; michael@0: if ((r=nr_transport_addr_to_netaddr(to, &addr))) { michael@0: return r; michael@0: } michael@0: michael@0: nsAutoPtr buf(new DataBuffer(static_cast(msg), len)); michael@0: michael@0: RUN_ON_THREAD(main_thread_, michael@0: mozilla::WrapRunnable(nsRefPtr(this), michael@0: &NrSocketIpc::sendto_m, michael@0: addr, buf), michael@0: NS_DISPATCH_NORMAL); michael@0: return 0; michael@0: } michael@0: michael@0: void NrSocketIpc::close() { michael@0: ASSERT_ON_THREAD(sts_thread_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: state_ = NR_CLOSING; michael@0: michael@0: RUN_ON_THREAD(main_thread_, michael@0: mozilla::WrapRunnable(nsRefPtr(this), michael@0: &NrSocketIpc::close_m), michael@0: NS_DISPATCH_NORMAL); michael@0: michael@0: //remove all enqueued messages michael@0: std::queue > empty; michael@0: std::swap(received_msgs_, empty); michael@0: } michael@0: michael@0: int NrSocketIpc::recvfrom(void *buf, size_t maxlen, size_t *len, int flags, michael@0: nr_transport_addr *from) { michael@0: ASSERT_ON_THREAD(sts_thread_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: int r, _status; michael@0: uint32_t consumed_len; michael@0: michael@0: *len = 0; michael@0: michael@0: if (state_ != NR_CONNECTED) { michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: if (received_msgs_.empty()) { michael@0: ABORT(R_WOULDBLOCK); michael@0: } michael@0: michael@0: { michael@0: RefPtr msg(received_msgs_.front()); michael@0: michael@0: received_msgs_.pop(); michael@0: michael@0: if ((r=nr_praddr_to_transport_addr(&msg->from, from, IPPROTO_UDP, 0))) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Get bogus address for received UDP packet"); michael@0: ABORT(r); michael@0: } michael@0: michael@0: consumed_len = std::min(maxlen, msg->data->len()); michael@0: if (consumed_len < msg->data->len()) { michael@0: r_log(LOG_GENERIC, LOG_DEBUG, "Partial received UDP packet will be discard"); michael@0: } michael@0: michael@0: memcpy(buf, msg->data->data(), consumed_len); michael@0: *len = consumed_len; michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int NrSocketIpc::getaddr(nr_transport_addr *addrp) { michael@0: ASSERT_ON_THREAD(sts_thread_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: if (state_ != NR_CONNECTED) { michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: return nr_transport_addr_copy(addrp, &my_addr_); michael@0: } michael@0: michael@0: int NrSocketIpc::connect(nr_transport_addr *addr) { michael@0: MOZ_ASSERT(false); michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: int NrSocketIpc::write(const void *msg, size_t len, size_t *written) { michael@0: MOZ_ASSERT(false); michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: int NrSocketIpc::read(void* buf, size_t maxlen, size_t *len) { michael@0: MOZ_ASSERT(false); michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: // Main thread executors michael@0: void NrSocketIpc::create_m(const nsACString &host, const uint16_t port) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: nsresult rv; michael@0: socket_child_ = do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv); michael@0: if (NS_FAILED(rv)) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to create UDPSocketChild"); michael@0: } michael@0: michael@0: socket_child_->SetFilterName(nsCString("stun")); michael@0: michael@0: if (NS_FAILED(socket_child_->Bind(this, host, port))) { michael@0: err_ = true; michael@0: MOZ_ASSERT(false, "Failed to create UDP socket"); michael@0: } michael@0: } michael@0: michael@0: void NrSocketIpc::sendto_m(const net::NetAddr &addr, nsAutoPtr buf) { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: michael@0: MOZ_ASSERT(socket_child_); michael@0: michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: michael@0: if (NS_FAILED(socket_child_->SendWithAddress(&addr, michael@0: buf->data(), michael@0: buf->len()))) { michael@0: err_ = true; michael@0: } michael@0: } michael@0: michael@0: void NrSocketIpc::close_m() { michael@0: ASSERT_ON_THREAD(main_thread_); michael@0: michael@0: if (socket_child_) { michael@0: socket_child_->Close(); michael@0: socket_child_ = nullptr; michael@0: } michael@0: } michael@0: michael@0: void NrSocketIpc::recv_callback_s(RefPtr msg) { michael@0: ASSERT_ON_THREAD(sts_thread_); michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(monitor_); michael@0: if (state_ != NR_CONNECTED) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: //enqueue received message michael@0: received_msgs_.push(msg); michael@0: michael@0: if ((poll_flags() & PR_POLL_READ)) { michael@0: fire_callback(NR_ASYNC_WAIT_READ); michael@0: } michael@0: } michael@0: michael@0: } // close namespace michael@0: michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // Bridge to the nr_socket interface michael@0: static int nr_socket_local_destroy(void **objp); michael@0: static int nr_socket_local_sendto(void *obj,const void *msg, size_t len, michael@0: int flags, nr_transport_addr *to); michael@0: static int nr_socket_local_recvfrom(void *obj,void * restrict buf, michael@0: size_t maxlen, size_t *len, int flags, nr_transport_addr *from); michael@0: static int nr_socket_local_getfd(void *obj, NR_SOCKET *fd); michael@0: static int nr_socket_local_getaddr(void *obj, nr_transport_addr *addrp); michael@0: static int nr_socket_local_close(void *obj); michael@0: static int nr_socket_local_connect(void *sock, nr_transport_addr *addr); michael@0: static int nr_socket_local_write(void *obj,const void *msg, size_t len, michael@0: size_t *written); michael@0: static int nr_socket_local_read(void *obj,void * restrict buf, size_t maxlen, michael@0: size_t *len); michael@0: michael@0: static nr_socket_vtbl nr_socket_local_vtbl={ michael@0: 1, michael@0: nr_socket_local_destroy, michael@0: nr_socket_local_sendto, michael@0: nr_socket_local_recvfrom, michael@0: nr_socket_local_getfd, michael@0: nr_socket_local_getaddr, michael@0: nr_socket_local_connect, michael@0: nr_socket_local_write, michael@0: nr_socket_local_read, michael@0: nr_socket_local_close michael@0: }; michael@0: michael@0: int nr_socket_local_create(nr_transport_addr *addr, nr_socket **sockp) { michael@0: NrSocketBase *sock = nullptr; michael@0: michael@0: // create IPC bridge for content process michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: sock = new NrSocket(); michael@0: } else { michael@0: nsCOMPtr main_thread; michael@0: NS_GetMainThread(getter_AddRefs(main_thread)); michael@0: sock = new NrSocketIpc(main_thread.get()); michael@0: } michael@0: michael@0: int r, _status; michael@0: michael@0: r = sock->create(addr); michael@0: if (r) michael@0: ABORT(r); michael@0: michael@0: r = nr_socket_create_int(static_cast(sock), michael@0: sock->vtbl(), sockp); michael@0: if (r) michael@0: ABORT(r); michael@0: michael@0: // Add a reference so that we can delete it in destroy() michael@0: sock->AddRef(); michael@0: michael@0: _status = 0; michael@0: michael@0: abort: michael@0: if (_status) { michael@0: delete sock; michael@0: } michael@0: return _status; michael@0: } michael@0: michael@0: michael@0: static int nr_socket_local_destroy(void **objp) { michael@0: if(!objp || !*objp) michael@0: return 0; michael@0: michael@0: NrSocketBase *sock = static_cast(*objp); michael@0: *objp=0; michael@0: michael@0: sock->close(); // Signal STS that we want not to listen michael@0: sock->Release(); // Decrement the ref count michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int nr_socket_local_sendto(void *obj,const void *msg, size_t len, michael@0: int flags, nr_transport_addr *addr) { michael@0: NrSocketBase *sock = static_cast(obj); michael@0: michael@0: return sock->sendto(msg, len, flags, addr); michael@0: } michael@0: michael@0: static int nr_socket_local_recvfrom(void *obj,void * restrict buf, michael@0: size_t maxlen, size_t *len, int flags, michael@0: nr_transport_addr *addr) { michael@0: NrSocketBase *sock = static_cast(obj); michael@0: michael@0: return sock->recvfrom(buf, maxlen, len, flags, addr); michael@0: } michael@0: michael@0: static int nr_socket_local_getfd(void *obj, NR_SOCKET *fd) { michael@0: NrSocketBase *sock = static_cast(obj); michael@0: michael@0: *fd = sock; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int nr_socket_local_getaddr(void *obj, nr_transport_addr *addrp) { michael@0: NrSocketBase *sock = static_cast(obj); michael@0: michael@0: return sock->getaddr(addrp); michael@0: } michael@0: michael@0: michael@0: static int nr_socket_local_close(void *obj) { michael@0: NrSocketBase *sock = static_cast(obj); michael@0: michael@0: sock->close(); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int nr_socket_local_write(void *obj, const void *msg, size_t len, michael@0: size_t *written) { michael@0: NrSocket *sock = static_cast(obj); michael@0: michael@0: return sock->write(msg, len, written); michael@0: } michael@0: michael@0: static int nr_socket_local_read(void *obj, void * restrict buf, size_t maxlen, michael@0: size_t *len) { michael@0: NrSocket *sock = static_cast(obj); michael@0: michael@0: return sock->read(buf, maxlen, len); michael@0: } michael@0: michael@0: static int nr_socket_local_connect(void *obj, nr_transport_addr *addr) { michael@0: NrSocket *sock = static_cast(obj); michael@0: michael@0: return sock->connect(addr); michael@0: } michael@0: michael@0: // Implement async api michael@0: int NR_async_wait(NR_SOCKET sock, int how, NR_async_cb cb,void *cb_arg, michael@0: char *function,int line) { michael@0: NrSocketBase *s = static_cast(sock); michael@0: michael@0: return s->async_wait(how, cb, cb_arg, function, line); michael@0: } michael@0: michael@0: int NR_async_cancel(NR_SOCKET sock,int how) { michael@0: NrSocketBase *s = static_cast(sock); michael@0: michael@0: return s->cancel(how); michael@0: } michael@0: michael@0: nr_socket_vtbl* NrSocketBase::vtbl() { michael@0: return &nr_socket_local_vtbl; michael@0: } michael@0: