diff --git a/src/inet/DNSResolver.cpp b/src/inet/DNSResolver.cpp index 92ab09255f41c1..e80cc5c09ab8a1 100644 --- a/src/inet/DNSResolver.cpp +++ b/src/inet/DNSResolver.cpp @@ -53,84 +53,11 @@ namespace Inet { chip::System::ObjectPool DNSResolver::sPool; -/** - * This method revolves a host name into a list of IP addresses. - * - * @note - * Even if the operation completes successfully, - * the result might be a zero-length list of IP addresses. - * Most of the error generated are returned via the - * application callback. - * - * @param[in] hostName A pointer to a C string representing the host name - * to be queried. - * @param[in] hostNameLen The string length of host name. - * @param[in] maxAddrs The maximum number of addresses to store in the DNS - * table. - * @param[in] options An integer value controlling how host name address - * resolution is performed. Values are from the #DNSOptions - * enumeration. - * @param[in] addrArray A pointer to the DNS table. - * @param[in] onComplete A pointer to the callback function when a DNS - * request is complete. - * @param[in] appState A pointer to the application state to be passed to - * onComplete when a DNS request is complete. - * - * @retval CHIP_NO_ERROR if a DNS request is handled - * successfully. - * - * @retval CHIP_ERROR_NOT_IMPLEMENTED if DNS resolution is not enabled on - * the underlying platform. - * - * @retval _other_ if other POSIX network or OS error - * was returned by the underlying DNS - * resolver implementation. - * - */ -CHIP_ERROR DNSResolver::Resolve(const char * hostName, uint16_t hostNameLen, uint8_t options, uint8_t maxAddrs, - IPAddress * addrArray, DNSResolver::OnResolveCompleteFunct onComplete, void * appState) -{ - CHIP_ERROR res = CHIP_NO_ERROR; - -#if !CHIP_SYSTEM_CONFIG_USE_SOCKETS && !LWIP_DNS - Release(); - return CHIP_ERROR_NOT_IMPLEMENTED; -#endif // !CHIP_SYSTEM_CONFIG_USE_SOCKETS && !LWIP_DNS - - uint8_t addrFamilyOption = (options & kDNSOption_AddrFamily_Mask); - uint8_t optionFlags = (options & kDNSOption_Flags_Mask); - - // Check that the supplied options are valid. - if ((addrFamilyOption != kDNSOption_AddrFamily_Any -#if INET_CONFIG_ENABLE_IPV4 - && addrFamilyOption != kDNSOption_AddrFamily_IPv4Only && addrFamilyOption != kDNSOption_AddrFamily_IPv4Preferred -#endif - && addrFamilyOption != kDNSOption_AddrFamily_IPv6Only && addrFamilyOption != kDNSOption_AddrFamily_IPv6Preferred) || - (optionFlags & ~kDNSOption_ValidFlags) != 0) - { - Release(); - return CHIP_ERROR_INVALID_ARGUMENT; - } - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS || (CHIP_SYSTEM_CONFIG_USE_LWIP && LWIP_DNS) - - // TODO: Eliminate the need for a local buffer when running on LwIP by changing - // the LwIP DNS interface to support non-nul terminated strings. - - char hostNameBuf[NL_DNS_HOSTNAME_MAX_LEN + 1]; // DNS limits hostnames to 253 max characters. - - memcpy(hostNameBuf, hostName, hostNameLen); - hostNameBuf[hostNameLen] = 0; - - AppState = appState; - AddrArray = addrArray; - MaxAddrs = maxAddrs; - NumAddrs = 0; - DNSOptions = options; - OnComplete = onComplete; - #if CHIP_SYSTEM_CONFIG_USE_LWIP +CHIP_ERROR DNSResolver::ResolveImpl(char * hostNameBuf) +{ +#if LWIP_DNS #if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 u8_t lwipAddrType; @@ -206,47 +133,14 @@ CHIP_ERROR DNSResolver::Resolve(const char * hostName, uint16_t hostNameLen, uin } return res; - -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - struct addrinfo gaiHints; - struct addrinfo * gaiResults = nullptr; - int gaiReturnCode; - - // Configure the hints argument for getaddrinfo() - InitAddrInfoHints(gaiHints); - - // Call getaddrinfo() to perform the name resolution. - gaiReturnCode = getaddrinfo(hostNameBuf, nullptr, &gaiHints, &gaiResults); - - // Process the return code and results list returned by getaddrinfo(). If the call - // was successful this will copy the resultant addresses into the caller's array. - res = ProcessGetAddrInfoResult(gaiReturnCode, gaiResults); - - // Invoke the caller's completion function. - onComplete(appState, res, NumAddrs, addrArray); - - // Release DNSResolver object. +#else // LWIP_DNS Release(); - - return CHIP_NO_ERROR; - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS || (CHIP_SYSTEM_CONFIG_USE_LWIP && LWIP_DNS) + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif // LWIP_DNS } -/** - * This method cancels DNS requests that are in progress. - * - * @retval CHIP_NO_ERROR. - * - */ CHIP_ERROR DNSResolver::Cancel() { -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // NOTE: LwIP does not support canceling DNS requests that are in progress. As a consequence, // we can't release the DNSResolver object until LwIP calls us back (because LwIP retains a // pointer to the DNSResolver object while the request is active). However, now that the @@ -270,26 +164,9 @@ CHIP_ERROR DNSResolver::Cancel() // Unlock LwIP stack UNLOCK_TCPIP_CORE(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS -#if INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS - // NOTE: DNS lookups can be canceled only when using the asynchronous mode. - - InetLayer & inet = Layer(); - - OnComplete = nullptr; - AppState = nullptr; - inet.mAsyncDNSResolver.Cancel(*this); - -#endif // INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - return CHIP_NO_ERROR; } -#if CHIP_SYSTEM_CONFIG_USE_LWIP - /** * This method is called by InetLayer on success, failure, or timeout of a * DNS request. @@ -347,6 +224,47 @@ void DNSResolver::LwIPHandleResolveComplete(const char * name, ip_addr_t * ipadd #if CHIP_SYSTEM_CONFIG_USE_SOCKETS +CHIP_ERROR DNSResolver::ResolveImpl(char * hostNameBuf) +{ + struct addrinfo gaiHints; + struct addrinfo * gaiResults = nullptr; + int gaiReturnCode; + + // Configure the hints argument for getaddrinfo() + InitAddrInfoHints(gaiHints); + + // Call getaddrinfo() to perform the name resolution. + gaiReturnCode = getaddrinfo(hostNameBuf, nullptr, &gaiHints, &gaiResults); + + // Process the return code and results list returned by getaddrinfo(). If the call + // was successful this will copy the resultant addresses into the caller's array. + CHIP_ERROR res = ProcessGetAddrInfoResult(gaiReturnCode, gaiResults); + + // Invoke the caller's completion function. + OnComplete(AppState, res, NumAddrs, AddrArray); + + // Release DNSResolver object. + Release(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DNSResolver::Cancel() +{ +#if INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS + // NOTE: DNS lookups can be canceled only when using the asynchronous mode. + + InetLayer & inet = Layer(); + + OnComplete = nullptr; + AppState = nullptr; + inet.mAsyncDNSResolver.Cancel(*this); + +#endif // INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS + + return CHIP_NO_ERROR; +} + void DNSResolver::InitAddrInfoHints(struct addrinfo & hints) { memset(&hints, 0, sizeof(hints)); @@ -523,7 +441,6 @@ uint8_t DNSResolver::CountAddresses(int family, const struct addrinfo * addrs) } #if INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS - void DNSResolver::HandleAsyncResolveComplete() { // Copy the resolved address to the application supplied buffer, but only if the request hasn't been canceled. @@ -535,7 +452,41 @@ void DNSResolver::HandleAsyncResolveComplete() Release(); } #endif // INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS + #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS +CHIP_ERROR DNSResolver::Resolve(const char * hostName, uint16_t hostNameLen, uint8_t options, uint8_t maxAddrs, + IPAddress * addrArray, DNSResolver::OnResolveCompleteFunct onComplete, void * appState) +{ + uint8_t addrFamilyOption = (options & kDNSOption_AddrFamily_Mask); + uint8_t optionFlags = (options & kDNSOption_Flags_Mask); + + // Check that the supplied options are valid. + if ((addrFamilyOption != kDNSOption_AddrFamily_Any +#if INET_CONFIG_ENABLE_IPV4 + && addrFamilyOption != kDNSOption_AddrFamily_IPv4Only && addrFamilyOption != kDNSOption_AddrFamily_IPv4Preferred +#endif + && addrFamilyOption != kDNSOption_AddrFamily_IPv6Only && addrFamilyOption != kDNSOption_AddrFamily_IPv6Preferred) || + (optionFlags & ~kDNSOption_ValidFlags) != 0) + { + Release(); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + char hostNameBuf[NL_DNS_HOSTNAME_MAX_LEN + 1]; // DNS limits hostnames to 253 max characters. + + memcpy(hostNameBuf, hostName, hostNameLen); + hostNameBuf[hostNameLen] = 0; + + AppState = appState; + AddrArray = addrArray; + MaxAddrs = maxAddrs; + NumAddrs = 0; + DNSOptions = options; + OnComplete = onComplete; + + return ResolveImpl(hostNameBuf); +} + } // namespace Inet } // namespace chip diff --git a/src/inet/DNSResolver.h b/src/inet/DNSResolver.h index 6975dcc337c3fa..6d1edf98533d48 100644 --- a/src/inet/DNSResolver.h +++ b/src/inet/DNSResolver.h @@ -103,6 +103,44 @@ class DNSResolver : public InetLayerBasis */ typedef void (*OnResolveCompleteFunct)(void * appState, CHIP_ERROR err, uint8_t addrCount, IPAddress * addrArray); + /** + * This method revolves a host name into a list of IP addresses. + * + * @note + * Even if the operation completes successfully, the result might be a zero-length list of IP addresses. + * Most of the error generated are returned via the application callback. + * + * @param[in] hostName A pointer to a C string representing the host name + * to be queried. + * @param[in] hostNameLen The string length of host name. + * @param[in] maxAddrs The maximum number of addresses to store in the DNS + * table. + * @param[in] options An integer value controlling how host name address + * resolution is performed. Values are from the #DNSOptions + * enumeration. + * @param[in] addrArray A pointer to the DNS table. + * @param[in] onComplete A pointer to the callback function when a DNS + * request is complete. + * @param[in] appState A pointer to the application state to be passed to + * onComplete when a DNS request is complete. + * + * @retval CHIP_NO_ERROR if a DNS request is handled + * successfully. + * @retval CHIP_ERROR_NOT_IMPLEMENTED if DNS resolution is not enabled on + * the underlying platform. + * @retval _other_ if other POSIX network or OS error + * was returned by the underlying DNS + * resolver implementation. + * + */ + CHIP_ERROR Resolve(const char * hostName, uint16_t hostNameLen, uint8_t options, uint8_t maxAddrs, IPAddress * addrArray, + OnResolveCompleteFunct onComplete, void * appState); + + /** + * This method cancels DNS requests that are in progress. + */ + CHIP_ERROR Cancel(); + static chip::System::ObjectPool sPool; /** @@ -130,6 +168,8 @@ class DNSResolver : public InetLayerBasis */ uint8_t DNSOptions; + CHIP_ERROR ResolveImpl(char * hostNameBuf); + #if CHIP_SYSTEM_CONFIG_USE_SOCKETS void InitAddrInfoHints(struct addrinfo & hints); @@ -154,10 +194,6 @@ class DNSResolver : public InetLayerBasis #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - CHIP_ERROR Resolve(const char * hostName, uint16_t hostNameLen, uint8_t options, uint8_t maxAddrs, IPAddress * addrArray, - OnResolveCompleteFunct onComplete, void * appState); - CHIP_ERROR Cancel(); - #if CHIP_SYSTEM_CONFIG_USE_LWIP void HandleResolveComplete(void); #if LWIP_VERSION_MAJOR > 1 diff --git a/src/inet/EndPointBasis.cpp b/src/inet/EndPointBasis.cpp index 63b233b5e849f5..a3e5d54486bc7f 100644 --- a/src/inet/EndPointBasis.cpp +++ b/src/inet/EndPointBasis.cpp @@ -30,20 +30,14 @@ namespace chip { namespace Inet { +#if CHIP_SYSTEM_CONFIG_USE_LWIP + void EndPointBasis::InitEndPointBasis(InetLayer & aInetLayer, void * aAppState) { InitInetLayerBasis(aInetLayer, aAppState); - -#if CHIP_SYSTEM_CONFIG_USE_LWIP mLwIPEndPointType = LwIPEndPointType::Unknown; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - mSocket = kInvalidSocketFd; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS } -#if CHIP_SYSTEM_CONFIG_USE_LWIP void EndPointBasis::DeferredFree(System::Object::ReleaseDeferralErrorTactic aTactic) { if (!CHIP_SYSTEM_CONFIG_USE_SOCKETS || (mVoid != nullptr)) @@ -55,7 +49,37 @@ void EndPointBasis::DeferredFree(System::Object::ReleaseDeferralErrorTactic aTac Release(); } } + #endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#if CHIP_SYSTEM_CONFIG_USE_SOCKETS + +void EndPointBasis::InitEndPointBasis(InetLayer & aInetLayer, void * aAppState) +{ + InitInetLayerBasis(aInetLayer, aAppState); + mSocket = kInvalidSocketFd; +} + +void EndPointBasis::DeferredFree(System::Object::ReleaseDeferralErrorTactic aTactic) +{ + Release(); +} + +#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + +#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK + +void EndPointBasis::InitEndPointBasis(InetLayer & aInetLayer, void * aAppState) +{ + InitInetLayerBasis(aInetLayer, aAppState); +} + +void EndPointBasis::DeferredFree(System::Object::ReleaseDeferralErrorTactic aTactic) +{ + Release(); +} + +#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK + } // namespace Inet } // namespace chip diff --git a/src/inet/EndPointBasis.h b/src/inet/EndPointBasis.h index f34db99496d996..154d32aa132447 100644 --- a/src/inet/EndPointBasis.h +++ b/src/inet/EndPointBasis.h @@ -54,6 +54,7 @@ class DLL_EXPORT EndPointBasis : public InetLayerBasis { protected: void InitEndPointBasis(InetLayer & aInetLayer, void * aAppState = nullptr); + void DeferredFree(System::Object::ReleaseDeferralErrorTactic aTactic); #if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK nw_parameters_t mParameters; @@ -88,8 +89,6 @@ class DLL_EXPORT EndPointBasis : public InetLayerBasis UCP = 3, TCP = 4 } mLwIPEndPointType; - - void DeferredFree(System::Object::ReleaseDeferralErrorTactic aTactic); #endif // CHIP_SYSTEM_CONFIG_USE_LWIP }; diff --git a/src/inet/IPEndPointBasis.cpp b/src/inet/IPEndPointBasis.cpp index 49ed37d400de07..42762d86bac403 100644 --- a/src/inet/IPEndPointBasis.cpp +++ b/src/inet/IPEndPointBasis.cpp @@ -49,6 +49,31 @@ #include #include #include + +#if INET_CONFIG_ENABLE_IPV4 +#define LWIP_IPV4_ADDR_T ip4_addr_t +#define IPV4_TO_LWIPADDR(aAddress) (aAddress).ToIPv4() +#endif // INET_CONFIG_ENABLE_IPV4 +#define LWIP_IPV6_ADDR_T ip6_addr_t +#define IPV6_TO_LWIPADDR(aAddress) (aAddress).ToIPv6() + +#if !defined(RAW_FLAGS_MULTICAST_LOOP) || !defined(UDP_FLAGS_MULTICAST_LOOP) || !defined(raw_clear_flags) || \ + !defined(raw_set_flags) || !defined(udp_clear_flags) || !defined(udp_set_flags) +#define HAVE_LWIP_MULTICAST_LOOP 0 +#else +#define HAVE_LWIP_MULTICAST_LOOP 1 +#endif // !defined(RAW_FLAGS_MULTICAST_LOOP) || !defined(UDP_FLAGS_MULTICAST_LOOP) || !defined(raw_clear_flags) || + // !defined(raw_set_flags) || !defined(udp_clear_flags) || !defined(udp_set_flags) + +// unusual define check for LWIP_IPV6_ND is because espressif fork +// of LWIP does not define the _ND constant. +#if LWIP_IPV6_MLD && (!defined(LWIP_IPV6_ND) || LWIP_IPV6_ND) && LWIP_IPV6 +#define HAVE_IPV6_MULTICAST +#else +// Within Project CHIP multicast support is highly desirable: used for mDNS +// as well as group communication. +#undef HAVE_IPV6_MULTICAST +#endif #endif // CHIP_SYSTEM_CONFIG_USE_LWIP #if CHIP_SYSTEM_CONFIG_USE_SOCKETS @@ -86,50 +111,19 @@ #error \ "Neither IPV6_DROP_MEMBERSHIP nor IPV6_LEAVE_GROUP are defined which are required for generalized IPv6 multicast group support." #endif // IPV6_DROP_MEMBERSHIP + +#if CHIP_SYSTEM_CONFIG_USE_ZEPHYR_SOCKET_EXTENSIONS +#include "ZephyrSocket.h" +#endif // CHIP_SYSTEM_CONFIG_USE_ZEPHYR_SOCKET_EXTENSIONS #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS #if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK #define INET_PORTSTRLEN 6 #endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK -#if CHIP_SYSTEM_CONFIG_USE_ZEPHYR_SOCKET_EXTENSIONS -#include "ZephyrSocket.h" -#endif // CHIP_SYSTEM_CONFIG_USE_ZEPHYR_SOCKET_EXTENSIONS - namespace chip { namespace Inet { -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS -union PeerSockAddr -{ - sockaddr any; - sockaddr_in in; - sockaddr_in6 in6; -}; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API -IPEndPointBasis::JoinMulticastGroupHandler IPEndPointBasis::sJoinMulticastGroupHandler; -IPEndPointBasis::LeaveMulticastGroupHandler IPEndPointBasis::sLeaveMulticastGroupHandler; -#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API - -#if CHIP_SYSTEM_CONFIG_USE_LWIP -#if INET_CONFIG_ENABLE_IPV4 -#define LWIP_IPV4_ADDR_T ip4_addr_t -#define IPV4_TO_LWIPADDR(aAddress) (aAddress).ToIPv4() -#endif // INET_CONFIG_ENABLE_IPV4 -#define LWIP_IPV6_ADDR_T ip6_addr_t -#define IPV6_TO_LWIPADDR(aAddress) (aAddress).ToIPv6() - -#if !defined(RAW_FLAGS_MULTICAST_LOOP) || !defined(UDP_FLAGS_MULTICAST_LOOP) || !defined(raw_clear_flags) || \ - !defined(raw_set_flags) || !defined(udp_clear_flags) || !defined(udp_set_flags) -#define HAVE_LWIP_MULTICAST_LOOP 0 -#else -#define HAVE_LWIP_MULTICAST_LOOP 1 -#endif // !defined(RAW_FLAGS_MULTICAST_LOOP) || !defined(UDP_FLAGS_MULTICAST_LOOP) || !defined(raw_clear_flags) || - // !defined(raw_set_flags) || !defined(udp_clear_flags) || !defined(udp_set_flags) -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - #if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS static CHIP_ERROR CheckMulticastGroupArgs(InterfaceId aInterfaceId, const IPAddress & aAddress) { @@ -142,6 +136,7 @@ static CHIP_ERROR CheckMulticastGroupArgs(InterfaceId aInterfaceId, const IPAddr #endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS #if CHIP_SYSTEM_CONFIG_USE_LWIP + #if INET_CONFIG_ENABLE_IPV4 #if LWIP_IPV4 && LWIP_IGMP static CHIP_ERROR LwIPIPv4JoinLeaveMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress, @@ -162,16 +157,6 @@ static CHIP_ERROR LwIPIPv4JoinLeaveMulticastGroup(InterfaceId aInterfaceId, cons #endif // LWIP_IPV4 && LWIP_IGMP #endif // INET_CONFIG_ENABLE_IPV4 -// unusual define check for LWIP_IPV6_ND is because espressif fork -// of LWIP does not define the _ND constant. -#if LWIP_IPV6_MLD && (!defined(LWIP_IPV6_ND) || LWIP_IPV6_ND) && LWIP_IPV6 -#define HAVE_IPV6_MULTICAST -#else -// Within Project CHIP multicast support is highly desirable: used for mDNS -// as well as group communication. -#undef HAVE_IPV6_MULTICAST -#endif - #ifdef HAVE_IPV6_MULTICAST static CHIP_ERROR LwIPIPv6JoinLeaveMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress, err_t (*aMethod)(struct netif *, const LWIP_IPV6_ADDR_T *)) @@ -189,153 +174,11 @@ static CHIP_ERROR LwIPIPv6JoinLeaveMulticastGroup(InterfaceId aInterfaceId, cons return chip::System::MapErrorLwIP(lStatus); } #endif // LWIP_IPV6_MLD && LWIP_IPV6_ND && LWIP_IPV6 -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS -#if IP_MULTICAST_LOOP || IPV6_MULTICAST_LOOP -static CHIP_ERROR SocketsSetMulticastLoopback(int aSocket, bool aLoopback, int aProtocol, int aOption) -{ - const unsigned int lValue = aLoopback; - if (setsockopt(aSocket, aProtocol, aOption, &lValue, sizeof(lValue)) != 0) - { - return CHIP_ERROR_POSIX(errno); - } - - return CHIP_NO_ERROR; -} -#endif // IP_MULTICAST_LOOP || IPV6_MULTICAST_LOOP - -static CHIP_ERROR SocketsSetMulticastLoopback(int aSocket, IPVersion aIPVersion, bool aLoopback) -{ -#ifdef IPV6_MULTICAST_LOOP - CHIP_ERROR lRetval; - - switch (aIPVersion) - { - - case kIPVersion_6: - lRetval = SocketsSetMulticastLoopback(aSocket, aLoopback, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); - break; - -#if INET_CONFIG_ENABLE_IPV4 - case kIPVersion_4: - lRetval = SocketsSetMulticastLoopback(aSocket, aLoopback, IPPROTO_IP, IP_MULTICAST_LOOP); - break; -#endif // INET_CONFIG_ENABLE_IPV4 - - default: - lRetval = INET_ERROR_WRONG_ADDRESS_TYPE; - break; - } - - return (lRetval); -#else // IPV6_MULTICAST_LOOP - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif // IPV6_MULTICAST_LOOP -} - -#if INET_CONFIG_ENABLE_IPV4 -static CHIP_ERROR SocketsIPv4JoinLeaveMulticastGroup(int aSocket, InterfaceId aInterfaceId, const IPAddress & aAddress, - int aCommand) -{ - IPAddress lInterfaceAddress; - bool lInterfaceAddressFound = false; - - for (InterfaceAddressIterator lAddressIterator; lAddressIterator.HasCurrent(); lAddressIterator.Next()) - { - const IPAddress lCurrentAddress = lAddressIterator.GetAddress(); - - if (lAddressIterator.GetInterface() == aInterfaceId) - { - if (lCurrentAddress.IsIPv4()) - { - lInterfaceAddressFound = true; - lInterfaceAddress = lCurrentAddress; - break; - } - } - } - - VerifyOrReturnError(lInterfaceAddressFound, INET_ERROR_ADDRESS_NOT_FOUND); - - struct ip_mreq lMulticastRequest; - memset(&lMulticastRequest, 0, sizeof(lMulticastRequest)); - lMulticastRequest.imr_interface = lInterfaceAddress.ToIPv4(); - lMulticastRequest.imr_multiaddr = aAddress.ToIPv4(); - - if (setsockopt(aSocket, IPPROTO_IP, aCommand, &lMulticastRequest, sizeof(lMulticastRequest)) != 0) - { - return CHIP_ERROR_POSIX(errno); - } - return CHIP_NO_ERROR; -} -#endif // INET_CONFIG_ENABLE_IPV4 - -#if INET_IPV6_ADD_MEMBERSHIP || INET_IPV6_DROP_MEMBERSHIP -static CHIP_ERROR SocketsIPv6JoinLeaveMulticastGroup(int aSocket, InterfaceId aInterfaceId, const IPAddress & aAddress, - int aCommand) -{ - VerifyOrReturnError(CanCastTo(aInterfaceId), CHIP_ERROR_UNEXPECTED_EVENT); - const unsigned int lIfIndex = static_cast(aInterfaceId); - - struct ipv6_mreq lMulticastRequest; - memset(&lMulticastRequest, 0, sizeof(lMulticastRequest)); - VerifyOrReturnError(CanCastTo(lIfIndex), CHIP_ERROR_UNEXPECTED_EVENT); - - lMulticastRequest.ipv6mr_interface = static_cast(lIfIndex); - lMulticastRequest.ipv6mr_multiaddr = aAddress.ToIPv6(); - - if (setsockopt(aSocket, IPPROTO_IPV6, aCommand, &lMulticastRequest, sizeof(lMulticastRequest)) != 0) - { - return CHIP_ERROR_POSIX(errno); - } - return CHIP_NO_ERROR; -} -#endif // INET_IPV6_ADD_MEMBERSHIP || INET_IPV6_DROP_MEMBERSHIP - -static CHIP_ERROR SocketsIPv6JoinMulticastGroup(int aSocket, InterfaceId aInterfaceId, const IPAddress & aAddress) -{ -#if INET_IPV6_ADD_MEMBERSHIP - return SocketsIPv6JoinLeaveMulticastGroup(aSocket, aInterfaceId, aAddress, INET_IPV6_ADD_MEMBERSHIP); -#else - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif -} - -static CHIP_ERROR SocketsIPv6LeaveMulticastGroup(int aSocket, InterfaceId aInterfaceId, const IPAddress & aAddress) -{ -#if INET_IPV6_DROP_MEMBERSHIP - return SocketsIPv6JoinLeaveMulticastGroup(aSocket, aInterfaceId, aAddress, INET_IPV6_DROP_MEMBERSHIP); -#else - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif -} - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -/** - * @brief Set whether IP multicast traffic should be looped back. - * - * @param[in] aIPVersion - * - * @param[in] aLoopback - * - * @retval CHIP_NO_ERROR - * success: multicast loopback behavior set - * @retval other - * another system or platform error - * - * @details - * Set whether or not IP multicast traffic should be looped back - * to this endpoint. - * - */ CHIP_ERROR IPEndPointBasis::SetMulticastLoopback(IPVersion aIPVersion, bool aLoopback) { CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; -#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS -#if CHIP_SYSTEM_CONFIG_USE_LWIP #if !HAVE_LWIP_MULTICAST_LOOP lRetval = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; #else @@ -368,218 +211,39 @@ CHIP_ERROR IPEndPointBasis::SetMulticastLoopback(IPVersion aIPVersion, bool aLoo default: lRetval = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; - break; - } - } - - lRetval = CHIP_NO_ERROR; -#endif // !HAVE_LWIP_MULTICAST_LOOP -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - lRetval = SocketsSetMulticastLoopback(mSocket, aIPVersion, aLoopback); - SuccessOrExit(lRetval); - -exit: -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS - return (lRetval); -} - -/** - * @brief Join an IP multicast group. - * - * @param[in] aInterfaceId the indicator of the network interface to - * add to the multicast group - * - * @param[in] aAddress the multicast group to add the - * interface to - * - * @retval CHIP_NO_ERROR - * success: multicast group removed - * - * @retval INET_ERROR_UNKNOWN_INTERFACE - * unknown network interface, \c aInterfaceId - * - * @retval INET_ERROR_WRONG_ADDRESS_TYPE - * \c aAddress is not \c kIPAddressType_IPv4 or - * \c kIPAddressType_IPv6 or is not multicast - * - * @retval other - * another system or platform error - * - * @details - * Join the endpoint to the supplied multicast group on the - * specified interface. - * - */ -CHIP_ERROR IPEndPointBasis::JoinMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress) -{ - CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; - -#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS - const IPAddressType lAddrType = aAddress.Type(); - lRetval = CheckMulticastGroupArgs(aInterfaceId, aAddress); - SuccessOrExit(lRetval); - - switch (lAddrType) - { - -#if INET_CONFIG_ENABLE_IPV4 - case kIPAddressType_IPv4: { -#if CHIP_SYSTEM_CONFIG_USE_LWIP -#if LWIP_IPV4 && LWIP_IGMP - lRetval = LwIPIPv4JoinLeaveMulticastGroup(aInterfaceId, aAddress, igmp_joingroup_netif); -#else // LWIP_IPV4 && LWIP_IGMP - lRetval = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif // LWIP_IPV4 && LWIP_IGMP - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - lRetval = SocketsIPv4JoinLeaveMulticastGroup(mSocket, aInterfaceId, aAddress, IP_ADD_MEMBERSHIP); - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - } - break; -#endif // INET_CONFIG_ENABLE_IPV4 - - case kIPAddressType_IPv6: { -#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API - if (sJoinMulticastGroupHandler != nullptr) - { - return sJoinMulticastGroupHandler(aInterfaceId, aAddress); - } -#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API - -#if CHIP_SYSTEM_CONFIG_USE_LWIP -#ifdef HAVE_IPV6_MULTICAST - lRetval = LwIPIPv6JoinLeaveMulticastGroup(aInterfaceId, aAddress, mld6_joingroup_netif); -#else // HAVE_IPV6_MULTICAST - lRetval = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif // HAVE_IPV6_MULTICAST - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - lRetval = SocketsIPv6JoinMulticastGroup(mSocket, aInterfaceId, aAddress); - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - } - break; - - default: - lRetval = INET_ERROR_WRONG_ADDRESS_TYPE; - break; - } - -exit: -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS - return (lRetval); -} - -/** - * @brief Leave an IP multicast group. - * - * @param[in] aInterfaceId the indicator of the network interface to - * remove from the multicast group - * - * @param[in] aAddress the multicast group to remove the - * interface from - * - * @retval CHIP_NO_ERROR - * success: multicast group removed - * - * @retval INET_ERROR_UNKNOWN_INTERFACE - * unknown network interface, \c aInterfaceId - * - * @retval INET_ERROR_WRONG_ADDRESS_TYPE - * \c aAddress is not \c kIPAddressType_IPv4 or - * \c kIPAddressType_IPv6 or is not multicast - * - * @retval other - * another system or platform error - * - * @details - * Remove the endpoint from the supplied multicast group on the - * specified interface. - * - */ -CHIP_ERROR IPEndPointBasis::LeaveMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress) -{ - CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; - -#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS - const IPAddressType lAddrType = aAddress.Type(); - lRetval = CheckMulticastGroupArgs(aInterfaceId, aAddress); - SuccessOrExit(lRetval); - - switch (lAddrType) - { - -#if INET_CONFIG_ENABLE_IPV4 - case kIPAddressType_IPv4: { -#if CHIP_SYSTEM_CONFIG_USE_LWIP -#if LWIP_IPV4 && LWIP_IGMP - lRetval = LwIPIPv4JoinLeaveMulticastGroup(aInterfaceId, aAddress, igmp_leavegroup_netif); -#else // LWIP_IPV4 && LWIP_IGMP - lRetval = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif // LWIP_IPV4 && LWIP_IGMP - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - lRetval = SocketsIPv4JoinLeaveMulticastGroup(mSocket, aInterfaceId, aAddress, IP_DROP_MEMBERSHIP); - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - } - break; -#endif // INET_CONFIG_ENABLE_IPV4 - - case kIPAddressType_IPv6: { -#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API - if (sLeaveMulticastGroupHandler != nullptr) - { - return sLeaveMulticastGroupHandler(aInterfaceId, aAddress); - } -#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API - -#if CHIP_SYSTEM_CONFIG_USE_LWIP -#if LWIP_IPV6_MLD && LWIP_IPV6_ND && LWIP_IPV6 - lRetval = LwIPIPv6JoinLeaveMulticastGroup(aInterfaceId, aAddress, mld6_leavegroup_netif); -#else // LWIP_IPV6_MLD && LWIP_IPV6_ND && LWIP_IPV6 - lRetval = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -#endif // LWIP_IPV6_MLD && LWIP_IPV6_ND && LWIP_IPV6 - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - lRetval = SocketsIPv6LeaveMulticastGroup(mSocket, aInterfaceId, aAddress); - SuccessOrExit(lRetval); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - } - break; - - default: - lRetval = INET_ERROR_WRONG_ADDRESS_TYPE; - break; + break; + } } -exit: -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_SOCKETS + lRetval = CHIP_NO_ERROR; +#endif // !HAVE_LWIP_MULTICAST_LOOP return (lRetval); } -void IPEndPointBasis::Init(InetLayer * aInetLayer) +void IPEndPointBasis::InitImpl() {} + +#if INET_CONFIG_ENABLE_IPV4 +CHIP_ERROR IPEndPointBasis::IPv4JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join) { - InitEndPointBasis(*aInetLayer); +#if LWIP_IPV4 && LWIP_IGMP + const auto method = join ? igmp_joingroup_netif : igmp_leavegroup_netif; + return LwIPIPv4JoinLeaveMulticastGroup(aInterfaceId, aAddress, method); +#else // LWIP_IPV4 && LWIP_IGMP + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +#endif // LWIP_IPV4 && LWIP_IGMP +} +#endif // INET_CONFIG_ENABLE_IPV4 -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - mBoundIntfId = INET_NULL_INTERFACEID; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS +CHIP_ERROR IPEndPointBasis::IPv6JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join) +{ +#ifdef HAVE_IPV6_MULTICAST + const auto method = join ? mld6_joingroup_netif : mld6_leavegroup_netif; + return LwIPIPv6JoinLeaveMulticastGroup(aInterfaceId, aAddress, method); +#else // HAVE_IPV6_MULTICAST + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +#endif // HAVE_IPV6_MULTICAST } -#if CHIP_SYSTEM_CONFIG_USE_LWIP void IPEndPointBasis::HandleDataReceived(System::PacketBufferHandle && aBuffer) { if ((mState == kState_Listening) && (OnMessageReceived != NULL)) @@ -661,9 +325,181 @@ CHIP_ERROR IPEndPointBasis::PostPacketBufferEvent(chip::System::LayerLwIP * aLay return error; } +struct netif * IPEndPointBasis::FindNetifFromInterfaceId(InterfaceId aInterfaceId) +{ + struct netif * lRetval = NULL; + +#if LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 0 && defined(NETIF_FOREACH) + NETIF_FOREACH(lRetval) + { + if (lRetval == aInterfaceId) + break; + } +#else // LWIP_VERSION_MAJOR < 2 || !defined(NETIF_FOREACH) + for (lRetval = netif_list; lRetval != NULL && lRetval != aInterfaceId; lRetval = lRetval->next) + ; +#endif // LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 0 && defined(NETIF_FOREACH) + + return (lRetval); +} + #endif // CHIP_SYSTEM_CONFIG_USE_LWIP #if CHIP_SYSTEM_CONFIG_USE_SOCKETS + +union PeerSockAddr +{ + sockaddr any; + sockaddr_in in; + sockaddr_in6 in6; +}; + +#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API +IPEndPointBasis::MulticastGroupHandler IPEndPointBasis::sJoinMulticastGroupHandler; +IPEndPointBasis::MulticastGroupHandler IPEndPointBasis::sLeaveMulticastGroupHandler; +#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API + +#if IP_MULTICAST_LOOP || IPV6_MULTICAST_LOOP +static CHIP_ERROR SocketsSetMulticastLoopback(int aSocket, bool aLoopback, int aProtocol, int aOption) +{ + const unsigned int lValue = aLoopback; + if (setsockopt(aSocket, aProtocol, aOption, &lValue, sizeof(lValue)) != 0) + { + return CHIP_ERROR_POSIX(errno); + } + + return CHIP_NO_ERROR; +} +#endif // IP_MULTICAST_LOOP || IPV6_MULTICAST_LOOP + +static CHIP_ERROR SocketsSetMulticastLoopback(int aSocket, IPVersion aIPVersion, bool aLoopback) +{ +#ifdef IPV6_MULTICAST_LOOP + CHIP_ERROR lRetval; + + switch (aIPVersion) + { + + case kIPVersion_6: + lRetval = SocketsSetMulticastLoopback(aSocket, aLoopback, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); + break; + +#if INET_CONFIG_ENABLE_IPV4 + case kIPVersion_4: + lRetval = SocketsSetMulticastLoopback(aSocket, aLoopback, IPPROTO_IP, IP_MULTICAST_LOOP); + break; +#endif // INET_CONFIG_ENABLE_IPV4 + + default: + lRetval = INET_ERROR_WRONG_ADDRESS_TYPE; + break; + } + + return (lRetval); +#else // IPV6_MULTICAST_LOOP + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +#endif // IPV6_MULTICAST_LOOP +} + +#if INET_CONFIG_ENABLE_IPV4 +static CHIP_ERROR SocketsIPv4JoinLeaveMulticastGroup(int aSocket, InterfaceId aInterfaceId, const IPAddress & aAddress, + int aCommand) +{ + IPAddress lInterfaceAddress; + bool lInterfaceAddressFound = false; + + for (InterfaceAddressIterator lAddressIterator; lAddressIterator.HasCurrent(); lAddressIterator.Next()) + { + const IPAddress lCurrentAddress = lAddressIterator.GetAddress(); + + if (lAddressIterator.GetInterface() == aInterfaceId) + { + if (lCurrentAddress.IsIPv4()) + { + lInterfaceAddressFound = true; + lInterfaceAddress = lCurrentAddress; + break; + } + } + } + + VerifyOrReturnError(lInterfaceAddressFound, INET_ERROR_ADDRESS_NOT_FOUND); + + struct ip_mreq lMulticastRequest; + memset(&lMulticastRequest, 0, sizeof(lMulticastRequest)); + lMulticastRequest.imr_interface = lInterfaceAddress.ToIPv4(); + lMulticastRequest.imr_multiaddr = aAddress.ToIPv4(); + + if (setsockopt(aSocket, IPPROTO_IP, aCommand, &lMulticastRequest, sizeof(lMulticastRequest)) != 0) + { + return CHIP_ERROR_POSIX(errno); + } + return CHIP_NO_ERROR; +} +#endif // INET_CONFIG_ENABLE_IPV4 + +#if INET_IPV6_ADD_MEMBERSHIP || INET_IPV6_DROP_MEMBERSHIP +static CHIP_ERROR SocketsIPv6JoinLeaveMulticastGroup(int aSocket, InterfaceId aInterfaceId, const IPAddress & aAddress, + int aCommand) +{ + VerifyOrReturnError(CanCastTo(aInterfaceId), CHIP_ERROR_UNEXPECTED_EVENT); + const unsigned int lIfIndex = static_cast(aInterfaceId); + + struct ipv6_mreq lMulticastRequest; + memset(&lMulticastRequest, 0, sizeof(lMulticastRequest)); + VerifyOrReturnError(CanCastTo(lIfIndex), CHIP_ERROR_UNEXPECTED_EVENT); + + lMulticastRequest.ipv6mr_interface = static_cast(lIfIndex); + lMulticastRequest.ipv6mr_multiaddr = aAddress.ToIPv6(); + + if (setsockopt(aSocket, IPPROTO_IPV6, aCommand, &lMulticastRequest, sizeof(lMulticastRequest)) != 0) + { + return CHIP_ERROR_POSIX(errno); + } + return CHIP_NO_ERROR; +} +#endif // INET_IPV6_ADD_MEMBERSHIP || INET_IPV6_DROP_MEMBERSHIP + +CHIP_ERROR IPEndPointBasis::SetMulticastLoopback(IPVersion aIPVersion, bool aLoopback) +{ + CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; + + lRetval = SocketsSetMulticastLoopback(mSocket, aIPVersion, aLoopback); + SuccessOrExit(lRetval); + +exit: + return (lRetval); +} + +void IPEndPointBasis::InitImpl() +{ + mBoundIntfId = INET_NULL_INTERFACEID; +} + +#if INET_CONFIG_ENABLE_IPV4 +CHIP_ERROR IPEndPointBasis::IPv4JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join) +{ + return SocketsIPv4JoinLeaveMulticastGroup(mSocket, aInterfaceId, aAddress, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP); +} +#endif // INET_CONFIG_ENABLE_IPV4 + +CHIP_ERROR IPEndPointBasis::IPv6JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join) +{ +#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API + MulticastGroupHandler handler = join ? sJoinMulticastGroupHandler : sLeaveMulticastGroupHandler; + if (handler != nullptr) + { + return handler(aInterfaceId, aAddress); + } +#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API +#if defined(INET_IPV6_ADD_MEMBERSHIP) && defined(INET_IPV6_DROP_MEMBERSHIP) + return SocketsIPv6JoinLeaveMulticastGroup(mSocket, aInterfaceId, aAddress, + join ? INET_IPV6_ADD_MEMBERSHIP : INET_IPV6_DROP_MEMBERSHIP); +#else // defined(INET_IPV6_ADD_MEMBERSHIP) && defined(INET_IPV6_DROP_MEMBERSHIP) + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +#endif // defined(INET_IPV6_ADD_MEMBERSHIP) && defined(INET_IPV6_DROP_MEMBERSHIP) +} + CHIP_ERROR IPEndPointBasis::Bind(IPAddressType aAddressType, const IPAddress & aAddress, uint16_t aPort, InterfaceId aInterfaceId) { CHIP_ERROR lRetval = CHIP_NO_ERROR; @@ -1140,9 +976,31 @@ void IPEndPointBasis::HandlePendingIO(uint16_t aPort) } } } + #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS #if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK + +CHIP_ERROR IPEndPointBasis::SetMulticastLoopback(IPVersion aIPVersion, bool aLoopback) +{ + CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; + return (lRetval); +} + +void IPEndPointBasis::InitImpl() {} + +#if INET_CONFIG_ENABLE_IPV4 +CHIP_ERROR IPEndPointBasis::IPv4JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} +#endif // INET_CONFIG_ENABLE_IPV4 + +CHIP_ERROR IPEndPointBasis::IPv6JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} + CHIP_ERROR IPEndPointBasis::ConfigureProtocol(IPAddressType aAddressType, const nw_parameters_t & aParameters) { CHIP_ERROR res = CHIP_NO_ERROR; @@ -1569,5 +1427,75 @@ CHIP_ERROR IPEndPointBasis::ReleaseConnection() #endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK +CHIP_ERROR IPEndPointBasis::JoinMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress) +{ + CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; + + const IPAddressType lAddrType = aAddress.Type(); + lRetval = CheckMulticastGroupArgs(aInterfaceId, aAddress); + SuccessOrExit(lRetval); + + switch (lAddrType) + { + +#if INET_CONFIG_ENABLE_IPV4 + case kIPAddressType_IPv4: { + return IPv4JoinLeaveMulticastGroupImpl(aInterfaceId, aAddress, true); + } + break; +#endif // INET_CONFIG_ENABLE_IPV4 + + case kIPAddressType_IPv6: { + return IPv6JoinLeaveMulticastGroupImpl(aInterfaceId, aAddress, true); + } + break; + + default: + lRetval = INET_ERROR_WRONG_ADDRESS_TYPE; + break; + } + +exit: + return (lRetval); +} + +CHIP_ERROR IPEndPointBasis::LeaveMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress) +{ + CHIP_ERROR lRetval = CHIP_ERROR_NOT_IMPLEMENTED; + + const IPAddressType lAddrType = aAddress.Type(); + lRetval = CheckMulticastGroupArgs(aInterfaceId, aAddress); + SuccessOrExit(lRetval); + + switch (lAddrType) + { + +#if INET_CONFIG_ENABLE_IPV4 + case kIPAddressType_IPv4: { + return IPv4JoinLeaveMulticastGroupImpl(aInterfaceId, aAddress, false); + } + break; +#endif // INET_CONFIG_ENABLE_IPV4 + + case kIPAddressType_IPv6: { + return IPv6JoinLeaveMulticastGroupImpl(aInterfaceId, aAddress, false); + } + break; + + default: + lRetval = INET_ERROR_WRONG_ADDRESS_TYPE; + break; + } + +exit: + return (lRetval); +} + +void IPEndPointBasis::Init(InetLayer * aInetLayer) +{ + InitEndPointBasis(*aInetLayer); + InitImpl(); +} + } // namespace Inet } // namespace chip diff --git a/src/inet/IPEndPointBasis.h b/src/inet/IPEndPointBasis.h index b3d86fb7f41e60..81869667964718 100644 --- a/src/inet/IPEndPointBasis.h +++ b/src/inet/IPEndPointBasis.h @@ -101,8 +101,36 @@ class DLL_EXPORT IPEndPointBasis : public EndPointBasis typedef void (*OnReceiveErrorFunct)(IPEndPointBasis * endPoint, CHIP_ERROR err, const IPPacketInfo * pktInfo); IPEndPointBasis() = default; + + /** + * Set whether IP multicast traffic should be looped back. + */ CHIP_ERROR SetMulticastLoopback(IPVersion aIPVersion, bool aLoopback); + + /** + * Join an IP multicast group. + * + * @param[in] aInterfaceId The indicator of the network interface to add to the multicast group. + * @param[in] aAddress The multicast group to add the interface to. + * + * @retval CHIP_NO_ERROR Success: multicast group removed. + * @retval INET_ERROR_UNKNOWN_INTERFACE Unknown network interface, \c aInterfaceId. + * @retval INET_ERROR_WRONG_ADDRESS_TYPE \c aAddress is not \c kIPv4 or \c kIPv6 or is not multicast. + * @retval other Another system or platform error. + */ CHIP_ERROR JoinMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress); + + /** + * Leave an IP multicast group. + * + * @param[in] aInterfaceId The indicator of the network interface to remove from the multicast group. + * @param[in] aAddress The multicast group to remove the interface from. + * + * @retval CHIP_NO_ERROR Success: multicast group removed + * @retval INET_ERROR_UNKNOWN_INTERFACE Unknown network interface, \c aInterfaceId + * @retval INET_ERROR_WRONG_ADDRESS_TYPE \c aAddress is not \c kIPv4 or \c kIPv6 or is not multicast. + * @retval other Another system or platform error + */ CHIP_ERROR LeaveMulticastGroup(InterfaceId aInterfaceId, const IPAddress & aAddress); protected: @@ -114,6 +142,13 @@ class DLL_EXPORT IPEndPointBasis : public EndPointBasis /** The endpoint's receive error event handling function delegate. */ OnReceiveErrorFunct OnReceiveError; +private: + IPEndPointBasis(const IPEndPointBasis &) = delete; + + void InitImpl(); + CHIP_ERROR IPv4JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join); + CHIP_ERROR IPv6JoinLeaveMulticastGroupImpl(InterfaceId aInterfaceId, const IPAddress & aAddress, bool join); + #if CHIP_SYSTEM_CONFIG_USE_LWIP public: static struct netif * FindNetifFromInterfaceId(InterfaceId aInterfaceId); @@ -127,6 +162,7 @@ class DLL_EXPORT IPEndPointBasis : public EndPointBasis #endif // CHIP_SYSTEM_CONFIG_USE_LWIP #if CHIP_SYSTEM_CONFIG_USE_SOCKETS +protected: InterfaceId mBoundIntfId; CHIP_ERROR Bind(IPAddressType aAddressType, const IPAddress & aAddress, uint16_t aPort, InterfaceId aInterfaceId); @@ -134,6 +170,18 @@ class DLL_EXPORT IPEndPointBasis : public EndPointBasis CHIP_ERROR SendMsg(const IPPacketInfo * aPktInfo, chip::System::PacketBufferHandle && aBuffer, uint16_t aSendFlags); CHIP_ERROR GetSocket(IPAddressType aAddressType, int aType, int aProtocol); void HandlePendingIO(uint16_t aPort); + +#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API +public: + using MulticastGroupHandler = CHIP_ERROR (*)(InterfaceId, const IPAddress &); + static void SetJoinMulticastGroupHandler(MulticastGroupHandler handler) { sJoinMulticastGroupHandler = handler; } + static void SetLeaveMulticastGroupHandler(MulticastGroupHandler handler) { sLeaveMulticastGroupHandler = handler; } + +private: + static MulticastGroupHandler sJoinMulticastGroupHandler; + static MulticastGroupHandler sLeaveMulticastGroupHandler; +#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API + #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS #if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK @@ -159,44 +207,7 @@ class DLL_EXPORT IPEndPointBasis : public EndPointBasis CHIP_ERROR ReleaseConnection(); void ReleaseAll(); #endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - -#if CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API -public: - using JoinMulticastGroupHandler = CHIP_ERROR (*)(InterfaceId, const IPAddress &); - using LeaveMulticastGroupHandler = CHIP_ERROR (*)(InterfaceId, const IPAddress &); - static void SetJoinMulticastGroupHandler(JoinMulticastGroupHandler handler) { sJoinMulticastGroupHandler = handler; } - static void SetLeaveMulticastGroupHandler(LeaveMulticastGroupHandler handler) { sLeaveMulticastGroupHandler = handler; } - -private: - static JoinMulticastGroupHandler sJoinMulticastGroupHandler; - static LeaveMulticastGroupHandler sLeaveMulticastGroupHandler; -#endif // CHIP_SYSTEM_CONFIG_USE_PLATFORM_MULTICAST_API - -private: - IPEndPointBasis(const IPEndPointBasis &) = delete; }; -#if CHIP_SYSTEM_CONFIG_USE_LWIP - -inline struct netif * IPEndPointBasis::FindNetifFromInterfaceId(InterfaceId aInterfaceId) -{ - struct netif * lRetval = NULL; - -#if LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 0 && defined(NETIF_FOREACH) - NETIF_FOREACH(lRetval) - { - if (lRetval == aInterfaceId) - break; - } -#else // LWIP_VERSION_MAJOR < 2 || !defined(NETIF_FOREACH) - for (lRetval = netif_list; lRetval != NULL && lRetval != aInterfaceId; lRetval = lRetval->next) - ; -#endif // LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 0 && defined(NETIF_FOREACH) - - return (lRetval); -} - -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - } // namespace Inet } // namespace chip diff --git a/src/inet/TCPEndPoint.cpp b/src/inet/TCPEndPoint.cpp index 2030d6920b50bd..137319a5e38fcb 100644 --- a/src/inet/TCPEndPoint.cpp +++ b/src/inet/TCPEndPoint.cpp @@ -34,14 +34,19 @@ #include "TCPEndPoint.h" -#include "InetFaultInjection.h" +#include #include +#include #include #include #include #include +#include +#include +#include + #if CHIP_SYSTEM_CONFIG_USE_LWIP #include #include @@ -56,13 +61,6 @@ #include #include #include -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#include "arpa-inet-compatibility.h" - -#include -#include -#include // SOCK_CLOEXEC not defined on all platforms, e.g. iOS/macOS: #ifdef SOCK_CLOEXEC @@ -86,6 +84,15 @@ // socket option for macOS & iOS systems. #define TCP_IDLE_INTERVAL_OPT_NAME TCP_KEEPALIVE #endif +#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + +namespace chip { +namespace Inet { + +chip::System::ObjectPool TCPEndPoint::sPool; + +#if CHIP_SYSTEM_CONFIG_USE_LWIP +namespace { /* * This logic to register a null operation callback with the LwIP TCP/IP task @@ -93,8 +100,6 @@ * which is necessary to ensure that initial SYN and SYN-ACK packets are * retransmitted during the 3-way handshake. */ -#if CHIP_SYSTEM_CONFIG_USE_LWIP -namespace { void nil_tcpip_callback(void * _aContext) {} @@ -104,30 +109,14 @@ err_t start_tcp_timers(void) } } // anonymous namespace -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -namespace chip { -namespace Inet { - -chip::System::ObjectPool TCPEndPoint::sPool; -CHIP_ERROR TCPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) +CHIP_ERROR TCPEndPoint::BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) { - CHIP_ERROR res = CHIP_NO_ERROR; - - if (State != kState_Ready) - return CHIP_ERROR_INCORRECT_STATE; - - if (addr != IPAddress::Any && addr.Type() != kIPAddressType_Any && addr.Type() != addrType) - return INET_ERROR_WRONG_ADDRESS_TYPE; - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // Lock LwIP stack LOCK_TCPIP_CORE(); // Get the appropriate type of PCB. - res = GetPCB(addrType); + CHIP_ERROR res = GetPCB(addrType); // Bind the PCB to the specified address/port. if (res == CHIP_NO_ERROR) @@ -180,111 +169,11 @@ CHIP_ERROR TCPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uin // Unlock LwIP stack UNLOCK_TCPIP_CORE(); - -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - res = GetSocket(addrType); - - if (res == CHIP_NO_ERROR && reuseAddr) - { - int n = 1; - setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); - -#ifdef SO_REUSEPORT - // Enable SO_REUSEPORT. This permits coexistence between an - // untargetted CHIP client and other services that listen on - // a CHIP port on a specific address (such as a CHIP client - // with TARGETTED_LISTEN or TCP proxying services). Note that - // one of the costs of this implementation is the - // non-deterministic connection dispatch when multple clients - // listen on the address wih the same degreee of selectivity, - // e.g. two untargetted-listen CHIP clients, or two - // targetted-listen CHIP clients with the same node id. - - if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n)) != 0) - { - ChipLogError(Inet, "SO_REUSEPORT: %d", errno); - } -#endif // defined(SO_REUSEPORT) - } - - if (res == CHIP_NO_ERROR) - { - if (addrType == kIPAddressType_IPv6) - { - struct sockaddr_in6 sa; - memset(&sa, 0, sizeof(sa)); - sa.sin6_family = AF_INET6; - sa.sin6_port = htons(port); - sa.sin6_flowinfo = 0; - sa.sin6_addr = addr.ToIPv6(); - sa.sin6_scope_id = 0; - - if (bind(mSocket, reinterpret_cast(&sa), static_cast(sizeof(sa))) != 0) - res = CHIP_ERROR_POSIX(errno); - } -#if INET_CONFIG_ENABLE_IPV4 - else if (addrType == kIPAddressType_IPv4) - { - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - sa.sin_addr = addr.ToIPv4(); - - if (bind(mSocket, reinterpret_cast(&sa), static_cast(sizeof(sa))) != 0) - res = CHIP_ERROR_POSIX(errno); - } -#endif // INET_CONFIG_ENABLE_IPV4 - else - res = INET_ERROR_WRONG_ADDRESS_TYPE; - } - -#if CHIP_SYSTEM_CONFIG_USE_DISPATCH - dispatch_queue_t dispatchQueue = static_cast(Layer().SystemLayer())->GetDispatchQueue(); - if (dispatchQueue != nullptr) - { - unsigned long fd = static_cast(mSocket); - - mReadableSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatchQueue); - ReturnErrorCodeIf(mReadableSource == nullptr, CHIP_ERROR_NO_MEMORY); - - mWriteableSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, dispatchQueue); - ReturnErrorCodeIf(mWriteableSource == nullptr, CHIP_ERROR_NO_MEMORY); - - dispatch_source_set_event_handler(mReadableSource, ^{ - this->HandlePendingIO(System::SocketEventFlags::kRead); - }); - - dispatch_source_set_event_handler(mWriteableSource, ^{ - this->HandlePendingIO(System::SocketEventFlags::kWrite); - }); - - dispatch_resume(mReadableSource); - dispatch_resume(mWriteableSource); - } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - - if (res == CHIP_NO_ERROR) - { - State = kState_Bound; - } - return res; } -CHIP_ERROR TCPEndPoint::Listen(uint16_t backlog) +CHIP_ERROR TCPEndPoint::ListenImpl(uint16_t backlog) { - CHIP_ERROR res = CHIP_NO_ERROR; - - if (State != kState_Bound) - return CHIP_ERROR_INCORRECT_STATE; - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // Start listening for incoming connections. mTCP = tcp_listen(mTCP); mLwIPEndPointType = LwIPEndPointType::TCP; @@ -293,53 +182,14 @@ CHIP_ERROR TCPEndPoint::Listen(uint16_t backlog) tcp_accept(mTCP, LwIPHandleIncomingConnection); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - if (listen(mSocket, backlog) != 0) - { - res = CHIP_ERROR_POSIX(errno); - } - else - { - // Enable non-blocking mode for the socket. - int flags = fcntl(mSocket, F_GETFL, 0); - fcntl(mSocket, F_SETFL, flags | O_NONBLOCK); - - // Wait for ability to read on this endpoint. - res = static_cast(Layer().SystemLayer()) - ->SetCallback(mWatch, HandlePendingIO, reinterpret_cast(this)); - if (res == CHIP_NO_ERROR) - { - res = static_cast(Layer().SystemLayer())->RequestCallbackOnPendingRead(mWatch); - } - } - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - - if (res == CHIP_NO_ERROR) - { - // Once Listening, bump the reference count. The corresponding call to Release() - // [or on LwIP, DeferredRelease()] will happen in DoClose(). - Retain(); - State = kState_Listening; - } - - return res; + return CHIP_NO_ERROR; } -CHIP_ERROR TCPEndPoint::Connect(const IPAddress & addr, uint16_t port, InterfaceId intfId) +CHIP_ERROR TCPEndPoint::ConnectImpl(const IPAddress & addr, uint16_t port, InterfaceId intfId) { - CHIP_ERROR res = CHIP_NO_ERROR; - - if (State != kState_Ready && State != kState_Bound) - return CHIP_ERROR_INCORRECT_STATE; - + CHIP_ERROR res = CHIP_NO_ERROR; IPAddressType addrType = addr.Type(); -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // LwIP does not provides an API for initiating a TCP connection via a specific interface. // As a work-around, if the destination is an IPv6 link-local address, we bind the PCB // to the link local address associated with the source interface; however this is only @@ -410,208 +260,145 @@ CHIP_ERROR TCPEndPoint::Connect(const IPAddress & addr, uint16_t port, Interface // Unlock LwIP stack UNLOCK_TCPIP_CORE(); + return res; +} -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +CHIP_ERROR TCPEndPoint::GetPeerInfo(IPAddress * retAddr, uint16_t * retPort) const +{ + CHIP_ERROR res = CHIP_NO_ERROR; -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - res = GetSocket(addrType); - if (res != CHIP_NO_ERROR) - return res; + // Lock LwIP stack + LOCK_TCPIP_CORE(); - if (intfId == INET_NULL_INTERFACEID) + if (mTCP != NULL) { - // The behavior when connecting to an IPv6 link-local address without specifying an outbound - // interface is ambiguous. So prevent it in all cases. - if (addr.IsIPv6LinkLocal()) - return INET_ERROR_WRONG_ADDRESS_TYPE; + *retPort = mTCP->remote_port; + +#if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 + *retAddr = IPAddress::FromLwIPAddr(mTCP->remote_ip); +#else // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 +#if INET_CONFIG_ENABLE_IPV4 + *retAddr = PCB_ISIPV6(mTCP) ? IPAddress::FromIPv6(mTCP->remote_ip.ip6) : IPAddress::FromIPv4(mTCP->remote_ip.ip4); +#else // !INET_CONFIG_ENABLE_IPV4 + *retAddr = IPAddress::FromIPv6(mTCP->remote_ip.ip6); +#endif // !INET_CONFIG_ENABLE_IPV4 +#endif // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 } else - { - // Try binding to the interface - - // If destination is link-local then there is no need to bind to - // interface or address on the interface. + res = CHIP_ERROR_CONNECTION_ABORTED; - if (!addr.IsIPv6LinkLocal()) - { -#ifdef SO_BINDTODEVICE - struct ::ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); + // Unlock LwIP stack + UNLOCK_TCPIP_CORE(); - res = GetInterfaceName(intfId, ifr.ifr_name, sizeof(ifr.ifr_name)); - if (res != CHIP_NO_ERROR) - return res; + return res; +} - // Attempt to bind to the interface using SO_BINDTODEVICE which requires privileged access. - // If the permission is denied(EACCES) because CHIP is running in a context - // that does not have privileged access, choose a source address on the - // interface to bind the connetion to. - int r = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); - if (r < 0 && errno != EACCES) - { - return res = CHIP_ERROR_POSIX(errno); - } +CHIP_ERROR TCPEndPoint::GetLocalInfo(IPAddress * retAddr, uint16_t * retPort) +{ + CHIP_ERROR res = CHIP_NO_ERROR; - if (r < 0) -#endif // SO_BINDTODEVICE - { - // Attempting to initiate a connection via a specific interface is not allowed. - // The only way to do this is to bind the local to an address on the desired - // interface. - res = BindSrcAddrFromIntf(addrType, intfId); - if (res != CHIP_NO_ERROR) - return res; - } - } - } + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - // Disable generation of SIGPIPE. -#ifdef SO_NOSIGPIPE - int n = 1; - setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, &n, sizeof(n)); -#endif // defined(SO_NOSIGPIPE) - - // Enable non-blocking mode for the socket. - int flags = fcntl(mSocket, F_GETFL, 0); - fcntl(mSocket, F_SETFL, flags | O_NONBLOCK); - - socklen_t sockaddrsize = 0; - const sockaddr * sockaddrptr = nullptr; + // Lock LwIP stack + LOCK_TCPIP_CORE(); - union + if (mTCP != NULL) { - sockaddr any; - sockaddr_in6 in6; -#if INET_CONFIG_ENABLE_IPV4 - sockaddr_in in; -#endif // INET_CONFIG_ENABLE_IPV4 - } sa; - memset(&sa, 0, sizeof(sa)); + *retPort = mTCP->local_port; - if (addrType == kIPAddressType_IPv6) - { - sa.in6.sin6_family = AF_INET6; - sa.in6.sin6_port = htons(port); - sa.in6.sin6_flowinfo = 0; - sa.in6.sin6_addr = addr.ToIPv6(); - sa.in6.sin6_scope_id = intfId; - sockaddrsize = sizeof(sockaddr_in6); - sockaddrptr = reinterpret_cast(&sa.in6); - } +#if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 + *retAddr = IPAddress::FromLwIPAddr(mTCP->local_ip); +#else // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 #if INET_CONFIG_ENABLE_IPV4 - else if (addrType == kIPAddressType_IPv4) - { - sa.in.sin_family = AF_INET; - sa.in.sin_port = htons(port); - sa.in.sin_addr = addr.ToIPv4(); - sockaddrsize = sizeof(sockaddr_in); - sockaddrptr = reinterpret_cast(&sa.in); - } -#endif // INET_CONFIG_ENABLE_IPV4 - else - return INET_ERROR_WRONG_ADDRESS_TYPE; - - int conRes = connect(mSocket, sockaddrptr, sockaddrsize); - - if (conRes == -1 && errno != EINPROGRESS) - { - res = CHIP_ERROR_POSIX(errno); - DoClose(res, true); - return res; - } - - ReturnErrorOnFailure(static_cast(Layer().SystemLayer()) - ->SetCallback(mWatch, HandlePendingIO, reinterpret_cast(this))); - - // Once Connecting or Connected, bump the reference count. The corresponding Release() - // [or on LwIP, DeferredRelease()] will happen in DoClose(). - Retain(); - - if (conRes == 0) - { - State = kState_Connected; - // Wait for ability to read on this endpoint. - ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->RequestCallbackOnPendingRead(mWatch)); - if (OnConnectComplete != nullptr) - OnConnectComplete(this, CHIP_NO_ERROR); + *retAddr = PCB_ISIPV6(mTCP) ? IPAddress::FromIPv6(mTCP->local_ip.ip6) : IPAddress::FromIPv4(mTCP->local_ip.ip4); +#else // !INET_CONFIG_ENABLE_IPV4 + *retAddr = IPAddress::FromIPv6(mTCP->local_ip.ip6); +#endif // !INET_CONFIG_ENABLE_IPV4 +#endif // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 } else - { - State = kState_Connecting; - // Wait for ability to write on this endpoint. - ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->RequestCallbackOnPendingWrite(mWatch)); - } - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + res = CHIP_ERROR_CONNECTION_ABORTED; - StartConnectTimerIfSet(); + // Unlock LwIP stack + UNLOCK_TCPIP_CORE(); return res; } -/** - * @brief Set timeout for Connect to succeed or return an error. - * - * @param[in] connTimeoutMsecs - * - * @note - * Setting a value of zero means use system defaults. - */ -void TCPEndPoint::SetConnectTimeout(const uint32_t connTimeoutMsecs) +CHIP_ERROR TCPEndPoint::GetInterfaceId(InterfaceId * retInterface) { - mConnectTimeoutMsecs = connTimeoutMsecs; + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; + // TODO: Does netif_get_by_index(mTCP->netif_idx) do the right thing? I + // can't quite tell whether LwIP supports a specific interface id for TCP at + // all. For now just claim no particular interface id. + *retInterface = INET_NULL_INTERFACEID; + return CHIP_NO_ERROR; } -void TCPEndPoint::StartConnectTimerIfSet() +CHIP_ERROR TCPEndPoint::SendQueuedImpl(bool queueWasEmpty) { - if (mConnectTimeoutMsecs > 0) +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + if (!mUserTimeoutTimerRunning) { - Layer().SystemLayer()->StartTimer(mConnectTimeoutMsecs, TCPConnectTimeoutHandler, this); + // Timer was not running before this send. So, start + // the timer. + StartTCPUserTimeoutTimer(); } +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + return CHIP_NO_ERROR; } -void TCPEndPoint::StopConnectTimer() +CHIP_ERROR TCPEndPoint::EnableNoDelay() { - Layer().SystemLayer()->CancelTimer(TCPConnectTimeoutHandler, this); -} + CHIP_ERROR res = CHIP_NO_ERROR; -void TCPEndPoint::TCPConnectTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState) -{ - TCPEndPoint * tcpEndPoint = reinterpret_cast(aAppState); + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - VerifyOrDie((aSystemLayer != nullptr) && (tcpEndPoint != nullptr)); + // Lock LwIP stack + LOCK_TCPIP_CORE(); - // Close Connection as we have timed out and Connect has not returned to - // stop this timer. - tcpEndPoint->DoClose(INET_ERROR_TCP_CONNECT_TIMEOUT, false); + if (mTCP != NULL) + tcp_nagle_disable(mTCP); + else + res = CHIP_ERROR_CONNECTION_ABORTED; + + // Unlock LwIP stack + UNLOCK_TCPIP_CORE(); + + return res; } -CHIP_ERROR TCPEndPoint::GetPeerInfo(IPAddress * retAddr, uint16_t * retPort) const +CHIP_ERROR TCPEndPoint::EnableKeepAlive(uint16_t interval, uint16_t timeoutCount) { CHIP_ERROR res = CHIP_NO_ERROR; if (!IsConnected()) return CHIP_ERROR_INCORRECT_STATE; -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if LWIP_TCP_KEEPALIVE // Lock LwIP stack LOCK_TCPIP_CORE(); if (mTCP != NULL) { - *retPort = mTCP->remote_port; + // Set the idle interval + mTCP->keep_idle = (uint32_t) interval * 1000; -#if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 - *retAddr = IPAddress::FromLwIPAddr(mTCP->remote_ip); -#else // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 -#if INET_CONFIG_ENABLE_IPV4 - *retAddr = PCB_ISIPV6(mTCP) ? IPAddress::FromIPv6(mTCP->remote_ip.ip6) : IPAddress::FromIPv4(mTCP->remote_ip.ip4); -#else // !INET_CONFIG_ENABLE_IPV4 - *retAddr = IPAddress::FromIPv6(mTCP->remote_ip.ip6); -#endif // !INET_CONFIG_ENABLE_IPV4 -#endif // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 + // Set the probe retransmission interval. + mTCP->keep_intvl = (uint32_t) interval * 1000; + + // Set the probe timeout count + mTCP->keep_cnt = timeoutCount; + + // Enable keepalives for the connection. + ip_set_option(mTCP, SOF_KEEPALIVE); } else res = CHIP_ERROR_CONNECTION_ABORTED; @@ -619,67 +406,31 @@ CHIP_ERROR TCPEndPoint::GetPeerInfo(IPAddress * retAddr, uint16_t * retPort) con // Unlock LwIP stack UNLOCK_TCPIP_CORE(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - union - { - sockaddr any; - sockaddr_in in; - sockaddr_in6 in6; - } sa; - memset(&sa, 0, sizeof(sa)); - socklen_t saLen = sizeof(sa); - - if (getpeername(mSocket, &sa.any, &saLen) != 0) - return CHIP_ERROR_POSIX(errno); +#else // LWIP_TCP_KEEPALIVE - if (sa.any.sa_family == AF_INET6) - { - *retAddr = IPAddress::FromIPv6(sa.in6.sin6_addr); - *retPort = ntohs(sa.in6.sin6_port); - } -#if INET_CONFIG_ENABLE_IPV4 - else if (sa.any.sa_family == AF_INET) - { - *retAddr = IPAddress::FromIPv4(sa.in.sin_addr); - *retPort = ntohs(sa.in.sin_port); - } -#endif // INET_CONFIG_ENABLE_IPV4 - else - return CHIP_ERROR_INCORRECT_STATE; + res = CHIP_ERROR_NOT_IMPLEMENTED; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS +#endif // LWIP_TCP_KEEPALIVE return res; } -CHIP_ERROR TCPEndPoint::GetLocalInfo(IPAddress * retAddr, uint16_t * retPort) +CHIP_ERROR TCPEndPoint::DisableKeepAlive() { CHIP_ERROR res = CHIP_NO_ERROR; if (!IsConnected()) return CHIP_ERROR_INCORRECT_STATE; -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if LWIP_TCP_KEEPALIVE // Lock LwIP stack LOCK_TCPIP_CORE(); if (mTCP != NULL) { - *retPort = mTCP->local_port; - -#if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 - *retAddr = IPAddress::FromLwIPAddr(mTCP->local_ip); -#else // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 -#if INET_CONFIG_ENABLE_IPV4 - *retAddr = PCB_ISIPV6(mTCP) ? IPAddress::FromIPv6(mTCP->local_ip.ip6) : IPAddress::FromIPv4(mTCP->local_ip.ip4); -#else // !INET_CONFIG_ENABLE_IPV4 - *retAddr = IPAddress::FromIPv6(mTCP->local_ip.ip6); -#endif // !INET_CONFIG_ENABLE_IPV4 -#endif // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 + // Disable keepalives on the connection. + ip_reset_option(mTCP, SOF_KEEPALIVE); } else res = CHIP_ERROR_CONNECTION_ABORTED; @@ -687,689 +438,1210 @@ CHIP_ERROR TCPEndPoint::GetLocalInfo(IPAddress * retAddr, uint16_t * retPort) // Unlock LwIP stack UNLOCK_TCPIP_CORE(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#else // LWIP_TCP_KEEPALIVE -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS + res = CHIP_ERROR_NOT_IMPLEMENTED; - union - { - sockaddr any; - sockaddr_in6 in6; -#if INET_CONFIG_ENABLE_IPV4 - sockaddr_in in; -#endif // INET_CONFIG_ENABLE_IPV4 - } sa; +#endif // LWIP_TCP_KEEPALIVE + return res; +} - memset(&sa, 0, sizeof(sa)); - socklen_t saLen = sizeof(sa); +CHIP_ERROR TCPEndPoint::AckReceive(uint16_t len) +{ + CHIP_ERROR res = CHIP_NO_ERROR; - if (getsockname(mSocket, &sa.any, &saLen) != 0) - return CHIP_ERROR_POSIX(errno); + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - if (sa.any.sa_family == AF_INET6) - { - *retAddr = IPAddress::FromIPv6(sa.in6.sin6_addr); - *retPort = ntohs(sa.in6.sin6_port); - } -#if INET_CONFIG_ENABLE_IPV4 - else if (sa.any.sa_family == AF_INET) - { - *retAddr = IPAddress::FromIPv4(sa.in.sin_addr); - *retPort = ntohs(sa.in.sin_port); - } -#endif // INET_CONFIG_ENABLE_IPV4 + // Lock LwIP stack + LOCK_TCPIP_CORE(); + + if (mTCP != NULL) + tcp_recved(mTCP, len); else - return CHIP_ERROR_INCORRECT_STATE; + res = CHIP_ERROR_CONNECTION_ABORTED; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + // Unlock LwIP stack + UNLOCK_TCPIP_CORE(); return res; } -CHIP_ERROR TCPEndPoint::GetInterfaceId(InterfaceId * retInterface) +CHIP_ERROR TCPEndPoint::SetUserTimeoutImpl(uint32_t userTimeoutMillis) { - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + return CHIP_ERROR_NOT_IMPLEMENTED; +} -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // TODO: Does netif_get_by_index(mTCP->netif_idx) do the right thing? I - // can't quite tell whether LwIP supports a specific interface id for TCP at - // all. For now just claim no particular interface id. - *retInterface = INET_NULL_INTERFACEID; - return CHIP_NO_ERROR; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +void TCPEndPoint::InitImpl() +{ + mUnackedLength = 0; +} -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - union - { - sockaddr any; - sockaddr_in6 in6; -#if INET_CONFIG_ENABLE_IPV4 - sockaddr_in in; -#endif // INET_CONFIG_ENABLE_IPV4 - } sa; +CHIP_ERROR TCPEndPoint::DriveSendingImpl() +{ + CHIP_ERROR err = CHIP_NO_ERROR; - memset(&sa, 0, sizeof(sa)); - socklen_t saLen = sizeof(sa); + // Lock LwIP stack + LOCK_TCPIP_CORE(); - if (getpeername(mSocket, &sa.any, &saLen) != 0) + // If the connection hasn't been aborted ... + if (mTCP != NULL) { - return CHIP_ERROR_POSIX(errno); - } + err_t lwipErr; - if (sa.any.sa_family == AF_INET6) - { - if (IPAddress::FromIPv6(sa.in6.sin6_addr).IsIPv6LinkLocal()) - { - *retInterface = sa.in6.sin6_scope_id; - } - else + // Determine the current send window size. This is the maximum amount we can write to the connection. + uint16_t sendWindowSize = tcp_sndbuf(mTCP); + + // If there's data to be sent and the send window is open... + bool canSend = (RemainingToSend() > 0 && sendWindowSize > 0); + if (canSend) { - // TODO: Is there still a meaningful interface id in this case? - *retInterface = INET_NULL_INTERFACEID; - } - return CHIP_NO_ERROR; - } + // Find first packet buffer with remaining data to send by skipping + // all sent but un-acked data. + TCPEndPoint::BufferOffset startOfUnsent = FindStartOfUnsent(); -#if INET_CONFIG_ENABLE_IPV4 - if (sa.any.sa_family == AF_INET) - { - // No interface id available for IPv4 sockets. - *retInterface = INET_NULL_INTERFACEID; - } -#endif // INET_CONFIG_ENABLE_IPV4 + // While there's data to be sent and a window to send it in... + do + { + VerifyOrDie(!startOfUnsent.buffer.IsNull()); - return CHIP_ERROR_INCORRECT_STATE; + uint16_t bufDataLen = startOfUnsent.buffer->DataLength(); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + // Get a pointer to the start of unsent data within the first buffer on the unsent queue. + const uint8_t * sendData = startOfUnsent.buffer->Start() + startOfUnsent.offset; - *retInterface = INET_NULL_INTERFACEID; - return CHIP_NO_ERROR; -} + // Determine the amount of data to send from the current buffer. + uint16_t sendLen = static_cast(bufDataLen - startOfUnsent.offset); + if (sendLen > sendWindowSize) + sendLen = sendWindowSize; -CHIP_ERROR TCPEndPoint::Send(System::PacketBufferHandle && data, bool push) -{ - CHIP_ERROR res = CHIP_NO_ERROR; + // Call LwIP to queue the data to be sent, telling it if there's more data to come. + // Data is queued in-place as a reference within the source packet buffer. It is + // critical that the underlying packet buffer not be freed until the data + // is acknowledged, otherwise retransmissions could use an invalid + // backing. Using TCP_WRITE_FLAG_COPY would eliminate this requirement, but overall + // requires many more memory allocations which may be problematic when very + // memory-constrained or when using pool-based allocations. + lwipErr = tcp_write(mTCP, sendData, sendLen, (canSend) ? TCP_WRITE_FLAG_MORE : 0); + if (lwipErr != ERR_OK) + { + err = chip::System::MapErrorLwIP(lwipErr); + break; + } + // Start accounting for the data sent as yet-to-be-acked. + // This cast is safe, because mUnackedLength + sendLen <= bufDataLen, which fits in uint16_t. + mUnackedLength = static_cast(mUnackedLength + sendLen); - if (State != kState_Connected && State != kState_ReceiveShutdown) - { - return CHIP_ERROR_INCORRECT_STATE; - } + // Adjust the unsent data offset by the length of data that was written. + // If the entire buffer has been sent advance to the next one. + // This cast is safe, because startOfUnsent.offset + sendLen <= bufDataLen, which fits in uint16_t. + startOfUnsent.offset = static_cast(startOfUnsent.offset + sendLen); + if (startOfUnsent.offset == bufDataLen) + { + startOfUnsent.buffer.Advance(); + startOfUnsent.offset = 0; + } - if (mSendQueue.IsNull()) - { - mSendQueue = std::move(data); -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - // Wait for ability to write on this endpoint. - ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->RequestCallbackOnPendingWrite(mWatch)); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - } - else - { - mSendQueue->AddToEnd(std::move(data)); - } + // Adjust the remaining window size. + sendWindowSize = static_cast(sendWindowSize - sendLen); -#if CHIP_SYSTEM_CONFIG_USE_LWIP + // Determine if there's more data to be sent after this buffer. + canSend = (RemainingToSend() > 0 && sendWindowSize > 0); + } while (canSend); -#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - if (!mUserTimeoutTimerRunning) - { - // Timer was not running before this send. So, start - // the timer. + // Call LwIP to send the queued data. + INET_FAULT_INJECT(FaultInjection::kFault_Send, err = chip::System::MapErrorLwIP(ERR_RTE)); - StartTCPUserTimeoutTimer(); + if (err == CHIP_NO_ERROR) + { + lwipErr = tcp_output(mTCP); + + if (lwipErr != ERR_OK) + err = chip::System::MapErrorLwIP(lwipErr); + } + } + + if (err == CHIP_NO_ERROR) + { + // If in the SendShutdown state and the unsent queue is now empty, shutdown the PCB for sending. + if (State == kState_SendShutdown && (RemainingToSend() == 0)) + { + lwipErr = tcp_shutdown(mTCP, 0, 1); + if (lwipErr != ERR_OK) + err = chip::System::MapErrorLwIP(lwipErr); + } + } } -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + else + err = CHIP_ERROR_CONNECTION_ABORTED; - if (push) - res = DriveSending(); + // Unlock LwIP stack + UNLOCK_TCPIP_CORE(); - return res; + return err; } -void TCPEndPoint::DisableReceive() -{ - ReceiveEnabled = false; -} +void TCPEndPoint::HandleConnectCompleteImpl() {} -void TCPEndPoint::EnableReceive() +void TCPEndPoint::DoCloseImpl(CHIP_ERROR err, int oldState) { - ReceiveEnabled = true; + // Lock LwIP stack + LOCK_TCPIP_CORE(); - DriveReceiving(); -} + // If the LwIP PCB hasn't been closed yet... + if (mTCP != NULL) + { + // If the endpoint was a connection endpoint (vs. a listening endpoint)... + if (oldState != kState_Listening) + { + // Prevent further callbacks for incoming data. This has the effect of instructing + // LwIP to discard any further data received from the peer. + tcp_recv(mTCP, NULL); -/** - * TCPEndPoint::EnableNoDelay - * - * @brief - * Switch off nagle buffering algorithm in TCP by setting the - * TCP_NODELAY socket options. - * - */ + // If entering the Closed state... + if (State == kState_Closed) + { + // Prevent further callbacks to the error handler. + // + // Note: It is important to understand that LwIP can continue to make callbacks after + // a PCB has been closed via the tcp_close() API. In particular, LwIP will continue + // to call the 'data sent' callback to signal the acknowledgment of data that was + // sent, but not acknowledged, prior to the close call. Additionally, LwIP will call + // the error callback if the peer fails to respond in a timely manner to the either + // sent data or the FIN. Unfortunately, there is no callback in the case where the + // connection closes successfully. Because of this, it is impossible know definitively + // when LwIP will no longer make callbacks to its user. Thus we must block further + // callbacks to prevent them from happening after the endpoint has been freed. + // + tcp_err(mTCP, NULL); -CHIP_ERROR TCPEndPoint::EnableNoDelay() -{ - CHIP_ERROR res = CHIP_NO_ERROR; + // If the endpoint is being closed without error, THEN call tcp_close() to close the underlying + // TCP connection gracefully, preserving any in-transit send data. + if (err == CHIP_NO_ERROR) + { + tcp_close(mTCP); + } - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + // OTHERWISE, call tcp_abort() to abort the TCP connection, discarding any in-transit data. + else + { + tcp_abort(mTCP); + } -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // Lock LwIP stack - LOCK_TCPIP_CORE(); + // Discard the reference to the PCB to ensure there is no further interaction with it + // after this point. + mTCP = NULL; + mLwIPEndPointType = LwIPEndPointType::Unknown; + } + } - if (mTCP != NULL) - tcp_nagle_disable(mTCP); - else - res = CHIP_ERROR_CONNECTION_ABORTED; + // OTHERWISE the endpoint was being used for listening, so simply close it. + else + { + tcp_close(mTCP); + + // Discard the reference to the PCB to ensure there is no further interaction with it + // after this point. + mTCP = NULL; + mLwIPEndPointType = LwIPEndPointType::Unknown; + } + } // Unlock LwIP stack UNLOCK_TCPIP_CORE(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS + if (State == kState_Closed) { - int val; - -#ifdef TCP_NODELAY - // Disable TCP Nagle buffering by setting TCP_NODELAY socket option to true - val = 1; - if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_NODELAY, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); -#endif // defined(TCP_NODELAY) + mUnackedLength = 0; } +} -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT +void TCPEndPoint::TCPUserTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState) +{ + TCPEndPoint * tcpEndPoint = reinterpret_cast(aAppState); - return res; + VerifyOrDie((aSystemLayer != nullptr) && (tcpEndPoint != nullptr)); + + // Set the timer running flag to false + tcpEndPoint->mUserTimeoutTimerRunning = false; + + // Close Connection as we have timed out and there is still + // data not sent out successfully. + + tcpEndPoint->DoClose(INET_ERROR_TCP_USER_TIMEOUT, false); } +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -CHIP_ERROR TCPEndPoint::EnableKeepAlive(uint16_t interval, uint16_t timeoutCount) +uint16_t TCPEndPoint::RemainingToSend() { - CHIP_ERROR res = CHIP_NO_ERROR; + if (mSendQueue.IsNull()) + { + return 0; + } + else + { + // We can never have reported more unacked data than there is pending + // in the send queue! This would indicate a critical accounting bug. + VerifyOrDie(mUnackedLength <= mSendQueue->TotalLength()); - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + return static_cast(mSendQueue->TotalLength() - mUnackedLength); + } +} -#if CHIP_SYSTEM_CONFIG_USE_LWIP +TCPEndPoint::BufferOffset TCPEndPoint::FindStartOfUnsent() +{ + // Find first packet buffer with remaining data to send by skipping + // all sent but un-acked data. This is necessary because of the Consume() + // call in HandleDataSent(), which potentially releases backing memory for + // fully-sent packet buffers, causing an invalidation of all possible + // offsets one might have cached. The TCP acnowledgements may come back + // with a variety of sizes depending on prior activity, and size of the + // send window. The only way to ensure we get the correct offsets into + // unsent data while retaining the buffers that have un-acked data is to + // traverse all sent-but-unacked data in the chain to reach the beginning + // of ready-to-send data. + TCPEndPoint::BufferOffset startOfUnsent(mSendQueue.Retain()); + uint16_t leftToSkip = mUnackedLength; -#if LWIP_TCP_KEEPALIVE + VerifyOrDie(leftToSkip < mSendQueue->TotalLength()); - // Lock LwIP stack - LOCK_TCPIP_CORE(); + while (leftToSkip > 0) + { + VerifyOrDie(!startOfUnsent.buffer.IsNull()); + uint16_t bufDataLen = startOfUnsent.buffer->DataLength(); + if (leftToSkip >= bufDataLen) + { + // We have more to skip than current packet buffer size. + // Follow the chain to continue. + startOfUnsent.buffer.Advance(); + leftToSkip = static_cast(leftToSkip - bufDataLen); + } + else + { + // Done skipping all data, currentUnsentBuf is first packet buffer + // containing unsent data. + startOfUnsent.offset = leftToSkip; + leftToSkip = 0; + } + } - if (mTCP != NULL) + return startOfUnsent; +} + +CHIP_ERROR TCPEndPoint::GetPCB(IPAddressType addrType) +{ + // IMMPORTANT: This method MUST be called with the LwIP stack LOCKED! + +#if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 + if (mTCP == NULL) { - // Set the idle interval - mTCP->keep_idle = (uint32_t) interval * 1000; + switch (addrType) + { + case kIPAddressType_IPv6: + mTCP = tcp_new_ip_type(IPADDR_TYPE_V6); + break; + +#if INET_CONFIG_ENABLE_IPV4 + case kIPAddressType_IPv4: + mTCP = tcp_new_ip_type(IPADDR_TYPE_V4); + break; +#endif // INET_CONFIG_ENABLE_IPV4 + + default: + return INET_ERROR_WRONG_ADDRESS_TYPE; + } + + if (mTCP == NULL) + { + return CHIP_ERROR_NO_MEMORY; + } + else + { + mLwIPEndPointType = LwIPEndPointType::TCP; + } + } + else + { + switch (IP_GET_TYPE(&mTCP->local_ip)) + { + case IPADDR_TYPE_V6: + if (addrType != kIPAddressType_IPv6) + return INET_ERROR_WRONG_ADDRESS_TYPE; + break; + +#if INET_CONFIG_ENABLE_IPV4 + case IPADDR_TYPE_V4: + if (addrType != kIPAddressType_IPv4) + return INET_ERROR_WRONG_ADDRESS_TYPE; + break; +#endif // INET_CONFIG_ENABLE_IPV4 + + default: + break; + } + } +#else // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 + if (mTCP == NULL) + { + if (addrType == kIPAddressType_IPv6) + mTCP = tcp_new_ip6(); +#if INET_CONFIG_ENABLE_IPV4 + else if (addrType == kIPAddressType_IPv4) + mTCP = tcp_new(); +#endif // INET_CONFIG_ENABLE_IPV4 + else + return INET_ERROR_WRONG_ADDRESS_TYPE; + if (mTCP == NULL) + { + return CHIP_ERROR_NO_MEMORY; + } + else + { + mLwIPEndPointType = LwIPEndPointType::TCP; + } + } + else + { +#if INET_CONFIG_ENABLE_IPV4 + const IPAddressType pcbType = PCB_ISIPV6(mTCP) ? kIPAddressType_IPv6 : kIPAddressType_IPv4; +#else // !INET_CONFIG_ENABLE_IPV4 + const IPAddressType pcbType = kIPAddressType_IPv6; +#endif // !INET_CONFIG_ENABLE_IPV4 + if (addrType != pcbType) + return INET_ERROR_WRONG_ADDRESS_TYPE; + } +#endif // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 + + return CHIP_NO_ERROR; +} + +void TCPEndPoint::HandleDataSent(uint16_t lenSent) +{ + if (IsConnected()) + { + // Ensure we do not have internal inconsistency in the lwIP, which + // could cause invalid pointer accesses. + if (lenSent > mUnackedLength) + { + ChipLogError(Inet, "Got more ACKed bytes (%d) than were pending (%d)", (int) lenSent, (int) mUnackedLength); + DoClose(CHIP_ERROR_UNEXPECTED_EVENT, false); + return; + } + else if (mSendQueue.IsNull()) + { + ChipLogError(Inet, "Got ACK for %d bytes but data backing gone", (int) lenSent); + DoClose(CHIP_ERROR_UNEXPECTED_EVENT, false); + return; + } + + // Consume data off the head of the send queue equal to the amount of data being acknowledged. + mSendQueue.Consume(lenSent); + mUnackedLength = static_cast(mUnackedLength - lenSent); + +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + // Only change the UserTimeout timer if lenSent > 0, + // indicating progress being made in sending data + // across. + if (lenSent > 0) + { + if (RemainingToSend() == 0) + { + // If the output queue has been flushed then stop the timer. + + StopTCPUserTimeoutTimer(); + +#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + // Notify up if all outstanding data has been acknowledged + + SetTCPSendIdleAndNotifyChange(true); +#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + } + else + { + // Progress is being made. So, shift the timer + // forward if it was started. + RestartTCPUserTimeoutTimer(); + } + } +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + + // Mark the connection as being active. + MarkActive(); + + // If requested, call the app's OnDataSent callback. + if (OnDataSent != NULL) + OnDataSent(this, lenSent); + + // If unsent data exists, attempt to send it now... + if (RemainingToSend() > 0) + DriveSending(); + + // If in the closing state and the send queue is now empty, attempt to transition to closed. + if ((State == kState_Closing) && (RemainingToSend() == 0)) + DoClose(CHIP_NO_ERROR, false); + } +} + +void TCPEndPoint::HandleDataReceived(System::PacketBufferHandle && buf) +{ + // Only receive new data while in the Connected or SendShutdown states. + if (State == kState_Connected || State == kState_SendShutdown) + { + // Mark the connection as being active. + MarkActive(); + + // If we received a data buffer, queue it on the receive queue. If there's already data in + // the queue, compact the data into the head buffer. + if (!buf.IsNull()) + { + if (mRcvQueue.IsNull()) + { + mRcvQueue = std::move(buf); + } + else + { + mRcvQueue->AddToEnd(std::move(buf)); + mRcvQueue->CompactHead(); + } + } + + // Otherwise buf == NULL means the other side closed the connection, so ... + else + { + + // If in the Connected state and the app has provided an OnPeerClose callback, + // enter the ReceiveShutdown state. Providing an OnPeerClose callback allows + // the app to decide whether to keep the send side of the connection open after + // the peer has closed. If no OnPeerClose is provided, we assume that the app + // wants to close both directions and automatically enter the Closing state. + if (State == kState_Connected && OnPeerClose != NULL) + State = kState_ReceiveShutdown; + else + State = kState_Closing; + + // Call the app's OnPeerClose. + if (OnPeerClose != NULL) + OnPeerClose(this); + } + + // Drive the received data into the app. + DriveReceiving(); + } +} + +void TCPEndPoint::HandleIncomingConnection(TCPEndPoint * conEP) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + IPAddress peerAddr; + uint16_t peerPort; + + if (State == kState_Listening) + { + // If there's no callback available, fail with an error. + if (OnConnectionReceived == NULL) + err = CHIP_ERROR_NO_CONNECTION_HANDLER; - // Set the probe retransmission interval. - mTCP->keep_intvl = (uint32_t) interval * 1000; + // Extract the peer's address information. + if (err == CHIP_NO_ERROR) + err = conEP->GetPeerInfo(&peerAddr, &peerPort); - // Set the probe timeout count - mTCP->keep_cnt = timeoutCount; + // If successful, call the app's callback function. + if (err == CHIP_NO_ERROR) + OnConnectionReceived(this, conEP, peerAddr, peerPort); - // Enable keepalives for the connection. - ip_set_option(mTCP, SOF_KEEPALIVE); + // Otherwise clean up and call the app's error callback. + else if (OnAcceptError != NULL) + OnAcceptError(this, err); } else - res = CHIP_ERROR_CONNECTION_ABORTED; - - // Unlock LwIP stack - UNLOCK_TCPIP_CORE(); - -#else // LWIP_TCP_KEEPALIVE - - res = CHIP_ERROR_NOT_IMPLEMENTED; - -#endif // LWIP_TCP_KEEPALIVE + err = CHIP_ERROR_INCORRECT_STATE; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + // If something failed above, abort and free the connection end point. + if (err != CHIP_NO_ERROR) + conEP->Free(); +} -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS +void TCPEndPoint::HandleError(CHIP_ERROR err) +{ + if (State == kState_Listening) { - int val; + if (OnAcceptError != NULL) + OnAcceptError(this, err); + } + else + DoClose(err, false); +} - // Set the idle interval - val = interval; - if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_IDLE_INTERVAL_OPT_NAME, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); +err_t TCPEndPoint::LwIPHandleConnectComplete(void * arg, struct tcp_pcb * tpcb, err_t lwipErr) +{ + err_t res = ERR_OK; - // Set the probe retransmission interval. - val = interval; - if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_KEEPINTVL, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); + if (arg != NULL) + { + CHIP_ERROR conErr; + TCPEndPoint * ep = static_cast(arg); + System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); - // Set the probe timeout count - val = timeoutCount; - if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_KEEPCNT, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); + if (lwipErr == ERR_OK) + { + // Setup LwIP callback functions for data transmission. + tcp_recv(ep->mTCP, LwIPHandleDataReceived); + tcp_sent(ep->mTCP, LwIPHandleDataSent); + } - // Enable keepalives for the connection. - val = 1; // enable - if (setsockopt(mSocket, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); + // Post callback to HandleConnectComplete. + conErr = chip::System::MapErrorLwIP(lwipErr); + if (lSystemLayer->PostEvent(*ep, kInetEvent_TCPConnectComplete, static_cast(conErr.AsInteger())) != + CHIP_NO_ERROR) + res = ERR_ABRT; } + else + res = ERR_ABRT; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + if (res != ERR_OK) + tcp_abort(tpcb); return res; } -/** - * TCPEndPoint::DisableKeepAlive - * - * @brief - * Disable TCP keepalive probes on the associated TCP connection. - * - * @note - * This method can only be called when the endpoint is in one of the connected states. - * - * This method does nothing if keepalives have not been enabled on the endpoint. - */ - -CHIP_ERROR TCPEndPoint::DisableKeepAlive() +err_t TCPEndPoint::LwIPHandleIncomingConnection(void * arg, struct tcp_pcb * tpcb, err_t lwipErr) { - CHIP_ERROR res = CHIP_NO_ERROR; + CHIP_ERROR err = chip::System::MapErrorLwIP(lwipErr); - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + if (arg != NULL) + { + TCPEndPoint * listenEP = static_cast(arg); + TCPEndPoint * conEP = NULL; + System::LayerLwIP * lSystemLayer = static_cast(listenEP->Layer().SystemLayer()); -#if CHIP_SYSTEM_CONFIG_USE_LWIP + // Tell LwIP we've accepted the connection so it can decrement the listen PCB's pending_accepts counter. + tcp_accepted(listenEP->mTCP); -#if LWIP_TCP_KEEPALIVE + // If we did in fact receive a connection, rather than an error, attempt to allocate an end point object. + // + // NOTE: Although most of the LwIP callbacks defer the real work to happen on the endpoint's thread + // (by posting events to the thread's event queue) we can't do that here because as soon as this + // function returns, LwIP is free to begin calling callbacks on the new PCB. For that to work we need + // to have an end point associated with the PCB. + // + if (err == CHIP_NO_ERROR) + { + InetLayer & lInetLayer = listenEP->Layer(); - // Lock LwIP stack - LOCK_TCPIP_CORE(); + err = lInetLayer.NewTCPEndPoint(&conEP); + } - if (mTCP != NULL) - { - // Disable keepalives on the connection. - ip_reset_option(mTCP, SOF_KEEPALIVE); - } - else - res = CHIP_ERROR_CONNECTION_ABORTED; + // Ensure that TCP timers have been started + if (err == CHIP_NO_ERROR) + { + err_t error = start_tcp_timers(); + if (error != ERR_OK) + { + err = chip::System::MapErrorLwIP(error); + } + } - // Unlock LwIP stack - UNLOCK_TCPIP_CORE(); + // If successful in allocating an end point... + if (err == CHIP_NO_ERROR) + { + // Put the new end point into the Connected state. + conEP->State = kState_Connected; + conEP->mTCP = tpcb; + conEP->mLwIPEndPointType = LwIPEndPointType::TCP; + conEP->Retain(); -#else // LWIP_TCP_KEEPALIVE + // Setup LwIP callback functions for the new PCB. + tcp_arg(tpcb, conEP); + tcp_recv(tpcb, LwIPHandleDataReceived); + tcp_sent(tpcb, LwIPHandleDataSent); + tcp_err(tpcb, LwIPHandleError); - res = CHIP_ERROR_NOT_IMPLEMENTED; + // Post a callback to the HandleConnectionReceived() function, passing it the new end point. + if (lSystemLayer->PostEvent(*listenEP, kInetEvent_TCPConnectionReceived, (uintptr_t) conEP) != CHIP_NO_ERROR) + { + err = CHIP_ERROR_CONNECTION_ABORTED; + conEP->Release(); // for the Retain() above + conEP->Release(); // for the Retain() in NewTCPEndPoint() + } + } -#endif // LWIP_TCP_KEEPALIVE + // Otherwise, there was an error accepting the connection, so post a callback to the HandleError function. + else + lSystemLayer->PostEvent(*listenEP, kInetEvent_TCPError, static_cast(err.AsInteger())); + } + else + err = CHIP_ERROR_CONNECTION_ABORTED; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + if (err != CHIP_NO_ERROR && tpcb != NULL) + { + tcp_abort(tpcb); + return ERR_ABRT; + } + else + { + return ERR_OK; + } +} -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS +err_t TCPEndPoint::LwIPHandleDataReceived(void * arg, struct tcp_pcb * tpcb, struct pbuf * p, err_t err) +{ + err_t res = ERR_OK; + if (arg != NULL) { - int val; + TCPEndPoint * ep = static_cast(arg); + System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); - // Disable keepalives on the connection. - val = 0; // disable - if (setsockopt(mSocket, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); + // Post callback to HandleDataReceived. + if (lSystemLayer->PostEvent(*ep, kInetEvent_TCPDataReceived, (uintptr_t) p) != CHIP_NO_ERROR) + res = ERR_ABRT; } + else + res = ERR_ABRT; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + if (res != ERR_OK) + tcp_abort(tpcb); return res; } -/** - * TCPEndPoint::SetUserTimeout - * - * @brief Set the TCP user timeout socket option. - * - * @details - * When the value is greater than 0, it specifies the maximum amount of - * time in milliseconds that transmitted data may remain - * unacknowledged before TCP will forcibly close the - * corresponding connection. If the option value is specified as 0, - * TCP will use the system default. - * See RFC 5482, for further details. - * - * @note - * This method can only be called when the endpoint is in one of the connected states. - * - * This method can be called multiple times to adjust the keepalive interval or timeout - * count. - */ -CHIP_ERROR TCPEndPoint::SetUserTimeout(uint32_t userTimeoutMillis) +err_t TCPEndPoint::LwIPHandleDataSent(void * arg, struct tcp_pcb * tpcb, u16_t len) { - CHIP_ERROR res = CHIP_NO_ERROR; + err_t res = ERR_OK; - if (!IsConnected()) + if (arg != NULL) { - return CHIP_ERROR_INCORRECT_STATE; - } - -#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + TCPEndPoint * ep = static_cast(arg); + System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); - // Store the User timeout configuration if it is being overridden. + // Post callback to HandleDataReceived. + if (lSystemLayer->PostEvent(*ep, kInetEvent_TCPDataSent, (uintptr_t) len) != CHIP_NO_ERROR) + res = ERR_ABRT; + } + else + res = ERR_ABRT; - mUserTimeoutMillis = userTimeoutMillis; + if (res != ERR_OK) + tcp_abort(tpcb); -#else // !INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + return res; +} -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS +void TCPEndPoint::LwIPHandleError(void * arg, err_t lwipErr) +{ + if (arg != NULL) + { + TCPEndPoint * ep = static_cast(arg); + System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); -#if defined(TCP_USER_TIMEOUT) - // Set the user timeout - uint32_t val = userTimeoutMillis; - if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_USER_TIMEOUT, &val, sizeof(val)) != 0) - return CHIP_ERROR_POSIX(errno); -#else // TCP_USER_TIMEOUT - res = CHIP_ERROR_NOT_IMPLEMENTED; -#endif // defined(TCP_USER_TIMEOUT) + // At this point LwIP has already freed the PCB. Since the thread that owns the TCPEndPoint may + // try to use the PCB before it receives the TCPError event posted below, we set the PCB to NULL + // as a means to signal the other thread that the connection has been aborted. The implication + // of this is that the mTCP field is shared state between the two threads and thus must only be + // accessed with the LwIP lock held. + ep->mTCP = NULL; + ep->mLwIPEndPointType = LwIPEndPointType::Unknown; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + // Post callback to HandleError. + CHIP_ERROR err = chip::System::MapErrorLwIP(lwipErr); + lSystemLayer->PostEvent(*ep, kInetEvent_TCPError, static_cast(err.AsInteger())); + } +} -#if CHIP_SYSTEM_CONFIG_USE_LWIP - res = CHIP_ERROR_NOT_IMPLEMENTED; #endif // CHIP_SYSTEM_CONFIG_USE_LWIP -#endif // !INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - - return res; -} +#if CHIP_SYSTEM_CONFIG_USE_SOCKETS -CHIP_ERROR TCPEndPoint::AckReceive(uint16_t len) +CHIP_ERROR TCPEndPoint::BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) { - CHIP_ERROR res = CHIP_NO_ERROR; + CHIP_ERROR res = GetSocket(addrType); - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + if (res == CHIP_NO_ERROR && reuseAddr) + { + int n = 1; + setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#ifdef SO_REUSEPORT + // Enable SO_REUSEPORT. This permits coexistence between an + // untargetted CHIP client and other services that listen on + // a CHIP port on a specific address (such as a CHIP client + // with TARGETTED_LISTEN or TCP proxying services). Note that + // one of the costs of this implementation is the + // non-deterministic connection dispatch when multple clients + // listen on the address wih the same degreee of selectivity, + // e.g. two untargetted-listen CHIP clients, or two + // targetted-listen CHIP clients with the same node id. - // Lock LwIP stack - LOCK_TCPIP_CORE(); + if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n)) != 0) + { + ChipLogError(Inet, "SO_REUSEPORT: %d", errno); + } +#endif // defined(SO_REUSEPORT) + } - if (mTCP != NULL) - tcp_recved(mTCP, len); - else - res = CHIP_ERROR_CONNECTION_ABORTED; + if (res == CHIP_NO_ERROR) + { + if (addrType == kIPAddressType_IPv6) + { + struct sockaddr_in6 sa; + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + sa.sin6_flowinfo = 0; + sa.sin6_addr = addr.ToIPv6(); + sa.sin6_scope_id = 0; - // Unlock LwIP stack - UNLOCK_TCPIP_CORE(); + if (bind(mSocket, reinterpret_cast(&sa), static_cast(sizeof(sa))) != 0) + res = CHIP_ERROR_POSIX(errno); + } +#if INET_CONFIG_ENABLE_IPV4 + else if (addrType == kIPAddressType_IPv4) + { + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr = addr.ToIPv4(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + if (bind(mSocket, reinterpret_cast(&sa), static_cast(sizeof(sa))) != 0) + res = CHIP_ERROR_POSIX(errno); + } +#endif // INET_CONFIG_ENABLE_IPV4 + else + res = INET_ERROR_WRONG_ADDRESS_TYPE; + } -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH + dispatch_queue_t dispatchQueue = static_cast(Layer().SystemLayer())->GetDispatchQueue(); + if (dispatchQueue != nullptr) + { + unsigned long fd = static_cast(mSocket); - // nothing to do for sockets case + mReadableSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatchQueue); + ReturnErrorCodeIf(mReadableSource == nullptr, CHIP_ERROR_NO_MEMORY); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + mWriteableSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, dispatchQueue); + ReturnErrorCodeIf(mWriteableSource == nullptr, CHIP_ERROR_NO_MEMORY); + + dispatch_source_set_event_handler(mReadableSource, ^{ + this->HandlePendingIO(System::SocketEventFlags::kRead); + }); + + dispatch_source_set_event_handler(mWriteableSource, ^{ + this->HandlePendingIO(System::SocketEventFlags::kWrite); + }); + dispatch_resume(mReadableSource); + dispatch_resume(mWriteableSource); + } +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH return res; } -CHIP_ERROR TCPEndPoint::SetReceivedDataForTesting(System::PacketBufferHandle && data) +CHIP_ERROR TCPEndPoint::ListenImpl(uint16_t backlog) { - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + CHIP_ERROR res = CHIP_NO_ERROR; - mRcvQueue = std::move(data); + if (listen(mSocket, backlog) != 0) + { + res = CHIP_ERROR_POSIX(errno); + } + else + { + // Enable non-blocking mode for the socket. + int flags = fcntl(mSocket, F_GETFL, 0); + fcntl(mSocket, F_SETFL, flags | O_NONBLOCK); - return CHIP_NO_ERROR; + // Wait for ability to read on this endpoint. + res = static_cast(Layer().SystemLayer()) + ->SetCallback(mWatch, HandlePendingIO, reinterpret_cast(this)); + if (res == CHIP_NO_ERROR) + { + res = static_cast(Layer().SystemLayer())->RequestCallbackOnPendingRead(mWatch); + } + } + return res; } -uint32_t TCPEndPoint::PendingSendLength() +CHIP_ERROR TCPEndPoint::ConnectImpl(const IPAddress & addr, uint16_t port, InterfaceId intfId) { - if (!mSendQueue.IsNull()) - return mSendQueue->TotalLength(); - return 0; -} + IPAddressType addrType = addr.Type(); + CHIP_ERROR res = GetSocket(addrType); + if (res != CHIP_NO_ERROR) + return res; -uint32_t TCPEndPoint::PendingReceiveLength() -{ - if (!mRcvQueue.IsNull()) - return mRcvQueue->TotalLength(); - return 0; -} + if (intfId == INET_NULL_INTERFACEID) + { + // The behavior when connecting to an IPv6 link-local address without specifying an outbound + // interface is ambiguous. So prevent it in all cases. + if (addr.IsIPv6LinkLocal()) + return INET_ERROR_WRONG_ADDRESS_TYPE; + } + else + { + // Try binding to the interface -CHIP_ERROR TCPEndPoint::Shutdown() -{ - CHIP_ERROR err = CHIP_NO_ERROR; + // If destination is link-local then there is no need to bind to + // interface or address on the interface. - if (!IsConnected()) - return CHIP_ERROR_INCORRECT_STATE; + if (!addr.IsIPv6LinkLocal()) + { +#ifdef SO_BINDTODEVICE + struct ::ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); - // If fully connected, enter the SendShutdown state. - if (State == kState_Connected) - { - State = kState_SendShutdown; - DriveSending(); + res = GetInterfaceName(intfId, ifr.ifr_name, sizeof(ifr.ifr_name)); + if (res != CHIP_NO_ERROR) + return res; + + // Attempt to bind to the interface using SO_BINDTODEVICE which requires privileged access. + // If the permission is denied(EACCES) because CHIP is running in a context + // that does not have privileged access, choose a source address on the + // interface to bind the connetion to. + int r = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); + if (r < 0 && errno != EACCES) + { + return res = CHIP_ERROR_POSIX(errno); + } + + if (r < 0) +#endif // SO_BINDTODEVICE + { + // Attempting to initiate a connection via a specific interface is not allowed. + // The only way to do this is to bind the local to an address on the desired + // interface. + res = BindSrcAddrFromIntf(addrType, intfId); + if (res != CHIP_NO_ERROR) + return res; + } + } } - // Otherwise, if the peer has already closed their end of the connection, - else if (State == kState_ReceiveShutdown) - err = DoClose(err, false); + // Disable generation of SIGPIPE. +#ifdef SO_NOSIGPIPE + int n = 1; + setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, &n, sizeof(n)); +#endif // defined(SO_NOSIGPIPE) - return err; -} + // Enable non-blocking mode for the socket. + int flags = fcntl(mSocket, F_GETFL, 0); + fcntl(mSocket, F_SETFL, flags | O_NONBLOCK); -CHIP_ERROR TCPEndPoint::Close() -{ - // Clear the receive queue. - mRcvQueue = nullptr; + socklen_t sockaddrsize = 0; + const sockaddr * sockaddrptr = nullptr; - // Suppress closing callbacks, since the application explicitly called Close(). - OnConnectionClosed = nullptr; - OnPeerClose = nullptr; - OnConnectComplete = nullptr; + union + { + sockaddr any; + sockaddr_in6 in6; +#if INET_CONFIG_ENABLE_IPV4 + sockaddr_in in; +#endif // INET_CONFIG_ENABLE_IPV4 + } sa; + memset(&sa, 0, sizeof(sa)); - // Perform a graceful close. - return DoClose(CHIP_NO_ERROR, true); -} + if (addrType == kIPAddressType_IPv6) + { + sa.in6.sin6_family = AF_INET6; + sa.in6.sin6_port = htons(port); + sa.in6.sin6_flowinfo = 0; + sa.in6.sin6_addr = addr.ToIPv6(); + sa.in6.sin6_scope_id = intfId; + sockaddrsize = sizeof(sockaddr_in6); + sockaddrptr = reinterpret_cast(&sa.in6); + } +#if INET_CONFIG_ENABLE_IPV4 + else if (addrType == kIPAddressType_IPv4) + { + sa.in.sin_family = AF_INET; + sa.in.sin_port = htons(port); + sa.in.sin_addr = addr.ToIPv4(); + sockaddrsize = sizeof(sockaddr_in); + sockaddrptr = reinterpret_cast(&sa.in); + } +#endif // INET_CONFIG_ENABLE_IPV4 + else + return INET_ERROR_WRONG_ADDRESS_TYPE; -void TCPEndPoint::Abort() -{ - // Suppress closing callbacks, since the application explicitly called Abort(). - OnConnectionClosed = nullptr; - OnPeerClose = nullptr; - OnConnectComplete = nullptr; + int conRes = connect(mSocket, sockaddrptr, sockaddrsize); - DoClose(CHIP_ERROR_CONNECTION_ABORTED, true); -} + if (conRes == -1 && errno != EINPROGRESS) + { + res = CHIP_ERROR_POSIX(errno); + DoClose(res, true); + return res; + } -void TCPEndPoint::Free() -{ - CHIP_ERROR err; + ReturnErrorOnFailure(static_cast(Layer().SystemLayer()) + ->SetCallback(mWatch, HandlePendingIO, reinterpret_cast(this))); - // Ensure no callbacks to the app after this point. - OnAcceptError = nullptr; - OnConnectComplete = nullptr; - OnConnectionReceived = nullptr; - OnConnectionClosed = nullptr; - OnPeerClose = nullptr; - OnDataReceived = nullptr; - OnDataSent = nullptr; + // Once Connecting or Connected, bump the reference count. The corresponding Release() + // [or on LwIP, DeferredRelease()] will happen in DoClose(). + Retain(); - // Ensure the end point is Closed or Closing. - err = Close(); - if (err != CHIP_NO_ERROR) - Abort(); + if (conRes == 0) + { + State = kState_Connected; + // Wait for ability to read on this endpoint. + ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->RequestCallbackOnPendingRead(mWatch)); + if (OnConnectComplete != nullptr) + OnConnectComplete(this, CHIP_NO_ERROR); + } + else + { + State = kState_Connecting; + // Wait for ability to write on this endpoint. + ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->RequestCallbackOnPendingWrite(mWatch)); + } - // Release the Retain() that happened when the end point was allocated - // [on LwIP, the object may still be alive if DoClose() used the - // EndPointBasis::DeferredFree() method.] - Release(); + return res; } -#if INET_TCP_IDLE_CHECK_INTERVAL > 0 -void TCPEndPoint::SetIdleTimeout(uint32_t timeoutMS) +CHIP_ERROR TCPEndPoint::GetPeerInfo(IPAddress * retAddr, uint16_t * retPort) const { - uint32_t newIdleTimeout = (timeoutMS + (INET_TCP_IDLE_CHECK_INTERVAL - 1)) / INET_TCP_IDLE_CHECK_INTERVAL; - InetLayer & lInetLayer = Layer(); - bool isIdleTimerRunning = lInetLayer.IsIdleTimerRunning(); + CHIP_ERROR res = CHIP_NO_ERROR; - if (newIdleTimeout > UINT16_MAX) - newIdleTimeout = UINT16_MAX; - mIdleTimeout = mRemainingIdleTime = static_cast(newIdleTimeout); + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - if (!isIdleTimerRunning && mIdleTimeout) + union { - Layer().SystemLayer()->StartTimer(INET_TCP_IDLE_CHECK_INTERVAL, InetLayer::HandleTCPInactivityTimer, &lInetLayer); + sockaddr any; + sockaddr_in in; + sockaddr_in6 in6; + } sa; + memset(&sa, 0, sizeof(sa)); + socklen_t saLen = sizeof(sa); + + if (getpeername(mSocket, &sa.any, &saLen) != 0) + return CHIP_ERROR_POSIX(errno); + + if (sa.any.sa_family == AF_INET6) + { + *retAddr = IPAddress::FromIPv6(sa.in6.sin6_addr); + *retPort = ntohs(sa.in6.sin6_port); } -} -#endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 +#if INET_CONFIG_ENABLE_IPV4 + else if (sa.any.sa_family == AF_INET) + { + *retAddr = IPAddress::FromIPv4(sa.in.sin_addr); + *retPort = ntohs(sa.in.sin_port); + } +#endif // INET_CONFIG_ENABLE_IPV4 + else + return CHIP_ERROR_INCORRECT_STATE; -bool TCPEndPoint::IsConnected(int state) -{ - return state == kState_Connected || state == kState_SendShutdown || state == kState_ReceiveShutdown || state == kState_Closing; + return res; } -void TCPEndPoint::Init(InetLayer * inetLayer) +CHIP_ERROR TCPEndPoint::GetLocalInfo(IPAddress * retAddr, uint16_t * retPort) { - InitEndPointBasis(*inetLayer); + CHIP_ERROR res = CHIP_NO_ERROR; - ReceiveEnabled = true; + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - // Initialize to zero for using system defaults. - mConnectTimeoutMsecs = 0; + union + { + sockaddr any; + sockaddr_in6 in6; +#if INET_CONFIG_ENABLE_IPV4 + sockaddr_in in; +#endif // INET_CONFIG_ENABLE_IPV4 + } sa; -#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - mUserTimeoutMillis = INET_CONFIG_DEFAULT_TCP_USER_TIMEOUT_MSEC; + memset(&sa, 0, sizeof(sa)); + socklen_t saLen = sizeof(sa); - mUserTimeoutTimerRunning = false; + if (getsockname(mSocket, &sa.any, &saLen) != 0) + return CHIP_ERROR_POSIX(errno); -#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - mIsTCPSendIdle = true; + if (sa.any.sa_family == AF_INET6) + { + *retAddr = IPAddress::FromIPv6(sa.in6.sin6_addr); + *retPort = ntohs(sa.in6.sin6_port); + } +#if INET_CONFIG_ENABLE_IPV4 + else if (sa.any.sa_family == AF_INET) + { + *retAddr = IPAddress::FromIPv4(sa.in.sin_addr); + *retPort = ntohs(sa.in.sin_port); + } +#endif // INET_CONFIG_ENABLE_IPV4 + else + return CHIP_ERROR_INCORRECT_STATE; - mTCPSendQueuePollPeriodMillis = INET_CONFIG_TCP_SEND_QUEUE_POLL_INTERVAL_MSEC; + return res; +} - mTCPSendQueueRemainingPollCount = MaxTCPSendQueuePolls(); +CHIP_ERROR TCPEndPoint::GetInterfaceId(InterfaceId * retInterface) +{ + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - OnTCPSendIdleChanged = NULL; -#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + union + { + sockaddr any; + sockaddr_in6 in6; +#if INET_CONFIG_ENABLE_IPV4 + sockaddr_in in; +#endif // INET_CONFIG_ENABLE_IPV4 + } sa; -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - mBytesWrittenSinceLastProbe = 0; + memset(&sa, 0, sizeof(sa)); + socklen_t saLen = sizeof(sa); - mLastTCPKernelSendQueueLen = 0; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + if (getpeername(mSocket, &sa.any, &saLen) != 0) + { + return CHIP_ERROR_POSIX(errno); + } -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + if (sa.any.sa_family == AF_INET6) + { + if (IPAddress::FromIPv6(sa.in6.sin6_addr).IsIPv6LinkLocal()) + { + *retInterface = sa.in6.sin6_scope_id; + } + else + { + // TODO: Is there still a meaningful interface id in this case? + *retInterface = INET_NULL_INTERFACEID; + } + return CHIP_NO_ERROR; + } -#if CHIP_SYSTEM_CONFIG_USE_LWIP - mUnackedLength = 0; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#if INET_CONFIG_ENABLE_IPV4 + if (sa.any.sa_family == AF_INET) + { + // No interface id available for IPv4 sockets. + *retInterface = INET_NULL_INTERFACEID; + return CHIP_NO_ERROR; + } +#endif // INET_CONFIG_ENABLE_IPV4 + + *retInterface = INET_NULL_INTERFACEID; + return CHIP_ERROR_INCORRECT_STATE; } -CHIP_ERROR TCPEndPoint::DriveSending() +CHIP_ERROR TCPEndPoint::SendQueuedImpl(bool queueWasEmpty) { - CHIP_ERROR err = CHIP_NO_ERROR; + if (queueWasEmpty) + { + // Wait for ability to write on this endpoint. + return static_cast(Layer().SystemLayer())->RequestCallbackOnPendingWrite(mWatch); + } + return CHIP_NO_ERROR; +} -#if CHIP_SYSTEM_CONFIG_USE_LWIP +CHIP_ERROR TCPEndPoint::EnableNoDelay() +{ + CHIP_ERROR res = CHIP_NO_ERROR; - // Lock LwIP stack - LOCK_TCPIP_CORE(); + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - // If the connection hasn't been aborted ... - if (mTCP != NULL) { - err_t lwipErr; + int val; - // Determine the current send window size. This is the maximum amount we can write to the connection. - uint16_t sendWindowSize = tcp_sndbuf(mTCP); +#ifdef TCP_NODELAY + // Disable TCP Nagle buffering by setting TCP_NODELAY socket option to true + val = 1; + if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_NODELAY, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); +#endif // defined(TCP_NODELAY) + } - // If there's data to be sent and the send window is open... - bool canSend = (RemainingToSend() > 0 && sendWindowSize > 0); - if (canSend) - { - // Find first packet buffer with remaining data to send by skipping - // all sent but un-acked data. - TCPEndPoint::BufferOffset startOfUnsent = FindStartOfUnsent(); + return res; +} - // While there's data to be sent and a window to send it in... - do - { - VerifyOrDie(!startOfUnsent.buffer.IsNull()); +CHIP_ERROR TCPEndPoint::EnableKeepAlive(uint16_t interval, uint16_t timeoutCount) +{ + CHIP_ERROR res = CHIP_NO_ERROR; - uint16_t bufDataLen = startOfUnsent.buffer->DataLength(); + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - // Get a pointer to the start of unsent data within the first buffer on the unsent queue. - const uint8_t * sendData = startOfUnsent.buffer->Start() + startOfUnsent.offset; + { + int val; - // Determine the amount of data to send from the current buffer. - uint16_t sendLen = static_cast(bufDataLen - startOfUnsent.offset); - if (sendLen > sendWindowSize) - sendLen = sendWindowSize; + // Set the idle interval + val = interval; + if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_IDLE_INTERVAL_OPT_NAME, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); - // Call LwIP to queue the data to be sent, telling it if there's more data to come. - // Data is queued in-place as a reference within the source packet buffer. It is - // critical that the underlying packet buffer not be freed until the data - // is acknowledged, otherwise retransmissions could use an invalid - // backing. Using TCP_WRITE_FLAG_COPY would eliminate this requirement, but overall - // requires many more memory allocations which may be problematic when very - // memory-constrained or when using pool-based allocations. - lwipErr = tcp_write(mTCP, sendData, sendLen, (canSend) ? TCP_WRITE_FLAG_MORE : 0); - if (lwipErr != ERR_OK) - { - err = chip::System::MapErrorLwIP(lwipErr); - break; - } - // Start accounting for the data sent as yet-to-be-acked. - // This cast is safe, because mUnackedLength + sendLen <= bufDataLen, which fits in uint16_t. - mUnackedLength = static_cast(mUnackedLength + sendLen); + // Set the probe retransmission interval. + val = interval; + if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_KEEPINTVL, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); - // Adjust the unsent data offset by the length of data that was written. - // If the entire buffer has been sent advance to the next one. - // This cast is safe, because startOfUnsent.offset + sendLen <= bufDataLen, which fits in uint16_t. - startOfUnsent.offset = static_cast(startOfUnsent.offset + sendLen); - if (startOfUnsent.offset == bufDataLen) - { - startOfUnsent.buffer.Advance(); - startOfUnsent.offset = 0; - } + // Set the probe timeout count + val = timeoutCount; + if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_KEEPCNT, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); - // Adjust the remaining window size. - sendWindowSize = static_cast(sendWindowSize - sendLen); + // Enable keepalives for the connection. + val = 1; // enable + if (setsockopt(mSocket, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); + } - // Determine if there's more data to be sent after this buffer. - canSend = (RemainingToSend() > 0 && sendWindowSize > 0); - } while (canSend); + return res; +} - // Call LwIP to send the queued data. - INET_FAULT_INJECT(FaultInjection::kFault_Send, err = chip::System::MapErrorLwIP(ERR_RTE)); +CHIP_ERROR TCPEndPoint::DisableKeepAlive() +{ + CHIP_ERROR res = CHIP_NO_ERROR; - if (err == CHIP_NO_ERROR) - { - lwipErr = tcp_output(mTCP); + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - if (lwipErr != ERR_OK) - err = chip::System::MapErrorLwIP(lwipErr); - } - } + { + int val; - if (err == CHIP_NO_ERROR) - { - // If in the SendShutdown state and the unsent queue is now empty, shutdown the PCB for sending. - if (State == kState_SendShutdown && (RemainingToSend() == 0)) - { - lwipErr = tcp_shutdown(mTCP, 0, 1); - if (lwipErr != ERR_OK) - err = chip::System::MapErrorLwIP(lwipErr); - } - } + // Disable keepalives on the connection. + val = 0; // disable + if (setsockopt(mSocket, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); } - else - err = CHIP_ERROR_CONNECTION_ABORTED; + return res; +} - // Unlock LwIP stack - UNLOCK_TCPIP_CORE(); +CHIP_ERROR TCPEndPoint::AckReceive(uint16_t len) +{ + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + // nothing to do for sockets case + return CHIP_NO_ERROR; +} -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS +CHIP_ERROR TCPEndPoint::SetUserTimeoutImpl(uint32_t userTimeoutMillis) +{ +#if defined(TCP_USER_TIMEOUT) + // Set the user timeout + uint32_t val = userTimeoutMillis; + if (setsockopt(mSocket, TCP_SOCKOPT_LEVEL, TCP_USER_TIMEOUT, &val, sizeof(val)) != 0) + return CHIP_ERROR_POSIX(errno); + return CHIP_NO_ERROR; +#else // TCP_USER_TIMEOUT + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif // defined(TCP_USER_TIMEOUT) +} + +void TCPEndPoint::InitImpl() +{ +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + mBytesWrittenSinceLastProbe = 0; + mLastTCPKernelSendQueueLen = 0; +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT +} + +CHIP_ERROR TCPEndPoint::DriveSendingImpl() +{ + CHIP_ERROR err = CHIP_NO_ERROR; #ifdef MSG_NOSIGNAL const int sendFlags = MSG_NOSIGNAL; @@ -1449,206 +1721,55 @@ CHIP_ERROR TCPEndPoint::DriveSending() if (!mUserTimeoutTimerRunning) { - // Timer was not running before this write. So, start - // the timer. - - StartTCPUserTimeoutTimer(); - } - else if (isProgressing) - { - // Progress is being made. So, shift the timer - // forward if it was started. - - RestartTCPUserTimeoutTimer(); - } -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - - if (lenSent < bufLen) - break; - } - - if (err == CHIP_NO_ERROR) - { - // If we're in the SendShutdown state and the send queue is now empty, shutdown writing on the socket. - if (State == kState_SendShutdown && mSendQueue.IsNull()) - { - if (shutdown(mSocket, SHUT_WR) != 0) - err = CHIP_ERROR_POSIX(errno); - } - } - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - - if (err != CHIP_NO_ERROR) - DoClose(err, false); - - CHIP_SYSTEM_FAULT_INJECT_ASYNC_EVENT(); - - return err; -} - -void TCPEndPoint::DriveReceiving() -{ - // If there's data in the receive queue and the app is ready to receive it then call the app's callback - // with the entire receive queue. - if (!mRcvQueue.IsNull() && ReceiveEnabled && OnDataReceived != nullptr) - { - // Acknowledgement is done after handling the buffers to allow the - // application processing to throttle flow. - uint16_t ackLength = mRcvQueue->TotalLength(); - CHIP_ERROR err = OnDataReceived(this, std::move(mRcvQueue)); - if (err != CHIP_NO_ERROR) - { - DoClose(err, false); - return; - } - AckReceive(ackLength); - } - - // If the connection is closing, and the receive queue is now empty, call DoClose() to complete - // the process of closing the connection. - if (State == kState_Closing && mRcvQueue.IsNull()) - DoClose(CHIP_NO_ERROR, false); -} - -void TCPEndPoint::HandleConnectComplete(CHIP_ERROR err) -{ - // If the connect succeeded enter the Connected state and call the app's callback. - if (err == CHIP_NO_ERROR) - { - // Stop the TCP Connect timer in case it is still running. - StopConnectTimer(); - - // Mark the connection as being active. - MarkActive(); - - State = kState_Connected; - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - // Wait for ability to read or write on this endpoint. - err = static_cast(Layer().SystemLayer())->RequestCallbackOnPendingRead(mWatch); - if (err == CHIP_NO_ERROR) - { - err = static_cast(Layer().SystemLayer())->RequestCallbackOnPendingWrite(mWatch); - } - if (err != CHIP_NO_ERROR) - { - DoClose(err, false); - return; - } -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - - if (OnConnectComplete != nullptr) - OnConnectComplete(this, CHIP_NO_ERROR); - } - - // Otherwise, close the connection with an error. - else - { - DoClose(err, false); - } -} - -CHIP_ERROR TCPEndPoint::DoClose(CHIP_ERROR err, bool suppressCallback) -{ - int oldState = State; - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - struct linger lingerStruct; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - - // If in one of the connected states (Connected, LocalShutdown, PeerShutdown or Closing) - // AND this is a graceful close (i.e. not prompted by an error) - // AND there is data waiting to be processed on either the send or receive queues - // ... THEN enter the Closing state, allowing the queued data to drain, - // ... OTHERWISE go straight to the Closed state. - if (IsConnected() && err == CHIP_NO_ERROR && (!mSendQueue.IsNull() || !mRcvQueue.IsNull())) - State = kState_Closing; - else - State = kState_Closed; - - if (oldState != kState_Closed) - { - // Stop the Connect timer in case it is still running. - StopConnectTimer(); - } - - // If not making a state transition, return immediately. - if (State == oldState) - { - return CHIP_NO_ERROR; - } - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - - // Lock LwIP stack - LOCK_TCPIP_CORE(); - - // If the LwIP PCB hasn't been closed yet... - if (mTCP != NULL) - { - // If the endpoint was a connection endpoint (vs. a listening endpoint)... - if (oldState != kState_Listening) - { - // Prevent further callbacks for incoming data. This has the effect of instructing - // LwIP to discard any further data received from the peer. - tcp_recv(mTCP, NULL); - - // If entering the Closed state... - if (State == kState_Closed) - { - // Prevent further callbacks to the error handler. - // - // Note: It is important to understand that LwIP can continue to make callbacks after - // a PCB has been closed via the tcp_close() API. In particular, LwIP will continue - // to call the 'data sent' callback to signal the acknowledgment of data that was - // sent, but not acknowledged, prior to the close call. Additionally, LwIP will call - // the error callback if the peer fails to respond in a timely manner to the either - // sent data or the FIN. Unfortunately, there is no callback in the case where the - // connection closes successfully. Because of this, it is impossible know definitively - // when LwIP will no longer make callbacks to its user. Thus we must block further - // callbacks to prevent them from happening after the endpoint has been freed. - // - tcp_err(mTCP, NULL); - - // If the endpoint is being closed without error, THEN call tcp_close() to close the underlying - // TCP connection gracefully, preserving any in-transit send data. - if (err == CHIP_NO_ERROR) - { - tcp_close(mTCP); - } + // Timer was not running before this write. So, start + // the timer. - // OTHERWISE, call tcp_abort() to abort the TCP connection, discarding any in-transit data. - else - { - tcp_abort(mTCP); - } + StartTCPUserTimeoutTimer(); + } + else if (isProgressing) + { + // Progress is being made. So, shift the timer + // forward if it was started. - // Discard the reference to the PCB to ensure there is no further interaction with it - // after this point. - mTCP = NULL; - mLwIPEndPointType = LwIPEndPointType::Unknown; - } + RestartTCPUserTimeoutTimer(); } +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - // OTHERWISE the endpoint was being used for listening, so simply close it. - else - { - tcp_close(mTCP); + if (lenSent < bufLen) + break; + } - // Discard the reference to the PCB to ensure there is no further interaction with it - // after this point. - mTCP = NULL; - mLwIPEndPointType = LwIPEndPointType::Unknown; + if (err == CHIP_NO_ERROR) + { + // If we're in the SendShutdown state and the send queue is now empty, shutdown writing on the socket. + if (State == kState_SendShutdown && mSendQueue.IsNull()) + { + if (shutdown(mSocket, SHUT_WR) != 0) + err = CHIP_ERROR_POSIX(errno); } } - // Unlock LwIP stack - UNLOCK_TCPIP_CORE(); + return err; +} -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +void TCPEndPoint::HandleConnectCompleteImpl() +{ + // Wait for ability to read or write on this endpoint. + CHIP_ERROR err = static_cast(Layer().SystemLayer())->RequestCallbackOnPendingRead(mWatch); + if (err == CHIP_NO_ERROR) + { + err = static_cast(Layer().SystemLayer())->RequestCallbackOnPendingWrite(mWatch); + } + if (err != CHIP_NO_ERROR) + { + DoClose(err, false); + return; + } +} -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS +void TCPEndPoint::DoCloseImpl(CHIP_ERROR err, int oldState) +{ + struct linger lingerStruct; // If the socket hasn't been closed already... if (mSocket != kInvalidSocketFd) @@ -1686,57 +1807,6 @@ CHIP_ERROR TCPEndPoint::DoClose(CHIP_ERROR err, bool suppressCallback) dispatch_release(mWriteableSource); } #endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - // Stop the TCP UserTimeout timer if it is running. - StopTCPUserTimeoutTimer(); -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - - // If entering the Closed state... - if (State == kState_Closed) - { - // Clear clear the send and receive queues. - mSendQueue = nullptr; - mRcvQueue = nullptr; -#if CHIP_SYSTEM_CONFIG_USE_LWIP - mUnackedLength = 0; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - - // Call the appropriate app callback if allowed. - if (!suppressCallback) - { - if (oldState == kState_Connecting) - { - if (OnConnectComplete != nullptr) - OnConnectComplete(this, err); - } - else if ((oldState == kState_Connected || oldState == kState_SendShutdown || oldState == kState_ReceiveShutdown || - oldState == kState_Closing) && - OnConnectionClosed != nullptr) - OnConnectionClosed(this, err); - } - - // Decrement the ref count that was added when the connection started (in Connect()) or listening started (in Listen()). - // - // When using LwIP, post a callback to Release() rather than calling it directly. Since up-calls - // from LwIP are delivered as events (via the LwIP* methods), we must ensure that all events have been - // cleared from the queue before the end point gets freed, otherwise we'll end up accessing freed memory. - // We achieve this by first preventing further up-calls from LwIP (via the call to tcp_abort() above) - // and then queuing the Release() call to happen after all existing events have been processed. - // - if (oldState != kState_Ready && oldState != kState_Bound) - { -#if CHIP_SYSTEM_CONFIG_USE_LWIP - DeferredFree(kReleaseDeferralErrorTactic_Ignore); -#else // !CHIP_SYSTEM_CONFIG_USE_LWIP - Release(); -#endif // !CHIP_SYSTEM_CONFIG_USE_LWIP - } - } - - return err; } #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT @@ -1749,7 +1819,6 @@ void TCPEndPoint::TCPUserTimeoutHandler(chip::System::Layer * aSystemLayer, void // Set the timer running flag to false tcpEndPoint->mUserTimeoutTimerRunning = false; -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS CHIP_ERROR err = CHIP_NO_ERROR; bool isProgressing = false; err = tcpEndPoint->CheckConnectionProgress(isProgressing); @@ -1808,1009 +1877,927 @@ void TCPEndPoint::TCPUserTimeoutHandler(chip::System::Layer * aSystemLayer, void tcpEndPoint->DoClose(err, false); } -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // Close Connection as we have timed out and there is still - // data not sent out successfully. - - tcpEndPoint->DoClose(INET_ERROR_TCP_USER_TIMEOUT, false); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP } +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -void TCPEndPoint::ScheduleNextTCPUserTimeoutPoll(uint32_t aTimeOut) +CHIP_ERROR TCPEndPoint::BindSrcAddrFromIntf(IPAddressType addrType, InterfaceId intfId) { - Layer().SystemLayer()->StartTimer(aTimeOut, TCPUserTimeoutHandler, this); -} + // If we are trying to make a TCP connection over a 'specified target interface', + // then we bind the TCPEndPoint to an IP address on that target interface + // and use that address as the source address for that connection. This is + // done in the event that directly binding the connection to the target + // interface is not allowed due to insufficient privileges. + VerifyOrReturnError(State != kState_Bound, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); -#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS -void TCPEndPoint::SetTCPSendIdleAndNotifyChange(bool aIsTCPSendIdle) -{ - if (mIsTCPSendIdle != aIsTCPSendIdle) + bool ipAddrFound = false; + for (InterfaceAddressIterator addrIter; addrIter.HasCurrent(); addrIter.Next()) { - ChipLogDetail(Inet, "TCP con send channel idle state changed : %s", aIsTCPSendIdle ? "false->true" : "true->false"); - - // Set the current Idle state - mIsTCPSendIdle = aIsTCPSendIdle; + const IPAddress curAddr = addrIter.GetAddress(); + const InterfaceId curIntfId = addrIter.GetInterface(); - if (OnTCPSendIdleChanged) + if (curIntfId == intfId) { - OnTCPSendIdleChanged(this, mIsTCPSendIdle); - } - } -} -#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - -void TCPEndPoint::StartTCPUserTimeoutTimer() -{ - uint32_t timeOut = mUserTimeoutMillis; - -#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - // Set timeout to the poll interval - - timeOut = mTCPSendQueuePollPeriodMillis; - - // Reset the poll count - - mTCPSendQueueRemainingPollCount = MaxTCPSendQueuePolls(); -#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - - ScheduleNextTCPUserTimeoutPoll(timeOut); - - mUserTimeoutTimerRunning = true; -} - -void TCPEndPoint::StopTCPUserTimeoutTimer() -{ - Layer().SystemLayer()->CancelTimer(TCPUserTimeoutHandler, this); - mUserTimeoutTimerRunning = false; -} - -void TCPEndPoint::RestartTCPUserTimeoutTimer() -{ - StopTCPUserTimeoutTimer(); - StartTCPUserTimeoutTimer(); -} - -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - -uint16_t TCPEndPoint::RemainingToSend() -{ - if (mSendQueue.IsNull()) - { - return 0; - } - else - { - // We can never have reported more unacked data than there is pending - // in the send queue! This would indicate a critical accounting bug. - VerifyOrDie(mUnackedLength <= mSendQueue->TotalLength()); - - return static_cast(mSendQueue->TotalLength() - mUnackedLength); - } -} + // Search for an IPv4 address on the TargetInterface -TCPEndPoint::BufferOffset TCPEndPoint::FindStartOfUnsent() -{ - // Find first packet buffer with remaining data to send by skipping - // all sent but un-acked data. This is necessary because of the Consume() - // call in HandleDataSent(), which potentially releases backing memory for - // fully-sent packet buffers, causing an invalidation of all possible - // offsets one might have cached. The TCP acnowledgements may come back - // with a variety of sizes depending on prior activity, and size of the - // send window. The only way to ensure we get the correct offsets into - // unsent data while retaining the buffers that have un-acked data is to - // traverse all sent-but-unacked data in the chain to reach the beginning - // of ready-to-send data. - TCPEndPoint::BufferOffset startOfUnsent(mSendQueue.Retain()); - uint16_t leftToSkip = mUnackedLength; +#if INET_CONFIG_ENABLE_IPV4 + if (addrType == kIPAddressType_IPv4) + { + if (curAddr.IsIPv4()) + { + // Bind to the IPv4 address of the TargetInterface + ipAddrFound = true; + ReturnErrorOnFailure(Bind(kIPAddressType_IPv4, curAddr, 0, true)); - VerifyOrDie(leftToSkip < mSendQueue->TotalLength()); + break; + } + } +#endif // INET_CONFIG_ENABLE_IPV4 + if (addrType == kIPAddressType_IPv6) + { + // Select an IPv6 address on the interface that is not + // a link local or a multicast address. + // TODO: Define a proper IPv6GlobalUnicast address checker. + if (!curAddr.IsIPv4() && !curAddr.IsIPv6LinkLocal() && !curAddr.IsMulticast()) + { + // Bind to the IPv6 address of the TargetInterface + ipAddrFound = true; + ReturnErrorOnFailure(Bind(kIPAddressType_IPv6, curAddr, 0, true)); - while (leftToSkip > 0) - { - VerifyOrDie(!startOfUnsent.buffer.IsNull()); - uint16_t bufDataLen = startOfUnsent.buffer->DataLength(); - if (leftToSkip >= bufDataLen) - { - // We have more to skip than current packet buffer size. - // Follow the chain to continue. - startOfUnsent.buffer.Advance(); - leftToSkip = static_cast(leftToSkip - bufDataLen); - } - else - { - // Done skipping all data, currentUnsentBuf is first packet buffer - // containing unsent data. - startOfUnsent.offset = leftToSkip; - leftToSkip = 0; + break; + } + } } } - return startOfUnsent; + VerifyOrReturnError(ipAddrFound, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + return CHIP_NO_ERROR; } -CHIP_ERROR TCPEndPoint::GetPCB(IPAddressType addrType) +CHIP_ERROR TCPEndPoint::GetSocket(IPAddressType addrType) { - // IMMPORTANT: This method MUST be called with the LwIP stack LOCKED! - -#if LWIP_VERSION_MAJOR > 1 || LWIP_VERSION_MINOR >= 5 - if (mTCP == NULL) + if (mSocket == kInvalidSocketFd) { - switch (addrType) - { - case kIPAddressType_IPv6: - mTCP = tcp_new_ip_type(IPADDR_TYPE_V6); - break; - + int family; + if (addrType == kIPAddressType_IPv6) + family = PF_INET6; #if INET_CONFIG_ENABLE_IPV4 - case kIPAddressType_IPv4: - mTCP = tcp_new_ip_type(IPADDR_TYPE_V4); - break; + else if (addrType == kIPAddressType_IPv4) + family = PF_INET; #endif // INET_CONFIG_ENABLE_IPV4 - - default: + else return INET_ERROR_WRONG_ADDRESS_TYPE; - } + mSocket = ::socket(family, SOCK_STREAM | SOCK_FLAGS, 0); + if (mSocket == -1) + return CHIP_ERROR_POSIX(errno); + ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->StartWatchingSocket(mSocket, &mWatch)); + mAddrType = addrType; - if (mTCP == NULL) + // If creating an IPv6 socket, tell the kernel that it will be IPv6 only. This makes it + // posible to bind two sockets to the same port, one for IPv4 and one for IPv6. +#ifdef IPV6_V6ONLY + if (family == PF_INET6) { - return CHIP_ERROR_NO_MEMORY; + int one = 1; + setsockopt(mSocket, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); } - else +#endif // defined(IPV6_V6ONLY) + + // On systems that support it, disable the delivery of SIGPIPE signals when writing to a closed + // socket. +#ifdef SO_NOSIGPIPE { - mLwIPEndPointType = LwIPEndPointType::TCP; + int one = 1; + int res = setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); + if (res != 0) + { + ChipLogError(Inet, "SO_NOSIGPIPE: %d", errno); + } } +#endif // defined(SO_NOSIGPIPE) } - else + else if (mAddrType != addrType) { - switch (IP_GET_TYPE(&mTCP->local_ip)) - { - case IPADDR_TYPE_V6: - if (addrType != kIPAddressType_IPv6) - return INET_ERROR_WRONG_ADDRESS_TYPE; - break; + return CHIP_ERROR_INCORRECT_STATE; + } -#if INET_CONFIG_ENABLE_IPV4 - case IPADDR_TYPE_V4: - if (addrType != kIPAddressType_IPv4) - return INET_ERROR_WRONG_ADDRESS_TYPE; - break; -#endif // INET_CONFIG_ENABLE_IPV4 + return CHIP_NO_ERROR; +} - default: - break; - } - } -#else // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 - if (mTCP == NULL) +// static +void TCPEndPoint::HandlePendingIO(System::SocketEvents events, intptr_t data) +{ + reinterpret_cast(data)->HandlePendingIO(events); +} + +void TCPEndPoint::HandlePendingIO(System::SocketEvents events) +{ + // Prevent the end point from being freed while in the middle of a callback. + Retain(); + + // If in the Listening state, and the app is ready to receive a connection, and there is a connection + // ready to be received on the socket, process the incoming connection. + if (State == kState_Listening) { - if (addrType == kIPAddressType_IPv6) - mTCP = tcp_new_ip6(); -#if INET_CONFIG_ENABLE_IPV4 - else if (addrType == kIPAddressType_IPv4) - mTCP = tcp_new(); -#endif // INET_CONFIG_ENABLE_IPV4 - else - return INET_ERROR_WRONG_ADDRESS_TYPE; - if (mTCP == NULL) + if (OnConnectionReceived != nullptr && events.Has(System::SocketEventFlags::kRead)) { - return CHIP_ERROR_NO_MEMORY; + HandleIncomingConnection(); } - else + } + + // If in the processes of initiating a connection... + else if (State == kState_Connecting) + { + // The socket being writable indicates the connection has completed (successfully or otherwise). + if (events.Has(System::SocketEventFlags::kWrite)) { - mLwIPEndPointType = LwIPEndPointType::TCP; +#if !__MBED__ + // Get the connection result from the socket. + int osConRes; + socklen_t optLen = sizeof(osConRes); + if (getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &osConRes, &optLen) != 0) + osConRes = errno; +#else + // On Mbed OS, connect blocks and never returns EINPROGRESS + // The socket option SO_ERROR is not available. + int osConRes = 0; +#endif + CHIP_ERROR conRes = CHIP_ERROR_POSIX(osConRes); + + // Process the connection result. + HandleConnectComplete(conRes); } } + else { -#if INET_CONFIG_ENABLE_IPV4 - const IPAddressType pcbType = PCB_ISIPV6(mTCP) ? kIPAddressType_IPv6 : kIPAddressType_IPv4; -#else // !INET_CONFIG_ENABLE_IPV4 - const IPAddressType pcbType = kIPAddressType_IPv6; -#endif // !INET_CONFIG_ENABLE_IPV4 - if (addrType != pcbType) - return INET_ERROR_WRONG_ADDRESS_TYPE; + // If in a state where sending is allowed, and there is data to be sent, and the socket is ready for + // writing, drive outbound data into the connection. + if (IsConnected() && !mSendQueue.IsNull() && events.Has(System::SocketEventFlags::kWrite)) + DriveSending(); + + // If in a state were receiving is allowed, and the app is ready to receive data, and data is ready + // on the socket, receive inbound data from the connection. + if ((State == kState_Connected || State == kState_SendShutdown) && ReceiveEnabled && OnDataReceived != nullptr && + events.Has(System::SocketEventFlags::kRead)) + ReceiveData(); } -#endif // LWIP_VERSION_MAJOR <= 1 || LWIP_VERSION_MINOR >= 5 - return CHIP_NO_ERROR; + Release(); } -void TCPEndPoint::HandleDataSent(uint16_t lenSent) +void TCPEndPoint::ReceiveData() { - if (IsConnected()) + System::PacketBufferHandle rcvBuf; + bool isNewBuf = true; + + if (mRcvQueue.IsNull()) + rcvBuf = System::PacketBufferHandle::New(kMaxReceiveMessageSize, 0); + else { - // Ensure we do not have internal inconsistency in the lwIP, which - // could cause invalid pointer accesses. - if (lenSent > mUnackedLength) + rcvBuf = mRcvQueue->Last(); + if (rcvBuf->AvailableDataLength() == 0) { - ChipLogError(Inet, "Got more ACKed bytes (%d) than were pending (%d)", (int) lenSent, (int) mUnackedLength); - DoClose(CHIP_ERROR_UNEXPECTED_EVENT, false); - return; + rcvBuf = System::PacketBufferHandle::New(kMaxReceiveMessageSize, 0); } - else if (mSendQueue.IsNull()) + else { - ChipLogError(Inet, "Got ACK for %d bytes but data backing gone", (int) lenSent); - DoClose(CHIP_ERROR_UNEXPECTED_EVENT, false); - return; + isNewBuf = false; + rcvBuf->CompactHead(); } + } - // Consume data off the head of the send queue equal to the amount of data being acknowledged. - mSendQueue.Consume(lenSent); - mUnackedLength = static_cast(mUnackedLength - lenSent); + if (rcvBuf.IsNull()) + { + DoClose(CHIP_ERROR_NO_MEMORY, false); + return; + } + + // Attempt to receive data from the socket. + ssize_t rcvLen = recv(mSocket, rcvBuf->Start() + rcvBuf->DataLength(), rcvBuf->AvailableDataLength(), 0); #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - // Only change the UserTimeout timer if lenSent > 0, - // indicating progress being made in sending data - // across. - if (lenSent > 0) - { - if (RemainingToSend() == 0) - { - // If the output queue has been flushed then stop the timer. + CHIP_ERROR err; + bool isProgressing = false; - StopTCPUserTimeoutTimer(); + err = CheckConnectionProgress(isProgressing); + if (err != CHIP_NO_ERROR) + { + DoClose(err, false); + + return; + } + + if (mLastTCPKernelSendQueueLen == 0) + { + // If the output queue has been flushed then stop the timer. + + StopTCPUserTimeoutTimer(); #if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - // Notify up if all outstanding data has been acknowledged + // Notify up if all outstanding data has been acknowledged - SetTCPSendIdleAndNotifyChange(true); -#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - } - else - { - // Progress is being made. So, shift the timer - // forward if it was started. - RestartTCPUserTimeoutTimer(); - } + if (mSendQueue.IsNull()) + { + SetTCPSendIdleAndNotifyChange(true); } +#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + } + else if (isProgressing && mUserTimeoutTimerRunning) + { + // Progress is being made. So, shift the timer + // forward if it was started. + RestartTCPUserTimeoutTimer(); + } #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + // If an error occurred, abort the connection. + if (rcvLen < 0) + { + int systemErrno = errno; + if (systemErrno == EAGAIN) + { + // Note: in this case, we opt to not retry the recv call, + // and instead we expect that the read flags will get + // reset correctly upon a subsequent return from the + // select call. + ChipLogError(Inet, "recv: EAGAIN, will retry"); - // Mark the connection as being active. - MarkActive(); - - // If requested, call the app's OnDataSent callback. - if (OnDataSent != NULL) - OnDataSent(this, lenSent); - - // If unsent data exists, attempt to send it now... - if (RemainingToSend() > 0) - DriveSending(); + return; + } - // If in the closing state and the send queue is now empty, attempt to transition to closed. - if ((State == kState_Closing) && (RemainingToSend() == 0)) - DoClose(CHIP_NO_ERROR, false); + DoClose(CHIP_ERROR_POSIX(systemErrno), false); } -} -void TCPEndPoint::HandleDataReceived(System::PacketBufferHandle && buf) -{ - // Only receive new data while in the Connected or SendShutdown states. - if (State == kState_Connected || State == kState_SendShutdown) + else { // Mark the connection as being active. MarkActive(); - // If we received a data buffer, queue it on the receive queue. If there's already data in - // the queue, compact the data into the head buffer. - if (!buf.IsNull()) - { - if (mRcvQueue.IsNull()) - { - mRcvQueue = std::move(buf); - } - else - { - mRcvQueue->AddToEnd(std::move(buf)); - mRcvQueue->CompactHead(); - } - } - - // Otherwise buf == NULL means the other side closed the connection, so ... - else + // If the peer closed their end of the connection... + if (rcvLen == 0) { - // If in the Connected state and the app has provided an OnPeerClose callback, // enter the ReceiveShutdown state. Providing an OnPeerClose callback allows // the app to decide whether to keep the send side of the connection open after // the peer has closed. If no OnPeerClose is provided, we assume that the app // wants to close both directions and automatically enter the Closing state. - if (State == kState_Connected && OnPeerClose != NULL) + if (State == kState_Connected && OnPeerClose != nullptr) State = kState_ReceiveShutdown; else State = kState_Closing; - + // Do not wait for ability to read on this endpoint. + (void) static_cast(Layer().SystemLayer())->ClearCallbackOnPendingRead(mWatch); // Call the app's OnPeerClose. - if (OnPeerClose != NULL) + if (OnPeerClose != nullptr) OnPeerClose(this); } - // Drive the received data into the app. - DriveReceiving(); + // Otherwise, add the new data onto the receive queue. + else + { + VerifyOrDie(rcvLen > 0); + size_t newDataLength = rcvBuf->DataLength() + static_cast(rcvLen); + VerifyOrDie(CanCastTo(newDataLength)); + if (isNewBuf) + { + rcvBuf->SetDataLength(static_cast(newDataLength)); + rcvBuf.RightSize(); + if (mRcvQueue.IsNull()) + mRcvQueue = std::move(rcvBuf); + else + mRcvQueue->AddToEnd(std::move(rcvBuf)); + } + else + { + rcvBuf->SetDataLength(static_cast(newDataLength), mRcvQueue); + } + } } + + // Drive any received data into the app. + DriveReceiving(); } -void TCPEndPoint::HandleIncomingConnection(TCPEndPoint * conEP) +void TCPEndPoint::HandleIncomingConnection() { - CHIP_ERROR err = CHIP_NO_ERROR; + CHIP_ERROR err = CHIP_NO_ERROR; + TCPEndPoint * conEP = nullptr; IPAddress peerAddr; uint16_t peerPort; - if (State == kState_Listening) + union { - // If there's no callback available, fail with an error. - if (OnConnectionReceived == NULL) - err = CHIP_ERROR_NO_CONNECTION_HANDLER; - - // Extract the peer's address information. - if (err == CHIP_NO_ERROR) - err = conEP->GetPeerInfo(&peerAddr, &peerPort); - - // If successful, call the app's callback function. - if (err == CHIP_NO_ERROR) - OnConnectionReceived(this, conEP, peerAddr, peerPort); - - // Otherwise clean up and call the app's error callback. - else if (OnAcceptError != NULL) - OnAcceptError(this, err); - } - else - err = CHIP_ERROR_INCORRECT_STATE; - - // If something failed above, abort and free the connection end point. - if (err != CHIP_NO_ERROR) - conEP->Free(); -} + sockaddr any; + sockaddr_in in; + sockaddr_in6 in6; + } sa; + memset(&sa, 0, sizeof(sa)); + socklen_t saLen = sizeof(sa); -void TCPEndPoint::HandleError(CHIP_ERROR err) -{ - if (State == kState_Listening) + // Accept the new connection. + int conSocket = accept(mSocket, &sa.any, &saLen); + if (conSocket == -1) { - if (OnAcceptError != NULL) - OnAcceptError(this, err); + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + return; + } + else + { + err = CHIP_ERROR_POSIX(errno); + } } - else - DoClose(err, false); -} -err_t TCPEndPoint::LwIPHandleConnectComplete(void * arg, struct tcp_pcb * tpcb, err_t lwipErr) -{ - err_t res = ERR_OK; + // If there's no callback available, fail with an error. + if (err == CHIP_NO_ERROR && OnConnectionReceived == nullptr) + err = CHIP_ERROR_NO_CONNECTION_HANDLER; - if (arg != NULL) + // Extract the peer's address information. + if (err == CHIP_NO_ERROR) { - CHIP_ERROR conErr; - TCPEndPoint * ep = static_cast(arg); - System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); - - if (lwipErr == ERR_OK) + if (sa.any.sa_family == AF_INET6) { - // Setup LwIP callback functions for data transmission. - tcp_recv(ep->mTCP, LwIPHandleDataReceived); - tcp_sent(ep->mTCP, LwIPHandleDataSent); + peerAddr = IPAddress::FromIPv6(sa.in6.sin6_addr); + peerPort = ntohs(sa.in6.sin6_port); } - - // Post callback to HandleConnectComplete. - conErr = chip::System::MapErrorLwIP(lwipErr); - if (lSystemLayer->PostEvent(*ep, kInetEvent_TCPConnectComplete, static_cast(conErr.AsInteger())) != - CHIP_NO_ERROR) - res = ERR_ABRT; +#if INET_CONFIG_ENABLE_IPV4 + else if (sa.any.sa_family == AF_INET) + { + peerAddr = IPAddress::FromIPv4(sa.in.sin_addr); + peerPort = ntohs(sa.in.sin_port); + } +#endif // INET_CONFIG_ENABLE_IPV4 + else + err = CHIP_ERROR_INCORRECT_STATE; } - else - res = ERR_ABRT; - - if (res != ERR_OK) - tcp_abort(tpcb); - - return res; -} - -err_t TCPEndPoint::LwIPHandleIncomingConnection(void * arg, struct tcp_pcb * tpcb, err_t lwipErr) -{ - CHIP_ERROR err = chip::System::MapErrorLwIP(lwipErr); - if (arg != NULL) + // Attempt to allocate an end point object. + if (err == CHIP_NO_ERROR) { - TCPEndPoint * listenEP = static_cast(arg); - TCPEndPoint * conEP = NULL; - System::LayerLwIP * lSystemLayer = static_cast(listenEP->Layer().SystemLayer()); + InetLayer & lInetLayer = Layer(); - // Tell LwIP we've accepted the connection so it can decrement the listen PCB's pending_accepts counter. - tcp_accepted(listenEP->mTCP); + err = lInetLayer.NewTCPEndPoint(&conEP); + } - // If we did in fact receive a connection, rather than an error, attempt to allocate an end point object. - // - // NOTE: Although most of the LwIP callbacks defer the real work to happen on the endpoint's thread - // (by posting events to the thread's event queue) we can't do that here because as soon as this - // function returns, LwIP is free to begin calling callbacks on the new PCB. For that to work we need - // to have an end point associated with the PCB. - // + // If all went well... + if (err == CHIP_NO_ERROR) + { + // Put the new end point into the Connected state. + conEP->mSocket = conSocket; + err = static_cast(Layer().SystemLayer())->StartWatchingSocket(conSocket, &conEP->mWatch); if (err == CHIP_NO_ERROR) { - InetLayer & lInetLayer = listenEP->Layer(); - - err = lInetLayer.NewTCPEndPoint(&conEP); - } + conEP->State = kState_Connected; +#if INET_CONFIG_ENABLE_IPV4 + conEP->mAddrType = (sa.any.sa_family == AF_INET6) ? kIPAddressType_IPv6 : kIPAddressType_IPv4; +#else // !INET_CONFIG_ENABLE_IPV4 + conEP->mAddrType = kIPAddressType_IPv6; +#endif // !INET_CONFIG_ENABLE_IPV4 + conEP->Retain(); - // Ensure that TCP timers have been started - if (err == CHIP_NO_ERROR) - { - err_t error = start_tcp_timers(); - if (error != ERR_OK) + // Wait for ability to read on this endpoint. + auto conEPLayer = static_cast(conEP->Layer().SystemLayer()); + err = conEPLayer->SetCallback(conEP->mWatch, HandlePendingIO, reinterpret_cast(conEP)); + if (err == CHIP_NO_ERROR) { - err = chip::System::MapErrorLwIP(error); + err = conEPLayer->RequestCallbackOnPendingRead(conEP->mWatch); + } + if (err == CHIP_NO_ERROR) + { + // Call the app's callback function. + OnConnectionReceived(this, conEP, peerAddr, peerPort); + return; } } + } - // If successful in allocating an end point... - if (err == CHIP_NO_ERROR) + // Otherwise immediately close the connection, clean up and call the app's error callback. + if (conSocket != -1) + close(conSocket); + if (conEP != nullptr) + { + if (conEP->State == kState_Connected) { - // Put the new end point into the Connected state. - conEP->State = kState_Connected; - conEP->mTCP = tpcb; - conEP->mLwIPEndPointType = LwIPEndPointType::TCP; - conEP->Retain(); + conEP->Release(); + } + conEP->Release(); + } + if (OnAcceptError != nullptr) + OnAcceptError(this, err); +} - // Setup LwIP callback functions for the new PCB. - tcp_arg(tpcb, conEP); - tcp_recv(tpcb, LwIPHandleDataReceived); - tcp_sent(tpcb, LwIPHandleDataSent); - tcp_err(tpcb, LwIPHandleError); +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT +/** + * This function probes the TCP output queue and checks if data is successfully + * being transferred to the other end. + */ +CHIP_ERROR TCPEndPoint::CheckConnectionProgress(bool & isProgressing) +{ + int currPendingBytesRaw = 0; + uint32_t currPendingBytes; // Will be initialized once we know it's safe. - // Post a callback to the HandleConnectionReceived() function, passing it the new end point. - if (lSystemLayer->PostEvent(*listenEP, kInetEvent_TCPConnectionReceived, (uintptr_t) conEP) != CHIP_NO_ERROR) - { - err = CHIP_ERROR_CONNECTION_ABORTED; - conEP->Release(); // for the Retain() above - conEP->Release(); // for the Retain() in NewTCPEndPoint() - } - } + // Fetch the bytes pending successful transmission in the TCP out queue. - // Otherwise, there was an error accepting the connection, so post a callback to the HandleError function. - else - lSystemLayer->PostEvent(*listenEP, kInetEvent_TCPError, static_cast(err.AsInteger())); + if (ioctl(mSocket, TIOCOUTQ, &currPendingBytesRaw) < 0) + { + return CHIP_ERROR_POSIX(errno); } - else - err = CHIP_ERROR_CONNECTION_ABORTED; - if (err != CHIP_NO_ERROR && tpcb != NULL) + if (!CanCastTo(currPendingBytesRaw)) { - tcp_abort(tpcb); - return ERR_ABRT; + return CHIP_ERROR_INCORRECT_STATE; + } + + currPendingBytes = static_cast(currPendingBytesRaw); + + if ((currPendingBytes != 0) && (mBytesWrittenSinceLastProbe + mLastTCPKernelSendQueueLen == currPendingBytes)) + { + // No progress has been made + + isProgressing = false; } else { - return ERR_OK; + // Data is flowing successfully + + isProgressing = true; } + + // Reset the value of the bytes written since the last probe into the tcp + // outqueue was made and update the last tcp outqueue sample. + + mBytesWrittenSinceLastProbe = 0; + + mLastTCPKernelSendQueueLen = currPendingBytes; + + return CHIP_NO_ERROR; } +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -err_t TCPEndPoint::LwIPHandleDataReceived(void * arg, struct tcp_pcb * tpcb, struct pbuf * p, err_t err) +#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + +CHIP_ERROR TCPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) { - err_t res = ERR_OK; + CHIP_ERROR res = CHIP_NO_ERROR; - if (arg != NULL) - { - TCPEndPoint * ep = static_cast(arg); - System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); + if (State != kState_Ready) + return CHIP_ERROR_INCORRECT_STATE; - // Post callback to HandleDataReceived. - if (lSystemLayer->PostEvent(*ep, kInetEvent_TCPDataReceived, (uintptr_t) p) != CHIP_NO_ERROR) - res = ERR_ABRT; - } - else - res = ERR_ABRT; + if (addr != IPAddress::Any && addr.Type() != kIPAddressType_Any && addr.Type() != addrType) + return INET_ERROR_WRONG_ADDRESS_TYPE; - if (res != ERR_OK) - tcp_abort(tpcb); + res = BindImpl(addrType, addr, port, reuseAddr); + + if (res == CHIP_NO_ERROR) + { + State = kState_Bound; + } return res; } -err_t TCPEndPoint::LwIPHandleDataSent(void * arg, struct tcp_pcb * tpcb, u16_t len) +CHIP_ERROR TCPEndPoint::Listen(uint16_t backlog) { - err_t res = ERR_OK; + CHIP_ERROR res = CHIP_NO_ERROR; - if (arg != NULL) - { - TCPEndPoint * ep = static_cast(arg); - System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); + if (State != kState_Bound) + return CHIP_ERROR_INCORRECT_STATE; - // Post callback to HandleDataReceived. - if (lSystemLayer->PostEvent(*ep, kInetEvent_TCPDataSent, (uintptr_t) len) != CHIP_NO_ERROR) - res = ERR_ABRT; - } - else - res = ERR_ABRT; + res = ListenImpl(backlog); - if (res != ERR_OK) - tcp_abort(tpcb); + if (res == CHIP_NO_ERROR) + { + // Once Listening, bump the reference count. The corresponding call to Release() + // [or on LwIP, DeferredRelease()] will happen in DoClose(). + Retain(); + State = kState_Listening; + } return res; } -void TCPEndPoint::LwIPHandleError(void * arg, err_t lwipErr) +CHIP_ERROR TCPEndPoint::Connect(const IPAddress & addr, uint16_t port, InterfaceId intfId) { - if (arg != NULL) - { - TCPEndPoint * ep = static_cast(arg); - System::LayerLwIP * lSystemLayer = static_cast(ep->Layer().SystemLayer()); + CHIP_ERROR res = CHIP_NO_ERROR; - // At this point LwIP has already freed the PCB. Since the thread that owns the TCPEndPoint may - // try to use the PCB before it receives the TCPError event posted below, we set the PCB to NULL - // as a means to signal the other thread that the connection has been aborted. The implication - // of this is that the mTCP field is shared state between the two threads and thus must only be - // accessed with the LwIP lock held. - ep->mTCP = NULL; - ep->mLwIPEndPointType = LwIPEndPointType::Unknown; + if (State != kState_Ready && State != kState_Bound) + return CHIP_ERROR_INCORRECT_STATE; - // Post callback to HandleError. - CHIP_ERROR err = chip::System::MapErrorLwIP(lwipErr); - lSystemLayer->PostEvent(*ep, kInetEvent_TCPError, static_cast(err.AsInteger())); - } -} + ReturnErrorOnFailure(ConnectImpl(addr, port, intfId)); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + StartConnectTimerIfSet(); -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS + return res; +} -CHIP_ERROR TCPEndPoint::BindSrcAddrFromIntf(IPAddressType addrType, InterfaceId intfId) +CHIP_ERROR TCPEndPoint::Send(System::PacketBufferHandle && data, bool push) { - // If we are trying to make a TCP connection over a 'specified target interface', - // then we bind the TCPEndPoint to an IP address on that target interface - // and use that address as the source address for that connection. This is - // done in the event that directly binding the connection to the target - // interface is not allowed due to insufficient privileges. - VerifyOrReturnError(State != kState_Bound, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + CHIP_ERROR res = CHIP_NO_ERROR; - bool ipAddrFound = false; - for (InterfaceAddressIterator addrIter; addrIter.HasCurrent(); addrIter.Next()) + if (State != kState_Connected && State != kState_ReceiveShutdown) { - const IPAddress curAddr = addrIter.GetAddress(); - const InterfaceId curIntfId = addrIter.GetInterface(); + return CHIP_ERROR_INCORRECT_STATE; + } + bool queueWasEmpty = mSendQueue.IsNull(); + if (queueWasEmpty) + { + mSendQueue = std::move(data); + } + else + { + mSendQueue->AddToEnd(std::move(data)); + } - if (curIntfId == intfId) - { - // Search for an IPv4 address on the TargetInterface + ReturnErrorOnFailure(SendQueuedImpl(queueWasEmpty)); -#if INET_CONFIG_ENABLE_IPV4 - if (addrType == kIPAddressType_IPv4) - { - if (curAddr.IsIPv4()) - { - // Bind to the IPv4 address of the TargetInterface - ipAddrFound = true; - ReturnErrorOnFailure(Bind(kIPAddressType_IPv4, curAddr, 0, true)); + if (push) + res = DriveSending(); - break; - } - } -#endif // INET_CONFIG_ENABLE_IPV4 - if (addrType == kIPAddressType_IPv6) - { - // Select an IPv6 address on the interface that is not - // a link local or a multicast address. - // TODO: Define a proper IPv6GlobalUnicast address checker. - if (!curAddr.IsIPv4() && !curAddr.IsIPv6LinkLocal() && !curAddr.IsMulticast()) - { - // Bind to the IPv6 address of the TargetInterface - ipAddrFound = true; - ReturnErrorOnFailure(Bind(kIPAddressType_IPv6, curAddr, 0, true)); + return res; +} - break; - } - } - } - } +CHIP_ERROR TCPEndPoint::SetReceivedDataForTesting(System::PacketBufferHandle && data) +{ + if (!IsConnected()) + return CHIP_ERROR_INCORRECT_STATE; - VerifyOrReturnError(ipAddrFound, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + mRcvQueue = std::move(data); return CHIP_NO_ERROR; } -CHIP_ERROR TCPEndPoint::GetSocket(IPAddressType addrType) +uint32_t TCPEndPoint::PendingSendLength() { - if (mSocket == kInvalidSocketFd) - { - int family; - if (addrType == kIPAddressType_IPv6) - family = PF_INET6; -#if INET_CONFIG_ENABLE_IPV4 - else if (addrType == kIPAddressType_IPv4) - family = PF_INET; -#endif // INET_CONFIG_ENABLE_IPV4 - else - return INET_ERROR_WRONG_ADDRESS_TYPE; - mSocket = ::socket(family, SOCK_STREAM | SOCK_FLAGS, 0); - if (mSocket == -1) - return CHIP_ERROR_POSIX(errno); - ReturnErrorOnFailure(static_cast(Layer().SystemLayer())->StartWatchingSocket(mSocket, &mWatch)); - mAddrType = addrType; + if (!mSendQueue.IsNull()) + return mSendQueue->TotalLength(); + return 0; +} - // If creating an IPv6 socket, tell the kernel that it will be IPv6 only. This makes it - // posible to bind two sockets to the same port, one for IPv4 and one for IPv6. -#ifdef IPV6_V6ONLY - if (family == PF_INET6) - { - int one = 1; - setsockopt(mSocket, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - } -#endif // defined(IPV6_V6ONLY) +uint32_t TCPEndPoint::PendingReceiveLength() +{ + if (!mRcvQueue.IsNull()) + return mRcvQueue->TotalLength(); + return 0; +} - // On systems that support it, disable the delivery of SIGPIPE signals when writing to a closed - // socket. -#ifdef SO_NOSIGPIPE - { - int one = 1; - int res = setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); - if (res != 0) - { - ChipLogError(Inet, "SO_NOSIGPIPE: %d", errno); - } - } -#endif // defined(SO_NOSIGPIPE) - } - else if (mAddrType != addrType) - { +CHIP_ERROR TCPEndPoint::Shutdown() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (!IsConnected()) return CHIP_ERROR_INCORRECT_STATE; + + // If fully connected, enter the SendShutdown state. + if (State == kState_Connected) + { + State = kState_SendShutdown; + DriveSending(); } - return CHIP_NO_ERROR; + // Otherwise, if the peer has already closed their end of the connection, + else if (State == kState_ReceiveShutdown) + err = DoClose(err, false); + + return err; +} + +CHIP_ERROR TCPEndPoint::Close() +{ + // Clear the receive queue. + mRcvQueue = nullptr; + + // Suppress closing callbacks, since the application explicitly called Close(). + OnConnectionClosed = nullptr; + OnPeerClose = nullptr; + OnConnectComplete = nullptr; + + // Perform a graceful close. + return DoClose(CHIP_NO_ERROR, true); } -// static -void TCPEndPoint::HandlePendingIO(System::SocketEvents events, intptr_t data) +void TCPEndPoint::Abort() { - reinterpret_cast(data)->HandlePendingIO(events); + // Suppress closing callbacks, since the application explicitly called Abort(). + OnConnectionClosed = nullptr; + OnPeerClose = nullptr; + OnConnectComplete = nullptr; + + DoClose(CHIP_ERROR_CONNECTION_ABORTED, true); } -void TCPEndPoint::HandlePendingIO(System::SocketEvents events) +void TCPEndPoint::Free() { - // Prevent the end point from being freed while in the middle of a callback. - Retain(); - - // If in the Listening state, and the app is ready to receive a connection, and there is a connection - // ready to be received on the socket, process the incoming connection. - if (State == kState_Listening) - { - if (OnConnectionReceived != nullptr && events.Has(System::SocketEventFlags::kRead)) - { - HandleIncomingConnection(); - } - } - - // If in the processes of initiating a connection... - else if (State == kState_Connecting) - { - // The socket being writable indicates the connection has completed (successfully or otherwise). - if (events.Has(System::SocketEventFlags::kWrite)) - { -#if !__MBED__ - // Get the connection result from the socket. - int osConRes; - socklen_t optLen = sizeof(osConRes); - if (getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &osConRes, &optLen) != 0) - osConRes = errno; -#else - // On Mbed OS, connect blocks and never returns EINPROGRESS - // The socket option SO_ERROR is not available. - int osConRes = 0; -#endif - CHIP_ERROR conRes = CHIP_ERROR_POSIX(osConRes); - - // Process the connection result. - HandleConnectComplete(conRes); - } - } + CHIP_ERROR err; - else - { - // If in a state where sending is allowed, and there is data to be sent, and the socket is ready for - // writing, drive outbound data into the connection. - if (IsConnected() && !mSendQueue.IsNull() && events.Has(System::SocketEventFlags::kWrite)) - DriveSending(); + // Ensure no callbacks to the app after this point. + OnAcceptError = nullptr; + OnConnectComplete = nullptr; + OnConnectionReceived = nullptr; + OnConnectionClosed = nullptr; + OnPeerClose = nullptr; + OnDataReceived = nullptr; + OnDataSent = nullptr; - // If in a state were receiving is allowed, and the app is ready to receive data, and data is ready - // on the socket, receive inbound data from the connection. - if ((State == kState_Connected || State == kState_SendShutdown) && ReceiveEnabled && OnDataReceived != nullptr && - events.Has(System::SocketEventFlags::kRead)) - ReceiveData(); - } + // Ensure the end point is Closed or Closing. + err = Close(); + if (err != CHIP_NO_ERROR) + Abort(); + // Release the Retain() that happened when the end point was allocated + // [on LwIP, the object may still be alive if DoClose() used the + // EndPointBasis::DeferredFree() method.] Release(); } -void TCPEndPoint::ReceiveData() +#if INET_TCP_IDLE_CHECK_INTERVAL > 0 +void TCPEndPoint::SetIdleTimeout(uint32_t timeoutMS) { - System::PacketBufferHandle rcvBuf; - bool isNewBuf = true; + uint32_t newIdleTimeout = (timeoutMS + (INET_TCP_IDLE_CHECK_INTERVAL - 1)) / INET_TCP_IDLE_CHECK_INTERVAL; + InetLayer & lInetLayer = Layer(); + bool isIdleTimerRunning = lInetLayer.IsIdleTimerRunning(); - if (mRcvQueue.IsNull()) - rcvBuf = System::PacketBufferHandle::New(kMaxReceiveMessageSize, 0); - else + if (newIdleTimeout > UINT16_MAX) + newIdleTimeout = UINT16_MAX; + mIdleTimeout = mRemainingIdleTime = static_cast(newIdleTimeout); + + if (!isIdleTimerRunning && mIdleTimeout) { - rcvBuf = mRcvQueue->Last(); - if (rcvBuf->AvailableDataLength() == 0) - { - rcvBuf = System::PacketBufferHandle::New(kMaxReceiveMessageSize, 0); - } - else - { - isNewBuf = false; - rcvBuf->CompactHead(); - } + Layer().SystemLayer()->StartTimer(INET_TCP_IDLE_CHECK_INTERVAL, InetLayer::HandleTCPInactivityTimer, &lInetLayer); } +} +#endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 - if (rcvBuf.IsNull()) +CHIP_ERROR TCPEndPoint::SetUserTimeout(uint32_t userTimeoutMillis) +{ + CHIP_ERROR res = CHIP_NO_ERROR; + + if (!IsConnected()) { - DoClose(CHIP_ERROR_NO_MEMORY, false); - return; + return CHIP_ERROR_INCORRECT_STATE; } - // Attempt to receive data from the socket. - ssize_t rcvLen = recv(mSocket, rcvBuf->Start() + rcvBuf->DataLength(), rcvBuf->AvailableDataLength(), 0); - #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - CHIP_ERROR err; - bool isProgressing = false; - err = CheckConnectionProgress(isProgressing); - if (err != CHIP_NO_ERROR) - { - DoClose(err, false); + // Store the User timeout configuration if it is being overridden. + mUserTimeoutMillis = userTimeoutMillis; - return; - } +#else // !INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - if (mLastTCPKernelSendQueueLen == 0) - { - // If the output queue has been flushed then stop the timer. + res = SetUserTimeoutImpl(userTimeoutMillis); - StopTCPUserTimeoutTimer(); +#endif // !INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - // Notify up if all outstanding data has been acknowledged + return res; +} - if (mSendQueue.IsNull()) - { - SetTCPSendIdleAndNotifyChange(true); - } -#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - } - else if (isProgressing && mUserTimeoutTimerRunning) +void TCPEndPoint::StartConnectTimerIfSet() +{ + if (mConnectTimeoutMsecs > 0) { - // Progress is being made. So, shift the timer - // forward if it was started. - RestartTCPUserTimeoutTimer(); + Layer().SystemLayer()->StartTimer(mConnectTimeoutMsecs, TCPConnectTimeoutHandler, this); } -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - // If an error occurred, abort the connection. - if (rcvLen < 0) - { - int systemErrno = errno; - if (systemErrno == EAGAIN) - { - // Note: in this case, we opt to not retry the recv call, - // and instead we expect that the read flags will get - // reset correctly upon a subsequent return from the - // select call. - ChipLogError(Inet, "recv: EAGAIN, will retry"); +} - return; - } +void TCPEndPoint::StopConnectTimer() +{ + Layer().SystemLayer()->CancelTimer(TCPConnectTimeoutHandler, this); +} - DoClose(CHIP_ERROR_POSIX(systemErrno), false); - } +void TCPEndPoint::TCPConnectTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState) +{ + TCPEndPoint * tcpEndPoint = reinterpret_cast(aAppState); - else - { - // Mark the connection as being active. - MarkActive(); + VerifyOrDie((aSystemLayer != nullptr) && (tcpEndPoint != nullptr)); - // If the peer closed their end of the connection... - if (rcvLen == 0) - { - // If in the Connected state and the app has provided an OnPeerClose callback, - // enter the ReceiveShutdown state. Providing an OnPeerClose callback allows - // the app to decide whether to keep the send side of the connection open after - // the peer has closed. If no OnPeerClose is provided, we assume that the app - // wants to close both directions and automatically enter the Closing state. - if (State == kState_Connected && OnPeerClose != nullptr) - State = kState_ReceiveShutdown; - else - State = kState_Closing; - // Do not wait for ability to read on this endpoint. - (void) static_cast(Layer().SystemLayer())->ClearCallbackOnPendingRead(mWatch); - // Call the app's OnPeerClose. - if (OnPeerClose != nullptr) - OnPeerClose(this); - } + // Close Connection as we have timed out and Connect has not returned to + // stop this timer. + tcpEndPoint->DoClose(INET_ERROR_TCP_CONNECT_TIMEOUT, false); +} - // Otherwise, add the new data onto the receive queue. - else - { - VerifyOrDie(rcvLen > 0); - size_t newDataLength = rcvBuf->DataLength() + static_cast(rcvLen); - VerifyOrDie(CanCastTo(newDataLength)); - if (isNewBuf) - { - rcvBuf->SetDataLength(static_cast(newDataLength)); - rcvBuf.RightSize(); - if (mRcvQueue.IsNull()) - mRcvQueue = std::move(rcvBuf); - else - mRcvQueue->AddToEnd(std::move(rcvBuf)); - } - else - { - rcvBuf->SetDataLength(static_cast(newDataLength), mRcvQueue); - } - } - } +bool TCPEndPoint::IsConnected(int state) +{ + return state == kState_Connected || state == kState_SendShutdown || state == kState_ReceiveShutdown || state == kState_Closing; +} + +void TCPEndPoint::Init(InetLayer * inetLayer) +{ + InitEndPointBasis(*inetLayer); + + ReceiveEnabled = true; + + // Initialize to zero for using system defaults. + mConnectTimeoutMsecs = 0; + +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + mUserTimeoutMillis = INET_CONFIG_DEFAULT_TCP_USER_TIMEOUT_MSEC; + + mUserTimeoutTimerRunning = false; + +#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + mIsTCPSendIdle = true; + + mTCPSendQueuePollPeriodMillis = INET_CONFIG_TCP_SEND_QUEUE_POLL_INTERVAL_MSEC; + + mTCPSendQueueRemainingPollCount = MaxTCPSendQueuePolls(); + + OnTCPSendIdleChanged = NULL; +#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - // Drive any received data into the app. - DriveReceiving(); + InitImpl(); } -void TCPEndPoint::HandleIncomingConnection() +CHIP_ERROR TCPEndPoint::DriveSending() { - CHIP_ERROR err = CHIP_NO_ERROR; - TCPEndPoint * conEP = nullptr; - IPAddress peerAddr; - uint16_t peerPort; + CHIP_ERROR err = DriveSendingImpl(); - union - { - sockaddr any; - sockaddr_in in; - sockaddr_in6 in6; - } sa; - memset(&sa, 0, sizeof(sa)); - socklen_t saLen = sizeof(sa); + if (err != CHIP_NO_ERROR) + DoClose(err, false); - // Accept the new connection. - int conSocket = accept(mSocket, &sa.any, &saLen); - if (conSocket == -1) + CHIP_SYSTEM_FAULT_INJECT_ASYNC_EVENT(); + + return err; +} + +void TCPEndPoint::DriveReceiving() +{ + // If there's data in the receive queue and the app is ready to receive it then call the app's callback + // with the entire receive queue. + if (!mRcvQueue.IsNull() && ReceiveEnabled && OnDataReceived != nullptr) { - if (errno == EAGAIN || errno == EWOULDBLOCK) + // Acknowledgement is done after handling the buffers to allow the + // application processing to throttle flow. + uint16_t ackLength = mRcvQueue->TotalLength(); + CHIP_ERROR err = OnDataReceived(this, std::move(mRcvQueue)); + if (err != CHIP_NO_ERROR) { + DoClose(err, false); return; } - else - { - err = CHIP_ERROR_POSIX(errno); - } + AckReceive(ackLength); } - // If there's no callback available, fail with an error. - if (err == CHIP_NO_ERROR && OnConnectionReceived == nullptr) - err = CHIP_ERROR_NO_CONNECTION_HANDLER; + // If the connection is closing, and the receive queue is now empty, call DoClose() to complete + // the process of closing the connection. + if (State == kState_Closing && mRcvQueue.IsNull()) + DoClose(CHIP_NO_ERROR, false); +} - // Extract the peer's address information. +void TCPEndPoint::HandleConnectComplete(CHIP_ERROR err) +{ + // If the connect succeeded enter the Connected state and call the app's callback. if (err == CHIP_NO_ERROR) { - if (sa.any.sa_family == AF_INET6) - { - peerAddr = IPAddress::FromIPv6(sa.in6.sin6_addr); - peerPort = ntohs(sa.in6.sin6_port); - } -#if INET_CONFIG_ENABLE_IPV4 - else if (sa.any.sa_family == AF_INET) - { - peerAddr = IPAddress::FromIPv4(sa.in.sin_addr); - peerPort = ntohs(sa.in.sin_port); - } -#endif // INET_CONFIG_ENABLE_IPV4 - else - err = CHIP_ERROR_INCORRECT_STATE; + // Stop the TCP Connect timer in case it is still running. + StopConnectTimer(); + + // Mark the connection as being active. + MarkActive(); + + State = kState_Connected; + + HandleConnectCompleteImpl(); + + if (OnConnectComplete != nullptr) + OnConnectComplete(this, CHIP_NO_ERROR); } - // Attempt to allocate an end point object. - if (err == CHIP_NO_ERROR) + // Otherwise, close the connection with an error. + else { - InetLayer & lInetLayer = Layer(); + DoClose(err, false); + } +} - err = lInetLayer.NewTCPEndPoint(&conEP); +CHIP_ERROR TCPEndPoint::DoClose(CHIP_ERROR err, bool suppressCallback) +{ + int oldState = State; + + // If in one of the connected states (Connected, LocalShutdown, PeerShutdown or Closing) + // AND this is a graceful close (i.e. not prompted by an error) + // AND there is data waiting to be processed on either the send or receive queues + // ... THEN enter the Closing state, allowing the queued data to drain, + // ... OTHERWISE go straight to the Closed state. + if (IsConnected() && err == CHIP_NO_ERROR && (!mSendQueue.IsNull() || !mRcvQueue.IsNull())) + State = kState_Closing; + else + State = kState_Closed; + + if (oldState != kState_Closed) + { + // Stop the Connect timer in case it is still running. + StopConnectTimer(); } - // If all went well... - if (err == CHIP_NO_ERROR) + // If not making a state transition, return immediately. + if (State == oldState) { - // Put the new end point into the Connected state. - conEP->mSocket = conSocket; - err = static_cast(Layer().SystemLayer())->StartWatchingSocket(conSocket, &conEP->mWatch); - if (err == CHIP_NO_ERROR) - { - conEP->State = kState_Connected; -#if INET_CONFIG_ENABLE_IPV4 - conEP->mAddrType = (sa.any.sa_family == AF_INET6) ? kIPAddressType_IPv6 : kIPAddressType_IPv4; -#else // !INET_CONFIG_ENABLE_IPV4 - conEP->mAddrType = kIPAddressType_IPv6; -#endif // !INET_CONFIG_ENABLE_IPV4 - conEP->Retain(); + return CHIP_NO_ERROR; + } - // Wait for ability to read on this endpoint. - auto conEPLayer = static_cast(conEP->Layer().SystemLayer()); - err = conEPLayer->SetCallback(conEP->mWatch, HandlePendingIO, reinterpret_cast(conEP)); - if (err == CHIP_NO_ERROR) - { - err = conEPLayer->RequestCallbackOnPendingRead(conEP->mWatch); - } - if (err == CHIP_NO_ERROR) + DoCloseImpl(err, oldState); + +#if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + // Stop the TCP UserTimeout timer if it is running. + StopTCPUserTimeoutTimer(); +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT + + // If entering the Closed state... + if (State == kState_Closed) + { + // Clear clear the send and receive queues. + mSendQueue = nullptr; + mRcvQueue = nullptr; + + // Call the appropriate app callback if allowed. + if (!suppressCallback) + { + if (oldState == kState_Connecting) { - // Call the app's callback function. - OnConnectionReceived(this, conEP, peerAddr, peerPort); - return; + if (OnConnectComplete != nullptr) + OnConnectComplete(this, err); } + else if ((oldState == kState_Connected || oldState == kState_SendShutdown || oldState == kState_ReceiveShutdown || + oldState == kState_Closing) && + OnConnectionClosed != nullptr) + OnConnectionClosed(this, err); } - } - // Otherwise immediately close the connection, clean up and call the app's error callback. - if (conSocket != -1) - close(conSocket); - if (conEP != nullptr) - { - if (conEP->State == kState_Connected) + // Decrement the ref count that was added when the connection started (in Connect()) or listening started (in Listen()). + // + // When using LwIP, post a callback to Release() rather than calling it directly. Since up-calls + // from LwIP are delivered as events (via the LwIP* methods), we must ensure that all events have been + // cleared from the queue before the end point gets freed, otherwise we'll end up accessing freed memory. + // We achieve this by first preventing further up-calls from LwIP (via the call to tcp_abort() above) + // and then queuing the Release() call to happen after all existing events have been processed. + // + if (oldState != kState_Ready && oldState != kState_Bound) { - conEP->Release(); + DeferredFree(kReleaseDeferralErrorTactic_Ignore); } - conEP->Release(); } - if (OnAcceptError != nullptr) - OnAcceptError(this, err); + + return err; } #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -/** - * This function probes the TCP output queue and checks if data is successfully - * being transferred to the other end. - */ -CHIP_ERROR TCPEndPoint::CheckConnectionProgress(bool & isProgressing) -{ - int currPendingBytesRaw = 0; - uint32_t currPendingBytes; // Will be initialized once we know it's safe. - // Fetch the bytes pending successful transmission in the TCP out queue. +void TCPEndPoint::ScheduleNextTCPUserTimeoutPoll(uint32_t aTimeOut) +{ + Layer().SystemLayer()->StartTimer(aTimeOut, TCPUserTimeoutHandler, this); +} - if (ioctl(mSocket, TIOCOUTQ, &currPendingBytesRaw) < 0) +#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS +void TCPEndPoint::SetTCPSendIdleAndNotifyChange(bool aIsTCPSendIdle) +{ + if (mIsTCPSendIdle != aIsTCPSendIdle) { - return CHIP_ERROR_POSIX(errno); - } + ChipLogDetail(Inet, "TCP con send channel idle state changed : %s", aIsTCPSendIdle ? "false->true" : "true->false"); - if (!CanCastTo(currPendingBytesRaw)) - { - return CHIP_ERROR_INCORRECT_STATE; + // Set the current Idle state + mIsTCPSendIdle = aIsTCPSendIdle; + + if (OnTCPSendIdleChanged) + { + OnTCPSendIdleChanged(this, mIsTCPSendIdle); + } } +} +#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - currPendingBytes = static_cast(currPendingBytesRaw); +void TCPEndPoint::StartTCPUserTimeoutTimer() +{ + uint32_t timeOut = mUserTimeoutMillis; - if ((currPendingBytes != 0) && (mBytesWrittenSinceLastProbe + mLastTCPKernelSendQueueLen == currPendingBytes)) - { - // No progress has been made +#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS + // Set timeout to the poll interval - isProgressing = false; - } - else - { - // Data is flowing successfully + timeOut = mTCPSendQueuePollPeriodMillis; - isProgressing = true; - } + // Reset the poll count - // Reset the value of the bytes written since the last probe into the tcp - // outqueue was made and update the last tcp outqueue sample. + mTCPSendQueueRemainingPollCount = MaxTCPSendQueuePolls(); +#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - mBytesWrittenSinceLastProbe = 0; + ScheduleNextTCPUserTimeoutPoll(timeOut); - mLastTCPKernelSendQueueLen = currPendingBytes; + mUserTimeoutTimerRunning = true; +} - return CHIP_NO_ERROR; +void TCPEndPoint::StopTCPUserTimeoutTimer() +{ + Layer().SystemLayer()->CancelTimer(TCPUserTimeoutHandler, this); + mUserTimeoutTimerRunning = false; } -#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS +void TCPEndPoint::RestartTCPUserTimeoutTimer() +{ + StopTCPUserTimeoutTimer(); + StartTCPUserTimeoutTimer(); +} + +#endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT } // namespace Inet } // namespace chip diff --git a/src/inet/TCPEndPoint.h b/src/inet/TCPEndPoint.h index 698e2dda2d4d91..812c427b24d607 100644 --- a/src/inet/TCPEndPoint.h +++ b/src/inet/TCPEndPoint.h @@ -223,7 +223,7 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis * Disable all event handlers. Data sent to an endpoint that disables * reception will be acknowledged until the receive window is exhausted. */ - void DisableReceive(); + void DisableReceive() { ReceiveEnabled = false; } /** * @brief Enable reception. @@ -232,10 +232,17 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis * Enable all event handlers. Data sent to an endpoint that disables * reception will be acknowledged until the receive window is exhausted. */ - void EnableReceive(); + void EnableReceive() + { + ReceiveEnabled = true; + DriveReceiving(); + } /** * @brief EnableNoDelay + * + * Switch off nagle buffering algorithm in TCP by setting the + * TCP_NODELAY socket options. */ CHIP_ERROR EnableNoDelay(); @@ -278,6 +285,9 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis /** * @brief Disable the TCP "keep-alive" option. * + * This method can only be called when the endpoint is in one of the connected states. + * This method does nothing if keepalives have not been enabled on the endpoint. + * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. @@ -287,26 +297,6 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis */ CHIP_ERROR DisableKeepAlive(); - /** - * @brief Set the TCP TCP_USER_TIMEOUT socket option. - * - * @param[in] userTimeoutMillis Tcp user timeout value in milliseconds. - * - * @retval CHIP_NO_ERROR success: address and port extracted. - * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. - * - * @retval other another system or platform error - * - * @details - * When the value is greater than 0, it specifies the maximum amount of - * time in milliseconds that transmitted data may remain - * unacknowledged before TCP will forcibly close the - * corresponding connection. If the option value is specified as 0, - * TCP will to use the system default. - * See RFC 5482, for further details. - */ - CHIP_ERROR SetUserTimeout(uint32_t userTimeoutMillis); - /** * @brief Acknowledge receipt of message text. * @@ -390,9 +380,12 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis /** * @brief Extract whether TCP connection is established. */ - bool IsConnected() const; + bool IsConnected() const { return IsConnected(State); } - void SetConnectTimeout(uint32_t connTimeoutMsecs); + /** + * Set timeout for Connect to succeed or return an error. + */ + void SetConnectTimeout(const uint32_t connTimeoutMsecs) { mConnectTimeoutMsecs = connTimeoutMsecs; } #if INET_TCP_IDLE_CHECK_INTERVAL > 0 /** @@ -413,14 +406,38 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis * @details * Reset the idle timer to zero. */ - void MarkActive(); + void MarkActive() + { +#if INET_TCP_IDLE_CHECK_INTERVAL > 0 + mRemainingIdleTime = mIdleTimeout; +#endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 + } /** - * @brief Obtain an identifier for the endpoint. + * @brief Set the TCP TCP_USER_TIMEOUT socket option. + * + * @param[in] userTimeoutMillis Tcp user timeout value in milliseconds. * - * @return Returns an opaque unique identifier for use logs. + * @retval CHIP_NO_ERROR success: address and port extracted. + * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. + * + * @retval other another system or platform error + * + * @details + * When the value is greater than 0, it specifies the maximum amount of + * time in milliseconds that transmitted data may remain + * unacknowledged before TCP will forcibly close the + * corresponding connection. If the option value is specified as 0, + * TCP will to use the system default. + * See RFC 5482, for further details. + * + * @note + * This method can only be called when the endpoint is in one of the connected states. + * + * This method can be called multiple times to adjust the keepalive interval or timeout + * count. */ - uint16_t LogId(); + CHIP_ERROR SetUserTimeout(uint32_t userTimeoutMillis); /** * @brief Type of connection establishment event handling function. @@ -621,7 +638,13 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis void ScheduleNextTCPUserTimeoutPoll(uint32_t aTimeOut); #if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS - uint16_t MaxTCPSendQueuePolls(void); + uint16_t MaxTCPSendQueuePolls(void) + { + // If the UserTimeout is configured less than or equal to the poll interval, + // return 1 to poll at least once instead of returning zero and timing out + // immediately. + return (mUserTimeoutMillis > mTCPSendQueuePollPeriodMillis) ? (mUserTimeoutMillis / mTCPSendQueuePollPeriodMillis) : 1; + } #endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS #if CHIP_SYSTEM_CONFIG_USE_SOCKETS @@ -650,6 +673,17 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis void StartConnectTimerIfSet(); void StopConnectTimer(); + CHIP_ERROR BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr); + CHIP_ERROR ListenImpl(uint16_t backlog); + CHIP_ERROR ConnectImpl(const IPAddress & addr, uint16_t port, InterfaceId intfId); + CHIP_ERROR SendQueuedImpl(bool queueWasEmpty); + CHIP_ERROR SetUserTimeoutImpl(uint32_t userTimeoutMillis); + + void InitImpl(); + CHIP_ERROR DriveSendingImpl(); + void HandleConnectCompleteImpl(); + void DoCloseImpl(CHIP_ERROR err, int oldState); + #if CHIP_SYSTEM_CONFIG_USE_LWIP struct BufferOffset { @@ -697,32 +731,5 @@ class DLL_EXPORT TCPEndPoint : public EndPointBasis #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS }; -#if INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS && INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT -inline uint16_t TCPEndPoint::MaxTCPSendQueuePolls(void) -{ - // If the UserTimeout is configured less than or equal to the poll interval, - // return 1 to poll at least once instead of returning zero and timing out - // immediately. - return (mUserTimeoutMillis > mTCPSendQueuePollPeriodMillis) ? (mUserTimeoutMillis / mTCPSendQueuePollPeriodMillis) : 1; -} -#endif // INET_CONFIG_ENABLE_TCP_SEND_IDLE_CALLBACKS && INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT - -inline bool TCPEndPoint::IsConnected() const -{ - return IsConnected(State); -} - -inline uint16_t TCPEndPoint::LogId() -{ - return static_cast(reinterpret_cast(this)); -} - -inline void TCPEndPoint::MarkActive() -{ -#if INET_TCP_IDLE_CHECK_INTERVAL > 0 - mRemainingIdleTime = mIdleTimeout; -#endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 -} - } // namespace Inet } // namespace chip diff --git a/src/inet/UDPEndPoint.cpp b/src/inet/UDPEndPoint.cpp index 340d8105511181..cf86c97c2c1e50 100644 --- a/src/inet/UDPEndPoint.cpp +++ b/src/inet/UDPEndPoint.cpp @@ -54,30 +54,31 @@ #include #include #include -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + +// SOCK_CLOEXEC not defined on all platforms, e.g. iOS/macOS: +#ifdef SOCK_CLOEXEC +#define SOCK_FLAGS SOCK_CLOEXEC +#else +#define SOCK_FLAGS 0 +#endif #if CHIP_SYSTEM_CONFIG_USE_ZEPHYR_SOCKET_EXTENSIONS #include "ZephyrSocket.h" #endif // CHIP_SYSTEM_CONFIG_USE_ZEPHYR_SOCKET_EXTENSIONS +#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS #include "arpa-inet-compatibility.h" #include #include -// SOCK_CLOEXEC not defined on all platforms, e.g. iOS/macOS: -#ifdef SOCK_CLOEXEC -#define SOCK_FLAGS SOCK_CLOEXEC -#else -#define SOCK_FLAGS 0 -#endif - namespace chip { namespace Inet { chip::System::ObjectPool UDPEndPoint::sPool; #if CHIP_SYSTEM_CONFIG_USE_LWIP + /* * Note that for LwIP InterfaceId is already defined to be 'struct * netif'; consequently, some of the checking performed here could @@ -119,53 +120,9 @@ static CHIP_ERROR LwIPBindInterface(struct udp_pcb * aUDP, InterfaceId intfId) return res; } -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP -/** - * @brief Bind the endpoint to an interface IP address. - * - * @param[in] addrType the protocol version of the IP address - * @param[in] addr the IP address (must be an interface address) - * @param[in] port the UDP port - * @param[in] intfId an optional network interface indicator - * - * @retval CHIP_NO_ERROR success: endpoint bound to address - * @retval CHIP_ERROR_INCORRECT_STATE endpoint has been bound previously - * @retval CHIP_ERROR_NO_MEMORY insufficient memory for endpoint - * - * @retval INET_ERROR_UNKNOWN_INTERFACE - * On some platforms, the optionally specified interface is not - * present. - * - * @retval INET_ERROR_WRONG_PROTOCOL_TYPE - * \c addrType does not match \c IPVer. - * - * @retval INET_ERROR_WRONG_ADDRESS_TYPE - * \c addrType is \c kIPAddressType_Any, or the type of \c addr is not - * equal to \c addrType. - * - * @retval other another system or platform error - * - * @details - * Binds the endpoint to the specified network interface IP address. - * - * On LwIP, this method must not be called with the LwIP stack lock - * already acquired. - */ -CHIP_ERROR UDPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId) +CHIP_ERROR UDPEndPoint::BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId) { - if (mState != kState_Ready && mState != kState_Bound) - { - return CHIP_ERROR_INCORRECT_STATE; - } - - if ((addr != IPAddress::Any) && (addr.Type() != kIPAddressType_Any) && (addr.Type() != addrType)) - { - return INET_ERROR_WRONG_ADDRESS_TYPE; - } - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - // Lock LwIP stack LOCK_TCPIP_CORE(); @@ -208,122 +165,45 @@ CHIP_ERROR UDPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uin // Unlock LwIP stack UNLOCK_TCPIP_CORE(); - ReturnErrorOnFailure(res); - -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - // Make sure we have the appropriate type of socket. - ReturnErrorOnFailure(GetSocket(addrType)); - ReturnErrorOnFailure(IPEndPointBasis::Bind(addrType, addr, port, intfId)); - - mBoundPort = port; - mBoundIntfId = intfId; - - // If an ephemeral port was requested, retrieve the actual bound port. - if (port == 0) - { - union - { - struct sockaddr any; - struct sockaddr_in in; - struct sockaddr_in6 in6; - } boundAddr; - socklen_t boundAddrLen = sizeof(boundAddr); - - if (getsockname(mSocket, &boundAddr.any, &boundAddrLen) == 0) - { - if (boundAddr.any.sa_family == AF_INET) - { - mBoundPort = ntohs(boundAddr.in.sin_port); - } - else if (boundAddr.any.sa_family == AF_INET6) - { - mBoundPort = ntohs(boundAddr.in6.sin6_port); - } - } - } - -#if CHIP_SYSTEM_CONFIG_USE_DISPATCH - dispatch_queue_t dispatchQueue = static_cast(Layer().SystemLayer())->GetDispatchQueue(); - if (dispatchQueue != nullptr) - { - unsigned long fd = static_cast(mSocket); - - mReadableSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatchQueue); - ReturnErrorCodeIf(mReadableSource == nullptr, CHIP_ERROR_NO_MEMORY); - - dispatch_source_set_event_handler(mReadableSource, ^{ - this->HandlePendingIO(System::SocketEventFlags::kRead); - }); - dispatch_resume(mReadableSource); - } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH - -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS + return res; +} -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK +CHIP_ERROR UDPEndPoint::BindInterfaceImpl(IPAddressType addrType, InterfaceId intfId) +{ + // A lock is required because the LwIP thread may be referring to intf_filter, + // while this code running in the Inet application is potentially modifying it. + // NOTE: this only supports LwIP interfaces whose number is no bigger than 9. + LOCK_TCPIP_CORE(); - nw_parameters_configure_protocol_block_t configure_tls; - nw_parameters_t parameters; + // Make sure we have the appropriate type of PCB. + CHIP_ERROR err = GetPCB(addrType); - if (intfId != INET_NULL_INTERFACEID) + if (err == CHIP_NO_ERROR) { - return CHIP_ERROR_NOT_IMPLEMENTED; + err = LwIPBindInterface(mUDP, intfId); } - configure_tls = NW_PARAMETERS_DISABLE_PROTOCOL; - parameters = nw_parameters_create_secure_udp(configure_tls, NW_PARAMETERS_DEFAULT_CONFIGURATION); - - ReturnErrorOnFailure(IPEndPointBasis::Bind(addrType, addr, port, parameters)); - - mParameters = parameters; - -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - - mState = kState_Bound; + UNLOCK_TCPIP_CORE(); - return CHIP_NO_ERROR; + return err; } -/** - * @brief Prepare the endpoint to receive UDP messages. - * - * @param[in] onMessageReceived The endpoint's message reception event handling function delegate. - * @param[in] onReceiveError The endpoint's receive error event handling function delegate. - * @param[in] appState Application state pointer. - * - * @retval CHIP_NO_ERROR success: endpoint ready to receive messages. - * @retval CHIP_ERROR_INCORRECT_STATE endpoint is already listening. - * - * @details - * If \c State is already \c kState_Listening, then no operation is - * performed, otherwise the \c mState is set to \c kState_Listening and - * the endpoint is prepared to received UDP messages, according to the - * semantics of the platform. - * - * On LwIP, this method must not be called with the LwIP stack lock - * already acquired - */ -CHIP_ERROR UDPEndPoint::Listen(OnMessageReceivedFunct onMessageReceived, OnReceiveErrorFunct onReceiveError, void * appState) +InterfaceId UDPEndPoint::GetBoundInterface() { - if (mState == kState_Listening) - { - return CHIP_NO_ERROR; - } - - if (mState != kState_Bound) - { - return CHIP_ERROR_INCORRECT_STATE; - } - - OnMessageReceived = onMessageReceived; - OnReceiveError = onReceiveError; - AppState = appState; +#if HAVE_LWIP_UDP_BIND_NETIF + return netif_get_by_index(mUDP->netif_idx); +#else + return mUDP->intf_filter; +#endif +} -#if CHIP_SYSTEM_CONFIG_USE_LWIP +uint16_t UDPEndPoint::GetBoundPort() +{ + return mUDP->local_port; +} +CHIP_ERROR UDPEndPoint::ListenImpl() +{ // Lock LwIP stack LOCK_TCPIP_CORE(); @@ -339,201 +219,14 @@ CHIP_ERROR UDPEndPoint::Listen(OnMessageReceivedFunct onMessageReceived, OnRecei // Unlock LwIP stack UNLOCK_TCPIP_CORE(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - ReturnErrorOnFailure(StartListener()); -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - - mState = kState_Listening; - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - // Wait for ability to read on this endpoint. - auto layer = static_cast(Layer().SystemLayer()); - ReturnErrorOnFailure(layer->SetCallback(mWatch, HandlePendingIO, reinterpret_cast(this))); - ReturnErrorOnFailure(layer->RequestCallbackOnPendingRead(mWatch)); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - return CHIP_NO_ERROR; } -/** - * @brief Close the endpoint. - * - * @details - * If mState != kState_Closed, then closes the endpoint, removing - * it from the set of endpoints eligible for communication events. - * - * On LwIP systems, this method must not be called with the LwIP stack - * lock already acquired. - */ -void UDPEndPoint::Close() -{ - if (mState != kState_Closed) - { -#if CHIP_SYSTEM_CONFIG_USE_LWIP - - // Lock LwIP stack - LOCK_TCPIP_CORE(); - - // Since UDP PCB is released synchronously here, but UDP endpoint itself might have to wait - // for destruction asynchronously, there could be more allocated UDP endpoints than UDP PCBs. - if (mUDP != NULL) - { - udp_remove(mUDP); - mUDP = NULL; - mLwIPEndPointType = LwIPEndPointType::Unknown; - } - - // Unlock LwIP stack - UNLOCK_TCPIP_CORE(); - -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - if (mSocket != kInvalidSocketFd) - { - static_cast(Layer().SystemLayer())->StopWatchingSocket(&mWatch); - close(mSocket); - mSocket = kInvalidSocketFd; - } - -#if CHIP_SYSTEM_CONFIG_USE_DISPATCH - if (mReadableSource) - { - dispatch_source_cancel(mReadableSource); - dispatch_release(mReadableSource); - } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - IPEndPointBasis::ReleaseAll(); -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - - mState = kState_Closed; - } -} - -/** - * @brief Close the endpoint and recycle its memory. - * - * @details - * Invokes the \c Close method, then invokes the - * InetLayerBasis::Release method to return the object to its - * memory pool. - * - * On LwIP systems, this method must not be called with the LwIP stack - * lock already acquired. - */ -void UDPEndPoint::Free() -{ - Close(); - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - DeferredFree(kReleaseDeferralErrorTactic_Die); -#else // !CHIP_SYSTEM_CONFIG_USE_LWIP - Release(); -#endif // !CHIP_SYSTEM_CONFIG_USE_LWIP -} - -/** - * A synonym for SendTo(addr, port, INET_NULL_INTERFACEID, msg, sendFlags). - */ -CHIP_ERROR UDPEndPoint::SendTo(const IPAddress & addr, uint16_t port, chip::System::PacketBufferHandle && msg, uint16_t sendFlags) -{ - return SendTo(addr, port, INET_NULL_INTERFACEID, std::move(msg), sendFlags); -} - -/** - * @brief Send a UDP message to the specified destination address. - * - * @param[in] addr the destination IP address - * @param[in] port the destination UDP port - * @param[in] intfId an optional network interface indicator - * @param[in] msg the packet buffer containing the UDP message - * @param[in] sendFlags optional transmit option flags - * - * @retval CHIP_NO_ERROR success: \c msg is queued for transmit. - * - * @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE - * the system does not support the requested operation. - * - * @retval INET_ERROR_WRONG_ADDRESS_TYPE - * the destination address and the bound interface address do not - * have matching protocol versions or address type. - * - * @retval CHIP_ERROR_MESSAGE_TOO_LONG - * \c msg does not contain the whole UDP message. - * - * @retval CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG - * On some platforms, only a truncated portion of \c msg was queued - * for transmit. - * - * @retval other - * another system or platform error - * - * @details - * If possible, then this method sends the UDP message \c msg to the - * destination \c addr (with \c intfId used as the scope - * identifier for IPv6 link-local destinations) and \c port with the - * transmit option flags encoded in \c sendFlags. - */ -CHIP_ERROR UDPEndPoint::SendTo(const IPAddress & addr, uint16_t port, InterfaceId intfId, chip::System::PacketBufferHandle && msg, - uint16_t sendFlags) -{ - IPPacketInfo pktInfo; - pktInfo.Clear(); - pktInfo.DestAddress = addr; - pktInfo.DestPort = port; - pktInfo.Interface = intfId; - return SendMsg(&pktInfo, std::move(msg), sendFlags); -} - -/** - * @brief Send a UDP message to a specified destination. - * - * @param[in] pktInfo source and destination information for the UDP message - * @param[in] msg a packet buffer containing the UDP message - * @param[in] sendFlags optional transmit option flags - * - * @retval CHIP_NO_ERROR - * success: \c msg is queued for transmit. - * - * @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE - * the system does not support the requested operation. - * - * @retval INET_ERROR_WRONG_ADDRESS_TYPE - * the destination address and the bound interface address do not - * have matching protocol versions or address type. - * - * @retval CHIP_ERROR_MESSAGE_TOO_LONG - * \c msg does not contain the whole UDP message. - * - * @retval CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG - * On some platforms, only a truncated portion of \c msg was queued - * for transmit. - * - * @retval other - * another system or platform error - * - * @details - * Send the UDP message in \c msg to the destination address and port given in - * \c pktInfo. If \c pktInfo contains an interface id, the message will be sent - * over the specified interface. If \c pktInfo contains a source address, the - * given address will be used as the source of the UDP message. - */ -CHIP_ERROR UDPEndPoint::SendMsg(const IPPacketInfo * pktInfo, System::PacketBufferHandle && msg, uint16_t sendFlags) +CHIP_ERROR UDPEndPoint::SendMsgImpl(const IPPacketInfo * pktInfo, System::PacketBufferHandle && msg, uint16_t sendFlags) { CHIP_ERROR res = CHIP_NO_ERROR; const IPAddress & destAddr = pktInfo->DestAddress; - INET_FAULT_INJECT(FaultInjection::kFault_Send, return INET_ERROR_UNKNOWN_INTERFACE;); - INET_FAULT_INJECT(FaultInjection::kFault_SendNonCritical, return CHIP_ERROR_NO_MEMORY;); - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - if (!msg.HasSoleOwnership()) { // when retaining a buffer, the caller expects the msg to be unmodified. @@ -542,7 +235,7 @@ CHIP_ERROR UDPEndPoint::SendMsg(const IPPacketInfo * pktInfo, System::PacketBuff // msg into a fresh object in this case, and queues that for transmission, leaving // the original msg available after return. msg = msg.CloneData(); - VerifyOrExit(!msg.IsNull(), res = CHIP_ERROR_NO_MEMORY); + VerifyOrReturnError(!msg.IsNull(), CHIP_ERROR_NO_MEMORY); } // Lock LwIP stack @@ -550,7 +243,7 @@ CHIP_ERROR UDPEndPoint::SendMsg(const IPPacketInfo * pktInfo, System::PacketBuff // Make sure we have the appropriate type of PCB based on the destination address. res = GetPCB(destAddr.Type()); - SuccessOrExit(res); + ReturnErrorOnFailure(res); // Send the message to the specified address/port. // If an outbound interface has been specified, call a specific version of the UDP sendto() @@ -637,140 +330,33 @@ CHIP_ERROR UDPEndPoint::SendMsg(const IPPacketInfo * pktInfo, System::PacketBuff // Unlock LwIP stack UNLOCK_TCPIP_CORE(); -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - - // Make sure we have the appropriate type of socket based on the - // destination address. - - res = GetSocket(destAddr.Type()); - SuccessOrExit(res); - - res = IPEndPointBasis::SendMsg(pktInfo, std::move(msg), sendFlags); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - res = IPEndPointBasis::SendMsg(pktInfo, std::move(msg), sendFlags); -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - -exit: - CHIP_SYSTEM_FAULT_INJECT_ASYNC_EVENT(); return res; } -/** - * @brief Bind the endpoint to a network interface. - * - * @param[in] addrType the protocol version of the IP address. - * - * @param[in] intfId indicator of the network interface. - * - * @retval CHIP_NO_ERROR success: endpoint bound to address - * @retval CHIP_ERROR_NO_MEMORY insufficient memory for endpoint - * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. - * - * @retval INET_ERROR_UNKNOWN_INTERFACE - * On some platforms, the interface is not present. - * - * @retval other another system or platform error - * - * @details - * Binds the endpoint to the specified network interface IP address. - * - * On LwIP, this method must not be called with the LwIP stack lock - * already acquired. - */ -CHIP_ERROR UDPEndPoint::BindInterface(IPAddressType addrType, InterfaceId intfId) +void UDPEndPoint::CloseImpl() { - if (mState != kState_Ready && mState != kState_Bound) - { - return CHIP_ERROR_INCORRECT_STATE; - } - -#if CHIP_SYSTEM_CONFIG_USE_LWIP - - // A lock is required because the LwIP thread may be referring to intf_filter, - // while this code running in the Inet application is potentially modifying it. - // NOTE: this only supports LwIP interfaces whose number is no bigger than 9. + // Lock LwIP stack LOCK_TCPIP_CORE(); - // Make sure we have the appropriate type of PCB. - CHIP_ERROR err = GetPCB(addrType); - - if (err == CHIP_NO_ERROR) + // Since UDP PCB is released synchronously here, but UDP endpoint itself might have to wait + // for destruction asynchronously, there could be more allocated UDP endpoints than UDP PCBs. + if (mUDP != NULL) { - err = LwIPBindInterface(mUDP, intfId); + udp_remove(mUDP); + mUDP = NULL; + mLwIPEndPointType = LwIPEndPointType::Unknown; } - UNLOCK_TCPIP_CORE(); - - ReturnErrorOnFailure(err); - -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - // Make sure we have the appropriate type of socket. - ReturnErrorOnFailure(GetSocket(addrType)); - ReturnErrorOnFailure(IPEndPointBasis::BindInterface(addrType, intfId)); -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - return INET_ERROR_UNKNOWN_INTERFACE; -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - - mState = kState_Bound; - - return CHIP_NO_ERROR; -} - -void UDPEndPoint::Init(InetLayer * inetLayer) -{ - IPEndPointBasis::Init(inetLayer); -} - -/** - * Get the bound interface on this endpoint. - * - * @return InterfaceId The bound interface id. - */ -InterfaceId UDPEndPoint::GetBoundInterface() -{ -#if CHIP_SYSTEM_CONFIG_USE_LWIP -#if HAVE_LWIP_UDP_BIND_NETIF - return netif_get_by_index(mUDP->netif_idx); -#else - return mUDP->intf_filter; -#endif -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - return mBoundIntfId; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - return INET_NULL_INTERFACEID; -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK -} - -uint16_t UDPEndPoint::GetBoundPort() -{ -#if CHIP_SYSTEM_CONFIG_USE_LWIP - return mUDP->local_port; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP - -#if CHIP_SYSTEM_CONFIG_USE_SOCKETS - return mBoundPort; -#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS - -#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK - nw_endpoint_t endpoint = nw_parameters_copy_local_endpoint(mParameters); - return nw_endpoint_get_port(endpoint); -#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK + // Unlock LwIP stack + UNLOCK_TCPIP_CORE(); } -#if CHIP_SYSTEM_CONFIG_USE_LWIP +void UDPEndPoint::Free() +{ + Close(); + DeferredFree(kReleaseDeferralErrorTactic_Die); +} void UDPEndPoint::HandleDataReceived(System::PacketBufferHandle && msg) { @@ -915,6 +501,122 @@ void UDPEndPoint::LwIPReceiveUDPMessage(void * arg, struct udp_pcb * pcb, struct #endif // CHIP_SYSTEM_CONFIG_USE_LWIP #if CHIP_SYSTEM_CONFIG_USE_SOCKETS + +CHIP_ERROR UDPEndPoint::BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId) +{ + // Make sure we have the appropriate type of socket. + ReturnErrorOnFailure(GetSocket(addrType)); + ReturnErrorOnFailure(IPEndPointBasis::Bind(addrType, addr, port, intfId)); + + mBoundPort = port; + mBoundIntfId = intfId; + + // If an ephemeral port was requested, retrieve the actual bound port. + if (port == 0) + { + union + { + struct sockaddr any; + struct sockaddr_in in; + struct sockaddr_in6 in6; + } boundAddr; + socklen_t boundAddrLen = sizeof(boundAddr); + + if (getsockname(mSocket, &boundAddr.any, &boundAddrLen) == 0) + { + if (boundAddr.any.sa_family == AF_INET) + { + mBoundPort = ntohs(boundAddr.in.sin_port); + } + else if (boundAddr.any.sa_family == AF_INET6) + { + mBoundPort = ntohs(boundAddr.in6.sin6_port); + } + } + } + +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH + dispatch_queue_t dispatchQueue = static_cast(Layer().SystemLayer())->GetDispatchQueue(); + if (dispatchQueue != nullptr) + { + unsigned long fd = static_cast(mSocket); + + mReadableSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatchQueue); + ReturnErrorCodeIf(mReadableSource == nullptr, CHIP_ERROR_NO_MEMORY); + + dispatch_source_set_event_handler(mReadableSource, ^{ + this->HandlePendingIO(System::SocketEventFlags::kRead); + }); + dispatch_resume(mReadableSource); + } +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH + + return CHIP_NO_ERROR; +} + +CHIP_ERROR UDPEndPoint::BindInterfaceImpl(IPAddressType addrType, InterfaceId intfId) +{ + // Make sure we have the appropriate type of socket. + ReturnErrorOnFailure(GetSocket(addrType)); + return IPEndPointBasis::BindInterface(addrType, intfId); +} + +InterfaceId UDPEndPoint::GetBoundInterface() +{ + return mBoundIntfId; +} + +uint16_t UDPEndPoint::GetBoundPort() +{ + return mBoundPort; +} + +CHIP_ERROR UDPEndPoint::ListenImpl() +{ + // Wait for ability to read on this endpoint. + auto layer = static_cast(Layer().SystemLayer()); + ReturnErrorOnFailure(layer->SetCallback(mWatch, HandlePendingIO, reinterpret_cast(this))); + return layer->RequestCallbackOnPendingRead(mWatch); +} + +CHIP_ERROR UDPEndPoint::SendMsgImpl(const IPPacketInfo * pktInfo, System::PacketBufferHandle && msg, uint16_t sendFlags) +{ + CHIP_ERROR res = CHIP_NO_ERROR; + const IPAddress & destAddr = pktInfo->DestAddress; + + // Make sure we have the appropriate type of socket based on the + // destination address. + + res = GetSocket(destAddr.Type()); + ReturnErrorOnFailure(res); + + return IPEndPointBasis::SendMsg(pktInfo, std::move(msg), sendFlags); +} + +void UDPEndPoint::CloseImpl() +{ + if (mSocket != kInvalidSocketFd) + { + static_cast(Layer().SystemLayer())->StopWatchingSocket(&mWatch); + close(mSocket); + mSocket = kInvalidSocketFd; + } + +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH + if (mReadableSource) + { + dispatch_source_cancel(mReadableSource); + dispatch_release(mReadableSource); + } +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH +} + +void UDPEndPoint::Free() +{ + Close(); + Release(); +} + CHIP_ERROR UDPEndPoint::GetSocket(IPAddressType aAddressType) { constexpr int lType = (SOCK_DGRAM | SOCK_FLAGS); @@ -941,5 +643,166 @@ void UDPEndPoint::HandlePendingIO(System::SocketEvents events) #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS +#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK + +CHIP_ERROR UDPEndPoint::BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId) +{ + nw_parameters_configure_protocol_block_t configure_tls; + nw_parameters_t parameters; + + if (intfId != INET_NULL_INTERFACEID) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + configure_tls = NW_PARAMETERS_DISABLE_PROTOCOL; + parameters = nw_parameters_create_secure_udp(configure_tls, NW_PARAMETERS_DEFAULT_CONFIGURATION); + + ReturnErrorOnFailure(IPEndPointBasis::Bind(addrType, addr, port, parameters)); + + mParameters = parameters; + return CHIP_NO_ERROR; +} + +CHIP_ERROR UDPEndPoint::BindInterfaceImpl(IPAddressType addrType, InterfaceId intfId) +{ + return INET_ERROR_UNKNOWN_INTERFACE; +} + +InterfaceId UDPEndPoint::GetBoundInterface() +{ + return INET_NULL_INTERFACEID; +} + +uint16_t UDPEndPoint::GetBoundPort() +{ + nw_endpoint_t endpoint = nw_parameters_copy_local_endpoint(mParameters); + return nw_endpoint_get_port(endpoint); +} + +CHIP_ERROR UDPEndPoint::ListenImpl() +{ + return StartListener(); +} + +CHIP_ERROR UDPEndPoint::SendMsgImpl(const IPPacketInfo * pktInfo, System::PacketBufferHandle && msg, uint16_t sendFlags) +{ + return IPEndPointBasis::SendMsg(pktInfo, std::move(msg), sendFlags); +} + +void UDPEndPoint::CloseImpl() +{ + IPEndPointBasis::ReleaseAll(); +} + +void UDPEndPoint::Free() +{ + Close(); + Release(); +} + +#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK + +CHIP_ERROR UDPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId) +{ + if (mState != kState_Ready && mState != kState_Bound) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + if ((addr != IPAddress::Any) && (addr.Type() != kIPAddressType_Any) && (addr.Type() != addrType)) + { + return INET_ERROR_WRONG_ADDRESS_TYPE; + } + + ReturnErrorOnFailure(BindImpl(addrType, addr, port, intfId)); + + mState = kState_Bound; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR UDPEndPoint::BindInterface(IPAddressType addrType, InterfaceId intfId) +{ + if (mState != kState_Ready && mState != kState_Bound) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + ReturnErrorOnFailure(BindInterfaceImpl(addrType, intfId)); + + mState = kState_Bound; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR UDPEndPoint::Listen(OnMessageReceivedFunct onMessageReceived, OnReceiveErrorFunct onReceiveError, void * appState) +{ + if (mState == kState_Listening) + { + return CHIP_NO_ERROR; + } + + if (mState != kState_Bound) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + OnMessageReceived = onMessageReceived; + OnReceiveError = onReceiveError; + AppState = appState; + + ReturnErrorOnFailure(ListenImpl()); + + mState = kState_Listening; + + return CHIP_NO_ERROR; +} + +/** + * A synonym for SendTo(addr, port, INET_NULL_INTERFACEID, msg, sendFlags). + */ +CHIP_ERROR UDPEndPoint::SendTo(const IPAddress & addr, uint16_t port, chip::System::PacketBufferHandle && msg, uint16_t sendFlags) +{ + return SendTo(addr, port, INET_NULL_INTERFACEID, std::move(msg), sendFlags); +} + +CHIP_ERROR UDPEndPoint::SendTo(const IPAddress & addr, uint16_t port, InterfaceId intfId, chip::System::PacketBufferHandle && msg, + uint16_t sendFlags) +{ + IPPacketInfo pktInfo; + pktInfo.Clear(); + pktInfo.DestAddress = addr; + pktInfo.DestPort = port; + pktInfo.Interface = intfId; + return SendMsg(&pktInfo, std::move(msg), sendFlags); +} + +CHIP_ERROR UDPEndPoint::SendMsg(const IPPacketInfo * pktInfo, System::PacketBufferHandle && msg, uint16_t sendFlags) +{ + INET_FAULT_INJECT(FaultInjection::kFault_Send, return INET_ERROR_UNKNOWN_INTERFACE;); + INET_FAULT_INJECT(FaultInjection::kFault_SendNonCritical, return CHIP_ERROR_NO_MEMORY;); + + CHIP_ERROR res = SendMsgImpl(pktInfo, std::move(msg), sendFlags); + + CHIP_SYSTEM_FAULT_INJECT_ASYNC_EVENT(); + + return res; +} + +void UDPEndPoint::Close() +{ + if (mState != kState_Closed) + { + CloseImpl(); + mState = kState_Closed; + } +} + +void UDPEndPoint::Init(InetLayer * inetLayer) +{ + IPEndPointBasis::Init(inetLayer); +} + } // namespace Inet } // namespace chip diff --git a/src/inet/UDPEndPoint.h b/src/inet/UDPEndPoint.h index 7a1574cd61852e..510162962682fb 100644 --- a/src/inet/UDPEndPoint.h +++ b/src/inet/UDPEndPoint.h @@ -58,16 +58,137 @@ class DLL_EXPORT UDPEndPoint : public IPEndPointBasis public: UDPEndPoint() = default; + /** + * Bind the endpoint to an interface IP address. + * + * Binds the endpoint to the specified network interface IP address. + * + * On LwIP, this method must not be called with the LwIP stack lock already acquired. + * + * @param[in] addrType Protocol version of the IP address. + * @param[in] addr IP address (must be an interface address). + * @param[in] port UDP port. + * @param[in] intfId An optional network interface indicator. + * + * @retval CHIP_NO_ERROR Success: endpoint bound to address. + * @retval CHIP_ERROR_INCORRECT_STATE Endpoint has been bound previously. + * @retval CHIP_ERROR_NO_MEMORY Insufficient memory for endpoint. + * @retval INET_ERROR_UNKNOWN_INTERFACE The optionally specified interface is not present. + * @retval INET_ERROR_WRONG_PROTOCOL_TYPE \c addrType does not match \c IPVer. + * @retval INET_ERROR_WRONG_ADDRESS_TYPE \c addrType is \c IPAddressType::kAny, or not equal to the type of \c addr. + * @retval other Another system or platform error + */ CHIP_ERROR Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId = INET_NULL_INTERFACEID); + + /** + * Bind the endpoint to a network interface. + * + * Binds the endpoint to the specified network interface IP address. + * + * On LwIP, this method must not be called with the LwIP stack lock already acquired. + * + * @param[in] addrType Protocol version of the IP address. + * @param[in] intfId Network interface. + * + * @retval CHIP_NO_ERROR Success: endpoint bound to address. + * @retval CHIP_ERROR_NO_MEMORY Insufficient memory for endpoint. + * @retval CHIP_ERROR_NOT_IMPLEMENTED System implementation not complete. + * @retval INET_ERROR_UNKNOWN_INTERFACE The interface is not present. + * @retval other Another system or platform error. + */ CHIP_ERROR BindInterface(IPAddressType addrType, InterfaceId intfId); + + /** + * Get the bound interface on this endpoint. + * + * @return InterfaceId The bound interface id. + */ InterfaceId GetBoundInterface(); + uint16_t GetBoundPort(); + + /** + * Prepare the endpoint to receive UDP messages. + * + * If \c mState is already \c State::kListening, then no operation is + * performed, otherwise the \c mState is set to \c State::kListening and + * the endpoint is prepared to received UDP messages, according to the + * semantics of the platform. + * + * On LwIP, this method must not be called with the LwIP stack lock already acquired. + * + * @param[in] onMessageReceived The endpoint's message reception event handling function delegate. + * @param[in] onReceiveError The endpoint's receive error event handling function delegate. + * @param[in] appState Application state pointer. + * + * @retval CHIP_NO_ERROR Success: endpoint ready to receive messages. + * @retval CHIP_ERROR_INCORRECT_STATE Endpoint is already listening. + */ CHIP_ERROR Listen(OnMessageReceivedFunct onMessageReceived, OnReceiveErrorFunct onReceiveError, void * appState = nullptr); - CHIP_ERROR SendTo(const IPAddress & addr, uint16_t port, chip::System::PacketBufferHandle && msg, uint16_t sendFlags = 0); + + /** + * Send a UDP message to the specified destination address. + * + * If possible, then this method sends the UDP message \c msg to the destination \c addr + * (with \c intfId used as the scope identifier for IPv6 link-local destinations) and \c port. + * + * @param[in] addr Destination IP address. + * @param[in] port Destination UDP port. + * @param[in] msg Packet buffer containing the UDP message. + * @param[in] intfId Optional network interface indicator. + * @param[in] sendFlags Unused and will be removed. + * + * @retval CHIP_NO_ERROR Success: \c msg is queued for transmit. + * @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE The system does not support the requested operation. + * @retval INET_ERROR_WRONG_ADDRESS_TYPE The destination address and the bound interface address do not have + * matching protocol versions or address type. + * @retval CHIP_ERROR_MESSAGE_TOO_LONG \c msg does not contain the whole UDP message. + * @retval CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG Only a truncated portion of \c msg was queued for transmit. + * @retval other Another system or platform error. + */ CHIP_ERROR SendTo(const IPAddress & addr, uint16_t port, InterfaceId intfId, chip::System::PacketBufferHandle && msg, uint16_t sendFlags = 0); + CHIP_ERROR SendTo(const IPAddress & addr, uint16_t port, chip::System::PacketBufferHandle && msg, uint16_t sendFlags = 0); + + /** + * Send a UDP message to a specified destination. + * + * Send the UDP message in \c msg to the destination address and port given in \c pktInfo. + * If \c pktInfo contains an interface id, the message will be sent over the specified interface. + * If \c pktInfo contains a source address, the given address will be used as the source of the UDP message. + * + * @param[in] pktInfo Source and destination information for the UDP message. + * @param[in] msg Packet buffer containing the UDP message. + * @param[in] sendFlags Unused and will be removed. + * + * @retval CHIP_NO_ERROR Success: \c msg is queued for transmit. + * @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE The system does not support the requested operation. + * @retval INET_ERROR_WRONG_ADDRESS_TYPE The destination address and the bound interface address do not have + * matching protocol versions or address type. + * @retval CHIP_ERROR_MESSAGE_TOO_LONG \c msg does not contain the whole UDP message. + * @retval CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG Only a truncated portion of \c msg was queued for transmit. + * @retval other Another system or platform error. + */ CHIP_ERROR SendMsg(const IPPacketInfo * pktInfo, chip::System::PacketBufferHandle && msg, uint16_t sendFlags = 0); + + /** + * Close the endpoint. + * + * If mState != State::kClosed, then closes the endpoint, removing + * it from the set of endpoints eligible for communication events. + * + * On LwIP systems, this method must not be called with the LwIP stack lock already acquired. + */ void Close(); + + /** + * Close the endpoint and recycle its memory. + * + * Invokes the \c Close method, then invokes the InetLayerBasis::Release method to return the object to its + * memory pool. + * + * On LwIP systems, this method must not be called with the LwIP stack lock already acquired. + */ void Free(); private: @@ -75,6 +196,12 @@ class DLL_EXPORT UDPEndPoint : public IPEndPointBasis static chip::System::ObjectPool sPool; + CHIP_ERROR BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, InterfaceId intfId); + CHIP_ERROR BindInterfaceImpl(IPAddressType addrType, InterfaceId intfId); + CHIP_ERROR ListenImpl(); + CHIP_ERROR SendMsgImpl(const IPPacketInfo * pktInfo, chip::System::PacketBufferHandle && msg, uint16_t sendFlags); + void CloseImpl(); + void Init(InetLayer * inetLayer); #if CHIP_SYSTEM_CONFIG_USE_LWIP