[udp6] update UDP socket definitions (#5231)

This commit updates the  public OT `otUdp{}` (like `otUdpConnect`, or
`otUpdSend()`) functions to requires a pointer the OpenThread instance
to be passed as their first parameter (this harmonizes the definitions
with other public APIs and removes the need for `otUdpSocket` to track
the instance).

The core implementation in `udp6` module is updated to define
`SocketHandle` as an internal mirror of `otUdpSocket` structure. The
socket related methods `Open`(), `Bind()`, `Connect()`, etc., are
moved to `Udp` class itself (with a `SocketHandle` passed as a
parameter). For core internal use `Socket` class is defined as a
sub-class of `SocketHandle` and `InstanceLocator` providing same
helper methods. The separation between `SocketHandle` and `Socket`
type addresses the problem that can be caused by the treating/casting
publicly provided `otUdpSockt` objects as `InstanceLocator`.
diff --git a/include/openthread/instance.h b/include/openthread/instance.h
index af9f2d9..e0e4010 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -53,7 +53,7 @@
  * @note This number versions both OpenThread platform and user APIs.
  *
  */
-#define OPENTHREAD_API_VERSION (14)
+#define OPENTHREAD_API_VERSION (15)
 
 /**
  * @addtogroup api-instance
diff --git a/include/openthread/udp.h b/include/openthread/udp.h
index 6e925ff..f11d5d7 100644
--- a/include/openthread/udp.h
+++ b/include/openthread/udp.h
@@ -126,7 +126,7 @@
     otSockAddr          mPeerName; ///< The peer IPv6 socket address.
     otUdpReceive        mHandler;  ///< A function pointer to the application callback.
     void *              mContext;  ///< A pointer to application-specific context.
-    void *              mHandle;   ///< A handle to platform's UDP
+    void *              mHandle;   ///< A handle to platform's UDP.
     struct otUdpSocket *mNext;     ///< A pointer to the next UDP socket (internal use only).
 } otUdpSocket;
 
@@ -137,7 +137,7 @@
  * OT_MESSAGE_PRIORITY_NORMAL by default.
  *
  * @param[in]  aInstance  A pointer to an OpenThread instance.
- * @param[in]  aSettings  A pointer to the message settings or NULL to set default settings.
+ * @param[in]  aSettings  A pointer to the message settings or NULL to use default settings.
  *
  * @returns A pointer to the message buffer or NULL if no message buffers are available or parameters are invalid.
  *
@@ -157,68 +157,51 @@
  * @retval OT_ERROR_NONE    Successfully opened the socket.
  * @retval OT_ERROR_FAILED  Failed to open the socket.
  *
- * @sa otUdpNewMessage
- * @sa otUdpClose
- * @sa otUdpBind
- * @sa otUdpConnect
- * @sa otUdpSend
- *
  */
 otError otUdpOpen(otInstance *aInstance, otUdpSocket *aSocket, otUdpReceive aCallback, void *aContext);
 
 /**
  * Close a UDP/IPv6 socket.
  *
- * @param[in]  aSocket  A pointer to a UDP socket structure.
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aSocket    A pointer to a UDP socket structure.
  *
- * @retval OT_ERROR_NONE  Successfully closed the socket.
- *
- * @sa otUdpNewMessage
- * @sa otUdpOpen
- * @sa otUdpBind
- * @sa otUdpConnect
- * @sa otUdpSend
+ * @retval OT_ERROR_NONE   Successfully closed the socket.
+ * @retval OT_ERROR_FAILED Failed to close UDP Socket.
  *
  */
-otError otUdpClose(otUdpSocket *aSocket);
+otError otUdpClose(otInstance *aInstance, otUdpSocket *aSocket);
 
 /**
  * Bind a UDP/IPv6 socket.
  *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
  * @param[in]  aSocket    A pointer to a UDP socket structure.
  * @param[in]  aSockName  A pointer to an IPv6 socket address structure.
  *
- * @retval OT_ERROR_NONE  Bind operation was successful.
- *
- * @sa otUdpNewMessage
- * @sa otUdpOpen
- * @sa otUdpConnect
- * @sa otUdpClose
- * @sa otUdpSend
+ * @retval OT_ERROR_NONE   Bind operation was successful.
+ * @retval OT_ERROR_FAILED Failed to bind UDP socket.
  *
  */
-otError otUdpBind(otUdpSocket *aSocket, otSockAddr *aSockName);
+otError otUdpBind(otInstance *aInstance, otUdpSocket *aSocket, const otSockAddr *aSockName);
 
 /**
  * Connect a UDP/IPv6 socket.
  *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
  * @param[in]  aSocket    A pointer to a UDP socket structure.
  * @param[in]  aSockName  A pointer to an IPv6 socket address structure.
  *
- * @retval OT_ERROR_NONE  Connect operation was successful.
- *
- * @sa otUdpNewMessage
- * @sa otUdpOpen
- * @sa otUdpBind
- * @sa otUdpClose
- * @sa otUdpSend
+ * @retval OT_ERROR_NONE   Connect operation was successful.
+ * @retval OT_ERROR_FAILED Failed to connect UDP socket.
  *
  */
-otError otUdpConnect(otUdpSocket *aSocket, otSockAddr *aSockName);
+otError otUdpConnect(otInstance *aInstance, otUdpSocket *aSocket, const otSockAddr *aSockName);
 
 /**
  * Send a UDP/IPv6 message.
  *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
  * @param[in]  aSocket       A pointer to a UDP socket structure.
  * @param[in]  aMessage      A pointer to a message buffer.
  * @param[in]  aMessageInfo  A pointer to a message info structure.
@@ -227,18 +210,22 @@
  * reference @p aMessage. If the return value is not OT_ERROR_NONE, the caller retains ownership of @p aMessage,
  * including freeing @p aMessage if the message buffer is no longer needed.
  *
- * @retval OT_ERROR_NONE            The message is successfully scheduled for sending.
- * @retval OT_ERROR_INVALID_ARGS    Invalid arguments are given.
- *
- * @sa otUdpNewMessage
- * @sa otUdpOpen
- * @sa otUdpClose
- * @sa otUdpBind
- * @sa otUdpConnect
- * @sa otUdpSend
+ * @retval OT_ERROR_NONE           The message is successfully scheduled for sending.
+ * @retval OT_ERROR_INVALID_ARGS   Invalid arguments are given.
+ * @retval OT_ERROR_NO_BUFS        Insufficient available buffer to add the UDP and IPv6 headers.
  *
  */
-otError otUdpSend(otUdpSocket *aSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo);
+otError otUdpSend(otInstance *aInstance, otUdpSocket *aSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo);
+
+/**
+ * This function gets the head of linked list of UDP Sockets.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ *
+ * @returns A pointer to the head of UDP Socket linked list.
+ *
+ */
+otUdpSocket *otUdpGetSockets(otInstance *aInstance);
 
 /**
  * @}
@@ -303,16 +290,6 @@
                          uint16_t            aSockPort);
 
 /**
- * This function gets the existing UDP Sockets.
- *
- * @param[in]  aInstance            A pointer to an OpenThread instance.
- *
- * @returns A pointer to the first UDP Socket.
- *
- */
-otUdpSocket *otUdpGetSockets(otInstance *aInstance);
-
-/**
  * @}
  *
  */
diff --git a/src/cli/cli_udp.cpp b/src/cli/cli_udp.cpp
index 24bdbe5..1454908 100644
--- a/src/cli/cli_udp.cpp
+++ b/src/cli/cli_udp.cpp
@@ -86,7 +86,7 @@
 
     sockaddr.mPort = static_cast<uint16_t>(value);
 
-    error = otUdpBind(&mSocket, &sockaddr);
+    error = otUdpBind(mInterpreter.mInstance, &mSocket, &sockaddr);
 
 exit:
     return error;
@@ -110,7 +110,7 @@
 
     sockaddr.mPort = static_cast<uint16_t>(value);
 
-    error = otUdpConnect(&mSocket, &sockaddr);
+    error = otUdpConnect(mInterpreter.mInstance, &mSocket, &sockaddr);
 
 exit:
     return error;
@@ -121,7 +121,7 @@
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
 
-    return otUdpClose(&mSocket);
+    return otUdpClose(mInterpreter.mInstance, &mSocket);
 }
 
 otError UdpExample::ProcessOpen(uint8_t aArgsLength, char *aArgs[])
@@ -218,7 +218,7 @@
     }
     }
 
-    error = otUdpSend(&mSocket, message, &messageInfo);
+    error = otUdpSend(mInterpreter.mInstance, &mSocket, message, &messageInfo);
 
 exit:
 
diff --git a/src/core/api/udp_api.cpp b/src/core/api/udp_api.cpp
index 2cd17a7..bd907e7 100644
--- a/src/core/api/udp_api.cpp
+++ b/src/core/api/udp_api.cpp
@@ -51,41 +51,48 @@
 
 otError otUdpOpen(otInstance *aInstance, otUdpSocket *aSocket, otUdpReceive aCallback, void *aContext)
 {
-    otError           error;
-    Instance &        instance = *static_cast<Instance *>(aInstance);
-    Ip6::Udp::Socket &socket   = *new (aSocket) Ip6::Udp::Socket(instance.Get<Ip6::Udp>());
+    Instance &instance = *static_cast<Instance *>(aInstance);
 
-    error = socket.Open(aCallback, aContext);
-
-    return error;
+    return instance.Get<Ip6::Udp>().Open(*static_cast<Ip6::Udp::SocketHandle *>(aSocket), aCallback, aContext);
 }
 
-otError otUdpClose(otUdpSocket *aSocket)
+otError otUdpClose(otInstance *aInstance, otUdpSocket *aSocket)
 {
-    otError           error  = OT_ERROR_INVALID_STATE;
-    Ip6::Udp::Socket &socket = *static_cast<Ip6::Udp::Socket *>(aSocket);
+    Instance &instance = *static_cast<Instance *>(aInstance);
 
-    error = socket.Close();
-
-    return error;
+    return instance.Get<Ip6::Udp>().Close(*static_cast<Ip6::Udp::SocketHandle *>(aSocket));
 }
 
-otError otUdpBind(otUdpSocket *aSocket, otSockAddr *aSockName)
+otError otUdpBind(otInstance *aInstance, otUdpSocket *aSocket, const otSockAddr *aSockName)
 {
-    Ip6::Udp::Socket &socket = *static_cast<Ip6::Udp::Socket *>(aSocket);
-    return socket.Bind(*static_cast<const Ip6::SockAddr *>(aSockName));
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Ip6::Udp>().Bind(*static_cast<Ip6::Udp::SocketHandle *>(aSocket),
+                                         *static_cast<const Ip6::SockAddr *>(aSockName));
 }
 
-otError otUdpConnect(otUdpSocket *aSocket, otSockAddr *aSockName)
+otError otUdpConnect(otInstance *aInstance, otUdpSocket *aSocket, const otSockAddr *aSockName)
 {
-    Ip6::Udp::Socket &socket = *static_cast<Ip6::Udp::Socket *>(aSocket);
-    return socket.Connect(*static_cast<const Ip6::SockAddr *>(aSockName));
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Ip6::Udp>().Connect(*static_cast<Ip6::Udp::SocketHandle *>(aSocket),
+                                            *static_cast<const Ip6::SockAddr *>(aSockName));
 }
 
-otError otUdpSend(otUdpSocket *aSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo)
+otError otUdpSend(otInstance *aInstance, otUdpSocket *aSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo)
 {
-    Ip6::Udp::Socket &socket = *static_cast<Ip6::Udp::Socket *>(aSocket);
-    return socket.SendTo(*static_cast<Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Ip6::Udp>().SendTo(*static_cast<Ip6::Udp::SocketHandle *>(aSocket),
+                                           *static_cast<Message *>(aMessage),
+                                           *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
+}
+
+otUdpSocket *otUdpGetSockets(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Ip6::Udp>().GetUdpSockets();
 }
 
 #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
@@ -119,13 +126,6 @@
 }
 #endif // OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
 
-otUdpSocket *otUdpGetSockets(otInstance *aInstance)
-{
-    Instance &instance = *static_cast<Instance *>(aInstance);
-
-    return instance.Get<Ip6::Udp>().GetUdpSockets();
-}
-
 otError otUdpAddReceiver(otInstance *aInstance, otUdpReceiver *aUdpReceiver)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
diff --git a/src/core/coap/coap.cpp b/src/core/coap/coap.cpp
index 9f7e271..5e34539 100644
--- a/src/core/coap/coap.cpp
+++ b/src/core/coap/coap.cpp
@@ -1014,7 +1014,7 @@
 
 Coap::Coap(Instance &aInstance)
     : CoapBase(aInstance, &Coap::Send)
-    , mSocket(aInstance.Get<Ip6::Udp>())
+    , mSocket(aInstance)
 {
 }
 
diff --git a/src/core/meshcop/dtls.cpp b/src/core/meshcop/dtls.cpp
index 9364f15..a97d79a 100644
--- a/src/core/meshcop/dtls.cpp
+++ b/src/core/meshcop/dtls.cpp
@@ -73,7 +73,7 @@
     , mConnectedHandler(nullptr)
     , mReceiveHandler(nullptr)
     , mContext(nullptr)
-    , mSocket(Get<Ip6::Udp>())
+    , mSocket(aInstance)
     , mTransportCallback(nullptr)
     , mTransportContext(nullptr)
     , mMessageSubType(Message::kSubTypeNone)
diff --git a/src/core/meshcop/joiner_router.cpp b/src/core/meshcop/joiner_router.cpp
index 1d4b69e..5107562 100644
--- a/src/core/meshcop/joiner_router.cpp
+++ b/src/core/meshcop/joiner_router.cpp
@@ -56,7 +56,7 @@
 JoinerRouter::JoinerRouter(Instance &aInstance)
     : InstanceLocator(aInstance)
     , Notifier::Receiver(aInstance, JoinerRouter::HandleNotifierEvents)
-    , mSocket(aInstance.Get<Ip6::Udp>())
+    , mSocket(aInstance)
     , mRelayTransmit(OT_URI_PATH_RELAY_TX, &JoinerRouter::HandleRelayTransmit, this)
     , mTimer(aInstance, JoinerRouter::HandleTimer, this)
     , mJoinerUdpPort(0)
diff --git a/src/core/net/dhcp6_client.cpp b/src/core/net/dhcp6_client.cpp
index 45841fa..63faa1c 100644
--- a/src/core/net/dhcp6_client.cpp
+++ b/src/core/net/dhcp6_client.cpp
@@ -53,7 +53,7 @@
 
 Dhcp6Client::Dhcp6Client(Instance &aInstance)
     : InstanceLocator(aInstance)
-    , mSocket(Get<Ip6::Udp>())
+    , mSocket(aInstance)
     , mTrickleTimer(aInstance, Dhcp6Client::HandleTrickleTimer, nullptr, this)
     , mStartTime(0)
     , mIdentityAssociationCurrent(nullptr)
diff --git a/src/core/net/dhcp6_server.cpp b/src/core/net/dhcp6_server.cpp
index bf99652..6bf2368 100644
--- a/src/core/net/dhcp6_server.cpp
+++ b/src/core/net/dhcp6_server.cpp
@@ -48,7 +48,7 @@
 
 Dhcp6Server::Dhcp6Server(Instance &aInstance)
     : InstanceLocator(aInstance)
-    , mSocket(Get<Ip6::Udp>())
+    , mSocket(aInstance)
     , mPrefixAgentsCount(0)
     , mPrefixAgentsMask(0)
 {
diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp
index a8afec9..0919d62 100644
--- a/src/core/net/dns_client.cpp
+++ b/src/core/net/dns_client.cpp
@@ -73,7 +73,7 @@
 }
 
 Client::Client(Ip6::Netif &aNetif)
-    : mSocket(aNetif.Get<Ip6::Udp>())
+    : mSocket(aNetif.GetInstance())
     , mMessageId(0)
     , mRetransmissionTimer(aNetif.GetInstance(), Client::HandleRetransmissionTimer, this)
 {
diff --git a/src/core/net/sntp_client.cpp b/src/core/net/sntp_client.cpp
index d7f7a41..a8bc0e4 100644
--- a/src/core/net/sntp_client.cpp
+++ b/src/core/net/sntp_client.cpp
@@ -90,7 +90,7 @@
 }
 
 Client::Client(Ip6::Netif &aNetif)
-    : mSocket(aNetif.Get<Ip6::Udp>())
+    : mSocket(aNetif.GetInstance())
     , mRetransmissionTimer(aNetif.GetInstance(), Client::HandleRetransmissionTimer, this)
     , mUnixEra(0)
 {
diff --git a/src/core/net/udp6.cpp b/src/core/net/udp6.cpp
index 9174eec..f0743a8 100644
--- a/src/core/net/udp6.cpp
+++ b/src/core/net/udp6.cpp
@@ -48,168 +48,7 @@
 namespace ot {
 namespace Ip6 {
 
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-static bool IsMle(Instance &aInstance, uint16_t aPort)
-{
-#if OPENTHREAD_FTD
-    return aPort == ot::Mle::kUdpPort || aPort == aInstance.Get<MeshCoP::JoinerRouter>().GetJoinerUdpPort();
-#else
-    OT_UNUSED_VARIABLE(aInstance);
-    return aPort == ot::Mle::kUdpPort;
-#endif
-}
-#endif
-
-Udp::Socket::Socket(Udp &aUdp)
-    : InstanceLocator(aUdp.GetInstance())
-{
-    mHandle = nullptr;
-}
-
-Message *Udp::Socket::NewMessage(uint16_t aReserved, const Message::Settings &aSettings)
-{
-    return Get<Udp>().NewMessage(aReserved, aSettings);
-}
-
-otError Udp::Socket::Open(otUdpReceive aHandler, void *aContext)
-{
-    otError error = OT_ERROR_NONE;
-
-    GetSockName().Clear();
-    GetPeerName().Clear();
-    mHandler = aHandler;
-    mContext = aContext;
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    SuccessOrExit(error = otPlatUdpSocket(this));
-#endif
-
-    Get<Udp>().AddSocket(*this);
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-exit:
-#endif
-    return error;
-}
-
-otError Udp::Socket::Bind(const SockAddr &aSockAddr)
-{
-    otError error = OT_ERROR_NONE;
-
-    mSockName = aSockAddr;
-
-    if (!IsBound())
-    {
-        do
-        {
-            mSockName.mPort = Get<Udp>().GetEphemeralPort();
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-            error = otPlatUdpBind(this);
-#endif
-        } while (error != OT_ERROR_NONE);
-    }
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    else if (!IsMle(GetInstance(), mSockName.mPort))
-    {
-        error = otPlatUdpBind(this);
-    }
-#endif
-
-    return error;
-}
-
-otError Udp::Socket::Connect(const SockAddr &aSockAddr)
-{
-    otError error = OT_ERROR_NONE;
-
-    mPeerName = aSockAddr;
-
-    if (!IsBound())
-    {
-        SuccessOrExit(error = Bind(GetSockName()));
-    }
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    if (!IsMle(GetInstance(), mSockName.mPort))
-    {
-        error = otPlatUdpConnect(this);
-    }
-#endif
-
-exit:
-    return error;
-}
-
-otError Udp::Socket::Close(void)
-{
-    otError error = OT_ERROR_NONE;
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    SuccessOrExit(error = otPlatUdpClose(this));
-#endif
-
-    Get<Udp>().RemoveSocket(*this);
-    GetSockName().Clear();
-    GetPeerName().Clear();
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-exit:
-#endif
-    return error;
-}
-
-otError Udp::Socket::SendTo(Message &aMessage, const MessageInfo &aMessageInfo)
-{
-    otError     error = OT_ERROR_NONE;
-    MessageInfo messageInfoLocal;
-
-    VerifyOrExit((aMessageInfo.GetSockPort() == 0) || (GetSockName().mPort == aMessageInfo.GetSockPort()),
-                 error = OT_ERROR_INVALID_ARGS);
-
-    messageInfoLocal = aMessageInfo;
-
-    if (messageInfoLocal.GetPeerAddr().IsUnspecified())
-    {
-        VerifyOrExit(!GetPeerName().GetAddress().IsUnspecified(), error = OT_ERROR_INVALID_ARGS);
-
-        messageInfoLocal.SetPeerAddr(GetPeerName().GetAddress());
-    }
-
-    if (messageInfoLocal.mPeerPort == 0)
-    {
-        VerifyOrExit(GetPeerName().mPort != 0, error = OT_ERROR_INVALID_ARGS);
-        messageInfoLocal.mPeerPort = GetPeerName().mPort;
-    }
-
-    if (messageInfoLocal.GetSockAddr().IsUnspecified())
-    {
-        messageInfoLocal.SetSockAddr(GetSockName().GetAddress());
-    }
-
-    if (!IsBound())
-    {
-        SuccessOrExit(error = Bind(GetSockName()));
-    }
-
-    messageInfoLocal.SetSockPort(GetSockName().mPort);
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    if (!IsMle(GetInstance(), mSockName.mPort) &&
-        !(mSockName.mPort == ot::kCoapUdpPort && aMessage.GetSubType() == Message::kSubTypeJoinerEntrust))
-    {
-        SuccessOrExit(error = otPlatUdpSend(this, &aMessage, &messageInfoLocal));
-    }
-    else
-#endif
-    {
-        SuccessOrExit(error = Get<Udp>().SendDatagram(aMessage, messageInfoLocal, kProtoUdp));
-    }
-
-exit:
-    return error;
-}
-
-bool Udp::Socket::Matches(const MessageInfo &aMessageInfo) const
+bool Udp::SocketHandle::Matches(const MessageInfo &aMessageInfo) const
 {
     bool matches = false;
 
@@ -235,6 +74,42 @@
     return matches;
 }
 
+Udp::Socket::Socket(Instance &aInstance)
+    : InstanceLocator(aInstance)
+{
+    mHandle = nullptr;
+}
+
+Message *Udp::Socket::NewMessage(uint16_t aReserved, const Message::Settings &aSettings)
+{
+    return Get<Udp>().NewMessage(aReserved, aSettings);
+}
+
+otError Udp::Socket::Open(otUdpReceive aHandler, void *aContext)
+{
+    return Get<Udp>().Open(*this, aHandler, aContext);
+}
+
+otError Udp::Socket::Bind(const SockAddr &aSockAddr)
+{
+    return Get<Udp>().Bind(*this, aSockAddr);
+}
+
+otError Udp::Socket::Connect(const SockAddr &aSockAddr)
+{
+    return Get<Udp>().Connect(*this, aSockAddr);
+}
+
+otError Udp::Socket::Close(void)
+{
+    return Get<Udp>().Close(*this);
+}
+
+otError Udp::Socket::SendTo(Message &aMessage, const MessageInfo &aMessageInfo)
+{
+    return Get<Udp>().SendTo(*this, aMessage, aMessageInfo);
+}
+
 Udp::Udp(Instance &aInstance)
     : InstanceLocator(aInstance)
     , mEphemeralPort(kDynamicPortMin)
@@ -263,12 +138,148 @@
     return error;
 }
 
-void Udp::AddSocket(Socket &aSocket)
+otError Udp::Open(SocketHandle &aSocket, otUdpReceive aHandler, void *aContext)
+{
+    otError error = OT_ERROR_NONE;
+
+    aSocket.GetSockName().Clear();
+    aSocket.GetPeerName().Clear();
+    aSocket.mHandler = aHandler;
+    aSocket.mContext = aContext;
+
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+    error = otPlatUdpSocket(&aSocket);
+#endif
+    SuccessOrExit(error);
+
+    AddSocket(aSocket);
+
+exit:
+    return error;
+}
+
+otError Udp::Bind(SocketHandle &aSocket, const SockAddr &aSockAddr)
+{
+    otError error = OT_ERROR_NONE;
+
+    aSocket.mSockName = aSockAddr;
+
+    if (!aSocket.IsBound())
+    {
+        do
+        {
+            aSocket.mSockName.mPort = GetEphemeralPort();
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+            error = otPlatUdpBind(&aSocket);
+#endif
+        } while (error != OT_ERROR_NONE);
+    }
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+    else if (!IsMlePort(aSocket.mSockName.mPort))
+    {
+        error = otPlatUdpBind(&aSocket);
+    }
+#endif
+
+    return error;
+}
+
+otError Udp::Connect(SocketHandle &aSocket, const SockAddr &aSockAddr)
+{
+    otError error = OT_ERROR_NONE;
+
+    aSocket.mPeerName = aSockAddr;
+
+    if (!aSocket.IsBound())
+    {
+        SuccessOrExit(error = Bind(aSocket, aSocket.GetSockName()));
+    }
+
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+    if (!IsMlePort(aSocket.mSockName.mPort))
+    {
+        error = otPlatUdpConnect(&aSocket);
+    }
+#endif
+
+exit:
+    return error;
+}
+
+otError Udp::Close(SocketHandle &aSocket)
+{
+    otError error = OT_ERROR_NONE;
+
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+    error = otPlatUdpClose(&aSocket);
+#endif
+    SuccessOrExit(error);
+
+    RemoveSocket(aSocket);
+    aSocket.GetSockName().Clear();
+    aSocket.GetPeerName().Clear();
+
+exit:
+    return error;
+}
+
+otError Udp::SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo)
+{
+    otError     error = OT_ERROR_NONE;
+    MessageInfo messageInfoLocal;
+
+    VerifyOrExit((aMessageInfo.GetSockPort() == 0) || (aSocket.GetSockName().mPort == aMessageInfo.GetSockPort()),
+                 error = OT_ERROR_INVALID_ARGS);
+
+    messageInfoLocal = aMessageInfo;
+
+    if (messageInfoLocal.GetPeerAddr().IsUnspecified())
+    {
+        VerifyOrExit(!aSocket.GetPeerName().GetAddress().IsUnspecified(), error = OT_ERROR_INVALID_ARGS);
+
+        messageInfoLocal.SetPeerAddr(aSocket.GetPeerName().GetAddress());
+    }
+
+    if (messageInfoLocal.mPeerPort == 0)
+    {
+        VerifyOrExit(aSocket.GetPeerName().mPort != 0, error = OT_ERROR_INVALID_ARGS);
+        messageInfoLocal.mPeerPort = aSocket.GetPeerName().mPort;
+    }
+
+    if (messageInfoLocal.GetSockAddr().IsUnspecified())
+    {
+        messageInfoLocal.SetSockAddr(aSocket.GetSockName().GetAddress());
+    }
+
+    if (!aSocket.IsBound())
+    {
+        SuccessOrExit(error = Bind(aSocket, aSocket.GetSockName()));
+    }
+
+    messageInfoLocal.SetSockPort(aSocket.GetSockName().mPort);
+
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+    if (!IsMlePort(aSocket.mSockName.mPort) &&
+        !(aSocket.mSockName.mPort == ot::kCoapUdpPort && aMessage.GetSubType() == Message::kSubTypeJoinerEntrust))
+    {
+        SuccessOrExit(error = otPlatUdpSend(&aSocket, &aMessage, &messageInfoLocal));
+    }
+    else
+#endif
+    {
+        SuccessOrExit(error = SendDatagram(aMessage, messageInfoLocal, kProtoUdp));
+    }
+
+exit:
+    return error;
+}
+
+void Udp::AddSocket(SocketHandle &aSocket)
 {
     IgnoreError(mSockets.Add(aSocket));
 }
 
-void Udp::RemoveSocket(Socket &aSocket)
+void Udp::RemoveSocket(SocketHandle &aSocket)
 {
     SuccessOrExit(mSockets.Remove(aSocket));
     aSocket.SetNext(nullptr);
@@ -358,7 +369,7 @@
     aMessageInfo.mSockPort = udpHeader.GetDestinationPort();
 
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    VerifyOrExit(IsMle(GetInstance(), aMessageInfo.mSockPort), OT_NOOP);
+    VerifyOrExit(IsMlePort(aMessageInfo.mSockPort), OT_NOOP);
 #endif
 
     for (Receiver *receiver = mReceivers.GetHead(); receiver; receiver = receiver->GetNext())
@@ -374,8 +385,8 @@
 
 void Udp::HandlePayload(Message &aMessage, MessageInfo &aMessageInfo)
 {
-    Socket *socket;
-    Socket *prev;
+    SocketHandle *socket;
+    SocketHandle *prev;
 
     socket = mSockets.FindMatching(aMessageInfo, prev);
     VerifyOrExit(socket != nullptr, OT_NOOP);
@@ -401,5 +412,16 @@
     aMessage.Write(aMessage.GetOffset() + Header::kChecksumFieldOffset, sizeof(aChecksum), &aChecksum);
 }
 
+bool Udp::IsMlePort(uint16_t aPort) const
+{
+    bool isMlePort = (aPort == Mle::kUdpPort);
+
+#if OPENTHREAD_FTD
+    isMlePort = isMlePort || (aPort == Get<MeshCoP::JoinerRouter>().GetJoinerUdpPort());
+#endif
+
+    return isMlePort;
+}
+
 } // namespace Ip6
 } // namespace ot
diff --git a/src/core/net/udp6.hpp b/src/core/net/udp6.hpp
index 35abc51..5cc29fe 100644
--- a/src/core/net/udp6.hpp
+++ b/src/core/net/udp6.hpp
@@ -68,55 +68,13 @@
      * This class implements a UDP/IPv6 socket.
      *
      */
-    class Socket : public otUdpSocket, public InstanceLocator, public LinkedListEntry<Socket>
+    class SocketHandle : public otUdpSocket, public LinkedListEntry<SocketHandle>
     {
         friend class Udp;
-        friend class LinkedList<Socket>;
+        friend class LinkedList<SocketHandle>;
 
     public:
         /**
-         * This constructor initializes the object.
-         *
-         * @param[in]  aUdp  A reference to the UDP transport object.
-         *
-         */
-        explicit Socket(Udp &aUdp);
-
-        /**
-         * This method returns a new UDP message with sufficient header space reserved.
-         *
-         * @param[in]  aReserved  The number of header bytes to reserve after the UDP header.
-         * @param[in]  aSettings  The message settings (default is used if not provided).
-         *
-         * @returns A pointer to the message or nullptr if no buffers are available.
-         *
-         */
-        Message *NewMessage(uint16_t aReserved, const Message::Settings &aSettings = Message::Settings::GetDefault());
-
-        /**
-         * This method opens the UDP socket.
-         *
-         * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
-         * @param[in]  aContext  A pointer to arbitrary context information.
-         *
-         * @retval OT_ERROR_NONE     Successfully opened the socket.
-         * @retval OT_ERROR_ALREADY  The socket is already open.
-         *
-         */
-        otError Open(otUdpReceive aHandler, void *aContext);
-
-        /**
-         * This method binds the UDP socket.
-         *
-         * @param[in]  aSockAddr  A reference to the socket address.
-         *
-         * @retval OT_ERROR_NONE    Successfully bound the socket.
-         * @retval OT_ERROR_FAILED  Failed to bind UDP Socket.
-         *
-         */
-        otError Bind(const SockAddr &aSockAddr);
-
-        /**
          * This method indicates whether or not the socket is bound.
          *
          * @retval TRUE if the socket is bound (i.e. source port is non-zero).
@@ -126,39 +84,6 @@
         bool IsBound(void) const { return mSockName.mPort != 0; }
 
         /**
-         * This method connects the UDP socket.
-         *
-         * @param[in]  aSockAddr  A reference to the socket address.
-         *
-         * @retval OT_ERROR_NONE    Successfully connected the socket.
-         * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
-         *
-         */
-        otError Connect(const SockAddr &aSockAddr);
-
-        /**
-         * This method closes the UDP socket.
-         *
-         * @retval OT_ERROR_NONE    Successfully closed the UDP socket.
-         * @retval OT_ERROR_FAILED  Failed to close UDP Socket.
-         *
-         */
-        otError Close(void);
-
-        /**
-         * This method sends a UDP message.
-         *
-         * @param[in]  aMessage      The message to send.
-         * @param[in]  aMessageInfo  The message info associated with @p aMessage.
-         *
-         * @retval OT_ERROR_NONE          Successfully sent the UDP message.
-         * @retval OT_ERROR_INVALID_ARGS  If no peer is specified in @p aMessageInfo or by connect().
-         * @retval OT_ERROR_NO_BUFS       Insufficient available buffer to add the UDP and IPv6 headers.
-         *
-         */
-        otError SendTo(Message &aMessage, const MessageInfo &aMessageInfo);
-
-        /**
          * This method returns the local socket address.
          *
          * @returns A reference to the local socket address.
@@ -200,6 +125,91 @@
     };
 
     /**
+     * This class implements a UDP/IPv6 socket.
+     *
+     */
+    class Socket : public InstanceLocator, public SocketHandle
+    {
+        friend class Udp;
+
+    public:
+        /**
+         * This constructor initializes the object.
+         *
+         * @param[in]  aInstance  A reference to OpenThread instance.
+         *
+         */
+        explicit Socket(Instance &aInstance);
+
+        /**
+         * This method returns a new UDP message with sufficient header space reserved.
+         *
+         * @param[in]  aReserved  The number of header bytes to reserve after the UDP header.
+         * @param[in]  aSettings  The message settings (default is used if not provided).
+         *
+         * @returns A pointer to the message or nullptr if no buffers are available.
+         *
+         */
+        Message *NewMessage(uint16_t aReserved, const Message::Settings &aSettings = Message::Settings::GetDefault());
+
+        /**
+         * This method opens the UDP socket.
+         *
+         * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
+         * @param[in]  aContext  A pointer to arbitrary context information.
+         *
+         * @retval OT_ERROR_NONE     Successfully opened the socket.
+         * @retval OT_ERROR_FAILED   Failed to open the socket.
+         *
+         */
+        otError Open(otUdpReceive aHandler, void *aContext);
+
+        /**
+         * This method binds the UDP socket.
+         *
+         * @param[in]  aSockAddr  A reference to the socket address.
+         *
+         * @retval OT_ERROR_NONE    Successfully bound the socket.
+         * @retval OT_ERROR_FAILED  Failed to bind UDP Socket.
+         *
+         */
+        otError Bind(const SockAddr &aSockAddr);
+
+        /**
+         * This method connects the UDP socket.
+         *
+         * @param[in]  aSockAddr  A reference to the socket address.
+         *
+         * @retval OT_ERROR_NONE    Successfully connected the socket.
+         * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
+         *
+         */
+        otError Connect(const SockAddr &aSockAddr);
+
+        /**
+         * This method closes the UDP socket.
+         *
+         * @retval OT_ERROR_NONE    Successfully closed the UDP socket.
+         * @retval OT_ERROR_FAILED  Failed to close UDP Socket.
+         *
+         */
+        otError Close(void);
+
+        /**
+         * This method sends a UDP message.
+         *
+         * @param[in]  aMessage      The message to send.
+         * @param[in]  aMessageInfo  The message info associated with @p aMessage.
+         *
+         * @retval OT_ERROR_NONE          Successfully sent the UDP message.
+         * @retval OT_ERROR_INVALID_ARGS  If no peer is specified in @p aMessageInfo or by Connect().
+         * @retval OT_ERROR_NO_BUFS       Insufficient available buffer to add the UDP and IPv6 headers.
+         *
+         */
+        otError SendTo(Message &aMessage, const MessageInfo &aMessageInfo);
+    };
+
+    /**
      * This class implements a UDP receiver.
      *
      */
@@ -320,7 +330,7 @@
     /**
      * This constructor initializes the object.
      *
-     * @param[in]  aIp6  A reference to OpenThread instance.
+     * @param[in]  aInstance  A reference to OpenThread instance.
      *
      */
     explicit Udp(Instance &aInstance);
@@ -348,20 +358,66 @@
     otError RemoveReceiver(Receiver &aReceiver);
 
     /**
-     * This method adds a UDP socket.
+     * This method opens a UDP socket.
      *
-     * @param[in]  aSocket  A reference to the UDP socket.
+     * @param[in]  aSocket   A reference to the socket.
+     * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
+     * @param[in]  aContext  A pointer to arbitrary context information.
+     *
+     * @retval OT_ERROR_NONE     Successfully opened the socket.
+     * @retval OT_ERROR_FAILED   Failed to open the socket.
      *
      */
-    void AddSocket(Socket &aSocket);
+    otError Open(SocketHandle &aSocket, otUdpReceive aHandler, void *aContext);
 
     /**
-     * This method removes a UDP socket.
+     * This method binds a UDP socket.
      *
-     * @param[in]  aSocket  A reference to the UDP socket.
+     * @param[in]  aSocket    A reference to the socket.
+     * @param[in]  aSockAddr  A reference to the socket address.
+     *
+     * @retval OT_ERROR_NONE    Successfully bound the socket.
+     * @retval OT_ERROR_FAILED  Failed to bind UDP Socket.
      *
      */
-    void RemoveSocket(Socket &aSocket);
+    otError Bind(SocketHandle &aSocket, const SockAddr &aSockAddr);
+
+    /**
+     * This method connects a UDP socket.
+     *
+     * @param[in]  aSocket    A reference to the socket.
+     * @param[in]  aSockAddr  A reference to the socket address.
+     *
+     * @retval OT_ERROR_NONE    Successfully connected the socket.
+     * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
+     *
+     */
+    otError Connect(SocketHandle &aSocket, const SockAddr &aSockAddr);
+
+    /**
+     * This method closes the UDP socket.
+     *
+     * @param[in]  aSocket    A reference to the socket.
+     *
+     * @retval OT_ERROR_NONE    Successfully closed the UDP socket.
+     * @retval OT_ERROR_FAILED  Failed to close UDP Socket.
+     *
+     */
+    otError Close(SocketHandle &aSocket);
+
+    /**
+     * This method sends a UDP message using a socket.
+     *
+     * @param[in]  aSocket       A reference to the socket.
+     * @param[in]  aMessage      The message to send.
+     * @param[in]  aMessageInfo  The message info associated with @p aMessage.
+     *
+     * @retval OT_ERROR_NONE          Successfully sent the UDP message.
+     * @retval OT_ERROR_INVALID_ARGS  If no peer is specified in @p aMessageInfo or by Connect().
+     * @retval OT_ERROR_NO_BUFS       Insufficient available buffer to add the UDP and IPv6 headers.
+     *
+     */
+    otError SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo);
 
     /**
      * This method returns a new ephemeral port.
@@ -431,7 +487,7 @@
      * @returns A pointer to the head of UDP Socket linked list.
      *
      */
-    Socket *GetUdpSockets(void) { return mSockets.GetHead(); }
+    SocketHandle *GetUdpSockets(void) { return mSockets.GetHead(); }
 
 #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
     /**
@@ -455,9 +511,13 @@
         kDynamicPortMax = 65535, ///< Service Name and Transport Protocol Port Number Registry
     };
 
-    uint16_t             mEphemeralPort;
-    LinkedList<Receiver> mReceivers;
-    LinkedList<Socket>   mSockets;
+    void AddSocket(SocketHandle &aSocket);
+    void RemoveSocket(SocketHandle &aSocket);
+    bool IsMlePort(uint16_t aPort) const;
+
+    uint16_t                 mEphemeralPort;
+    LinkedList<Receiver>     mReceivers;
+    LinkedList<SocketHandle> mSockets;
 #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
     void *         mUdpForwarderContext;
     otUdpForwarder mUdpForwarder;
diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp
index 2191975..afc72e8 100644
--- a/src/core/thread/mle.cpp
+++ b/src/core/thread/mle.cpp
@@ -89,7 +89,7 @@
     , mParentLinkMargin(0)
     , mParentIsSingleton(false)
     , mReceivedResponseFromParent(false)
-    , mSocket(aInstance.Get<Ip6::Udp>())
+    , mSocket(aInstance)
     , mTimeout(kMleEndDeviceTimeout)
 #if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
     , mPreviousParentRloc(Mac::kShortAddrInvalid)