summaryrefslogtreecommitdiff
path: root/test/unit/api
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-10-16 08:10:14 -0600
committerTom Rini <trini@konsulko.com>2024-10-16 08:10:14 -0600
commitf3f86fd1fe0fb288356bff78f8a6fa2edf89e3fc (patch)
treef0a99ea87d92f63895a6d053e3185838ebecf2d0 /test/unit/api
Squashed 'lib/lwip/lwip/' content from commit 0a0452b2c39b
git-subtree-dir: lib/lwip/lwip git-subtree-split: 0a0452b2c39bdd91e252aef045c115f88f6ca773
Diffstat (limited to 'test/unit/api')
-rw-r--r--test/unit/api/test_sockets.c852
-rw-r--r--test/unit/api/test_sockets.h8
2 files changed, 860 insertions, 0 deletions
diff --git a/test/unit/api/test_sockets.c b/test/unit/api/test_sockets.c
new file mode 100644
index 00000000000..3faa5ff9f18
--- /dev/null
+++ b/test/unit/api/test_sockets.c
@@ -0,0 +1,852 @@
+#include "test_sockets.h"
+
+#include "lwip/mem.h"
+#include "lwip/opt.h"
+#include "lwip/sockets.h"
+#include "lwip/priv/sockets_priv.h"
+#include "lwip/stats.h"
+
+#include "lwip/tcpip.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/api.h"
+
+
+static int
+test_sockets_get_used_count(void)
+{
+ int used = 0;
+ int i;
+
+ for (i = 0; i < NUM_SOCKETS; i++) {
+ struct lwip_sock* s = lwip_socket_dbg_get_socket(i);
+ if (s != NULL) {
+ if (s->fd_used) {
+ used++;
+ }
+ }
+ }
+ return used;
+}
+
+
+/* Setups/teardown functions */
+
+static void
+sockets_setup(void)
+{
+ /* expect full free heap */
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+sockets_teardown(void)
+{
+ fail_unless(test_sockets_get_used_count() == 0);
+ /* poll until all memory is released... */
+ tcpip_thread_poll_one();
+ while (tcp_tw_pcbs) {
+ tcp_abort(tcp_tw_pcbs);
+ tcpip_thread_poll_one();
+ }
+ tcpip_thread_poll_one();
+ /* ensure full free heap */
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+#ifndef NUM_SOCKETS
+#define NUM_SOCKETS MEMP_NUM_NETCONN
+#endif
+
+#if LWIP_SOCKET
+static int
+test_sockets_alloc_socket_nonblocking(int domain, int type)
+{
+ int s = lwip_socket(domain, type, 0);
+ if (s >= 0) {
+ int ret = lwip_fcntl(s, F_SETFL, O_NONBLOCK);
+ fail_unless(ret == 0);
+ }
+ return s;
+}
+
+/* Verify basic sockets functionality
+ */
+START_TEST(test_sockets_basics)
+{
+ int s, i, ret;
+ int s2[NUM_SOCKETS];
+ LWIP_UNUSED_ARG(_i);
+
+ s = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s >= 0);
+ lwip_close(s);
+
+ for (i = 0; i < NUM_SOCKETS; i++) {
+ s2[i] = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s2[i] >= 0);
+ }
+
+ /* all sockets used, now it should fail */
+ s = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s == -1);
+ /* close one socket */
+ ret = lwip_close(s2[0]);
+ fail_unless(ret == 0);
+ /* now it should succeed */
+ s2[0] = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s2[0] >= 0);
+
+ /* close all sockets */
+ for (i = 0; i < NUM_SOCKETS; i++) {
+ ret = lwip_close(s2[i]);
+ fail_unless(ret == 0);
+ }
+}
+END_TEST
+
+static void test_sockets_allfunctions_basic_domain(int domain)
+{
+ int s, s2, s3, ret;
+ struct sockaddr_storage addr, addr2;
+ socklen_t addrlen, addr2len;
+ char buf[4];
+ /* listen socket */
+ s = lwip_socket(domain, SOCK_STREAM, 0);
+ fail_unless(s >= 0);
+
+ ret = lwip_listen(s, 0);
+ fail_unless(ret == 0);
+
+ addrlen = sizeof(addr);
+ ret = lwip_getsockname(s, (struct sockaddr*)&addr, &addrlen);
+ fail_unless(ret == 0);
+
+ s2 = test_sockets_alloc_socket_nonblocking(domain, SOCK_STREAM);
+ fail_unless(s2 >= 0);
+ /* nonblocking connect s2 to s (but use loopback address) */
+ if (domain == AF_INET) {
+#if LWIP_IPV4
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
+ addr4->sin_addr.s_addr = PP_HTONL(INADDR_LOOPBACK);
+#endif
+ } else {
+#if LWIP_IPV6
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
+ struct in6_addr lo6 = IN6ADDR_LOOPBACK_INIT;
+ addr6->sin6_addr = lo6;
+#endif
+ }
+ ret = lwip_connect(s2, (struct sockaddr*)&addr, addrlen);
+ fail_unless(ret == -1);
+ fail_unless(errno == EINPROGRESS);
+ ret = lwip_connect(s2, (struct sockaddr*)&addr, addrlen);
+ fail_unless(ret == -1);
+ fail_unless(errno == EALREADY);
+
+ while(tcpip_thread_poll_one());
+
+ s3 = lwip_accept(s, (struct sockaddr*)&addr2, &addr2len);
+ fail_unless(s3 >= 0);
+
+ ret = lwip_connect(s2, (struct sockaddr*)&addr, addrlen);
+ fail_unless(ret == -1);
+ fail_unless(errno == EISCONN);
+
+ /* write from server to client */
+ ret = lwip_write(s3, "test", 4);
+ fail_unless(ret == 4);
+
+ ret = lwip_shutdown(s3, SHUT_WR);
+ fail_unless(ret == 0);
+
+ while(tcpip_thread_poll_one());
+
+ ret = lwip_recv(s2, buf, 3, MSG_PEEK);
+ fail_unless(ret == 3);
+
+ ret = lwip_recv(s2, buf, 3, MSG_PEEK);
+ fail_unless(ret == 3);
+
+ ret = lwip_read(s2, buf, 4);
+ fail_unless(ret == 4);
+
+ ret = lwip_read(s2, buf, 1);
+ fail_unless(ret == 0);
+
+ ret = lwip_read(s2, buf, 1);
+ fail_unless(ret == -1);
+
+ ret = lwip_write(s2, "foo", 3);
+ fail_unless(ret == 3);
+
+ ret = lwip_close(s2);
+ fail_unless(ret == 0);
+
+ while(tcpip_thread_poll_one());
+
+ /* read one byte more than available to check handling FIN */
+ ret = lwip_read(s3, buf, 4);
+ fail_unless(ret == 3);
+
+ ret = lwip_read(s3, buf, 1);
+ fail_unless(ret == 0);
+
+ ret = lwip_read(s3, buf, 1);
+ fail_unless(ret == -1);
+
+ while(tcpip_thread_poll_one());
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+ ret = lwip_close(s3);
+ fail_unless(ret == 0);
+}
+
+/* Try to step through all sockets functions once...
+ */
+START_TEST(test_sockets_allfunctions_basic)
+{
+ LWIP_UNUSED_ARG(_i);
+#if LWIP_IPV4
+ test_sockets_allfunctions_basic_domain(AF_INET);
+#endif
+#if LWIP_IPV6
+ test_sockets_allfunctions_basic_domain(AF_INET6);
+#endif
+}
+END_TEST
+
+static void test_sockets_init_loopback_addr(int domain, struct sockaddr_storage *addr_st, socklen_t *sz)
+{
+ memset(addr_st, 0, sizeof(*addr_st));
+ switch(domain) {
+#if LWIP_IPV6
+ case AF_INET6: {
+ struct sockaddr_in6 *addr = (struct sockaddr_in6*)addr_st;
+ struct in6_addr lo6 = IN6ADDR_LOOPBACK_INIT;
+ addr->sin6_family = AF_INET6;
+ addr->sin6_port = 0; /* use ephemeral port */
+ addr->sin6_addr = lo6;
+ *sz = sizeof(*addr);
+ }
+ break;
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+ case AF_INET: {
+ struct sockaddr_in *addr = (struct sockaddr_in*)addr_st;
+ addr->sin_family = AF_INET;
+ addr->sin_port = 0; /* use ephemeral port */
+ addr->sin_addr.s_addr = PP_HTONL(INADDR_LOOPBACK);
+ *sz = sizeof(*addr);
+ }
+ break;
+#endif /* LWIP_IPV4 */
+ default:
+ *sz = 0;
+ fail();
+ break;
+ }
+}
+
+static void test_sockets_msgapi_update_iovs(struct msghdr *msg, size_t bytes)
+{
+ msg_iovlen_t i;
+
+ /* note: this modifies the underyling iov_base and iov_len for a partial
+ read for an individual vector. This updates the msg->msg_iov pointer
+ to skip fully consumed vectors */
+
+ /* process fully consumed vectors */
+ for (i = 0; i < msg->msg_iovlen; i++) {
+ if (msg->msg_iov[i].iov_len <= bytes) {
+ /* reduce bytes by amount of this vector */
+ bytes -= msg->msg_iov[i].iov_len;
+ } else {
+ break; /* iov not fully consumed */
+ }
+ }
+
+ /* slide down over fully consumed vectors */
+ msg->msg_iov = &msg->msg_iov[i];
+ msg->msg_iovlen -= i;
+
+ /* update new first vector with any remaining amount */
+ msg->msg_iov[0].iov_base = ((u8_t *)msg->msg_iov[0].iov_base + bytes);
+ msg->msg_iov[0].iov_len -= bytes;
+}
+
+static void test_sockets_msgapi_tcp(int domain)
+{
+ #define BUF_SZ (TCP_SND_BUF/4)
+ #define TOTAL_DATA_SZ (BUF_SZ*8) /* ~(TCP_SND_BUF*2) that accounts for integer rounding */
+ #define NEED_TRAILER (BUF_SZ % 4 != 0)
+ int listnr, s1, s2, i, ret, opt;
+ int bytes_written, bytes_read;
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_size;
+ struct iovec siovs[8];
+ struct msghdr smsg;
+ u8_t * snd_buf;
+ struct iovec riovs[5];
+ struct iovec riovs_tmp[5];
+ struct msghdr rmsg;
+ u8_t * rcv_buf;
+ int rcv_off;
+ int rcv_trailer = 0;
+ u8_t val;
+
+ test_sockets_init_loopback_addr(domain, &addr_storage, &addr_size);
+
+ listnr = test_sockets_alloc_socket_nonblocking(domain, SOCK_STREAM);
+ fail_unless(listnr >= 0);
+ s1 = test_sockets_alloc_socket_nonblocking(domain, SOCK_STREAM);
+ fail_unless(s1 >= 0);
+
+ /* setup a listener socket on loopback with ephemeral port */
+ ret = lwip_bind(listnr, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+ ret = lwip_listen(listnr, 0);
+ fail_unless(ret == 0);
+
+ /* update address with ephemeral port */
+ ret = lwip_getsockname(listnr, (struct sockaddr*)&addr_storage, &addr_size);
+ fail_unless(ret == 0);
+
+ /* connect, won't complete until we accept it */
+ ret = lwip_connect(s1, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == -1);
+ fail_unless(errno == EINPROGRESS);
+
+ while (tcpip_thread_poll_one());
+
+ /* accept, creating the other side of the connection */
+ s2 = lwip_accept(listnr, NULL, NULL);
+ fail_unless(s2 >= 0);
+
+ /* double check s1 is connected */
+ ret = lwip_connect(s1, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == -1);
+ fail_unless(errno == EISCONN);
+
+ /* set s2 to non-blocking, not inherited from listener */
+ opt = lwip_fcntl(s2, F_GETFL, 0);
+ fail_unless(opt == O_RDWR);
+ opt = O_NONBLOCK;
+ ret = lwip_fcntl(s2, F_SETFL, opt);
+ fail_unless(ret == 0);
+
+ /* we are done with listener, close it */
+ ret = lwip_close(listnr);
+ fail_unless(ret == 0);
+
+ /* allocate a buffer for a stream of incrementing hex (0x00..0xFF) which we will use
+ to create an input vector set that is larger than the TCP's send buffer. This will
+ force execution of the partial IO vector send case */
+ snd_buf = (u8_t*)mem_malloc(BUF_SZ);
+ val = 0x00;
+ fail_unless(snd_buf != NULL);
+ for (i = 0; i < BUF_SZ; i++,val++) {
+ snd_buf[i] = val;
+ }
+
+ /* send the buffer 8 times in one message, equating to TOTAL_DATA_SZ */
+ for (i = 0; i < 8; i++) {
+ siovs[i].iov_base = snd_buf;
+ siovs[i].iov_len = BUF_SZ;
+ }
+
+ /* allocate a receive buffer, same size as snd_buf for easy verification */
+ rcv_buf = (u8_t*)mem_calloc(1, BUF_SZ);
+ fail_unless(rcv_buf != NULL);
+ /* split across iovs */
+ for (i = 0; i < 4; i++) {
+ riovs[i].iov_base = &rcv_buf[i*(BUF_SZ/4)];
+ riovs[i].iov_len = BUF_SZ/4;
+ }
+ /* handling trailing bytes if buffer doesn't evenly divide by 4 */
+#if NEED_TRAILER
+ if ((BUF_SZ % 4) != 0) {
+ riovs[5].iov_base = &rcv_buf[4*(BUF_SZ/4)];
+ riovs[5].iov_len = BUF_SZ - (4*(BUF_SZ/4));
+ rcv_trailer = 1;
+ }
+#endif /* NEED_TRAILER */
+
+ /* we use a copy of riovs since we'll be modifying base and len during
+ receiving. This gives us an easy way to reset the iovs for next recvmsg */
+ memcpy(riovs_tmp, riovs, sizeof(riovs));
+
+ memset(&smsg, 0, sizeof(smsg));
+ smsg.msg_iov = siovs;
+ smsg.msg_iovlen = 8;
+
+ memset(&rmsg, 0, sizeof(rmsg));
+ rmsg.msg_iov = riovs_tmp;
+ rmsg.msg_iovlen = (rcv_trailer ? 5 : 4);
+
+ bytes_written = 0;
+ bytes_read = 0;
+ rcv_off = 0;
+
+ while (bytes_written < TOTAL_DATA_SZ && (bytes_read < TOTAL_DATA_SZ)) {
+ /* send data */
+ if (bytes_written < TOTAL_DATA_SZ) {
+ ret = lwip_sendmsg(s1, &smsg, 0);
+ /* note: since we always receive after sending, there will be open
+ space in the send buffer */
+ fail_unless(ret > 0);
+
+ bytes_written += ret;
+ if (bytes_written < TOTAL_DATA_SZ) {
+ test_sockets_msgapi_update_iovs(&smsg, (size_t)ret);
+ }
+ }
+
+ while (tcpip_thread_poll_one());
+
+ /* receive and verify data */
+ do {
+ if (bytes_read < TOTAL_DATA_SZ) {
+ ret = lwip_recvmsg(s2, &rmsg, 0);
+ fail_unless(ret > 0 || (ret == -1 && errno == EWOULDBLOCK));
+
+ if (ret > 0) {
+ rcv_off += ret;
+ /* we have received a full buffer */
+ if (rcv_off == BUF_SZ) {
+ /* note: since iovs are just pointers, compare underlying buf */
+ fail_unless(!memcmp(snd_buf, rcv_buf, BUF_SZ));
+ bytes_read += BUF_SZ;
+ /* reset receive state for next buffer */
+ rcv_off = 0;
+ memset(rcv_buf, 0, BUF_SZ);
+ memcpy(riovs_tmp, riovs, sizeof(riovs));
+ rmsg.msg_iov = riovs_tmp;
+ rmsg.msg_iovlen = (rcv_trailer ? 5 : 4);
+ } else { /* partial read */
+ test_sockets_msgapi_update_iovs(&rmsg, (size_t)ret);
+ }
+ }
+ } else {
+ break;
+ }
+ } while(ret > 0);
+ }
+
+ ret = lwip_close(s1);
+ fail_unless(ret == 0);
+ ret = lwip_close(s2);
+ fail_unless(ret == 0);
+ mem_free(snd_buf);
+ mem_free(rcv_buf);
+}
+
+static void test_sockets_msgapi_udp_send_recv_loop(int s, struct msghdr *smsg, struct msghdr *rmsg)
+{
+ int i, ret;
+
+ /* send/receive our datagram of IO vectors 10 times */
+ for (i = 0; i < 10; i++) {
+ ret = lwip_sendmsg(s, smsg, 0);
+ fail_unless(ret == 4);
+
+ while (tcpip_thread_poll_one());
+
+ /* receive the datagram split across 4 buffers */
+ ret = lwip_recvmsg(s, rmsg, 0);
+ fail_unless(ret == 4);
+
+ /* verify data */
+ fail_unless(*((u8_t*)rmsg->msg_iov[0].iov_base) == 0xDE);
+ fail_unless(*((u8_t*)rmsg->msg_iov[1].iov_base) == 0xAD);
+ fail_unless(*((u8_t*)rmsg->msg_iov[2].iov_base) == 0xBE);
+ fail_unless(*((u8_t*)rmsg->msg_iov[3].iov_base) == 0xEF);
+
+ /* clear rcv_buf to ensure no data is being skipped */
+ *((u8_t*)rmsg->msg_iov[0].iov_base) = 0x00;
+ *((u8_t*)rmsg->msg_iov[1].iov_base) = 0x00;
+ *((u8_t*)rmsg->msg_iov[2].iov_base) = 0x00;
+ *((u8_t*)rmsg->msg_iov[3].iov_base) = 0x00;
+ }
+}
+
+static void test_sockets_msgapi_udp(int domain)
+{
+ int s, i, ret;
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_size;
+ struct iovec riovs[4];
+ struct msghdr rmsg;
+ u8_t rcv_buf[4];
+ struct iovec siovs[4];
+ struct msghdr smsg;
+ u8_t snd_buf[4] = {0xDE, 0xAD, 0xBE, 0xEF};
+
+ /* initialize IO vectors with data */
+ for (i = 0; i < 4; i++) {
+ siovs[i].iov_base = &snd_buf[i];
+ siovs[i].iov_len = sizeof(u8_t);
+ riovs[i].iov_base = &rcv_buf[i];
+ riovs[i].iov_len = sizeof(u8_t);
+ }
+
+ test_sockets_init_loopback_addr(domain, &addr_storage, &addr_size);
+
+ s = test_sockets_alloc_socket_nonblocking(domain, SOCK_DGRAM);
+ fail_unless(s >= 0);
+
+ ret = lwip_bind(s, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+
+ /* Update addr with epehermal port */
+ ret = lwip_getsockname(s, (struct sockaddr*)&addr_storage, &addr_size);
+ fail_unless(ret == 0);
+ switch(domain) {
+#if LWIP_IPV6
+ case AF_INET6:
+ fail_unless(addr_size == sizeof(struct sockaddr_in6));
+ break;
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+ case AF_INET:
+ fail_unless(addr_size == sizeof(struct sockaddr_in));
+ break;
+#endif /* LWIP_IPV6 */
+ default:
+ fail();
+ break;
+ }
+
+ /* send and receive the datagram in 4 pieces */
+ memset(&smsg, 0, sizeof(smsg));
+ smsg.msg_iov = siovs;
+ smsg.msg_iovlen = 4;
+ memset(&rmsg, 0, sizeof(rmsg));
+ rmsg.msg_iov = riovs;
+ rmsg.msg_iovlen = 4;
+
+ /* perform a sendmsg with remote host (self) */
+ smsg.msg_name = &addr_storage;
+ smsg.msg_namelen = addr_size;
+
+ test_sockets_msgapi_udp_send_recv_loop(s, &smsg, &rmsg);
+
+ /* Connect to self, allowing us to not pass message name */
+ ret = lwip_connect(s, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+
+ smsg.msg_name = NULL;
+ smsg.msg_namelen = 0;
+
+ test_sockets_msgapi_udp_send_recv_loop(s, &smsg, &rmsg);
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+}
+
+#if LWIP_IPV4
+static void test_sockets_msgapi_cmsg(int domain)
+{
+ int s, ret, enable;
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_size;
+ struct iovec iov;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ struct in_pktinfo *pktinfo;
+ u8_t rcv_buf[4];
+ u8_t snd_buf[4] = {0xDE, 0xAD, 0xBE, 0xEF};
+ u8_t cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+
+ test_sockets_init_loopback_addr(domain, &addr_storage, &addr_size);
+
+ s = test_sockets_alloc_socket_nonblocking(domain, SOCK_DGRAM);
+ fail_unless(s >= 0);
+
+ ret = lwip_bind(s, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+
+ /* Update addr with epehermal port */
+ ret = lwip_getsockname(s, (struct sockaddr*)&addr_storage, &addr_size);
+ fail_unless(ret == 0);
+
+ enable = 1;
+ ret = lwip_setsockopt(s, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
+ fail_unless(ret == 0);
+
+ /* Receive full message, including control message */
+ iov.iov_base = rcv_buf;
+ iov.iov_len = sizeof(rcv_buf);
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+ msg.msg_flags = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+
+ memset(rcv_buf, 0, sizeof(rcv_buf));
+ ret = lwip_sendto(s, snd_buf, sizeof(snd_buf), 0, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == sizeof(snd_buf));
+
+ tcpip_thread_poll_one();
+
+ ret = lwip_recvmsg(s, &msg, 0);
+ fail_unless(ret == sizeof(rcv_buf));
+ fail_unless(!memcmp(rcv_buf, snd_buf, sizeof(rcv_buf)));
+
+ /* Verify message header */
+ cmsg = CMSG_FIRSTHDR(&msg);
+ fail_unless(cmsg != NULL);
+ fail_unless(cmsg->cmsg_len > 0);
+ fail_unless(cmsg->cmsg_level == IPPROTO_IP);
+ fail_unless(cmsg->cmsg_type == IP_PKTINFO);
+
+ /* Verify message data */
+ pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
+ /* We only have loopback interface enabled */
+ fail_unless(pktinfo->ipi_ifindex == 1);
+ fail_unless(pktinfo->ipi_addr.s_addr == PP_HTONL(INADDR_LOOPBACK));
+
+ /* Verify there are no additional messages */
+ cmsg = CMSG_NXTHDR(&msg, cmsg);
+ fail_unless(cmsg == NULL);
+
+ /* Send datagram again, testing truncation */
+ memset(rcv_buf, 0, sizeof(rcv_buf));
+ ret = lwip_sendto(s, snd_buf, sizeof(snd_buf), 0, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == sizeof(snd_buf));
+
+ tcpip_thread_poll_one();
+
+ msg.msg_controllen = 1;
+ msg.msg_flags = 0;
+ ret = lwip_recvmsg(s, &msg, 0);
+ fail_unless(ret == sizeof(rcv_buf));
+ fail_unless(!memcmp(rcv_buf, snd_buf, sizeof(rcv_buf)));
+ /* Ensure truncation was returned */
+ fail_unless(msg.msg_flags & MSG_CTRUNC);
+ /* Ensure no control messages were returned */
+ fail_unless(msg.msg_controllen == 0);
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+}
+#endif /* LWIP_IPV4 */
+
+START_TEST(test_sockets_msgapis)
+{
+ LWIP_UNUSED_ARG(_i);
+#if LWIP_IPV4
+ test_sockets_msgapi_udp(AF_INET);
+ test_sockets_msgapi_tcp(AF_INET);
+ test_sockets_msgapi_cmsg(AF_INET);
+#endif
+#if LWIP_IPV6
+ test_sockets_msgapi_udp(AF_INET6);
+ test_sockets_msgapi_tcp(AF_INET6);
+#endif
+}
+END_TEST
+
+START_TEST(test_sockets_select)
+{
+#if LWIP_SOCKET_SELECT
+ int s;
+ int ret;
+ fd_set readset;
+ fd_set writeset;
+ fd_set errset;
+ struct timeval tv;
+
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ s = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s >= 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ FD_ZERO(&readset);
+ FD_SET(s, &readset);
+ FD_ZERO(&writeset);
+ FD_SET(s, &writeset);
+ FD_ZERO(&errset);
+ FD_SET(s, &errset);
+
+ tv.tv_sec = tv.tv_usec = 0;
+ ret = lwip_select(s + 1, &readset, &writeset, &errset, &tv);
+ fail_unless(ret == 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+
+#endif
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+START_TEST(test_sockets_recv_after_rst)
+{
+ int sl, sact;
+ int spass = -1;
+ int ret;
+ struct sockaddr_in sa_listen;
+ const u16_t port = 1234;
+ int arg;
+ const char txbuf[] = "something";
+ char rxbuf[16];
+ struct lwip_sock *sact_sock;
+ int err;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ memset(&sa_listen, 0, sizeof(sa_listen));
+ sa_listen.sin_family = AF_INET;
+ sa_listen.sin_port = PP_HTONS(port);
+ sa_listen.sin_addr.s_addr = PP_HTONL(INADDR_LOOPBACK);
+
+ /* set up the listener */
+ sl = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(sl >= 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ ret = lwip_bind(sl, (struct sockaddr *)&sa_listen, sizeof(sa_listen));
+ fail_unless(ret == 0);
+ ret = lwip_listen(sl, 0);
+ fail_unless(ret == 0);
+
+ /* set up the client */
+ sact = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(sact >= 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+ /* set the client to nonblocking to simplify this test */
+ arg = 1;
+ ret = lwip_ioctl(sact, FIONBIO, &arg);
+ fail_unless(ret == 0);
+ /* connect */
+ do {
+ ret = lwip_connect(sact, (struct sockaddr *)&sa_listen, sizeof(sa_listen));
+ err = errno;
+ fail_unless((ret == 0) || (ret == -1));
+ if (ret != 0) {
+ if (err == EISCONN) {
+ /* Although this is not valid, use EISCONN as an indicator for successful connection.
+ This marks us as "connect phase is done". On error, we would either have a different
+ errno code or "send" fails later... -> good enough for this test. */
+ ret = 0;
+ } else {
+ fail_unless(err == EINPROGRESS);
+ if (err != EINPROGRESS) {
+ goto cleanup;
+ }
+ /* we're in progress: little side check: test for EALREADY */
+ ret = lwip_connect(sact, (struct sockaddr *)&sa_listen, sizeof(sa_listen));
+ err = errno;
+ fail_unless(ret == -1);
+ fail_unless(err == EALREADY);
+ if ((ret != -1) || (err != EALREADY)) {
+ goto cleanup;
+ }
+ }
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+ }
+ } while (ret != 0);
+ fail_unless(ret == 0);
+
+ /* accept the server connection part */
+ spass = lwip_accept(sl, NULL, NULL);
+ fail_unless(spass >= 0);
+
+ /* write data from client */
+ ret = lwip_send(sact, txbuf, sizeof(txbuf), 0);
+ fail_unless(ret == sizeof(txbuf));
+
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* issue RST (This is a HACK, don't try this in your own app!) */
+ sact_sock = lwip_socket_dbg_get_socket(sact);
+ fail_unless(sact_sock != NULL);
+ if (sact_sock != NULL) {
+ struct netconn *sact_conn = sact_sock->conn;
+ fail_unless(sact_conn != NULL);
+ if (sact_conn != NULL) {
+ struct tcp_pcb *pcb = sact_conn->pcb.tcp;
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
+ pcb->local_port, pcb->remote_port);
+ }
+ }
+ }
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive data first */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret > 0);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive RST indication */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret == -1);
+ err = errno;
+ fail_unless(err == ECONNRESET);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive ENOTCONN indication */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret == -1);
+ err = errno;
+ fail_unless(err == ENOTCONN);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive ENOTCONN indication */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret == -1);
+ err = errno;
+ fail_unless(err == ENOTCONN);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+cleanup:
+ ret = lwip_close(sl);
+ fail_unless(ret == 0);
+ ret = lwip_close(sact);
+ fail_unless(ret == 0);
+ if (spass >= 0) {
+ ret = lwip_close(spass);
+ fail_unless(ret == 0);
+ }
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+sockets_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_sockets_basics),
+ TESTFUNC(test_sockets_allfunctions_basic),
+ TESTFUNC(test_sockets_msgapis),
+ TESTFUNC(test_sockets_select),
+ TESTFUNC(test_sockets_recv_after_rst),
+ };
+ return create_suite("SOCKETS", tests, sizeof(tests)/sizeof(testfunc), sockets_setup, sockets_teardown);
+}
+
+#else /* LWIP_SOCKET */
+
+Suite *
+sockets_suite(void)
+{
+ return create_suite("SOCKETS", NULL, 0, NULL, NULL);
+}
+#endif /* LWIP_SOCKET */
diff --git a/test/unit/api/test_sockets.h b/test/unit/api/test_sockets.h
new file mode 100644
index 00000000000..0abb7a3057a
--- /dev/null
+++ b/test/unit/api/test_sockets.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_SOCKETS_H
+#define LWIP_HDR_TEST_SOCKETS_H
+
+#include "../lwip_check.h"
+
+Suite *sockets_suite(void);
+
+#endif