1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/AsyncEventDispatcher.h"
9 #include "mozilla/dom/File.h"
10 #include "mozilla/dom/ErrorEvent.h"
11 #include "mozilla/dom/network/UDPSocketChild.h"
12 #include "mozilla/dom/UDPMessageEvent.h"
13 #include "mozilla/dom/UDPSocketBinding.h"
14 #include "mozilla/dom/UnionTypes.h"
15 #include "mozilla/dom/RootedDictionary.h"
16 #include "mozilla/net/DNS.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsContentUtils.h"
19 #include "nsINetAddr.h"
20 #include "nsStringStream.h"
22 namespace mozilla::dom
{
24 NS_IMPL_ISUPPORTS(UDPSocket::ListenerProxy
, nsIUDPSocketListener
,
27 NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket
)
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket
,
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened
)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed
)
33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket
, DOMEventTargetHelper
)
36 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpened
)
37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed
)
38 tmp
->CloseWithReason(NS_OK
);
39 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
41 NS_IMPL_ADDREF_INHERITED(UDPSocket
, DOMEventTargetHelper
)
42 NS_IMPL_RELEASE_INHERITED(UDPSocket
, DOMEventTargetHelper
)
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UDPSocket
)
45 NS_INTERFACE_MAP_ENTRY(nsIUDPSocketListener
)
46 NS_INTERFACE_MAP_ENTRY(nsIUDPSocketInternal
)
47 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
50 already_AddRefed
<UDPSocket
> UDPSocket::Constructor(const GlobalObject
& aGlobal
,
51 const UDPOptions
& aOptions
,
53 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
54 do_QueryInterface(aGlobal
.GetAsSupports());
56 aRv
.Throw(NS_ERROR_FAILURE
);
60 bool addressReuse
= aOptions
.mAddressReuse
;
61 bool loopback
= aOptions
.mLoopback
;
63 nsCString remoteAddress
;
64 if (aOptions
.mRemoteAddress
.WasPassed()) {
65 CopyUTF16toUTF8(aOptions
.mRemoteAddress
.Value(), remoteAddress
);
67 remoteAddress
.SetIsVoid(true);
70 Nullable
<uint16_t> remotePort
;
71 if (aOptions
.mRemotePort
.WasPassed()) {
72 remotePort
.SetValue(aOptions
.mRemotePort
.Value());
74 if (remotePort
.Value() == 0) {
75 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
80 nsString localAddress
;
81 if (aOptions
.mLocalAddress
.WasPassed()) {
82 localAddress
= aOptions
.mLocalAddress
.Value();
84 // check if localAddress is a valid IPv4/6 address
85 NS_ConvertUTF16toUTF8
address(localAddress
);
86 if (!net::HostIsIPLiteral(address
)) {
87 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
91 SetDOMStringToNull(localAddress
);
94 Nullable
<uint16_t> localPort
;
95 if (aOptions
.mLocalPort
.WasPassed()) {
96 localPort
.SetValue(aOptions
.mLocalPort
.Value());
98 if (localPort
.Value() == 0) {
99 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
104 RefPtr
<UDPSocket
> socket
=
105 new UDPSocket(ownerWindow
, remoteAddress
, remotePort
);
106 aRv
= socket
->Init(localAddress
, localPort
, addressReuse
, loopback
);
108 if (NS_WARN_IF(aRv
.Failed())) {
112 return socket
.forget();
115 UDPSocket::UDPSocket(nsPIDOMWindowInner
* aOwner
,
116 const nsCString
& aRemoteAddress
,
117 const Nullable
<uint16_t>& aRemotePort
)
118 : DOMEventTargetHelper(aOwner
),
119 mRemoteAddress(aRemoteAddress
),
120 mRemotePort(aRemotePort
),
121 mAddressReuse(false),
123 mReadyState(SocketReadyState::Opening
) {
126 Document
* aDoc
= aOwner
->GetExtantDoc();
128 aDoc
->DisallowBFCaching();
132 UDPSocket::~UDPSocket() { CloseWithReason(NS_OK
); }
134 JSObject
* UDPSocket::WrapObject(JSContext
* aCx
,
135 JS::Handle
<JSObject
*> aGivenProto
) {
136 return UDPSocket_Binding::Wrap(aCx
, this, aGivenProto
);
139 void UDPSocket::DisconnectFromOwner() {
140 DOMEventTargetHelper::DisconnectFromOwner();
141 CloseWithReason(NS_OK
);
144 already_AddRefed
<Promise
> UDPSocket::Close() {
147 RefPtr
<Promise
> promise
= mClosed
;
149 if (mReadyState
== SocketReadyState::Closed
) {
150 return promise
.forget();
153 CloseWithReason(NS_OK
);
154 return promise
.forget();
157 void UDPSocket::CloseWithReason(nsresult aReason
) {
158 if (mReadyState
== SocketReadyState::Closed
) {
163 if (mReadyState
== SocketReadyState::Opening
) {
164 // reject openedPromise with AbortError if socket is closed without error
165 nsresult openFailedReason
=
166 NS_FAILED(aReason
) ? aReason
: NS_ERROR_DOM_ABORT_ERR
;
167 mOpened
->MaybeReject(openFailedReason
);
171 mReadyState
= SocketReadyState::Closed
;
173 if (mListenerProxy
) {
174 mListenerProxy
->Disconnect();
175 mListenerProxy
= nullptr;
184 mSocketChild
->Close();
185 mSocketChild
= nullptr;
189 if (NS_SUCCEEDED(aReason
)) {
190 mClosed
->MaybeResolveWithUndefined();
192 mClosed
->MaybeReject(aReason
);
196 mPendingMcastCommands
.Clear();
199 void UDPSocket::JoinMulticastGroup(const nsAString
& aMulticastGroupAddress
,
201 if (mReadyState
== SocketReadyState::Closed
) {
202 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
206 if (mReadyState
== SocketReadyState::Opening
) {
207 MulticastCommand
joinCommand(MulticastCommand::Join
,
208 aMulticastGroupAddress
);
209 mPendingMcastCommands
.AppendElement(joinCommand
);
213 MOZ_ASSERT(mSocket
|| mSocketChild
);
215 NS_ConvertUTF16toUTF8
address(aMulticastGroupAddress
);
218 MOZ_ASSERT(!mSocketChild
);
220 aRv
= mSocket
->JoinMulticast(address
, ""_ns
);
221 NS_WARNING_ASSERTION(!aRv
.Failed(), "JoinMulticast failed");
226 MOZ_ASSERT(mSocketChild
);
228 mSocketChild
->JoinMulticast(address
, ""_ns
);
231 void UDPSocket::LeaveMulticastGroup(const nsAString
& aMulticastGroupAddress
,
233 if (mReadyState
== SocketReadyState::Closed
) {
234 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
238 if (mReadyState
== SocketReadyState::Opening
) {
239 MulticastCommand
leaveCommand(MulticastCommand::Leave
,
240 aMulticastGroupAddress
);
241 mPendingMcastCommands
.AppendElement(leaveCommand
);
245 MOZ_ASSERT(mSocket
|| mSocketChild
);
247 nsCString address
= NS_ConvertUTF16toUTF8(aMulticastGroupAddress
);
249 MOZ_ASSERT(!mSocketChild
);
251 aRv
= mSocket
->LeaveMulticast(address
, ""_ns
);
252 NS_WARNING_ASSERTION(!aRv
.Failed(), "mSocket->LeaveMulticast failed");
256 MOZ_ASSERT(mSocketChild
);
258 mSocketChild
->LeaveMulticast(address
, ""_ns
);
261 nsresult
UDPSocket::DoPendingMcastCommand() {
262 MOZ_ASSERT(mReadyState
== SocketReadyState::Open
,
263 "Multicast command can only be executed after socket opened");
265 for (uint32_t i
= 0; i
< mPendingMcastCommands
.Length(); ++i
) {
266 MulticastCommand
& command
= mPendingMcastCommands
[i
];
269 switch (command
.mCommand
) {
270 case MulticastCommand::Join
: {
271 JoinMulticastGroup(command
.mAddress
, rv
);
274 case MulticastCommand::Leave
: {
275 LeaveMulticastGroup(command
.mAddress
, rv
);
280 if (NS_WARN_IF(rv
.Failed())) {
281 return rv
.StealNSResult();
285 mPendingMcastCommands
.Clear();
289 bool UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView
& aData
,
290 const Optional
<nsAString
>& aRemoteAddress
,
291 const Optional
<Nullable
<uint16_t>>& aRemotePort
,
293 if (mReadyState
!= SocketReadyState::Open
) {
294 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
298 MOZ_ASSERT(mSocket
|| mSocketChild
);
300 // If the remote address and port were not specified in the constructor or as
301 // arguments, throw InvalidAccessError.
302 nsCString remoteAddress
;
303 if (aRemoteAddress
.WasPassed()) {
304 CopyUTF16toUTF8(aRemoteAddress
.Value(), remoteAddress
);
305 UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__
, remoteAddress
.get()));
306 } else if (!mRemoteAddress
.IsVoid()) {
307 remoteAddress
= mRemoteAddress
;
308 UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__
, remoteAddress
.get()));
310 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
315 if (aRemotePort
.WasPassed() && !aRemotePort
.Value().IsNull()) {
316 remotePort
= aRemotePort
.Value().Value();
317 } else if (!mRemotePort
.IsNull()) {
318 remotePort
= mRemotePort
.Value();
320 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
324 nsCOMPtr
<nsIInputStream
> stream
;
325 if (aData
.IsBlob()) {
326 Blob
& blob
= aData
.GetAsBlob();
328 blob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
329 if (NS_WARN_IF(aRv
.Failed())) {
334 nsCOMPtr
<nsIStringInputStream
> strStream
=
335 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID
, &rv
);
336 if (NS_WARN_IF(NS_FAILED(rv
))) {
341 if (aData
.IsString()) {
342 NS_ConvertUTF16toUTF8
data(aData
.GetAsString());
343 aRv
= strStream
->SetData(data
.BeginReading(), data
.Length());
346 if (!AppendTypedArrayDataTo(aData
, data
)) {
347 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
350 size_t length
= data
.length();
351 aRv
= strStream
->AdoptData(data
.extractOrCopyRawBuffer(), length
);
354 if (NS_WARN_IF(aRv
.Failed())) {
362 aRv
= mSocket
->SendBinaryStream(remoteAddress
, remotePort
, stream
);
363 } else if (mSocketChild
) {
364 aRv
= mSocketChild
->SendBinaryStream(remoteAddress
, remotePort
, stream
);
367 if (NS_WARN_IF(aRv
.Failed())) {
374 nsresult
UDPSocket::InitLocal(const nsAString
& aLocalAddress
,
375 const uint16_t& aLocalPort
) {
377 nsCOMPtr
<nsIUDPSocket
> sock
=
378 do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv
);
383 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
385 return NS_ERROR_FAILURE
;
388 nsCOMPtr
<nsIPrincipal
> principal
= global
->PrincipalOrNull();
390 return NS_ERROR_FAILURE
;
393 if (aLocalAddress
.IsEmpty()) {
394 rv
= sock
->Init(aLocalPort
, /* loopback = */ false, principal
,
395 mAddressReuse
, /* optionalArgc = */ 1);
398 PR_InitializeNetAddr(PR_IpAddrAny
, aLocalPort
, &prAddr
);
399 PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress
).BeginReading(),
401 UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__
,
402 NS_ConvertUTF16toUTF8(aLocalAddress
).get(), aLocalPort
));
404 mozilla::net::NetAddr
addr(&prAddr
);
405 rv
= sock
->InitWithAddress(&addr
, principal
, mAddressReuse
,
406 /* optionalArgc = */ 1);
412 rv
= sock
->SetMulticastLoopback(mLoopback
);
419 // Get real local address and local port
420 nsCOMPtr
<nsINetAddr
> localAddr
;
421 rv
= mSocket
->GetLocalAddr(getter_AddRefs(localAddr
));
426 nsCString localAddress
;
427 rv
= localAddr
->GetAddress(localAddress
);
431 CopyUTF8toUTF16(localAddress
, mLocalAddress
);
434 rv
= localAddr
->GetPort(&localPort
);
438 mLocalPort
.SetValue(localPort
);
440 mListenerProxy
= new ListenerProxy(this);
442 rv
= mSocket
->AsyncListen(mListenerProxy
);
447 mReadyState
= SocketReadyState::Open
;
448 rv
= DoPendingMcastCommand();
453 mOpened
->MaybeResolveWithUndefined();
458 nsresult
UDPSocket::InitRemote(const nsAString
& aLocalAddress
,
459 const uint16_t& aLocalPort
) {
460 RefPtr
<UDPSocketChild
> sock
= new UDPSocketChild();
462 mListenerProxy
= new ListenerProxy(this);
464 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
466 return NS_ERROR_FAILURE
;
469 nsCOMPtr
<nsIPrincipal
> principal
= global
->PrincipalOrNull();
471 return NS_ERROR_FAILURE
;
474 nsresult rv
= sock
->Bind(mListenerProxy
, principal
,
475 NS_ConvertUTF16toUTF8(aLocalAddress
), aLocalPort
,
476 mAddressReuse
, mLoopback
, 0, 0);
487 nsresult
UDPSocket::Init(const nsString
& aLocalAddress
,
488 const Nullable
<uint16_t>& aLocalPort
,
489 const bool& aAddressReuse
, const bool& aLoopback
) {
490 MOZ_ASSERT(!mSocket
&& !mSocketChild
);
492 mLocalAddress
= aLocalAddress
;
493 mLocalPort
= aLocalPort
;
494 mAddressReuse
= aAddressReuse
;
495 mLoopback
= aLoopback
;
497 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
500 mOpened
= Promise::Create(global
, rv
);
501 if (NS_WARN_IF(rv
.Failed())) {
502 return rv
.StealNSResult();
505 mClosed
= Promise::Create(global
, rv
);
506 if (NS_WARN_IF(rv
.Failed())) {
507 return rv
.StealNSResult();
510 class OpenSocketRunnable final
: public Runnable
{
512 explicit OpenSocketRunnable(UDPSocket
* aSocket
)
513 : mozilla::Runnable("OpenSocketRunnable"), mSocket(aSocket
) {}
515 NS_IMETHOD
Run() override
{
518 if (mSocket
->mReadyState
!= SocketReadyState::Opening
) {
522 uint16_t localPort
= 0;
523 if (!mSocket
->mLocalPort
.IsNull()) {
524 localPort
= mSocket
->mLocalPort
.Value();
528 if (!XRE_IsParentProcess()) {
529 rv
= mSocket
->InitRemote(mSocket
->mLocalAddress
, localPort
);
531 rv
= mSocket
->InitLocal(mSocket
->mLocalAddress
, localPort
);
534 if (NS_WARN_IF(NS_FAILED(rv
))) {
535 mSocket
->CloseWithReason(NS_ERROR_DOM_NETWORK_ERR
);
542 RefPtr
<UDPSocket
> mSocket
;
545 nsCOMPtr
<nsIRunnable
> runnable
= new OpenSocketRunnable(this);
547 return NS_DispatchToMainThread(runnable
);
550 void UDPSocket::HandleReceivedData(const nsACString
& aRemoteAddress
,
551 const uint16_t& aRemotePort
,
552 const nsTArray
<uint8_t>& aData
) {
553 if (mReadyState
!= SocketReadyState::Open
) {
557 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
561 if (NS_FAILED(DispatchReceivedData(aRemoteAddress
, aRemotePort
, aData
))) {
562 CloseWithReason(NS_ERROR_UNEXPECTED
);
566 nsresult
UDPSocket::DispatchReceivedData(const nsACString
& aRemoteAddress
,
567 const uint16_t& aRemotePort
,
568 const nsTArray
<uint8_t>& aData
) {
571 if (NS_WARN_IF(!jsapi
.Init(GetOwnerWindow()))) {
572 return NS_ERROR_FAILURE
;
575 JSContext
* cx
= jsapi
.cx();
577 // Copy packet data to ArrayBuffer
579 JS::Rooted
<JSObject
*> arrayBuf(cx
, ArrayBuffer::Create(cx
, aData
, error
));
581 if (NS_WARN_IF(error
.Failed())) {
582 return error
.StealNSResult();
585 JS::Rooted
<JS::Value
> jsData(cx
, JS::ObjectValue(*arrayBuf
));
588 RootedDictionary
<UDPMessageEventInit
> init(cx
);
589 CopyUTF8toUTF16(aRemoteAddress
, init
.mRemoteAddress
);
590 init
.mRemotePort
= aRemotePort
;
593 RefPtr
<UDPMessageEvent
> udpEvent
=
594 UDPMessageEvent::Constructor(this, u
"message"_ns
, init
);
596 if (NS_WARN_IF(!udpEvent
)) {
597 return NS_ERROR_FAILURE
;
600 udpEvent
->SetTrusted(true);
602 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
603 new AsyncEventDispatcher(this, udpEvent
.forget());
605 return asyncDispatcher
->PostDOMEvent();
608 // nsIUDPSocketListener
611 UDPSocket::OnPacketReceived(nsIUDPSocket
* aSocket
, nsIUDPMessage
* aMessage
) {
612 // nsIUDPSocketListener callbacks should be invoked on main thread.
613 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
615 // Create appropriate JS object for message
616 FallibleTArray
<uint8_t>& buffer
= aMessage
->GetDataAsTArray();
618 nsCOMPtr
<nsINetAddr
> addr
;
619 if (NS_WARN_IF(NS_FAILED(aMessage
->GetFromAddr(getter_AddRefs(addr
))))) {
623 nsCString remoteAddress
;
624 if (NS_WARN_IF(NS_FAILED(addr
->GetAddress(remoteAddress
)))) {
629 if (NS_WARN_IF(NS_FAILED(addr
->GetPort(&remotePort
)))) {
633 HandleReceivedData(remoteAddress
, remotePort
, buffer
);
638 UDPSocket::OnStopListening(nsIUDPSocket
* aSocket
, nsresult aStatus
) {
639 // nsIUDPSocketListener callbacks should be invoked on main thread.
640 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
642 CloseWithReason(aStatus
);
647 // nsIUDPSocketInternal
650 UDPSocket::CallListenerError(const nsACString
& aMessage
,
651 const nsACString
& aFilename
,
652 uint32_t aLineNumber
) {
653 CloseWithReason(NS_ERROR_DOM_NETWORK_ERR
);
659 UDPSocket::CallListenerReceivedData(const nsACString
& aRemoteAddress
,
660 uint16_t aRemotePort
,
661 const nsTArray
<uint8_t>& aData
) {
662 HandleReceivedData(aRemoteAddress
, aRemotePort
, aData
);
668 UDPSocket::CallListenerOpened() {
669 if (mReadyState
!= SocketReadyState::Opening
) {
673 MOZ_ASSERT(mSocketChild
);
675 // Get real local address and local port
676 CopyUTF8toUTF16(mSocketChild
->LocalAddress(), mLocalAddress
);
678 mLocalPort
.SetValue(mSocketChild
->LocalPort());
680 mReadyState
= SocketReadyState::Open
;
681 nsresult rv
= DoPendingMcastCommand();
683 if (NS_WARN_IF(NS_FAILED(rv
))) {
688 mOpened
->MaybeResolveWithUndefined();
694 UDPSocket::CallListenerConnected() {
695 // This shouldn't be called here.
702 UDPSocket::CallListenerClosed() {
703 CloseWithReason(NS_OK
);
708 } // namespace mozilla::dom