diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 480a36ece..82fb9eaa6 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -63,7 +63,29 @@ cd /tmp || die } [ $BUILD_TARGET != posix-app-pty ] || { - sudo apt-get install socat expect || die + sudo apt-get install socat expect libdbus-1-dev autoconf-archive || die + JOBS=$(getconf _NPROCESSORS_ONLN) + ( + WPANTUND_TMPDIR=/tmp/wpantund + git clone --depth 1 https://github.com/openthread/wpantund.git $WPANTUND_TMPDIR + cd $WPANTUND_TMPDIR + ./bootstrap.sh + ./configure --prefix= --exec-prefix=/usr --disable-ncp-dummy --enable-static-link-ncp-plugin=spinel + make -j $JOBS + sudo make install + ) || die + ( + LIBCOAP_TMPDIR=/tmp/libcoap + mkdir $LIBCOAP_TMPDIR + cd $LIBCOAP_TMPDIR + wget https://github.com/obgm/libcoap/archive/bsd-licensed.tar.gz + tar xvf bsd-licensed.tar.gz + cd libcoap-bsd-licensed + ./autogen.sh + ./configure --prefix= --exec-prefix=/usr --with-boost=internal --disable-tests --disable-documentation + make -j $JOBS + sudo make install + ) || die } [ $BUILD_TARGET != scan-build ] || { diff --git a/.travis/check-posix-app-pty b/.travis/check-posix-app-pty new file mode 100755 index 000000000..38d0d5a24 --- /dev/null +++ b/.travis/check-posix-app-pty @@ -0,0 +1,115 @@ +#!/bin/bash +# +# Copyright (c) 2018, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +set -e +set -x + +die() { + echo " *** ERROR: " $* + exit 1 +} + +at_exit() { + EXIT_CODE=$? + sudo killall wpantund || true + killall socat || true + exit $EXIT_CODE +} + +build() { + ./bootstrap + COVERAGE=1 make -f examples/Makefile-posix + COVERAGE=1 make -f src/posix/Makefile-posix PLATFORM_UDP=1 +} + +check() { + trap at_exit INT TERM EXIT + + SOCAT_OUTPUT=/tmp/ot-socat + socat -d -d pty,raw,b115200,echo=0 pty,raw,b115200,echo=0 > /dev/null 2> $SOCAT_OUTPUT & + while true; do + if test $(head -n2 $SOCAT_OUTPUT | wc -l) = 2; then + RADIO_PTY=$(head -n1 $SOCAT_OUTPUT | grep -o '/dev/.\+') + CORE_PTY=$(head -n2 $SOCAT_OUTPUT | tail -n1 | grep -o '/dev/.\+') + break + fi + echo 'Waiting for socat ready...' + sleep 1 + done + echo 'RADIO_PTY' $DEVICE_PTY + echo 'CORE_PTY' $CORE_PTY + + RADIO_NCP_PATH="$(pwd)/$(ls output/*linux*/bin/ot-ncp-radio)" + $RADIO_NCP_PATH 1 > $RADIO_PTY < $RADIO_PTY & + OT_NCP_PATH="$(pwd)/$(ls output/posix/*linux*/bin/ot-ncp)" + PLATFORM_NETIF=wpan0 sudo -E wpantund -I wpan0 -o Thread:Config:FilterRLOCAddresses 0 -s "system:${OT_NCP_PATH} ${CORE_PTY}" & + + while true; do + sleep 5 + if sudo wpanctl status; then + break + else + echo 'Still waiting for wpantund' + fi + done + + sudo wpanctl leave || true + sudo wpanctl form -c 18 OpenThreadTest + + mleid=$(sudo wpanctl status | grep MeshLocalAddress | cut -d'"' -f4) + echo "ML-EID is: ${mleid}" + + netstat -an | grep -q 61631 || die 'TMF port is not available!' + xaddress=$(sudo wpanctl get 'NCP:ExtendedAddress' | cut -d= -f2 | tr -d ' []') + echo "Extended address is: ${xaddress}" + + # Retrievie extended address through network diagnostic get + coap_response=$(echo -n '120100' | xxd -r -p | coap-client -m POST coap://[${mleid}]:61631/d/dg -f- | xxd -p -u | grep 0008) + echo "CoAP response is: ${coap_response}" + + # Verify CoAP response contains the extended address + [[ "${coap_response}" = *${xaddress}* ]] || die 'failed to get extended address' + + # Leave so that code coverage will be flushed + sudo wpanctl leave || true +} + +main() { + case $1 in + check) + check + ;; + *) + build + check + ;; + esac +} + +main "$@" diff --git a/.travis/script.sh b/.travis/script.sh index 743cfc06b..67d6a2873 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -490,49 +490,7 @@ python --version || die } [ $BUILD_TARGET != posix-app-pty ] || { - ./bootstrap - COVERAGE=1 make -f examples/Makefile-posix - COVERAGE=1 make -f src/posix/Makefile-posix - - SOCAT_OUTPUT=/tmp/ot-socat - socat -d -d pty,raw,echo=0 pty,raw,echo=0 > /dev/null 2> $SOCAT_OUTPUT & - while true; do - if test $(head -n2 $SOCAT_OUTPUT | wc -l) = 2; then - RADIO_PTY=$(head -n1 $SOCAT_OUTPUT | grep -o '/dev/.\+') - CORE_PTY=$(head -n2 $SOCAT_OUTPUT | tail -n1 | grep -o '/dev/.\+') - break - fi - echo 'Waiting for socat ready...' - sleep 1 - done - echo 'RADIO_PTY' $DEVICE_PTY - echo 'CORE_PTY' $CORE_PTY - - RADIO_NCP_PATH="$(pwd)/$(ls output/*linux*/bin/ot-ncp-radio)" - $RADIO_NCP_PATH 1 > $RADIO_PTY < $RADIO_PTY & - OT_CLI_PATH="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli)" - - expect <@.])], + [ + case "${enableval}" in + no|yes) + enable_platform_udp=${enableval} + ;; + *) + AC_MSG_ERROR([Invalid value ${enable_platform_udp} for --enable-platform-udp]) + ;; + esac + ], + [enable_platform_udp=no]) + +if test "$enable_platform_udp" = "yes"; then + OPENTHREAD_ENABLE_PLATFORM_UDP=1 +else + OPENTHREAD_ENABLE_PLATFORM_UDP=0 +fi + +AC_MSG_RESULT(${enable_platform_udp}) +AC_SUBST(OPENTHREAD_ENABLE_PLATFORM_UDP) +AM_CONDITIONAL([OPENTHREAD_ENABLE_PLATFORM_UDP], [test "${enable_platform_udp}" = "yes"]) +AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_PLATFORM_UDP], [${OPENTHREAD_ENABLE_PLATFORM_UDP}], [Define to 1 to enable platform UDP.]) + # # Thread Commissioner # diff --git a/include/openthread/platform/Makefile.am b/include/openthread/platform/Makefile.am index 179ad3b0c..e0cc7838e 100644 --- a/include/openthread/platform/Makefile.am +++ b/include/openthread/platform/Makefile.am @@ -40,6 +40,7 @@ ot_platform_headers = \ random.h \ time.h \ uart.h \ + udp.h \ spi-slave.h \ settings.h \ messagepool.h \ diff --git a/include/openthread/platform/udp.h b/include/openthread/platform/udp.h new file mode 100644 index 000000000..e8d0e7ab7 --- /dev/null +++ b/include/openthread/platform/udp.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes the abstraction for the platform UDP service. + */ + +#ifndef OPENTHREAD_PLATFORM_UDP_H_ +#define OPENTHREAD_PLATFORM_UDP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This function initializes the UDP socket by platform. + * + * @param[in] aUdpSocket A pointer to the UDP socket. + * + * @retval OT_ERROR_NONE Successfully initialized UDP socket by platform. + * @retval OT_ERROR_FAILED Failed to initialize UDP Socket. + * + */ +otError otPlatUdpSocket(otUdpSocket *aUdpSocket); + +/** + * This function closes the UDP socket by platform. + * + * @param[in] aUdpSocket A pointer to the UDP socket. + * + * @retval OT_ERROR_NONE Successfully closed UDP socket by platform. + * @retval OT_ERROR_FAILED Failed to close UDP Socket. + * + */ +otError otPlatUdpClose(otUdpSocket *aUdpSocket); + +/** + * This function binds the UDP socket by platform. + * + * @param[in] aUdpSocket A pointer to the UDP socket. + * + * @retval OT_ERROR_NONE Successfully binded UDP socket by platform. + * @retval OT_ERROR_FAILED Failed to bind UDP socket. + * + */ +otError otPlatUdpBind(otUdpSocket *aUdpSocket); + +/** + * This function connects UDP socket by platform. + * + * @param[in] aUdpSocket A pointer to the UDP socket. + * + * @retval OT_ERROR_NONE Successfully connected by platform. + * @retval OT_ERROR_FAILED Failed to connect UDP socket. + * + */ +otError otPlatUdpConnect(otUdpSocket *aUdpSocket); + +/** + * This function sends UDP payload by platform. + * + * @param[in] aUdpSocket A pointer to the UDP socket. + * @param[in] aMessage A pointer to the message to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * + * @retval OT_ERROR_NONE Successfully sent by platform, and @p aMessage is freed. + * @retval OT_ERROR_FAILED Failed to binded UDP socket. + * + */ +otError otPlatUdpSend(otUdpSocket *aUdpSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_PLATFORM_UDP_H_ diff --git a/include/openthread/udp.h b/include/openthread/udp.h index 3c4238417..ae336ddd7 100644 --- a/include/openthread/udp.h +++ b/include/openthread/udp.h @@ -89,6 +89,7 @@ typedef struct otUdpSocket 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 struct otUdpSocket *mNext; ///< A pointer to the next UDP socket (internal use only). } otUdpSocket; @@ -253,6 +254,16 @@ void otUdpProxyReceive(otInstance * aInstance, const otIp6Address *aPeerAddr, 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/core/api/udp_api.cpp b/src/core/api/udp_api.cpp index 672c9809f..710ba0bd6 100644 --- a/src/core/api/udp_api.cpp +++ b/src/core/api/udp_api.cpp @@ -124,3 +124,12 @@ void otUdpProxyReceive(otInstance * aInstance, static_cast(aMessage)->Free(); } #endif // OPENTHREAD_ENABLE_UDP_PROXY + +#if OPENTHREAD_ENABLE_PLATFORM_UDP +otUdpSocket *otUdpGetSockets(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().GetUdp().GetUdpSockets(); +} +#endif diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index 55b663df5..ee465e416 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -644,6 +644,7 @@ otError Ip6::ProcessReceiveCallback(const Message & aMessage, break; +#if OPENTHREAD_ENABLE_PLATFORM_UDP == 0 case kCoapUdpPort: // do not pass TMF messages @@ -653,8 +654,15 @@ otError Ip6::ProcessReceiveCallback(const Message & aMessage, } break; +#endif // OPENTHREAD_ENABLE_PLATFORM_UDP default: +#if OPENTHREAD_FTD + if (udp.GetDestinationPort() == GetInstance().Get().GetJoinerUdpPort()) + { + ExitNow(error = OT_ERROR_NO_ROUTE); + } +#endif break; } diff --git a/src/core/net/udp6.cpp b/src/core/net/udp6.cpp index c6aec4aa0..5bc8757dd 100644 --- a/src/core/net/udp6.cpp +++ b/src/core/net/udp6.cpp @@ -35,6 +35,8 @@ #include +#include + #include "common/code_utils.hpp" #include "common/encoding.hpp" #include "common/instance.hpp" @@ -45,9 +47,21 @@ using ot::Encoding::BigEndian::HostSwap16; namespace ot { namespace Ip6 { +#if OPENTHREAD_ENABLE_PLATFORM_UDP +static bool IsMle(Instance &aInstance, uint16_t aPort) +{ +#if OPENTHREAD_FTD + return aPort == ot::Mle::kUdpPort || aPort == aInstance.Get().GetJoinerUdpPort(); +#else + return aPort == ot::Mle::kUdpPort; +#endif +} +#endif + UdpSocket::UdpSocket(Udp &aUdp) : InstanceLocator(aUdp.GetInstance()) { + mHandle = NULL; } Udp &UdpSocket::GetUdp(void) @@ -62,36 +76,70 @@ Message *UdpSocket::NewMessage(uint16_t aReserved, uint8_t aPriority) otError UdpSocket::Open(otUdpReceive aHandler, void *aContext) { + otError error; + memset(&mSockName, 0, sizeof(mSockName)); memset(&mPeerName, 0, sizeof(mPeerName)); mHandler = aHandler; mContext = aContext; - return GetUdp().AddSocket(*this); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + SuccessOrExit(error = otPlatUdpSocket(this)); +#endif + SuccessOrExit(error = GetUdp().AddSocket(*this)); + +exit: + return error; } otError UdpSocket::Bind(const SockAddr &aSockAddr) { + otError error = OT_ERROR_NONE; + mSockName = aSockAddr; - if (GetSockName().mPort == 0) + if (mSockName.mPort == 0) { - mSockName.mPort = GetUdp().GetEphemeralPort(); + do + { + mSockName.mPort = GetUdp().GetEphemeralPort(); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + error = otPlatUdpBind(this); +#endif + } while (error != OT_ERROR_NONE); } +#if OPENTHREAD_ENABLE_PLATFORM_UDP + else if (!IsMle(GetInstance(), mSockName.mPort)) + { + error = otPlatUdpBind(this); + } +#endif - return OT_ERROR_NONE; + return error; } otError UdpSocket::Connect(const SockAddr &aSockAddr) { + otError error = OT_ERROR_NONE; + mPeerName = aSockAddr; - return OT_ERROR_NONE; + +#if OPENTHREAD_ENABLE_PLATFORM_UDP + if (!IsMle(GetInstance(), mSockName.mPort)) + { + error = otPlatUdpConnect(this); + } +#endif + return error; } otError UdpSocket::Close(void) { otError error = OT_ERROR_NONE; +#if OPENTHREAD_ENABLE_PLATFORM_UDP + SuccessOrExit(error = otPlatUdpClose(this)); +#endif SuccessOrExit(error = GetUdp().RemoveSocket(*this)); memset(&mSockName, 0, sizeof(mSockName)); memset(&mPeerName, 0, sizeof(mPeerName)); @@ -127,11 +175,21 @@ otError UdpSocket::SendTo(Message &aMessage, const MessageInfo &aMessageInfo) if (GetSockName().mPort == 0) { - GetSockName().mPort = GetUdp().GetEphemeralPort(); + SuccessOrExit(error = Bind(GetSockName())); } messageInfoLocal.SetSockPort(GetSockName().mPort); - SuccessOrExit(error = GetUdp().SendDatagram(aMessage, messageInfoLocal, kProtoUdp)); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + if (!IsMle(GetInstance(), mSockName.mPort) && + !(mSockName.mPort == ot::kCoapUdpPort && aMessage.GetSubType() == Message::kSubTypeJoinerEntrust)) + { + SuccessOrExit(error = otPlatUdpSend(this, &aMessage, &messageInfoLocal)); + } + else +#endif + { + SuccessOrExit(error = GetUdp().SendDatagram(aMessage, messageInfoLocal, kProtoUdp)); + } exit: return error; @@ -305,6 +363,10 @@ otError Udp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo) aMessageInfo.mPeerPort = udpHeader.GetSourcePort(); aMessageInfo.mSockPort = udpHeader.GetDestinationPort(); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + VerifyOrExit(IsMle(GetInstance(), aMessageInfo.mSockPort)); +#endif + for (UdpReceiver *receiver = mReceivers; receiver; receiver = receiver->GetNext()) { VerifyOrExit(!receiver->HandleMessage(aMessage, aMessageInfo)); diff --git a/src/core/net/udp6.hpp b/src/core/net/udp6.hpp index 2539d8b29..33487f3e7 100644 --- a/src/core/net/udp6.hpp +++ b/src/core/net/udp6.hpp @@ -324,6 +324,10 @@ public: */ otError UpdateChecksum(Message &aMessage, uint16_t aPseudoHeaderChecksum); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + otUdpSocket *GetUdpSockets(void) { return mSockets; } +#endif + #if OPENTHREAD_ENABLE_UDP_PROXY /** * This method sets the proxy sender. diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix index 113b0a218..241995bd3 100644 --- a/src/posix/Makefile-posix +++ b/src/posix/Makefile-posix @@ -90,6 +90,12 @@ ifeq ($(VIRTUAL_TIME),1) COMMONCFLAGS += -DOPENTHREAD_POSIX_VIRTUAL_TIME=1 endif +ifeq ($(PLATFORM_UDP),1) +configure_OPTIONS += \ + --enable-platform-udp \ + $(NULL) +endif + CPPFLAGS += \ $(COMMONCFLAGS) \ $(NULL) diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index 5f0c4b96b..c57314fb6 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -65,6 +65,12 @@ nodist_libopenthread_posix_a_SOURCES = \ inc_%: ../../ncp/% echo '#include "$<"' > $@ +if OPENTHREAD_ENABLE_PLATFORM_UDP +libopenthread_posix_a_SOURCES += \ + udp.cpp \ + $(NULL) +endif + noinst_HEADERS = \ frame_queue.hpp \ openthread-system.h \ diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 35e312ec0..23f243824 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -315,6 +315,30 @@ void otSimRadioSpinelProcess(otInstance *aInstance, const struct Event *aEvent); #define otSysGetTime(aTime) gettimeofday(aTime, NULL) #endif +/** + * This function initializes platform UDP driver. + * + */ +void platformUdpInit(void); + +/** + * This function performs platform UDP driver processing. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aReadFdSet A pointer to the read file descriptors. + * + */ +void platformUdpProcess(otInstance *aInstance, const fd_set *aReadSet); + +/** + * This function updates the file descriptor sets with file descriptors used by the platform UDP driver. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[inout] aReadFdSet A pointer to the read file descriptors. + * @param[inout] aMaxFd A pointer to the max file descriptor. + */ +void platformUdpUpdateFdSet(otInstance *aInstance, fd_set *aReadFdSet, int *aMaxFd); + #ifdef __cplusplus } #endif diff --git a/src/posix/platform/system.c b/src/posix/platform/system.c index b37a9833a..7af1e327c 100644 --- a/src/posix/platform/system.c +++ b/src/posix/platform/system.c @@ -107,6 +107,9 @@ void otSysInit(int aArgCount, char *aArgVector[]) platformAlarmInit(speedUpFactor); platformRadioInit(radioFile, radioConfig); platformRandomInit(); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + platformUdpInit(); +#endif } bool otSysPseudoResetWasRequested(void) @@ -170,6 +173,9 @@ void otSysProcessDrivers(otInstance *aInstance) platformAlarmUpdateTimeout(&timeout); platformUartUpdateFdSet(&readFdSet, &writeFdSet, &errorFdSet, &maxFd); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + platformUdpUpdateFdSet(aInstance, &readFdSet, &maxFd); +#endif #if OPENTHREAD_POSIX_VIRTUAL_TIME otSimUpdateFdSet(&readFdSet, &writeFdSet, &errorFdSet, &maxFd, &timeout); #else @@ -230,4 +236,7 @@ void otSysProcessDrivers(otInstance *aInstance) #endif platformUartProcess(&readFdSet, &writeFdSet, &errorFdSet); platformAlarmProcess(aInstance); +#if OPENTHREAD_ENABLE_PLATFORM_UDP + platformUdpProcess(aInstance, &readFdSet); +#endif } diff --git a/src/posix/platform/udp.cpp b/src/posix/platform/udp.cpp new file mode 100644 index 000000000..701fddb0e --- /dev/null +++ b/src/posix/platform/udp.cpp @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes the platform UDP driver. + */ + +#include "platform-posix.h" + +#include +#include +#include +#include + +#include +#include + +#include "common/code_utils.hpp" + +static int sPlatNetifIndex = 0; + +static const size_t kMaxUdpSize = 1280; + +static void *FdToHandle(int aFd) +{ + return reinterpret_cast(aFd); +} + +static int FdFromHandle(void *aHandle) +{ + return reinterpret_cast(aHandle); +} + +static bool IsLinkLocal(const struct in6_addr &aAddress) +{ + return aAddress.s6_addr[0] == 0xfe && aAddress.s6_addr[1] == 0x80; +} + +static bool IsMulticast(const struct in6_addr &aAddress) +{ + return aAddress.s6_addr[0] == 0xff; +} + +static otError transmitPacket(int aFd, uint8_t *aPayload, uint16_t aLength, const otMessageInfo &aMessageInfo) +{ + struct sockaddr_in6 peerAddr; + uint8_t control[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))]; + ssize_t controlLength = 0; + struct iovec iov; + struct msghdr msg; + struct cmsghdr * cmsg; + ssize_t rval; + + memset(&peerAddr, 0, sizeof(peerAddr)); + peerAddr.sin6_port = htons(aMessageInfo.mPeerPort); + peerAddr.sin6_family = AF_INET6; + memcpy(&peerAddr.sin6_addr, &aMessageInfo.mPeerAddr, sizeof(peerAddr.sin6_addr)); + + if (IsLinkLocal(peerAddr.sin6_addr) && aMessageInfo.mInterfaceId == OT_NETIF_INTERFACE_ID_THREAD) + { + // sin6_scope_id only works for link local destinations + peerAddr.sin6_scope_id = sPlatNetifIndex; + } + + memset(control, 0, sizeof(control)); + + iov.iov_base = aPayload; + iov.iov_len = aLength; + + msg.msg_name = &peerAddr; + msg.msg_namelen = sizeof(peerAddr); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + + cmsg = CMSG_FIRSTHDR(&msg); + + { + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_HOPLIMIT; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + *reinterpret_cast(CMSG_DATA(cmsg)) = (aMessageInfo.mHopLimit ? aMessageInfo.mHopLimit : -1); + + cmsg = CMSG_NXTHDR(&msg, cmsg); + controlLength += CMSG_SPACE(sizeof(int)); + } + + { + struct in6_pktinfo *pktinfo = NULL; + + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo)); + + pktinfo = reinterpret_cast(CMSG_DATA(cmsg)); + pktinfo->ipi6_ifindex = (aMessageInfo.mInterfaceId == OT_NETIF_INTERFACE_ID_THREAD ? sPlatNetifIndex : 0); + + if (!IsMulticast(reinterpret_cast(aMessageInfo.mSockAddr)) && + memcmp(&aMessageInfo.mSockAddr, &in6addr_any, sizeof(aMessageInfo.mSockAddr))) + { + memcpy(&pktinfo->ipi6_addr, &aMessageInfo.mSockAddr, sizeof(pktinfo->ipi6_addr)); + } + + if (pktinfo->ipi6_ifindex || memcmp(&pktinfo->ipi6_addr, &in6addr_any, sizeof(pktinfo->ipi6_addr))) + { + controlLength += CMSG_SPACE(sizeof(*pktinfo)); + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + } + + msg.msg_controllen = controlLength; + + rval = sendmsg(aFd, &msg, 0); + VerifyOrExit(rval > 0, perror("sendmsg")); + +exit: + return rval > 0 ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +static otError receivePacket(int aFd, uint8_t *aPayload, uint16_t &aLength, otMessageInfo &aMessageInfo) +{ + struct sockaddr_in6 peerAddr; + uint8_t control[kMaxUdpSize]; + struct iovec iov; + struct msghdr msg; + ssize_t rval; + + iov.iov_base = aPayload; + iov.iov_len = aLength; + + msg.msg_name = &peerAddr; + msg.msg_namelen = sizeof(peerAddr); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + + rval = recvmsg(aFd, &msg, 0); + VerifyOrExit(rval > 0, perror("recvmsg")); + aLength = static_cast(rval); + + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == IPPROTO_IPV6) + { + if (cmsg->cmsg_type == IPV6_HOPLIMIT) + { + int hoplimit = *reinterpret_cast(CMSG_DATA(cmsg)); + aMessageInfo.mHopLimit = hoplimit; + } + else if (cmsg->cmsg_type == IPV6_PKTINFO) + { + struct in6_pktinfo *pktinfo; + + pktinfo = reinterpret_cast(CMSG_DATA(cmsg)); + memcpy(&aMessageInfo.mSockAddr, &pktinfo->ipi6_addr, sizeof(aMessageInfo.mSockAddr)); + } + } + } + + aMessageInfo.mPeerPort = ntohs(peerAddr.sin6_port); + memcpy(&aMessageInfo.mPeerAddr, &peerAddr.sin6_addr, sizeof(aMessageInfo.mPeerAddr)); + +exit: + return rval > 0 ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +otError otPlatUdpSocket(otUdpSocket *aUdpSocket) +{ + otError error = OT_ERROR_NONE; + int fd; + + assert(aUdpSocket->mHandle == NULL); + + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + VerifyOrExit(fd > 0, error = OT_ERROR_FAILED); + + aUdpSocket->mHandle = FdToHandle(fd); + +exit: + return error; +} + +otError otPlatUdpClose(otUdpSocket *aUdpSocket) +{ + otError error = OT_ERROR_NONE; + int fd; + + VerifyOrExit(aUdpSocket->mHandle != NULL, error = OT_ERROR_INVALID_ARGS); + fd = FdFromHandle(aUdpSocket->mHandle); + VerifyOrExit(0 == close(fd), error = OT_ERROR_FAILED); + + aUdpSocket->mHandle = NULL; + +exit: + return error; +} + +otError otPlatUdpBind(otUdpSocket *aUdpSocket) +{ + otError error = OT_ERROR_NONE; + int fd; + + assert(sPlatNetifIndex != 0); + assert(aUdpSocket->mHandle != NULL); + VerifyOrExit(sPlatNetifIndex != 0, error = OT_ERROR_INVALID_STATE); + VerifyOrExit(aUdpSocket->mHandle != NULL, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aUdpSocket->mSockName.mPort != 0, error = OT_ERROR_INVALID_ARGS); + fd = FdFromHandle(aUdpSocket->mHandle); + + { + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(struct sockaddr_in6)); + sin6.sin6_port = htons(aUdpSocket->mSockName.mPort); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &aUdpSocket->mSockName.mAddress, sizeof(sin6.sin6_addr)); + VerifyOrExit(0 == bind(fd, reinterpret_cast(&sin6), sizeof(sin6)), error = OT_ERROR_FAILED); + } + + { + int on = 1; + VerifyOrExit(0 == setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)), error = OT_ERROR_FAILED); + VerifyOrExit(0 == setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)), error = OT_ERROR_FAILED); + } + + VerifyOrExit(0 == setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &sPlatNetifIndex, sizeof(sPlatNetifIndex)), + error = OT_ERROR_FAILED); + +exit: + if (error == OT_ERROR_FAILED) + { + perror("otPlatUdpBind"); + } + + return error; +} + +otError otPlatUdpConnect(otUdpSocket *aUdpSocket) +{ + otError error = OT_ERROR_NONE; + struct sockaddr_in6 sin6; + int fd; + + VerifyOrExit(aUdpSocket->mHandle != NULL, error = OT_ERROR_INVALID_ARGS); + + fd = FdFromHandle(aUdpSocket->mHandle); + + memset(&sin6, 0, sizeof(struct sockaddr_in6)); + sin6.sin6_port = htons(aUdpSocket->mPeerName.mPort); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &aUdpSocket->mPeerName.mAddress, sizeof(sin6.sin6_addr)); + + VerifyOrExit(0 == connect(fd, reinterpret_cast(&sin6), sizeof(sin6)), error = OT_ERROR_FAILED); + +exit: + return error; +} + +otError otPlatUdpSend(otUdpSocket *aUdpSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + otError error = OT_ERROR_NONE; + int fd; + + VerifyOrExit(aUdpSocket->mHandle != NULL, error = OT_ERROR_INVALID_ARGS); + fd = FdFromHandle(aUdpSocket->mHandle); + + { + uint8_t payload[kMaxUdpSize]; + uint16_t len = otMessageGetLength(aMessage); + + VerifyOrExit(len == otMessageRead(aMessage, 0, payload, len), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = transmitPacket(fd, payload, len, *aMessageInfo)); + } + +exit: + if (error == OT_ERROR_NONE) + { + otMessageFree(aMessage); + } + + return error; +} + +void platformUdpUpdateFdSet(otInstance *aInstance, fd_set *aReadFdSet, int *aMaxFd) +{ + VerifyOrExit(sPlatNetifIndex != 0); + + for (otUdpSocket *socket = otUdpGetSockets(aInstance); socket != NULL; socket = socket->mNext) + { + int fd; + + if (socket->mHandle == NULL) + { + continue; + } + + fd = FdFromHandle(socket->mHandle); + FD_SET(fd, aReadFdSet); + + if (aMaxFd != NULL && *aMaxFd < fd) + { + *aMaxFd = fd; + } + } + +exit: + return; +} + +void platformUdpInit(void) +{ + const char *platformNetif = getenv("PLATFORM_NETIF"); + sPlatNetifIndex = if_nametoindex(platformNetif); + + if (sPlatNetifIndex == 0) + { + perror("if_nametoindex"); + } +} + +void platformUdpProcess(otInstance *aInstance, const fd_set *aReadFdSet) +{ + VerifyOrExit(sPlatNetifIndex != 0); + + for (otUdpSocket *socket = otUdpGetSockets(aInstance); socket != NULL; socket = socket->mNext) + { + int fd = FdFromHandle(socket->mHandle); + + if (fd > 0 && FD_ISSET(fd, aReadFdSet)) + { + otMessageInfo messageInfo; + otMessage * message = NULL; + uint8_t payload[kMaxUdpSize]; + uint16_t length = sizeof(payload); + + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.mSockPort = socket->mSockName.mPort; + messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD; + + if (OT_ERROR_NONE != receivePacket(fd, payload, length, messageInfo)) + { + continue; + } + + message = otUdpNewMessage(aInstance, false); + + if (message == NULL) + { + continue; + } + + if (otMessageAppend(message, payload, length) != OT_ERROR_NONE) + { + otMessageFree(message); + continue; + } + + socket->mHandler(socket->mContext, message, &messageInfo); + otMessageFree(message); + // only process one socket a time + break; + } + } + +exit: + return; +}