Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* vim:set ts=2 sw=2 et cindent: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsSocketTransport2.h"
7 #include "nsServerSocket.h"
8 #include "nsProxyRelease.h"
9 #include "nsAutoPtr.h"
10 #include "nsError.h"
11 #include "nsNetCID.h"
12 #include "prnetdb.h"
13 #include "prio.h"
14 #include "nsThreadUtils.h"
15 #include "mozilla/Attributes.h"
16 #include "mozilla/Endian.h"
17 #include "mozilla/net/DNS.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsIFile.h"
21 using namespace mozilla;
22 using namespace mozilla::net;
24 static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
26 //-----------------------------------------------------------------------------
28 typedef void (nsServerSocket:: *nsServerSocketFunc)(void);
30 static nsresult
31 PostEvent(nsServerSocket *s, nsServerSocketFunc func)
32 {
33 nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(s, func);
34 if (!ev)
35 return NS_ERROR_OUT_OF_MEMORY;
37 if (!gSocketTransportService)
38 return NS_ERROR_FAILURE;
40 return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
41 }
43 //-----------------------------------------------------------------------------
44 // nsServerSocket
45 //-----------------------------------------------------------------------------
47 nsServerSocket::nsServerSocket()
48 : mLock("nsServerSocket.mLock")
49 , mFD(nullptr)
50 , mAttached(false)
51 , mKeepWhenOffline(false)
52 {
53 // we want to be able to access the STS directly, and it may not have been
54 // constructed yet. the STS constructor sets gSocketTransportService.
55 if (!gSocketTransportService)
56 {
57 // This call can fail if we're offline, for example.
58 nsCOMPtr<nsISocketTransportService> sts =
59 do_GetService(kSocketTransportServiceCID);
60 }
61 // make sure the STS sticks around as long as we do
62 NS_IF_ADDREF(gSocketTransportService);
63 }
65 nsServerSocket::~nsServerSocket()
66 {
67 Close(); // just in case :)
69 // release our reference to the STS
70 nsSocketTransportService *serv = gSocketTransportService;
71 NS_IF_RELEASE(serv);
72 }
74 void
75 nsServerSocket::OnMsgClose()
76 {
77 SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this));
79 if (NS_FAILED(mCondition))
80 return;
82 // tear down socket. this signals the STS to detach our socket handler.
83 mCondition = NS_BINDING_ABORTED;
85 // if we are attached, then we'll close the socket in our OnSocketDetached.
86 // otherwise, call OnSocketDetached from here.
87 if (!mAttached)
88 OnSocketDetached(mFD);
89 }
91 void
92 nsServerSocket::OnMsgAttach()
93 {
94 SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this));
96 if (NS_FAILED(mCondition))
97 return;
99 mCondition = TryAttach();
101 // if we hit an error while trying to attach then bail...
102 if (NS_FAILED(mCondition))
103 {
104 NS_ASSERTION(!mAttached, "should not be attached already");
105 OnSocketDetached(mFD);
106 }
107 }
109 nsresult
110 nsServerSocket::TryAttach()
111 {
112 nsresult rv;
114 if (!gSocketTransportService)
115 return NS_ERROR_FAILURE;
117 //
118 // find out if it is going to be ok to attach another socket to the STS.
119 // if not then we have to wait for the STS to tell us that it is ok.
120 // the notification is asynchronous, which means that when we could be
121 // in a race to call AttachSocket once notified. for this reason, when
122 // we get notified, we just re-enter this function. as a result, we are
123 // sure to ask again before calling AttachSocket. in this way we deal
124 // with the race condition. though it isn't the most elegant solution,
125 // it is far simpler than trying to build a system that would guarantee
126 // FIFO ordering (which wouldn't even be that valuable IMO). see bug
127 // 194402 for more info.
128 //
129 if (!gSocketTransportService->CanAttachSocket())
130 {
131 nsCOMPtr<nsIRunnable> event =
132 NS_NewRunnableMethod(this, &nsServerSocket::OnMsgAttach);
133 if (!event)
134 return NS_ERROR_OUT_OF_MEMORY;
136 nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
137 if (NS_FAILED(rv))
138 return rv;
139 }
141 //
142 // ok, we can now attach our socket to the STS for polling
143 //
144 rv = gSocketTransportService->AttachSocket(mFD, this);
145 if (NS_FAILED(rv))
146 return rv;
148 mAttached = true;
150 //
151 // now, configure our poll flags for listening...
152 //
153 mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
154 return NS_OK;
155 }
157 //-----------------------------------------------------------------------------
158 // nsServerSocket::nsASocketHandler
159 //-----------------------------------------------------------------------------
161 void
162 nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
163 {
164 NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
165 NS_ASSERTION(mFD == fd, "wrong file descriptor");
166 NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
168 if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL))
169 {
170 NS_WARNING("error polling on listening socket");
171 mCondition = NS_ERROR_UNEXPECTED;
172 return;
173 }
175 PRFileDesc *clientFD;
176 PRNetAddr prClientAddr;
177 NetAddr clientAddr;
179 // NSPR doesn't tell us the peer address's length (as provided by the
180 // 'accept' system call), so we can't distinguish between named,
181 // unnamed, and abstract peer addresses. Clear prClientAddr first, so
182 // that the path will at least be reliably empty for unnamed and
183 // abstract addresses, and not garbage when the peer is unnamed.
184 memset(&prClientAddr, 0, sizeof(prClientAddr));
186 clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT);
187 PRNetAddrToNetAddr(&prClientAddr, &clientAddr);
188 if (!clientFD)
189 {
190 NS_WARNING("PR_Accept failed");
191 mCondition = NS_ERROR_UNEXPECTED;
192 }
193 else
194 {
195 nsRefPtr<nsSocketTransport> trans = new nsSocketTransport;
196 if (!trans)
197 mCondition = NS_ERROR_OUT_OF_MEMORY;
198 else
199 {
200 nsresult rv = trans->InitWithConnectedSocket(clientFD, &clientAddr);
201 if (NS_FAILED(rv))
202 mCondition = rv;
203 else
204 mListener->OnSocketAccepted(this, trans);
205 }
206 }
207 }
209 void
210 nsServerSocket::OnSocketDetached(PRFileDesc *fd)
211 {
212 // force a failure condition if none set; maybe the STS is shutting down :-/
213 if (NS_SUCCEEDED(mCondition))
214 mCondition = NS_ERROR_ABORT;
216 if (mFD)
217 {
218 NS_ASSERTION(mFD == fd, "wrong file descriptor");
219 PR_Close(mFD);
220 mFD = nullptr;
221 }
223 if (mListener)
224 {
225 mListener->OnStopListening(this, mCondition);
227 // need to atomically clear mListener. see our Close() method.
228 nsIServerSocketListener *listener = nullptr;
229 {
230 MutexAutoLock lock(mLock);
231 mListener.swap(listener);
232 }
233 // XXX we need to proxy the release to the listener's target thread to work
234 // around bug 337492.
235 if (listener)
236 NS_ProxyRelease(mListenerTarget, listener);
237 }
238 }
240 void
241 nsServerSocket::IsLocal(bool *aIsLocal)
242 {
243 #if defined(XP_UNIX)
244 // Unix-domain sockets are always local.
245 if (mAddr.raw.family == PR_AF_LOCAL)
246 {
247 *aIsLocal = true;
248 return;
249 }
250 #endif
252 // If bound to loopback, this server socket only accepts local connections.
253 *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback);
254 }
256 void
257 nsServerSocket::KeepWhenOffline(bool *aKeepWhenOffline)
258 {
259 *aKeepWhenOffline = mKeepWhenOffline;
260 }
262 //-----------------------------------------------------------------------------
263 // nsServerSocket::nsISupports
264 //-----------------------------------------------------------------------------
266 NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket)
269 //-----------------------------------------------------------------------------
270 // nsServerSocket::nsIServerSocket
271 //-----------------------------------------------------------------------------
273 NS_IMETHODIMP
274 nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog)
275 {
276 return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, aBackLog);
277 }
279 NS_IMETHODIMP
280 nsServerSocket::InitWithFilename(nsIFile *aPath, uint32_t aPermissions, int32_t aBacklog)
281 {
282 #if defined(XP_UNIX)
283 nsresult rv;
285 nsAutoCString path;
286 rv = aPath->GetNativePath(path);
287 if (NS_FAILED(rv))
288 return rv;
290 // Create a Unix domain PRNetAddr referring to the given path.
291 PRNetAddr addr;
292 if (path.Length() > sizeof(addr.local.path) - 1)
293 return NS_ERROR_FILE_NAME_TOO_LONG;
294 addr.local.family = PR_AF_LOCAL;
295 memcpy(addr.local.path, path.get(), path.Length());
296 addr.local.path[path.Length()] = '\0';
298 rv = InitWithAddress(&addr, aBacklog);
299 if (NS_FAILED(rv))
300 return rv;
302 return aPath->SetPermissions(aPermissions);
303 #else
304 return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
305 #endif
306 }
308 NS_IMETHODIMP
309 nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags,
310 int32_t aBackLog)
311 {
312 PRNetAddrValue val;
313 PRNetAddr addr;
315 if (aPort < 0)
316 aPort = 0;
317 if (aFlags & nsIServerSocket::LoopbackOnly)
318 val = PR_IpAddrLoopback;
319 else
320 val = PR_IpAddrAny;
321 PR_SetNetAddr(val, PR_AF_INET, aPort, &addr);
323 mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0);
324 return InitWithAddress(&addr, aBackLog);
325 }
327 NS_IMETHODIMP
328 nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
329 {
330 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
332 //
333 // configure listening socket...
334 //
336 mFD = PR_OpenTCPSocket(aAddr->raw.family);
337 if (!mFD)
338 {
339 NS_WARNING("unable to create server socket");
340 return ErrorAccordingToNSPR(PR_GetError());
341 }
343 PRSocketOptionData opt;
345 opt.option = PR_SockOpt_Reuseaddr;
346 opt.value.reuse_addr = true;
347 PR_SetSocketOption(mFD, &opt);
349 opt.option = PR_SockOpt_Nonblocking;
350 opt.value.non_blocking = true;
351 PR_SetSocketOption(mFD, &opt);
353 if (PR_Bind(mFD, aAddr) != PR_SUCCESS)
354 {
355 NS_WARNING("failed to bind socket");
356 goto fail;
357 }
359 if (aBackLog < 0)
360 aBackLog = 5; // seems like a reasonable default
362 if (PR_Listen(mFD, aBackLog) != PR_SUCCESS)
363 {
364 NS_WARNING("cannot listen on socket");
365 goto fail;
366 }
368 // get the resulting socket address, which may be different than what
369 // we passed to bind.
370 if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS)
371 {
372 NS_WARNING("cannot get socket name");
373 goto fail;
374 }
376 // wait until AsyncListen is called before polling the socket for
377 // client connections.
378 return NS_OK;
380 fail:
381 nsresult rv = ErrorAccordingToNSPR(PR_GetError());
382 Close();
383 return rv;
384 }
386 NS_IMETHODIMP
387 nsServerSocket::Close()
388 {
389 {
390 MutexAutoLock lock(mLock);
391 // we want to proxy the close operation to the socket thread if a listener
392 // has been set. otherwise, we should just close the socket here...
393 if (!mListener)
394 {
395 if (mFD)
396 {
397 PR_Close(mFD);
398 mFD = nullptr;
399 }
400 return NS_OK;
401 }
402 }
403 return PostEvent(this, &nsServerSocket::OnMsgClose);
404 }
406 namespace {
408 class ServerSocketListenerProxy MOZ_FINAL : public nsIServerSocketListener
409 {
410 public:
411 ServerSocketListenerProxy(nsIServerSocketListener* aListener)
412 : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>(aListener))
413 , mTargetThread(do_GetCurrentThread())
414 { }
416 NS_DECL_THREADSAFE_ISUPPORTS
417 NS_DECL_NSISERVERSOCKETLISTENER
419 class OnSocketAcceptedRunnable : public nsRunnable
420 {
421 public:
422 OnSocketAcceptedRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
423 nsIServerSocket* aServ,
424 nsISocketTransport* aTransport)
425 : mListener(aListener)
426 , mServ(aServ)
427 , mTransport(aTransport)
428 { }
430 NS_DECL_NSIRUNNABLE
432 private:
433 nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
434 nsCOMPtr<nsIServerSocket> mServ;
435 nsCOMPtr<nsISocketTransport> mTransport;
436 };
438 class OnStopListeningRunnable : public nsRunnable
439 {
440 public:
441 OnStopListeningRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
442 nsIServerSocket* aServ,
443 nsresult aStatus)
444 : mListener(aListener)
445 , mServ(aServ)
446 , mStatus(aStatus)
447 { }
449 NS_DECL_NSIRUNNABLE
451 private:
452 nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
453 nsCOMPtr<nsIServerSocket> mServ;
454 nsresult mStatus;
455 };
457 private:
458 nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
459 nsCOMPtr<nsIEventTarget> mTargetThread;
460 };
462 NS_IMPL_ISUPPORTS(ServerSocketListenerProxy,
463 nsIServerSocketListener)
465 NS_IMETHODIMP
466 ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ,
467 nsISocketTransport* aTransport)
468 {
469 nsRefPtr<OnSocketAcceptedRunnable> r =
470 new OnSocketAcceptedRunnable(mListener, aServ, aTransport);
471 return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
472 }
474 NS_IMETHODIMP
475 ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ,
476 nsresult aStatus)
477 {
478 nsRefPtr<OnStopListeningRunnable> r =
479 new OnStopListeningRunnable(mListener, aServ, aStatus);
480 return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
481 }
483 NS_IMETHODIMP
484 ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run()
485 {
486 mListener->OnSocketAccepted(mServ, mTransport);
487 return NS_OK;
488 }
490 NS_IMETHODIMP
491 ServerSocketListenerProxy::OnStopListeningRunnable::Run()
492 {
493 mListener->OnStopListening(mServ, mStatus);
494 return NS_OK;
495 }
497 } // anonymous namespace
499 NS_IMETHODIMP
500 nsServerSocket::AsyncListen(nsIServerSocketListener *aListener)
501 {
502 // ensuring mFD implies ensuring mLock
503 NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
504 NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
505 {
506 MutexAutoLock lock(mLock);
507 mListener = new ServerSocketListenerProxy(aListener);
508 mListenerTarget = NS_GetCurrentThread();
509 }
510 return PostEvent(this, &nsServerSocket::OnMsgAttach);
511 }
513 NS_IMETHODIMP
514 nsServerSocket::GetPort(int32_t *aResult)
515 {
516 // no need to enter the lock here
517 uint16_t port;
518 if (mAddr.raw.family == PR_AF_INET)
519 port = mAddr.inet.port;
520 else if (mAddr.raw.family == PR_AF_INET6)
521 port = mAddr.ipv6.port;
522 else
523 return NS_ERROR_FAILURE;
525 *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port));
526 return NS_OK;
527 }
529 NS_IMETHODIMP
530 nsServerSocket::GetAddress(PRNetAddr *aResult)
531 {
532 // no need to enter the lock here
533 memcpy(aResult, &mAddr, sizeof(mAddr));
534 return NS_OK;
535 }