michael@0: /* vim:set ts=2 sw=2 et cindent: */ 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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsSocketTransport2.h" michael@0: #include "nsServerSocket.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsError.h" michael@0: #include "nsNetCID.h" michael@0: #include "prnetdb.h" michael@0: #include "prio.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Endian.h" michael@0: #include "mozilla/net/DNS.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIFile.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: typedef void (nsServerSocket:: *nsServerSocketFunc)(void); michael@0: michael@0: static nsresult michael@0: PostEvent(nsServerSocket *s, nsServerSocketFunc func) michael@0: { michael@0: nsCOMPtr ev = NS_NewRunnableMethod(s, func); michael@0: if (!ev) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (!gSocketTransportService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsServerSocket michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsServerSocket::nsServerSocket() michael@0: : mLock("nsServerSocket.mLock") michael@0: , mFD(nullptr) michael@0: , mAttached(false) michael@0: , mKeepWhenOffline(false) michael@0: { michael@0: // we want to be able to access the STS directly, and it may not have been michael@0: // constructed yet. the STS constructor sets gSocketTransportService. michael@0: if (!gSocketTransportService) michael@0: { michael@0: // This call can fail if we're offline, for example. michael@0: nsCOMPtr sts = michael@0: do_GetService(kSocketTransportServiceCID); michael@0: } michael@0: // make sure the STS sticks around as long as we do michael@0: NS_IF_ADDREF(gSocketTransportService); michael@0: } michael@0: michael@0: nsServerSocket::~nsServerSocket() michael@0: { michael@0: Close(); // just in case :) michael@0: michael@0: // release our reference to the STS michael@0: nsSocketTransportService *serv = gSocketTransportService; michael@0: NS_IF_RELEASE(serv); michael@0: } michael@0: michael@0: void michael@0: nsServerSocket::OnMsgClose() michael@0: { michael@0: SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this)); michael@0: michael@0: if (NS_FAILED(mCondition)) michael@0: return; michael@0: michael@0: // tear down socket. this signals the STS to detach our socket handler. michael@0: mCondition = NS_BINDING_ABORTED; michael@0: michael@0: // if we are attached, then we'll close the socket in our OnSocketDetached. michael@0: // otherwise, call OnSocketDetached from here. michael@0: if (!mAttached) michael@0: OnSocketDetached(mFD); michael@0: } michael@0: michael@0: void michael@0: nsServerSocket::OnMsgAttach() michael@0: { michael@0: SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this)); michael@0: michael@0: if (NS_FAILED(mCondition)) michael@0: return; michael@0: michael@0: mCondition = TryAttach(); michael@0: michael@0: // if we hit an error while trying to attach then bail... michael@0: if (NS_FAILED(mCondition)) michael@0: { michael@0: NS_ASSERTION(!mAttached, "should not be attached already"); michael@0: OnSocketDetached(mFD); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsServerSocket::TryAttach() michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!gSocketTransportService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // michael@0: // find out if it is going to be ok to attach another socket to the STS. michael@0: // if not then we have to wait for the STS to tell us that it is ok. michael@0: // the notification is asynchronous, which means that when we could be michael@0: // in a race to call AttachSocket once notified. for this reason, when michael@0: // we get notified, we just re-enter this function. as a result, we are michael@0: // sure to ask again before calling AttachSocket. in this way we deal michael@0: // with the race condition. though it isn't the most elegant solution, michael@0: // it is far simpler than trying to build a system that would guarantee michael@0: // FIFO ordering (which wouldn't even be that valuable IMO). see bug michael@0: // 194402 for more info. michael@0: // michael@0: if (!gSocketTransportService->CanAttachSocket()) michael@0: { michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(this, &nsServerSocket::OnMsgAttach); michael@0: if (!event) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: // michael@0: // ok, we can now attach our socket to the STS for polling michael@0: // michael@0: rv = gSocketTransportService->AttachSocket(mFD, this); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mAttached = true; michael@0: michael@0: // michael@0: // now, configure our poll flags for listening... michael@0: // michael@0: mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsServerSocket::nsASocketHandler michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags) michael@0: { michael@0: NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops"); michael@0: NS_ASSERTION(mFD == fd, "wrong file descriptor"); michael@0: NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached"); michael@0: michael@0: if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) michael@0: { michael@0: NS_WARNING("error polling on listening socket"); michael@0: mCondition = NS_ERROR_UNEXPECTED; michael@0: return; michael@0: } michael@0: michael@0: PRFileDesc *clientFD; michael@0: PRNetAddr prClientAddr; michael@0: NetAddr clientAddr; michael@0: michael@0: // NSPR doesn't tell us the peer address's length (as provided by the michael@0: // 'accept' system call), so we can't distinguish between named, michael@0: // unnamed, and abstract peer addresses. Clear prClientAddr first, so michael@0: // that the path will at least be reliably empty for unnamed and michael@0: // abstract addresses, and not garbage when the peer is unnamed. michael@0: memset(&prClientAddr, 0, sizeof(prClientAddr)); michael@0: michael@0: clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT); michael@0: PRNetAddrToNetAddr(&prClientAddr, &clientAddr); michael@0: if (!clientFD) michael@0: { michael@0: NS_WARNING("PR_Accept failed"); michael@0: mCondition = NS_ERROR_UNEXPECTED; michael@0: } michael@0: else michael@0: { michael@0: nsRefPtr trans = new nsSocketTransport; michael@0: if (!trans) michael@0: mCondition = NS_ERROR_OUT_OF_MEMORY; michael@0: else michael@0: { michael@0: nsresult rv = trans->InitWithConnectedSocket(clientFD, &clientAddr); michael@0: if (NS_FAILED(rv)) michael@0: mCondition = rv; michael@0: else michael@0: mListener->OnSocketAccepted(this, trans); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsServerSocket::OnSocketDetached(PRFileDesc *fd) michael@0: { michael@0: // force a failure condition if none set; maybe the STS is shutting down :-/ michael@0: if (NS_SUCCEEDED(mCondition)) michael@0: mCondition = NS_ERROR_ABORT; michael@0: michael@0: if (mFD) michael@0: { michael@0: NS_ASSERTION(mFD == fd, "wrong file descriptor"); michael@0: PR_Close(mFD); michael@0: mFD = nullptr; michael@0: } michael@0: michael@0: if (mListener) michael@0: { michael@0: mListener->OnStopListening(this, mCondition); michael@0: michael@0: // need to atomically clear mListener. see our Close() method. michael@0: nsIServerSocketListener *listener = nullptr; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mListener.swap(listener); michael@0: } michael@0: // XXX we need to proxy the release to the listener's target thread to work michael@0: // around bug 337492. michael@0: if (listener) michael@0: NS_ProxyRelease(mListenerTarget, listener); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsServerSocket::IsLocal(bool *aIsLocal) michael@0: { michael@0: #if defined(XP_UNIX) michael@0: // Unix-domain sockets are always local. michael@0: if (mAddr.raw.family == PR_AF_LOCAL) michael@0: { michael@0: *aIsLocal = true; michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: // If bound to loopback, this server socket only accepts local connections. michael@0: *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback); michael@0: } michael@0: michael@0: void michael@0: nsServerSocket::KeepWhenOffline(bool *aKeepWhenOffline) michael@0: { michael@0: *aKeepWhenOffline = mKeepWhenOffline; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsServerSocket::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket) michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsServerSocket::nsIServerSocket michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) michael@0: { michael@0: return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, aBackLog); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::InitWithFilename(nsIFile *aPath, uint32_t aPermissions, int32_t aBacklog) michael@0: { michael@0: #if defined(XP_UNIX) michael@0: nsresult rv; michael@0: michael@0: nsAutoCString path; michael@0: rv = aPath->GetNativePath(path); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Create a Unix domain PRNetAddr referring to the given path. michael@0: PRNetAddr addr; michael@0: if (path.Length() > sizeof(addr.local.path) - 1) michael@0: return NS_ERROR_FILE_NAME_TOO_LONG; michael@0: addr.local.family = PR_AF_LOCAL; michael@0: memcpy(addr.local.path, path.get(), path.Length()); michael@0: addr.local.path[path.Length()] = '\0'; michael@0: michael@0: rv = InitWithAddress(&addr, aBacklog); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return aPath->SetPermissions(aPermissions); michael@0: #else michael@0: return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags, michael@0: int32_t aBackLog) michael@0: { michael@0: PRNetAddrValue val; michael@0: PRNetAddr addr; michael@0: michael@0: if (aPort < 0) michael@0: aPort = 0; michael@0: if (aFlags & nsIServerSocket::LoopbackOnly) michael@0: val = PR_IpAddrLoopback; michael@0: else michael@0: val = PR_IpAddrAny; michael@0: PR_SetNetAddr(val, PR_AF_INET, aPort, &addr); michael@0: michael@0: mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0); michael@0: return InitWithAddress(&addr, aBackLog); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog) michael@0: { michael@0: NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: // michael@0: // configure listening socket... michael@0: // michael@0: michael@0: mFD = PR_OpenTCPSocket(aAddr->raw.family); michael@0: if (!mFD) michael@0: { michael@0: NS_WARNING("unable to create server socket"); michael@0: return ErrorAccordingToNSPR(PR_GetError()); michael@0: } michael@0: michael@0: PRSocketOptionData opt; michael@0: michael@0: opt.option = PR_SockOpt_Reuseaddr; michael@0: opt.value.reuse_addr = true; michael@0: PR_SetSocketOption(mFD, &opt); michael@0: michael@0: opt.option = PR_SockOpt_Nonblocking; michael@0: opt.value.non_blocking = true; michael@0: PR_SetSocketOption(mFD, &opt); michael@0: michael@0: if (PR_Bind(mFD, aAddr) != PR_SUCCESS) michael@0: { michael@0: NS_WARNING("failed to bind socket"); michael@0: goto fail; michael@0: } michael@0: michael@0: if (aBackLog < 0) michael@0: aBackLog = 5; // seems like a reasonable default michael@0: michael@0: if (PR_Listen(mFD, aBackLog) != PR_SUCCESS) michael@0: { michael@0: NS_WARNING("cannot listen on socket"); michael@0: goto fail; michael@0: } michael@0: michael@0: // get the resulting socket address, which may be different than what michael@0: // we passed to bind. michael@0: if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS) michael@0: { michael@0: NS_WARNING("cannot get socket name"); michael@0: goto fail; michael@0: } michael@0: michael@0: // wait until AsyncListen is called before polling the socket for michael@0: // client connections. michael@0: return NS_OK; michael@0: michael@0: fail: michael@0: nsresult rv = ErrorAccordingToNSPR(PR_GetError()); michael@0: Close(); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::Close() michael@0: { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: // we want to proxy the close operation to the socket thread if a listener michael@0: // has been set. otherwise, we should just close the socket here... michael@0: if (!mListener) michael@0: { michael@0: if (mFD) michael@0: { michael@0: PR_Close(mFD); michael@0: mFD = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return PostEvent(this, &nsServerSocket::OnMsgClose); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class ServerSocketListenerProxy MOZ_FINAL : public nsIServerSocketListener michael@0: { michael@0: public: michael@0: ServerSocketListenerProxy(nsIServerSocketListener* aListener) michael@0: : mListener(new nsMainThreadPtrHolder(aListener)) michael@0: , mTargetThread(do_GetCurrentThread()) michael@0: { } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSISERVERSOCKETLISTENER michael@0: michael@0: class OnSocketAcceptedRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: OnSocketAcceptedRunnable(const nsMainThreadPtrHandle& aListener, michael@0: nsIServerSocket* aServ, michael@0: nsISocketTransport* aTransport) michael@0: : mListener(aListener) michael@0: , mServ(aServ) michael@0: , mTransport(aTransport) michael@0: { } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: private: michael@0: nsMainThreadPtrHandle mListener; michael@0: nsCOMPtr mServ; michael@0: nsCOMPtr mTransport; michael@0: }; michael@0: michael@0: class OnStopListeningRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: OnStopListeningRunnable(const nsMainThreadPtrHandle& aListener, michael@0: nsIServerSocket* aServ, michael@0: nsresult aStatus) michael@0: : mListener(aListener) michael@0: , mServ(aServ) michael@0: , mStatus(aStatus) michael@0: { } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: private: michael@0: nsMainThreadPtrHandle mListener; michael@0: nsCOMPtr mServ; michael@0: nsresult mStatus; michael@0: }; michael@0: michael@0: private: michael@0: nsMainThreadPtrHandle mListener; michael@0: nsCOMPtr mTargetThread; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ServerSocketListenerProxy, michael@0: nsIServerSocketListener) michael@0: michael@0: NS_IMETHODIMP michael@0: ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ, michael@0: nsISocketTransport* aTransport) michael@0: { michael@0: nsRefPtr r = michael@0: new OnSocketAcceptedRunnable(mListener, aServ, aTransport); michael@0: return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ, michael@0: nsresult aStatus) michael@0: { michael@0: nsRefPtr r = michael@0: new OnStopListeningRunnable(mListener, aServ, aStatus); michael@0: return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run() michael@0: { michael@0: mListener->OnSocketAccepted(mServ, mTransport); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ServerSocketListenerProxy::OnStopListeningRunnable::Run() michael@0: { michael@0: mListener->OnStopListening(mServ, mStatus); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::AsyncListen(nsIServerSocketListener *aListener) michael@0: { michael@0: // ensuring mFD implies ensuring mLock michael@0: NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED); michael@0: NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS); michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mListener = new ServerSocketListenerProxy(aListener); michael@0: mListenerTarget = NS_GetCurrentThread(); michael@0: } michael@0: return PostEvent(this, &nsServerSocket::OnMsgAttach); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::GetPort(int32_t *aResult) michael@0: { michael@0: // no need to enter the lock here michael@0: uint16_t port; michael@0: if (mAddr.raw.family == PR_AF_INET) michael@0: port = mAddr.inet.port; michael@0: else if (mAddr.raw.family == PR_AF_INET6) michael@0: port = mAddr.ipv6.port; michael@0: else michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aResult = static_cast(NetworkEndian::readUint16(&port)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsServerSocket::GetAddress(PRNetAddr *aResult) michael@0: { michael@0: // no need to enter the lock here michael@0: memcpy(aResult, &mAddr, sizeof(mAddr)); michael@0: return NS_OK; michael@0: }