|
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/. */ |
|
5 |
|
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" |
|
20 |
|
21 using namespace mozilla; |
|
22 using namespace mozilla::net; |
|
23 |
|
24 static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); |
|
25 |
|
26 //----------------------------------------------------------------------------- |
|
27 |
|
28 typedef void (nsServerSocket:: *nsServerSocketFunc)(void); |
|
29 |
|
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; |
|
36 |
|
37 if (!gSocketTransportService) |
|
38 return NS_ERROR_FAILURE; |
|
39 |
|
40 return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL); |
|
41 } |
|
42 |
|
43 //----------------------------------------------------------------------------- |
|
44 // nsServerSocket |
|
45 //----------------------------------------------------------------------------- |
|
46 |
|
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 } |
|
64 |
|
65 nsServerSocket::~nsServerSocket() |
|
66 { |
|
67 Close(); // just in case :) |
|
68 |
|
69 // release our reference to the STS |
|
70 nsSocketTransportService *serv = gSocketTransportService; |
|
71 NS_IF_RELEASE(serv); |
|
72 } |
|
73 |
|
74 void |
|
75 nsServerSocket::OnMsgClose() |
|
76 { |
|
77 SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this)); |
|
78 |
|
79 if (NS_FAILED(mCondition)) |
|
80 return; |
|
81 |
|
82 // tear down socket. this signals the STS to detach our socket handler. |
|
83 mCondition = NS_BINDING_ABORTED; |
|
84 |
|
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 } |
|
90 |
|
91 void |
|
92 nsServerSocket::OnMsgAttach() |
|
93 { |
|
94 SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this)); |
|
95 |
|
96 if (NS_FAILED(mCondition)) |
|
97 return; |
|
98 |
|
99 mCondition = TryAttach(); |
|
100 |
|
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 } |
|
108 |
|
109 nsresult |
|
110 nsServerSocket::TryAttach() |
|
111 { |
|
112 nsresult rv; |
|
113 |
|
114 if (!gSocketTransportService) |
|
115 return NS_ERROR_FAILURE; |
|
116 |
|
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; |
|
135 |
|
136 nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event); |
|
137 if (NS_FAILED(rv)) |
|
138 return rv; |
|
139 } |
|
140 |
|
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; |
|
147 |
|
148 mAttached = true; |
|
149 |
|
150 // |
|
151 // now, configure our poll flags for listening... |
|
152 // |
|
153 mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT); |
|
154 return NS_OK; |
|
155 } |
|
156 |
|
157 //----------------------------------------------------------------------------- |
|
158 // nsServerSocket::nsASocketHandler |
|
159 //----------------------------------------------------------------------------- |
|
160 |
|
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"); |
|
167 |
|
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 } |
|
174 |
|
175 PRFileDesc *clientFD; |
|
176 PRNetAddr prClientAddr; |
|
177 NetAddr clientAddr; |
|
178 |
|
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)); |
|
185 |
|
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 } |
|
208 |
|
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; |
|
215 |
|
216 if (mFD) |
|
217 { |
|
218 NS_ASSERTION(mFD == fd, "wrong file descriptor"); |
|
219 PR_Close(mFD); |
|
220 mFD = nullptr; |
|
221 } |
|
222 |
|
223 if (mListener) |
|
224 { |
|
225 mListener->OnStopListening(this, mCondition); |
|
226 |
|
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 } |
|
239 |
|
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 |
|
251 |
|
252 // If bound to loopback, this server socket only accepts local connections. |
|
253 *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback); |
|
254 } |
|
255 |
|
256 void |
|
257 nsServerSocket::KeepWhenOffline(bool *aKeepWhenOffline) |
|
258 { |
|
259 *aKeepWhenOffline = mKeepWhenOffline; |
|
260 } |
|
261 |
|
262 //----------------------------------------------------------------------------- |
|
263 // nsServerSocket::nsISupports |
|
264 //----------------------------------------------------------------------------- |
|
265 |
|
266 NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket) |
|
267 |
|
268 |
|
269 //----------------------------------------------------------------------------- |
|
270 // nsServerSocket::nsIServerSocket |
|
271 //----------------------------------------------------------------------------- |
|
272 |
|
273 NS_IMETHODIMP |
|
274 nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) |
|
275 { |
|
276 return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, aBackLog); |
|
277 } |
|
278 |
|
279 NS_IMETHODIMP |
|
280 nsServerSocket::InitWithFilename(nsIFile *aPath, uint32_t aPermissions, int32_t aBacklog) |
|
281 { |
|
282 #if defined(XP_UNIX) |
|
283 nsresult rv; |
|
284 |
|
285 nsAutoCString path; |
|
286 rv = aPath->GetNativePath(path); |
|
287 if (NS_FAILED(rv)) |
|
288 return rv; |
|
289 |
|
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'; |
|
297 |
|
298 rv = InitWithAddress(&addr, aBacklog); |
|
299 if (NS_FAILED(rv)) |
|
300 return rv; |
|
301 |
|
302 return aPath->SetPermissions(aPermissions); |
|
303 #else |
|
304 return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; |
|
305 #endif |
|
306 } |
|
307 |
|
308 NS_IMETHODIMP |
|
309 nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags, |
|
310 int32_t aBackLog) |
|
311 { |
|
312 PRNetAddrValue val; |
|
313 PRNetAddr addr; |
|
314 |
|
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); |
|
322 |
|
323 mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0); |
|
324 return InitWithAddress(&addr, aBackLog); |
|
325 } |
|
326 |
|
327 NS_IMETHODIMP |
|
328 nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog) |
|
329 { |
|
330 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); |
|
331 |
|
332 // |
|
333 // configure listening socket... |
|
334 // |
|
335 |
|
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 } |
|
342 |
|
343 PRSocketOptionData opt; |
|
344 |
|
345 opt.option = PR_SockOpt_Reuseaddr; |
|
346 opt.value.reuse_addr = true; |
|
347 PR_SetSocketOption(mFD, &opt); |
|
348 |
|
349 opt.option = PR_SockOpt_Nonblocking; |
|
350 opt.value.non_blocking = true; |
|
351 PR_SetSocketOption(mFD, &opt); |
|
352 |
|
353 if (PR_Bind(mFD, aAddr) != PR_SUCCESS) |
|
354 { |
|
355 NS_WARNING("failed to bind socket"); |
|
356 goto fail; |
|
357 } |
|
358 |
|
359 if (aBackLog < 0) |
|
360 aBackLog = 5; // seems like a reasonable default |
|
361 |
|
362 if (PR_Listen(mFD, aBackLog) != PR_SUCCESS) |
|
363 { |
|
364 NS_WARNING("cannot listen on socket"); |
|
365 goto fail; |
|
366 } |
|
367 |
|
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 } |
|
375 |
|
376 // wait until AsyncListen is called before polling the socket for |
|
377 // client connections. |
|
378 return NS_OK; |
|
379 |
|
380 fail: |
|
381 nsresult rv = ErrorAccordingToNSPR(PR_GetError()); |
|
382 Close(); |
|
383 return rv; |
|
384 } |
|
385 |
|
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 } |
|
405 |
|
406 namespace { |
|
407 |
|
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 { } |
|
415 |
|
416 NS_DECL_THREADSAFE_ISUPPORTS |
|
417 NS_DECL_NSISERVERSOCKETLISTENER |
|
418 |
|
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 { } |
|
429 |
|
430 NS_DECL_NSIRUNNABLE |
|
431 |
|
432 private: |
|
433 nsMainThreadPtrHandle<nsIServerSocketListener> mListener; |
|
434 nsCOMPtr<nsIServerSocket> mServ; |
|
435 nsCOMPtr<nsISocketTransport> mTransport; |
|
436 }; |
|
437 |
|
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 { } |
|
448 |
|
449 NS_DECL_NSIRUNNABLE |
|
450 |
|
451 private: |
|
452 nsMainThreadPtrHandle<nsIServerSocketListener> mListener; |
|
453 nsCOMPtr<nsIServerSocket> mServ; |
|
454 nsresult mStatus; |
|
455 }; |
|
456 |
|
457 private: |
|
458 nsMainThreadPtrHandle<nsIServerSocketListener> mListener; |
|
459 nsCOMPtr<nsIEventTarget> mTargetThread; |
|
460 }; |
|
461 |
|
462 NS_IMPL_ISUPPORTS(ServerSocketListenerProxy, |
|
463 nsIServerSocketListener) |
|
464 |
|
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 } |
|
473 |
|
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 } |
|
482 |
|
483 NS_IMETHODIMP |
|
484 ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run() |
|
485 { |
|
486 mListener->OnSocketAccepted(mServ, mTransport); |
|
487 return NS_OK; |
|
488 } |
|
489 |
|
490 NS_IMETHODIMP |
|
491 ServerSocketListenerProxy::OnStopListeningRunnable::Run() |
|
492 { |
|
493 mListener->OnStopListening(mServ, mStatus); |
|
494 return NS_OK; |
|
495 } |
|
496 |
|
497 } // anonymous namespace |
|
498 |
|
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 } |
|
512 |
|
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; |
|
524 |
|
525 *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port)); |
|
526 return NS_OK; |
|
527 } |
|
528 |
|
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 } |