diff --git a/src/core/ddsi/src/ddsi_ipaddr.c b/src/core/ddsi/src/ddsi_ipaddr.c index 78913bf..733240f 100644 --- a/src/core/ddsi/src/ddsi_ipaddr.c +++ b/src/core/ddsi/src/ddsi_ipaddr.c @@ -74,15 +74,39 @@ enum ddsi_nearby_address_result ddsi_ipaddr_is_nearby_address (ddsi_tran_factory enum ddsi_locator_from_string_result ddsi_ipaddr_from_string (ddsi_tran_factory_t tran, nn_locator_t *loc, const char *str, int32_t kind) { + int af = AF_INET; os_sockaddr_storage tmpaddr; - int ipv4 = (kind == NN_LOCATOR_KIND_UDPv4 || kind == NN_LOCATOR_KIND_TCPv4); - assert (kind == NN_LOCATOR_KIND_UDPv4 || kind == NN_LOCATOR_KIND_TCPv4 || - kind == NN_LOCATOR_KIND_TCPv6 || kind == NN_LOCATOR_KIND_UDPv6); + + switch (kind) { + case NN_LOCATOR_KIND_UDPv4: + case NN_LOCATOR_KIND_TCPv4: + break; +#if OS_SOCKET_HAS_IPV6 + case NN_LOCATOR_KIND_UDPv6: + case NN_LOCATOR_KIND_TCPv6: + af = AF_INET6; + break; +#endif /* OS_SOCKET_HAS_IPV6 */ + default: + return AFSR_MISMATCH; + } + (void)tran; - if (!os_sockaddrStringToAddress (str, (os_sockaddr *) &tmpaddr, ipv4)) - return AFSR_INVALID; - if ((ipv4 && tmpaddr.ss_family != AF_INET) || (!ipv4 && tmpaddr.ss_family != AF_INET6)) + if (os_sockaddrfromstr(af, str, (os_sockaddr *) &tmpaddr) != 0) { +#if OS_SOCKET_HAS_DNS + /* Not a valid IP address. User may have specified a hostname instead. */ + os_hostent_t *hent = NULL; + if (os_gethostbyname(str, af, &hent) != 0) { + return AFSR_UNKNOWN; + } + memcpy(&tmpaddr, &hent->addrs[0], sizeof(hent->addrs[0])); +#else + return AFSR_INVALID; +#endif /* OS_SOCKET_HAS_DNS */ + } + if (tmpaddr.ss_family != af) { return AFSR_MISMATCH; + } ddsi_ipaddr_to_loc (loc, (os_sockaddr *)&tmpaddr, kind); /* This is just an address, so there is no valid value for port, other than INVALID. Without a guarantee that tmpaddr has port 0, best is to set it explicitly here */ @@ -100,7 +124,7 @@ char *ddsi_ipaddr_to_string (ddsi_tran_factory_t tran, char *dst, size_t sizeof_ switch (src.ss_family) { case AF_INET: - os_sockaddrAddressToString ((const os_sockaddr *) &src, dst, sizeof_dst); + os_sockaddrtostr ((const os_sockaddr *) &src, dst, sizeof_dst); if (with_port) { pos = strlen (dst); assert(pos <= sizeof_dst); @@ -110,7 +134,7 @@ char *ddsi_ipaddr_to_string (ddsi_tran_factory_t tran, char *dst, size_t sizeof_ #if OS_SOCKET_HAS_IPV6 case AF_INET6: dst[0] = '['; - os_sockaddrAddressToString ((const os_sockaddr *) &src, dst + 1, sizeof_dst); + os_sockaddrtostr ((const os_sockaddr *) &src, dst + 1, sizeof_dst); pos = strlen (dst); if (with_port) { assert(pos <= sizeof_dst); diff --git a/src/core/ddsi/src/ddsi_udp.c b/src/core/ddsi/src/ddsi_udp.c index ed09c58..e2d2d31 100644 --- a/src/core/ddsi/src/ddsi_udp.c +++ b/src/core/ddsi/src/ddsi_udp.c @@ -484,7 +484,7 @@ static char *ddsi_udp_locator_to_string (ddsi_tran_factory_t tran, char *dst, si memset (&src, 0, sizeof (src)); src.sin_family = AF_INET; memcpy (&src.sin_addr.s_addr, &mcgen.ipv4, 4); - os_sockaddrAddressToString ((const os_sockaddr *) &src, dst, sizeof_dst); + os_sockaddrtostr ((const os_sockaddr *) &src, dst, sizeof_dst); pos = strlen (dst); assert (pos <= sizeof_dst); cnt = snprintf (dst + pos, sizeof_dst - pos, ";%u;%u;%u", mcgen.base, mcgen.count, mcgen.idx); diff --git a/src/os/CMakeLists.txt b/src/os/CMakeLists.txt index 24fb186..bdd4874 100644 --- a/src/os/CMakeLists.txt +++ b/src/os/CMakeLists.txt @@ -25,7 +25,8 @@ set(sources src/os_time.c src/os_errno.c src/os_iter.c - src/os_strlcpy.c) + src/os_strlcpy.c + src/os_dns.c) string(TOLOWER ${CMAKE_SYSTEM_NAME} system_name) set(system_sources diff --git a/src/os/include/os/os_errno.h b/src/os/include/os/os_errno.h index 8c55f50..5ee172d 100644 --- a/src/os/include/os/os_errno.h +++ b/src/os/include/os/os_errno.h @@ -22,7 +22,20 @@ extern "C" { #include #endif -#include /* Required on Windows platforms too */ +#include /* Required on Windows platforms too. */ + + +/* DNS runtime functions require custom error codes to be defined. For POSIX + platforms a high enough 32-bit number (not too close to any system specified + error number) should be safe. Microsoft Windows error codes, however, are + encoded after the HRESULT (https://en.wikipedia.org/wiki/HRESULT) scheme. To + avoid clashes the customer code bit (0x20000000) must be set. */ +#define OS_ERRBASE (0x20000000) + +#define OS_HOST_NOT_FOUND (OS_ERRBASE + 1) +#define OS_NO_DATA (OS_ERRBASE + 2) +#define OS_NO_RECOVERY (OS_ERRBASE + 3) +#define OS_TRY_AGAIN (OS_ERRBASE + 4) /** \brief Get error code set by last operation that failed * @@ -41,6 +54,28 @@ extern "C" { os_setErrno ( int err); + /** + * \brief Return string describing internal error number. + * + * @param[in] errnum + * @param[out] buf + * @param[in] buflen + * + * @returns 0 on success or a valid error number on failure. + * + * @retval 0 + * Success. The error message is copied to the buffer. + * @retval EINVAL + * Not a valid internal error code. + * @retval ERANGE + * Buffer is not large enough to store the error message. + */ + OSAPI_EXPORT int _Success_(return == 0) + os_errstr( + _In_ int errnum, + _Out_writes_opt_z_(buflen) char *buf, + _In_ size_t buflen); + /** * \brief Get description for specified error code * diff --git a/src/os/include/os/os_socket.h b/src/os/include/os/os_socket.h index 9691e4e..cb59f93 100644 --- a/src/os/include/os/os_socket.h +++ b/src/os/include/os/os_socket.h @@ -48,7 +48,7 @@ extern "C" { typedef struct sockaddr os_sockaddr; typedef struct sockaddr_storage os_sockaddr_storage; -#if defined(OS_SOCKET_HAS_IPV6) && OS_SOCKET_HAS_IPV6 == 1 +#if OS_SOCKET_HAS_IPV6 typedef struct ipv6_mreq os_ipv6_mreq; typedef struct in6_addr os_in6_addr; @@ -243,37 +243,83 @@ extern "C" { const os_sockaddr* thatSock, const os_sockaddr* mask); - /** - * Convert a socket address to a string format presentation representation - * @param sa The socket address struct. - * @param buffer A character buffer to hold the string rep of the address. - * @param buflen The (max) size of the buffer - * @return Pointer to start of string - */ - OSAPI_EXPORT char* - os_sockaddrAddressToString(const os_sockaddr* sa, - char* buffer, size_t buflen); +#ifdef OS_SOCKET_HAS_DNS + + typedef struct { + size_t naddrs; + os_sockaddr_storage addrs[]; + } os_hostent_t; /** - * Convert the provided addressString into a os_sockaddr. - * - * @param addressString The string representation of a network address. - * @param addressOut A pointer to an os_sockaddr. Must be big enough for - * the address type specified by the string. This implies it should - * generally be the address of an os_sockaddr_storage for safety's sake. - * @param isIPv4 If the addressString is a hostname specifies whether - * and IPv4 address should be returned. If false an Ipv6 address will be - * requested. If the address is in either valid decimal presentation format - * param will be ignored. - * @return true on successful conversion. false otherwise - */ - _Success_(return) OSAPI_EXPORT bool - os_sockaddrStringToAddress( - _In_z_ const char *addressString, - _When_(isIPv4, _Out_writes_bytes_(sizeof(os_sockaddr_in))) - _When_(!isIPv4, _Out_writes_bytes_(sizeof(os_sockaddr_in6))) - os_sockaddr *addressOut, - _In_ bool isIPv4); + * Lookup addresses for given host name. + * + * @param[in] name Host name to resolve. + * @param[in] af Address family, either AF_INET, AF_INET6 or AF_UNSPEC. + * @param[out] hent Structure of type os_hostent_t. + * + * @returns 0 on success or valid error number on failure. + * + * @retval 0 + * Success. Host name successfully resolved to address(es). + * @retval OS_HOST_NOT_FOUND + * Host not found. + * @retval OS_NO_DATA + * Valid name, no data record of requested type. + * @retval OS_NO_RECOVERY + * Nonrecoverable error. + * @retval OS_TRY_AGAIN + * Nonauthoratitative host not found. + */ + OSAPI_EXPORT _Success_(return == 0) int + os_gethostbyname( + _In_z_ const char *name, + _In_ int af, + _Out_ os_hostent_t **hent); + +#endif /* OS_SOCKET_HAS_DNS */ + + /** + * Convert IPv4 and IPv6 addresses from text to socket address. + * + * @param af[in] Address family, either AF_INET or AF_INET6. + * @param str[in] Network address in text form. + * @param sa[out] Pointer to a sufficiently large enough socket address + * structure. This implies it should generally be the + * address to a structure of type struct sockaddr_storage. + * + * @return 0 on success or a valid error number on failure. + */ + OSAPI_EXPORT _Success_(return) int + os_sockaddrfromstr( + _In_ int af, + _In_z_ const char *str, + _When_(af == AF_INET, _Out_writes_bytes_(sizeof(os_sockaddr_in))) +#if OS_SOCKET_HAS_IPV6 + _When_(af == AF_INET6, _Out_writes_bytes_(sizeof(os_sockaddr_in6))) +#endif + void *sa); + + /** + * Convert a socket address to text form. + * + * @param[in] sa Socket address structure. + * @param[out] buf Buffer to which resulting string is copied. + * @param[in] size Number of bytes available in the buffer. + * + * @returns 0 on success or a valid error number on failure. + * + * @retval 0 + * Success. Socket address structure converted to text form. + * @retval EAFNOSUPPORT + * Socket address structure of unsupported valid address family. + * @retval ENOSPC + * Text form would exceed the size specified by size. + */ + OSAPI_EXPORT _Success_(return == 0) int + os_sockaddrtostr( + _In_ const void *sa, + _Out_writes_z_(size) char *buf, + _In_ size_t size); /* docced in implementation file */ OSAPI_EXPORT bool diff --git a/src/os/include/os/posix/os_platform_socket.h b/src/os/include/os/posix/os_platform_socket.h index fa9944d..263711c 100644 --- a/src/os/include/os/posix/os_platform_socket.h +++ b/src/os/include/os/posix/os_platform_socket.h @@ -25,6 +25,7 @@ #include #include #include +#include #ifdef __APPLE__ #include @@ -36,6 +37,7 @@ extern "C" { #endif /* Keep defines before common header */ +#define OS_SOCKET_HAS_DNS 1 #define OS_SOCKET_HAS_IPV6 1 #define OS_SOCKET_HAS_SA_LEN 1 #define OS_NO_SIOCGIFINDEX 1 diff --git a/src/os/include/os/windows/os_platform_socket.h b/src/os/include/os/windows/os_platform_socket.h index 30ff75a..f9caa18 100644 --- a/src/os/include/os/windows/os_platform_socket.h +++ b/src/os/include/os/windows/os_platform_socket.h @@ -25,6 +25,7 @@ extern "C" { /* Keep defines before common header */ #define OS_SOCKET_HAS_IPV6 1 +#define OS_SOCKET_HAS_DNS 1 #define OS_SOCKET_HAS_SA_LEN 0 #define OS_NO_SIOCGIFINDEX 1 #define OS_NO_NETLINK 1 diff --git a/src/os/src/os_dns.c b/src/os/src/os_dns.c new file mode 100644 index 0000000..36a29f0 --- /dev/null +++ b/src/os/src/os_dns.c @@ -0,0 +1,146 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#define _GNU_SOURCE + +#include +#include + +#include "os/os.h" + +int +os_gethostbyname( + const char *name, + int af, + os_hostent_t **hentp) +{ + int err = 0; + int gai_err = 0; + struct addrinfo hints, *res = NULL; + os_hostent_t *hent = NULL; + + assert(name != NULL); + assert(hentp != NULL); + + switch (af) { +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: +#endif + case AF_INET: + case AF_UNSPEC: + break; + default: + return EAFNOSUPPORT; + } + + /* Windows returns all registered addresses on the local computer if the + "nodename" parameter is an empty string. *NIX return HOST_NOT_FOUND. + Deny empty hostnames to keep behavior across platforms consistent. */ + if (strlen(name) == 0) { + return OS_HOST_NOT_FOUND; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + + gai_err = getaddrinfo(name, NULL, &hints, &res); + /* gai_strerror cannot be used because Windows does not offer a thread-safe + implementation and lwIP (there maybe others as well) does not offer an + implementation at all. + + NOTE: Error codes returned by getaddrinfo map directly onto Windows + Socket error codes and WSAGetLastError can be used instead. */ + DDS_TRACE("getaddrinfo for %s returned %d\n", name, gai_err); + switch (gai_err) { +#if defined(EAI_AGAIN) + case EAI_AGAIN: + /* Name server returned a temporary failure indication. */ + err = OS_TRY_AGAIN; + break; +#endif + case EAI_FAIL: + /* Name server returned a permanent failure indication. */ + err = OS_NO_RECOVERY; + break; +/* Windows defines EAI_NODATA to EAI_NONAME. */ +#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) + case EAI_NODATA: + /* Host exists, but does not have any network addresses defined. */ + err = OS_NO_DATA; + break; +#endif +#if defined(EAI_ADDRFAMILY) + case EAI_ADDRFAMILY: /* Host has no addresses in requested family. */ +#endif +#if defined(EAI_NOSECURENAME) /* Windows */ + case EAI_NOSECURENAME: +#endif + case EAI_NONAME: + /* Host does not exist. */ + err = OS_HOST_NOT_FOUND; + break; + case EAI_MEMORY: + /* Out of memory. */ + err = ENOMEM; + break; +#if defined(EAI_SYSTEM) + case EAI_SYSTEM: + /* Other system error. */ + err = errno; + break; +#endif + case EAI_BADFLAGS: /* Invalid flags in hints.ai_flags. */ + case EAI_FAMILY: /* Address family not supported. */ + case EAI_SERVICE: /* Service not available for socket type. */ + case EAI_SOCKTYPE: /* Socket type not supported. */ + case 0: + { + struct addrinfo *ai; + size_t addrno, naddrs, size; + + assert(gai_err == 0); + assert(res != NULL); + + naddrs = 0; + for (ai = res; ai != NULL; ai = ai->ai_next) { + naddrs++; + } + + size = sizeof(*hent) + (naddrs * sizeof(hent->addrs[0])); + if ((hent = os_malloc_0_s(size)) != NULL) { + hent->naddrs = naddrs; + for (addrno = 0, ai = res; + addrno < naddrs && ai != NULL; + addrno++, ai = ai->ai_next) + { + memcpy(&hent->addrs[addrno], res->ai_addr, res->ai_addrlen); + } + } else { + err = ENOMEM; + } + } + break; + default: + DDS_FATAL("getaddrinfo returned unkown error %d\n", gai_err); + } + + if (res != NULL) { + freeaddrinfo(res); + } + + if (err == 0) { + *hentp = hent; + } else { + os_free(hent); + } + + return err; +} diff --git a/src/os/src/os_errno.c b/src/os/src/os_errno.c index 404db3c..33dd4ce 100644 --- a/src/os/src/os_errno.c +++ b/src/os/src/os_errno.c @@ -15,6 +15,40 @@ #include #include +static struct { int err; const char *str; } errstrs[] = +{ + { OS_HOST_NOT_FOUND, "Host not found" }, + { OS_NO_DATA, "Valid name, no data record of requested type" }, + { OS_NO_RECOVERY, "Nonrecoverable error" }, + { OS_TRY_AGAIN, "Nonauthoritative host not found" }, +}; + +int os_errstr(_In_ int errnum, char *buf, size_t buflen) +{ + int err = 0; + const char *str = NULL; + unsigned idx = (unsigned)(errnum - (OS_ERRBASE + 1)); + static const unsigned max = (unsigned)(sizeof(errstrs)/sizeof(errstrs[0])); + + if (errnum > OS_ERRBASE && idx < max) { + size_t len; + assert(errstrs[idx].err == errnum); + str = errstrs[idx].str; + assert(str != NULL); + len = strlen(str); + if (len < buflen) { + memcpy(buf, str, len); + buf[len] = '\0'; + } else { + err = ERANGE; + } + } else { + err = EINVAL; + } + + return err; +} + #define MIN_BUFLEN (64) #define MAX_BUFLEN (1024) diff --git a/src/os/src/os_socket.c b/src/os/src/os_socket.c index 8905dfe..71105ce 100644 --- a/src/os/src/os_socket.c +++ b/src/os/src/os_socket.c @@ -37,14 +37,6 @@ const os_in6_addr os_in6addr_loopback = { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } #endif #endif -#ifndef OS_INET_NTOP -#define OS_INET_NTOP inet_ntop -#endif - -#ifndef OS_INET_PTON -#define OS_INET_PTON inet_pton -#endif - const int afs[] = { #ifdef __linux AF_PACKET, @@ -220,57 +212,99 @@ os_sockaddrSameSubnet(const os_sockaddr* thisSock, return result; } -#if WIN32 -/* - * gai_strerror under Windows is not thread safe. See getaddrinfo on MSDN: - * https://msdn.microsoft.com/en-us/library/windows/desktop/ms738520.aspx - * - * The error codes that getaddrinfo returns map directly onto WSA error codes. - * os_strerror_r can therefore safely be used to retrieve their description. - */ -#define os_gai_strerror(errnum) os_strerror(errnum) -#else -#define os_gai_strerror(errnum) gai_strerror(errnum) -#endif /* WIN32 */ - -_Success_(return) bool -os_sockaddrStringToAddress( - _In_z_ const char *addressString, - _When_(isIPv4, _Out_writes_bytes_(sizeof(os_sockaddr_in))) - _When_(!isIPv4, _Out_writes_bytes_(sizeof(os_sockaddr_in6))) - os_sockaddr *addressOut, - _In_ bool isIPv4) +int _Success_(return == 0) +os_sockaddrfromstr( + _In_ int af, + _In_z_ const char *str, + _When_(af == AF_INET, _Out_writes_bytes(sizeof(os_sockaddr_in))) +#if OS_SOCKET_HAS_IPV6 + _When_(af == AF_INET6, _Out_writes_bytes(sizeof(os_sockaddr_in6))) +#endif /* OS_SOCKET_HAS_IPV6 */ + void *sa) { - int ret; - const char *fmt; - struct addrinfo hints; - struct addrinfo *res = NULL; + int err = 0; - assert(addressString != NULL); - assert(addressOut != NULL); + assert(str != NULL); + assert(sa != NULL); - memset(&hints, 0, sizeof(hints)); -#if (OS_SOCKET_HAS_IPV6 == 1) - hints.ai_family = (isIPv4 ? AF_INET : AF_INET6); -#else - hints.ai_family = AF_INET; - OS_UNUSED_ARG(isIPv4); -#endif /* IPv6 */ - hints.ai_socktype = SOCK_DGRAM; - - ret = getaddrinfo(addressString, NULL, &hints, &res); - if (ret != 0) { - fmt = "getaddrinfo(\"%s\") failed: %s"; - DDS_TRACE(fmt, addressString, os_gai_strerror(ret)); - } else if (res != NULL) { - memcpy(addressOut, res->ai_addr, res->ai_addrlen); - freeaddrinfo(res); - } else { - fmt = "getaddrinfo(\"%s\") did not return any results"; - DDS_TRACE(fmt, addressString); + switch (af) { + case AF_INET: + { + struct in_addr buf; + if (inet_pton(af, str, &buf) != 1) { + err = EINVAL; + } else { + memset(sa, 0, sizeof(os_sockaddr_in)); + ((os_sockaddr_in *)sa)->sin_family = AF_INET; + memcpy(&((os_sockaddr_in *)sa)->sin_addr, &buf, sizeof(buf)); + } + } + break; +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + { + struct in6_addr buf; + if (inet_pton(af, str, &buf) != 1) { + err = EINVAL; + } else { + memset(sa, 0, sizeof(os_sockaddr_in6)); + ((os_sockaddr_in6 *)sa)->sin6_family = AF_INET6; + memcpy(&((os_sockaddr_in6 *)sa)->sin6_addr, &buf, sizeof(buf)); + } + } + break; +#endif /* OS_SOCKET_HAS_IPV6 */ + default: + err = EAFNOSUPPORT; + break; } - return (ret == 0 && res != NULL); + return err; +} + +int _Success_(return == 0) +os_sockaddrtostr( + _In_ const void *sa, + _Out_writes_z_(size) char *buf, + _In_ size_t size) +{ + int err = 0; + const char *ptr; + + assert(sa != NULL); + assert(buf != NULL); + + switch (((os_sockaddr *)sa)->sa_family) { + case AF_INET: + ptr = inet_ntop( + AF_INET, &((os_sockaddr_in *)sa)->sin_addr, buf, (socklen_t)size); + break; +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + ptr = inet_ntop( + AF_INET6, &((os_sockaddr_in6 *)sa)->sin6_addr, buf, (socklen_t)size); + break; +#endif + default: + return EAFNOSUPPORT; + } + + if (ptr == NULL) { +#if WIN32 + err = GetLastError(); + if (ERROR_INVALID_PARAMETER) { + /* ERROR_INVALID_PARAMETER is returned if the buffer is a null + pointer or the size of the buffer is not sufficiently large + enough. *NIX platforms set errno to ENOSPC if the buffer is not + large enough to store the IP address in text form. */ + err = ENOSPC; + } +#else + err = errno; +#endif + } + + return err; } /** @@ -290,7 +324,7 @@ os_sockaddrIsLoopback(const os_sockaddr* thisSock) if (linkLocalLoopbackPtr == NULL) { /* Initialise once (where 'once' implies some small integer) */ - os_sockaddrStringToAddress("fe80::1", (os_sockaddr*) &linkLocalLoopback, false /* ! ipv4 */ ); + os_sockaddrfromstr(AF_INET6, "fe80::1", (os_sockaddr*) &linkLocalLoopback); linkLocalLoopbackPtr = (os_sockaddr*) &linkLocalLoopback; } @@ -328,28 +362,3 @@ os_sockaddrSetInAddrAny( ((os_sockaddr_in*)sa)->sin_addr.s_addr = htonl(INADDR_ANY); } } - -char* -os_sockaddrAddressToString(const os_sockaddr* sa, - char* buffer, size_t buflen) -{ - assert (buflen <= 0x7fffffff); - - switch(sa->sa_family) { - case AF_INET: - OS_INET_NTOP(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), - buffer, (socklen_t) buflen); - break; -#if (OS_SOCKET_HAS_IPV6 == 1) - case AF_INET6: - OS_INET_NTOP(AF_INET6, &(((os_sockaddr_in6 *)sa)->sin6_addr), - buffer, (socklen_t) buflen); - break; -#endif - default: - (void) snprintf(buffer, buflen, "Unknown address family"); - break; - } - - return buffer; -} diff --git a/src/os/src/snippets/code/os_posix_errno.c b/src/os/src/snippets/code/os_posix_errno.c index c6e9aea..fa14408 100644 --- a/src/os/src/snippets/code/os_posix_errno.c +++ b/src/os/src/snippets/code/os_posix_errno.c @@ -33,16 +33,18 @@ os_setErrno (int err) } int -os_strerror_r (int err, char *str, size_t len) +os_strerror_r (int errnum, char *buf, size_t buflen) { - int res; + int err; - assert(str != NULL); - assert(len > 0); + assert(buf != NULL); + assert(buflen > 0); - str[0] = '\0'; /* null-terminate in case nothing is written */ - res = strerror_r(err, str, len); - str[len - 1] = '\0'; /* always null-terminate, just to be safe */ + if ((err = os_errstr(errnum, buf, buflen)) == EINVAL) { + buf[0] = '\0'; /* null-terminate in case nothing is written */ + err = strerror_r(errnum, buf, buflen); + buf[buflen - 1] = '\0'; /* always null-terminate, just to be safe */ + } - return res; + return err; } diff --git a/src/os/src/vxworks/os_platform_errno.c b/src/os/src/vxworks/os_platform_errno.c index dcafe51..2bda2d1 100644 --- a/src/os/src/vxworks/os_platform_errno.c +++ b/src/os/src/vxworks/os_platform_errno.c @@ -39,44 +39,49 @@ strerrorIf( details. NAME_MAX is defined in limits.h. */ int -os_strerror_r(int err, char *str, size_t len) +os_strerror_r(int errnum, char *buf, size_t buflen) { - int res = EINVAL; + int err; + const char *str; - assert(str != NULL); + assert(buf != NULL); - if (len < (NAME_MAX + 1)) { - res = ERANGE; - } else if (strerrorIf(err) != NULL) { - (void)strerror_r(err, str); + if ((err = os_errstr(errnum, buf, buflen)) == EINVAL) { + err = 0; + if (buflen < (NAME_MAX + 1)) { + err = ERANGE; + } else if (strerrorIf(errnum) != NULL) { + (void)strerror_r(errnum, buf); + } } - return res; + return err; } #else int -os_strerror_r(int err, char *str, size_t len) +os_strerror_r(int errnum, char *buf, size_t buflen) { - int res = 0; + int err; + const char *str; - assert(str != NULL); + assert(buf != NULL); - /* VxWorks's strerror_r always returns 0 (zero), so the only way to decide - if the error was truncated is to check if the last position in the - buffer is overwritten by strerror_r. */ - str[len - 1] = 'x'; - - (void)strerror_r (err, str, len); - - if (str[len - 1] != 'x') { - res = ERANGE; + if ((err = os_errstr(errnum, buf, buflen)) == EINVAL) { + /* VxWorks's strerror_r always returns 0 (zero), so the only way to + decide if the error was truncated is to check if the last position + in the buffer is overwritten by strerror_r. */ + err = 0; + buf[buflen - 1] = 'x'; + (void)strerror_r(errnum, buf, buflen); + if (buf[buflen - 1] != 'x') { + err = ERANGE; + } + buf[buflen - 1] = '\0'; /* Always null terminate, just to be safe. */ } - str[len - 1] = '\0'; /* always null terminate, just to be safe */ - - return res; + return err; } #endif /* _WRS_KERNEL */ diff --git a/src/os/src/windows/os_platform_errno.c b/src/os/src/windows/os_platform_errno.c index ea1ca99..55e3d03 100644 --- a/src/os/src/windows/os_platform_errno.c +++ b/src/os/src/windows/os_platform_errno.c @@ -51,43 +51,46 @@ os_setErrno(int err) int os_strerror_r( - _In_ int err, - _Out_writes_z_(len) char *str, - _In_ size_t len) + _In_ int errnum, + _Out_writes_z_(buflen) char *buf, + _In_ size_t buflen) { - int res = 0, errs[2]; + int err = 0, errs[2]; DWORD cnt; - assert(str != NULL); - assert(len > 0); + assert(buf != NULL); + assert(buflen > 0); - str[0] = '\0'; /* null-terminate in case nothing is written */ - errs[0] = os_getErrno(); - cnt = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS | - FORMAT_MESSAGE_MAX_WIDTH_MASK, - NULL, - err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)str, - (DWORD)len, - NULL); + if ((err = os_errstr(errnum, buf, buflen)) == EINVAL) { + err = 0; + buf[0] = '\0'; /* Null-terminate in case nothing is written. */ + errs[0] = os_getErrno(); + cnt = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, + errnum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)buf, + (DWORD)buflen, + NULL); - errs[1] = os_getErrno(); - if (cnt == 0) { - if (errs[1] == ERROR_MORE_DATA) { - res = ERANGE; - } else { - res = EINVAL; + errs[1] = os_getErrno(); + if (cnt == 0) { + if (errs[1] == ERROR_MORE_DATA) { + err = ERANGE; + } else { + err = EINVAL; + } } + + /* os_strerror_r should not modify errno itself. */ + if (errs[0] != errs[1]) { + os_setErrno(errs[0]); + } + + buf[buflen - 1] = '\0'; /* Always null-terminate, just to be safe. */ } - /* os_strerror_r should not modify errno itself */ - if (errs[0] != errs[1]) { - os_setErrno(errs[0]); - } - - str[len - 1] = '\0'; /* always null-terminate, just to be safe */ - - return res; + return err; } diff --git a/src/os/tests/CMakeLists.txt b/src/os/tests/CMakeLists.txt index 8ac99bd..76ecc26 100644 --- a/src/os/tests/CMakeLists.txt +++ b/src/os/tests/CMakeLists.txt @@ -26,7 +26,8 @@ set(sources "thread_cleanup.c" "strcasecmp.c" "log.c" - "strlcpy.c") + "strlcpy.c" + "socket.c") add_cunit_executable(cunit_abstraction ${sources}) target_link_libraries(cunit_abstraction OSAPI) diff --git a/src/os/tests/error_no.c b/src/os/tests/error_no.c index a04b0e1..1a1833d 100644 --- a/src/os/tests/error_no.c +++ b/src/os/tests/error_no.c @@ -45,3 +45,23 @@ CU_Test(os_errno, get_and_set) printf ("Ending tc_os_errno\n"); } + +CU_Test(os_errstr, no_space) +{ + int err; + char buf[1] = { 0 }; + err = os_errstr(OS_HOST_NOT_FOUND, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, ERANGE); +} + +/* os_errstr only provides string representations for internal error codes. */ +CU_Test(os_errstr, bad_errno) +{ + int err; + char buf[128]; + buf[0] = '\0'; + err = os_errstr(OS_ERRBASE, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, EINVAL); + err = os_errstr(EINVAL, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, EINVAL); +} diff --git a/src/os/tests/socket.c b/src/os/tests/socket.c new file mode 100644 index 0000000..55abed1 --- /dev/null +++ b/src/os/tests/socket.c @@ -0,0 +1,167 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include + +#include "CUnit/Theory.h" + +#include "os/os.h" + +OS_WARNING_MSVC_OFF(4305) +#if OS_ENDIANNESS == OS_BIG_ENDIAN +static const struct sockaddr_in ipv4_loopback = + { .sin_family = AF_INET, .sin_addr = { .s_addr = 0x7f000001 } }; +#else +static const struct sockaddr_in ipv4_loopback = + { .sin_family = AF_INET, .sin_addr = { .s_addr = 0x0100007f } }; +#endif /* OS_ENDIANNESS */ +OS_WARNING_MSVC_ON(4305) + +#if OS_SOCKET_HAS_IPV6 +static const struct sockaddr_in6 ipv6_loopback = + { .sin6_family = AF_INET6, .sin6_addr = IN6ADDR_LOOPBACK_INIT }; +#endif + +static void setup(void) +{ + os_osInit(); +} + +static void teardown(void) +{ + os_osExit(); +} + +CU_Test(os_sockaddrfromstr, bad_family) +{ + int err; + os_sockaddr_storage sa; + err = os_sockaddrfromstr(AF_UNSPEC, "127.0.0.1", &sa); + CU_ASSERT_EQUAL(err, EAFNOSUPPORT); +} + +static void sockaddrfromstr_test(char *str, int af, int ret) +{ + int err; + os_sockaddr_storage ss; + err = os_sockaddrfromstr(af, str, &ss); + CU_ASSERT_EQUAL(err, ret); + if (err == 0) { + CU_ASSERT_EQUAL(ss.ss_family, af); + } +} + +CU_TheoryDataPoints(os_sockaddrfromstr, ipv4) = { + CU_DataPoints(char *, "127.0.0.1", "0.0.0.0", "nip"), + CU_DataPoints(int, AF_INET, AF_INET, AF_INET), + CU_DataPoints(int, 0, 0, EINVAL) +}; + +CU_Theory((char *str, int af, int ret), os_sockaddrfromstr, ipv4, .init=setup, .fini=teardown) +{ + sockaddrfromstr_test(str, af, ret); +} + +#if OS_SOCKET_HAS_IPV6 +CU_TheoryDataPoints(os_sockaddrfromstr, ipv6) = { + CU_DataPoints(char *, "127.0.0.1", "::1", "::1", "::", "nip"), + CU_DataPoints(int, AF_INET6, AF_INET6, AF_INET, AF_INET6, AF_INET6), + CU_DataPoints(int, EINVAL, 0, EINVAL, 0, EINVAL) +}; + +CU_Theory((char *str, int af, int ret), os_sockaddrfromstr, ipv6, .init=setup, .fini=teardown) +{ + sockaddrfromstr_test(str, af, ret); +} +#endif /* OS_SOCKET_HAS_IPV6 */ + +CU_Test(os_sockaddrtostr, bad_sockaddr, .init=setup, .fini=teardown) +{ + int err; + char buf[128] = { 0 }; + os_sockaddr_in sa; + memcpy(&sa, &ipv4_loopback, sizeof(ipv4_loopback)); + sa.sin_family = AF_UNSPEC; + err = os_sockaddrtostr(&sa, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, EAFNOSUPPORT); +} + +CU_Test(os_sockaddrtostr, no_space, .init=setup, .fini=teardown) +{ + int err; + char buf[1] = { 0 }; + err = os_sockaddrtostr(&ipv4_loopback, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, ENOSPC); +} + +CU_Test(os_sockaddrtostr, ipv4) +{ + int err; + char buf[128] = { 0 }; + err = os_sockaddrtostr(&ipv4_loopback, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, 0); + CU_ASSERT_STRING_EQUAL(buf, "127.0.0.1"); +} + +CU_Test(os_sockaddrtostr, ipv6) +{ + int err; + char buf[128] = { 0 }; + err = os_sockaddrtostr(&ipv6_loopback, buf, sizeof(buf)); + CU_ASSERT_EQUAL(err, 0); + CU_ASSERT_STRING_EQUAL(buf, "::1"); +} + +#if OS_SOCKET_HAS_DNS +static void gethostbyname_test(char *name, int af, int ret) +{ + int err; + os_hostent_t *hent = NULL; + err = os_gethostbyname(name, af, &hent); + CU_ASSERT_EQUAL(err, ret); + if (err == 0) { + CU_ASSERT_FATAL(hent->naddrs > 0); + if (af != AF_UNSPEC) { + CU_ASSERT_EQUAL(hent->addrs[0].ss_family, af); + } + } + os_free(hent); +} + +CU_TheoryDataPoints(os_gethostbyname, ipv4) = { + CU_DataPoints(char *, "", "_nah", "127.0.0.1", "127.0.0.1"), + CU_DataPoints(int, AF_UNSPEC, AF_UNSPEC, AF_INET, AF_UNSPEC), + CU_DataPoints(int, OS_HOST_NOT_FOUND, OS_HOST_NOT_FOUND, 0, 0) +}; + +CU_Theory((char *name, int af, int ret), os_gethostbyname, ipv4, .init=setup, .fini=teardown) +{ + gethostbyname_test(name, af, ret); +} + +#if OS_SOCKET_HAS_IPV6 +/* Lookup of IPv4 address and specifying AF_INET6 is not invalid as it may + return an IPV4-mapped IPv6 address. */ +CU_TheoryDataPoints(os_gethostbyname, ipv6) = { + CU_DataPoints(char *, "::1", "::1", "::1"), + CU_DataPoints(int, AF_INET, AF_INET6, AF_UNSPEC), + CU_DataPoints(int, OS_HOST_NOT_FOUND, 0, 0) +}; + +CU_Theory((char *name, int af, int ret), os_gethostbyname, ipv6, .init=setup, .fini=teardown) +{ + gethostbyname_test(name, af, ret); +} +#endif /* OS_SOCKET_HAS_IPV6 */ +#endif /* OS_SOCKET_HAS_DNS */