diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 1e4b4a0e..7e45086d 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] jobs: - build: + build-and-test: strategy: matrix: compiler: [gcc, clang] @@ -12,6 +12,7 @@ jobs: LSAN_OPTIONS: verbosity=1:log_threads=1 WGET_PATH: http://download.savannah.nongnu.org/releases/lwip CONTRIB: contrib-2.1.0 + WGET_CHECK2JUNIT_PY: https://raw.githubusercontent.com/gsauthof/utility/42792030/check2junit.py runs-on: ubuntu-latest steps: @@ -22,6 +23,8 @@ jobs: wget --no-verbose ${WGET_PATH}/${CONTRIB}.zip unzip -oq ${CONTRIB}.zip patch -s -p0 < test/${CONTRIB}.patch + python -m pip install lxml junit-xml + wget --no-verbose ${WGET_CHECK2JUNIT_PY} - name: Build and Run unit tests with make run: | @@ -55,6 +58,50 @@ jobs: - name: Build and run unit tests with cmake run: | + export LWIP_DIR=`pwd` cd ${CONTRIB}/ports/unix/check mkdir build && cd build && cmake -DLWIP_DIR=`pwd`/../../../../.. .. -G Ninja cmake --build . && ./lwip_unittests + python ${LWIP_DIR}/check2junit.py lwip_unittests.xml > ${LWIP_DIR}/unit_tests.xml + + - name: Build and run test apps + run: | + export LWIP_DIR=`pwd` && export LWIP_CONTRIB_DIR=`pwd`/${CONTRIB} + cd test/apps + # Prepare a failing report in case we get stuck (check in no-fork mode) + python socket_linger_stress_test.py failed > ${LWIP_DIR}/socket_linger_stress_test.xml + for cfg in config_no_linger config_linger config_linger_reuse; do + cmake -DCI_BUILD=1 -DTEST_CONFIG=${cfg} -B ${cfg} -G Ninja . + cmake --build ${cfg}/ + timeout 10 ./${cfg}/lwip_test_apps + python ${LWIP_DIR}/check2junit.py lwip_test_apps.xml > ${LWIP_DIR}/${cfg}.xml + done + # Run the lingering test multiple times + for run in {1..10000}; do ( timeout 10 ./config_linger/lwip_test_apps ) || exit 1 ; done + # All good, regenerate the stress test-report, since the test succeeded + python socket_linger_stress_test.py > ${LWIP_DIR}/socket_linger_stress_test.xml + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v2 + with: + name: Test Results ${{ matrix.compiler }} + path: "*.xml" + + publish-test-results: + name: "Publish Tests Results" + needs: build-and-test + runs-on: ubuntu-latest + permissions: + checks: write + if: always() + steps: + - name: Download Artifacts + uses: actions/download-artifact@v2 + with: + path: artifacts + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + junit_files: "artifacts/**/*.xml" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a27ae2fe..e3624e85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -90,6 +90,22 @@ validate_opts: - export LWIPDIR=../../../../src/ - chmod +x iteropts.sh && ./iteropts.sh +run_test_apps: + stage: host_test + tags: + - host_test + script: + - *get_contrib + - *get_cmake + - export LWIP_DIR=`pwd` && export LWIP_CONTRIB_DIR=`pwd`/${CONTRIB} + - cd test/apps + - for cfg in config_no_linger config_linger config_linger_reuse; do + - cmake -DCI_BUILD=1 -DTEST_CONFIG=${cfg} -B ${cfg} -G Ninja . + - cmake --build ${cfg}/ + - timeout 10 ./${cfg}/lwip_test_apps + - mv lwip_test_apps.xml ${cfg}.xml + - done + - for run in {1..10000}; do ( timeout 10 ./config_linger/lwip_test_apps ) || exit 1 ; done .add_gh_key_remote: &add_gh_key_remote | command -v ssh-agent >/dev/null || exit 1 diff --git a/test/apps/CMakeLists.txt b/test/apps/CMakeLists.txt new file mode 100644 index 00000000..8527f432 --- /dev/null +++ b/test/apps/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.8) + +project(lwip_test_apps C) + +if (NOT CMAKE_SYSTEM_NAME STREQUAL Linux AND NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) + message(FATAL_ERROR "Unit test are currently only working on Linux or Darwin") +endif() + +if (NOT LWIP_DIR OR NOT LWIP_CONTRIB_DIR) + set(LWIP_DIR "$ENV{LWIP_DIR}") + set(LWIP_CONTRIB_DIR "$ENV{LWIP_CONTRIB_DIR}") +endif() + +if (NOT CI_BUILD) + set(LWIP_USE_SANITIZERS true) +endif() + +include(${LWIP_CONTRIB_DIR}/ports/CMakeCommon.cmake) + +if(CMAKE_C_COMPILER_ID STREQUAL Clang) + # check.h causes 'error: token pasting of ',' and __VA_ARGS__ is a GNU extension' with clang 9.0.0 +endif() + +set (LWIP_DEFINITIONS -DLWIP_DEBUG -DLWIP_NOASSERT_ON_ERROR -DLWIP_OPTTEST_FILE) +set (LWIP_INCLUDE_DIRS + "${LWIP_DIR}/test/apps" + "${LWIP_CONTRIB_DIR}/ports/unix/port/include" + "${LWIP_DIR}/test/unit" + "${LWIP_DIR}/src/include" + "${LWIP_CONTRIB_DIR}/" + "${CMAKE_CURRENT_SOURCE_DIR}/" + "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_CONFIG}" +) + +include(${LWIP_CONTRIB_DIR}/ports/unix/Filelists.cmake) +include(${LWIP_DIR}/src/Filelists.cmake) +include(${LWIP_DIR}/test/apps/Filelists.cmake) + +add_executable(lwip_test_apps ${LWIP_TESTFILES}) +target_include_directories(lwip_test_apps PRIVATE ${LWIP_INCLUDE_DIRS}) +target_compile_options(lwip_test_apps PRIVATE ${LWIP_COMPILER_FLAGS}) +target_compile_definitions(lwip_test_apps PRIVATE ${LWIP_DEFINITIONS} ${LWIP_MBEDTLS_DEFINITIONS}) + +find_library(LIBCHECK check) +find_library(LIBM m) +target_link_libraries(lwip_test_apps ${LWIP_SANITIZER_LIBS} lwipallapps lwipcore ${LIBCHECK} ${LIBM}) + +if (NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) + # check installed via brew on Darwin doesn't have a separate subunit library (must be statically linked) + find_library(LIBSUBUNIT subunit) + target_link_libraries(lwip_test_apps ${LIBSUBUNIT}) +endif() + +if (CMAKE_SYSTEM_NAME STREQUAL Linux) + find_library(LIBUTIL util) + find_library(LIBPTHREAD pthread) + find_library(LIBRT rt) + target_link_libraries(lwip_test_apps ${LIBUTIL} ${LIBPTHREAD} ${LIBRT}) +endif() + +if (CMAKE_SYSTEM_NAME STREQUAL Darwin) + # Darwin doesn't have pthreads or POSIX real-time extensions libs + find_library(LIBUTIL util) + target_link_libraries(lwip_test_apps ${LIBUTIL}) +endif() diff --git a/test/apps/Filelists.cmake b/test/apps/Filelists.cmake new file mode 100644 index 00000000..4acc8ce0 --- /dev/null +++ b/test/apps/Filelists.cmake @@ -0,0 +1,16 @@ +# This file is indended to be included in end-user CMakeLists.txt +# include(/path/to/Filelists.cmake) +# It assumes the variable LWIP_DIR is defined pointing to the +# root path of lwIP sources. +# +# This file is NOT designed (on purpose) to be used as cmake +# subdir via add_subdirectory() +# The intention is to provide greater flexibility to users to +# create their own targets using the *_SRCS variables. + +set(LWIP_TESTDIR ${LWIP_DIR}/test/apps) +set(LWIP_TESTFILES + ${LWIP_TESTDIR}/test_apps.c + ${LWIP_TESTDIR}/test_sockets.c + ${LWIP_TESTDIR}/linux/sys_arch.c +) diff --git a/test/apps/config.h b/test/apps/config.h new file mode 100644 index 00000000..782194c8 --- /dev/null +++ b/test/apps/config.h @@ -0,0 +1,4 @@ +#ifndef LWIP_HDR_LWIP_CHECK_CONFIG_H +#define LWIP_HDR_LWIP_CHECK_CONFIG_H + +#endif /* LWIP_TEST_APPS_CONFIG_H */ diff --git a/test/apps/config_linger/lwipopts_test.h b/test/apps/config_linger/lwipopts_test.h new file mode 100644 index 00000000..78129c69 --- /dev/null +++ b/test/apps/config_linger/lwipopts_test.h @@ -0,0 +1,37 @@ +#ifndef LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H +#define LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H + +#define LWIP_SO_LINGER 1 + +#ifdef LWIP_DEBUG + +#define LWIP_DBG_MIN_LEVEL 0 +#define PPP_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define DNS_DEBUG LWIP_DBG_OFF +#define AUTOIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define IGMP_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#endif + +#endif /* LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H */ diff --git a/test/apps/config_linger_reuse/lwipopts_test.h b/test/apps/config_linger_reuse/lwipopts_test.h new file mode 100644 index 00000000..a23da24c --- /dev/null +++ b/test/apps/config_linger_reuse/lwipopts_test.h @@ -0,0 +1,7 @@ +#ifndef LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H +#define LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H + +#define LWIP_SO_LINGER 1 +#define SO_REUSE 1 + +#endif /* LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H */ diff --git a/test/apps/config_no_linger/lwipopts_test.h b/test/apps/config_no_linger/lwipopts_test.h new file mode 100644 index 00000000..993eec1d --- /dev/null +++ b/test/apps/config_no_linger/lwipopts_test.h @@ -0,0 +1,37 @@ +#ifndef LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H +#define LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H + +#define LWIP_SO_LINGER 0 + +#ifdef LWIP_DEBUG + +#define LWIP_DBG_MIN_LEVEL 0 +#define PPP_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define DNS_DEBUG LWIP_DBG_OFF +#define AUTOIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define IGMP_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#endif + +#endif /* LWIP_HDR_TEST_APPS_LWIPOPTS_TEST_H */ diff --git a/test/apps/linux/sys_arch.c b/test/apps/linux/sys_arch.c new file mode 100644 index 00000000..4c4ba9ea --- /dev/null +++ b/test/apps/linux/sys_arch.c @@ -0,0 +1,812 @@ +/* + * SPDX-FileCopyrightText: 2001-2003 Swedish Institute of Computer Science + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2022 Espressif Systems (Shanghai) CO LTD + */ + +/* + * Wed Apr 17 16:05:29 EDT 2002 (James Roth) + * + * - Fixed an unlikely sys_thread_new() race condition. + * + * - Made current_thread() work with threads which where + * not created with sys_thread_new(). This includes + * the main thread and threads made with pthread_create(). + * + * - Catch overflows where more than SYS_MBOX_SIZE messages + * are waiting to be read. The sys_mbox_post() routine + * will block until there is more room instead of just + * leaking messages. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* pull in pthread_setname_np() on Linux */ +#endif + +#include "lwip/debug.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "lwip/def.h" + +#ifdef LWIP_UNIX_MACH +#include +#include +#endif + +#include "lwip/sys.h" +#include "lwip/opt.h" +#include "lwip/stats.h" +#include "lwip/tcpip.h" + +#if LWIP_NETCONN_SEM_PER_THREAD +/* pthread key to *our* thread local storage */ +static pthread_key_t sys_thread_sem_key; +#endif + +/* Return code for an interrupted timed wait */ +#define SYS_ARCH_INTR 0xfffffffeUL + +static void +get_monotonic_time(struct timespec *ts) +{ +#ifdef LWIP_UNIX_MACH + /* darwin impl (no CLOCK_MONOTONIC) */ + u64_t t = mach_absolute_time(); + mach_timebase_info_data_t timebase_info = {0, 0}; + mach_timebase_info(&timebase_info); + u64_t nano = (t * timebase_info.numer) / (timebase_info.denom); + u64_t sec = nano/1000000000L; + nano -= sec * 1000000000L; + ts->tv_sec = sec; + ts->tv_nsec = nano; +#else + clock_gettime(CLOCK_MONOTONIC, ts); +#endif +} + +#if SYS_LIGHTWEIGHT_PROT +static pthread_mutex_t lwprot_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_t lwprot_thread = (pthread_t)0xDEAD; +static int lwprot_count = 0; +#endif /* SYS_LIGHTWEIGHT_PROT */ + +#if !NO_SYS + +static struct sys_thread *threads = NULL; +static pthread_mutex_t threads_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct sys_mbox_msg { + struct sys_mbox_msg *next; + void *msg; +}; + +#define SYS_MBOX_SIZE 128 + +struct sys_mbox { + int first, last; + void *msgs[SYS_MBOX_SIZE]; + struct sys_sem *not_empty; + struct sys_sem *not_full; + struct sys_sem *mutex; + int wait_send; +}; + +struct sys_sem { + unsigned int c; + pthread_condattr_t condattr; + pthread_cond_t cond; + pthread_mutex_t mutex; +}; + +struct sys_mutex { + pthread_mutex_t mutex; +}; + +struct sys_thread { + struct sys_thread *next; + pthread_t pthread; +}; + +static struct sys_sem *sys_sem_new_internal(u8_t count); +static void sys_sem_free_internal(struct sys_sem *sem); + +static u32_t cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex, + u32_t timeout); + +/*-----------------------------------------------------------------------------------*/ +/* Threads */ +static struct sys_thread * +introduce_thread(pthread_t id) +{ + struct sys_thread *thread; + + thread = (struct sys_thread *)mem_malloc(sizeof(struct sys_thread)); + + if (thread != NULL) { + pthread_mutex_lock(&threads_mutex); + thread->next = threads; + thread->pthread = id; + threads = thread; + pthread_mutex_unlock(&threads_mutex); + } + + return thread; +} + +struct thread_wrapper_data +{ + lwip_thread_fn function; + void *arg; +}; + +static void * +thread_wrapper(void *arg) +{ + struct thread_wrapper_data *thread_data = (struct thread_wrapper_data *)arg; + + thread_data->function(thread_data->arg); + + /* we should never get here */ + free(arg); + return NULL; +} + +sys_thread_t +sys_thread_new(const char *name, lwip_thread_fn function, void *arg, int stacksize, int prio) +{ + int code; + pthread_t tmp; + struct sys_thread *st = NULL; + struct thread_wrapper_data *thread_data; + LWIP_UNUSED_ARG(name); + LWIP_UNUSED_ARG(stacksize); + LWIP_UNUSED_ARG(prio); + + thread_data = (struct thread_wrapper_data *)mem_malloc(sizeof(struct thread_wrapper_data)); + thread_data->arg = arg; + thread_data->function = function; + code = pthread_create(&tmp, + NULL, + thread_wrapper, + thread_data); + +#ifdef LWIP_UNIX_LINUX + pthread_setname_np(tmp, name); +#endif + + if (0 == code) { + st = introduce_thread(tmp); + } + + if (NULL == st) { + LWIP_DEBUGF(SYS_DEBUG, ("sys_thread_new: pthread_create %d, st = 0x%lx", + code, (unsigned long)st)); + abort(); + } + return st; +} + +#if LWIP_TCPIP_CORE_LOCKING +static pthread_t lwip_core_lock_holder_thread_id; +void sys_lock_tcpip_core(void) +{ + sys_mutex_lock(&lock_tcpip_core); + lwip_core_lock_holder_thread_id = pthread_self(); +} + +void sys_unlock_tcpip_core(void) +{ + lwip_core_lock_holder_thread_id = 0; + sys_mutex_unlock(&lock_tcpip_core); +} +#endif /* LWIP_TCPIP_CORE_LOCKING */ + +static pthread_t lwip_tcpip_thread_id; +void sys_mark_tcpip_thread(void) +{ + lwip_tcpip_thread_id = pthread_self(); +} + +void sys_check_core_locking(void) +{ + /* Embedded systems should check we are NOT in an interrupt context here */ + + if (lwip_tcpip_thread_id != 0) { + pthread_t current_thread_id = pthread_self(); + +#if LWIP_TCPIP_CORE_LOCKING + LWIP_ASSERT("Function called without core lock", current_thread_id == lwip_core_lock_holder_thread_id); +#else /* LWIP_TCPIP_CORE_LOCKING */ + LWIP_ASSERT("Function called from wrong thread", current_thread_id == lwip_tcpip_thread_id); +#endif /* LWIP_TCPIP_CORE_LOCKING */ + } +} + +/*-----------------------------------------------------------------------------------*/ +/* Mailbox */ +err_t +sys_mbox_new(struct sys_mbox **mb, int size) +{ + struct sys_mbox *mbox; + LWIP_UNUSED_ARG(size); + + mbox = (struct sys_mbox *)malloc(sizeof(struct sys_mbox)); + if (mbox == NULL) { + return ERR_MEM; + } + mbox->first = mbox->last = 0; + mbox->not_empty = sys_sem_new_internal(0); + mbox->not_full = sys_sem_new_internal(0); + mbox->mutex = sys_sem_new_internal(1); + mbox->wait_send = 0; + + SYS_STATS_INC_USED(mbox); + *mb = mbox; + return ERR_OK; +} + +void +sys_mbox_free(struct sys_mbox **mb) +{ + if ((mb != NULL) && (*mb != SYS_MBOX_NULL)) { + struct sys_mbox *mbox = *mb; + SYS_STATS_DEC(mbox.used); + sys_arch_sem_wait(&mbox->mutex, 0); + + sys_sem_free_internal(mbox->not_empty); + sys_sem_free_internal(mbox->not_full); + sys_sem_free_internal(mbox->mutex); + mbox->not_empty = mbox->not_full = mbox->mutex = NULL; + /* LWIP_DEBUGF("sys_mbox_free: mbox 0x%lx\n", mbox); */ + free(mbox); + } +} + +err_t +sys_mbox_trypost(struct sys_mbox **mb, void *msg) +{ + u8_t first; + struct sys_mbox *mbox; + LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL)); + mbox = *mb; + + sys_arch_sem_wait(&mbox->mutex, 0); + + LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_trypost: mbox %p msg %p\n", + (void *)mbox, (void *)msg)); + + if ((mbox->last + 1) >= (mbox->first + SYS_MBOX_SIZE)) { + sys_sem_signal(&mbox->mutex); + return ERR_MEM; + } + + mbox->msgs[mbox->last % SYS_MBOX_SIZE] = msg; + + if (mbox->last == mbox->first) { + first = 1; + } else { + first = 0; + } + + mbox->last++; + + if (first) { + sys_sem_signal(&mbox->not_empty); + } + + sys_sem_signal(&mbox->mutex); + + return ERR_OK; +} + +err_t +sys_mbox_trypost_fromisr(sys_mbox_t *q, void *msg) +{ + return sys_mbox_trypost(q, msg); +} + +void +sys_mbox_post(struct sys_mbox **mb, void *msg) +{ + u8_t first; + struct sys_mbox *mbox; + LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL)); + mbox = *mb; + + sys_arch_sem_wait(&mbox->mutex, 0); + + LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_post: mbox %p msg %p\n", (void *)mbox, (void *)msg)); + + while ((mbox->last + 1) >= (mbox->first + SYS_MBOX_SIZE)) { + mbox->wait_send++; + sys_sem_signal(&mbox->mutex); + sys_arch_sem_wait(&mbox->not_full, 0); + sys_arch_sem_wait(&mbox->mutex, 0); + mbox->wait_send--; + } + + mbox->msgs[mbox->last % SYS_MBOX_SIZE] = msg; + + if (mbox->last == mbox->first) { + first = 1; + } else { + first = 0; + } + + mbox->last++; + + if (first) { + sys_sem_signal(&mbox->not_empty); + } + + sys_sem_signal(&mbox->mutex); +} + +u32_t +sys_arch_mbox_tryfetch(struct sys_mbox **mb, void **msg) +{ + struct sys_mbox *mbox; + LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL)); + mbox = *mb; + + sys_arch_sem_wait(&mbox->mutex, 0); + + if (mbox->first == mbox->last) { + sys_sem_signal(&mbox->mutex); + return SYS_MBOX_EMPTY; + } + + if (msg != NULL) { + LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_tryfetch: mbox %p msg %p\n", (void *)mbox, *msg)); + *msg = mbox->msgs[mbox->first % SYS_MBOX_SIZE]; + } + else{ + LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_tryfetch: mbox %p, null msg\n", (void *)mbox)); + } + + mbox->first++; + + if (mbox->wait_send) { + sys_sem_signal(&mbox->not_full); + } + + sys_sem_signal(&mbox->mutex); + + return 0; +} + +u32_t +sys_arch_mbox_fetch(struct sys_mbox **mb, void **msg, u32_t timeout) +{ + u32_t time_needed = 0; + struct sys_mbox *mbox; + LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL)); + mbox = *mb; + + /* The mutex lock is quick so we don't bother with the timeout + stuff here. */ + sys_arch_sem_wait(&mbox->mutex, 0); + + while (mbox->first == mbox->last) { + sys_sem_signal(&mbox->mutex); + + /* We block while waiting for a mail to arrive in the mailbox. We + must be prepared to timeout. */ + if (timeout != 0) { + time_needed = sys_arch_sem_wait(&mbox->not_empty, timeout); + + if (time_needed == SYS_ARCH_TIMEOUT) { + return SYS_ARCH_TIMEOUT; + } + } else { + sys_arch_sem_wait(&mbox->not_empty, 0); + } + + sys_arch_sem_wait(&mbox->mutex, 0); + } + + if (msg != NULL) { + LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_fetch: mbox %p msg %p\n", (void *)mbox, *msg)); + *msg = mbox->msgs[mbox->first % SYS_MBOX_SIZE]; + } + else{ + LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_fetch: mbox %p, null msg\n", (void *)mbox)); + } + + mbox->first++; + + if (mbox->wait_send) { + sys_sem_signal(&mbox->not_full); + } + + sys_sem_signal(&mbox->mutex); + + return time_needed; +} + +/*-----------------------------------------------------------------------------------*/ +/* Semaphore */ +static struct sys_sem * +sys_sem_new_internal(u8_t count) +{ + struct sys_sem *sem; + + sem = (struct sys_sem *)malloc(sizeof(struct sys_sem)); + if (sem != NULL) { + sem->c = count; + pthread_condattr_init(&(sem->condattr)); +#if !(defined(LWIP_UNIX_MACH) || (defined(LWIP_UNIX_ANDROID) && __ANDROID_API__ < 21)) + pthread_condattr_setclock(&(sem->condattr), CLOCK_MONOTONIC); +#endif + pthread_cond_init(&(sem->cond), &(sem->condattr)); + pthread_mutex_init(&(sem->mutex), NULL); + } + return sem; +} + +err_t +sys_sem_new(struct sys_sem **sem, u8_t count) +{ + SYS_STATS_INC_USED(sem); + *sem = sys_sem_new_internal(count); + if (*sem == NULL) { + return ERR_MEM; + } + return ERR_OK; +} + +static u32_t +cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, u32_t timeout) +{ + struct timespec rtime1, rtime2, ts; + int ret; + +#ifdef LWIP_UNIX_HURD + #define pthread_cond_wait pthread_hurd_cond_wait_np + #define pthread_cond_timedwait pthread_hurd_cond_timedwait_np +#endif + + if (timeout == 0) { + ret = pthread_cond_wait(cond, mutex); + return +#ifdef LWIP_UNIX_HURD + /* On the Hurd, ret == 1 means the RPC has been cancelled. + * The thread is awakened (not terminated) and execution must continue */ + ret == 1 ? SYS_ARCH_INTR : +#endif + (u32_t)ret; + } + + /* Get a timestamp and add the timeout value. */ + get_monotonic_time(&rtime1); +#if defined(LWIP_UNIX_MACH) || (defined(LWIP_UNIX_ANDROID) && __ANDROID_API__ < 21) + ts.tv_sec = timeout / 1000L; + ts.tv_nsec = (timeout % 1000L) * 1000000L; + ret = pthread_cond_timedwait_relative_np(cond, mutex, &ts); +#else + ts.tv_sec = rtime1.tv_sec + timeout / 1000L; + ts.tv_nsec = rtime1.tv_nsec + (timeout % 1000L) * 1000000L; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000L; + } + + ret = pthread_cond_timedwait(cond, mutex, &ts); +#endif + if (ret == ETIMEDOUT) { + return SYS_ARCH_TIMEOUT; +#ifdef LWIP_UNIX_HURD + /* On the Hurd, ret == 1 means the RPC has been cancelled. + * The thread is awakened (not terminated) and execution must continue */ + } else if (ret == EINTR) { + return SYS_ARCH_INTR; +#endif + } + + /* Calculate for how long we waited for the cond. */ + get_monotonic_time(&rtime2); + ts.tv_sec = rtime2.tv_sec - rtime1.tv_sec; + ts.tv_nsec = rtime2.tv_nsec - rtime1.tv_nsec; + if (ts.tv_nsec < 0) { + ts.tv_sec--; + ts.tv_nsec += 1000000000L; + } + return (u32_t)(ts.tv_sec * 1000L + ts.tv_nsec / 1000000L); +} + +u32_t +sys_arch_sem_wait(struct sys_sem **s, u32_t timeout) +{ + u32_t time_needed = 0; + struct sys_sem *sem; + LWIP_ASSERT("invalid sem", (s != NULL) && (*s != NULL)); + sem = *s; + + pthread_mutex_lock(&(sem->mutex)); + while (sem->c <= 0) { + if (timeout > 0) { + time_needed = cond_wait(&(sem->cond), &(sem->mutex), timeout); + + if (time_needed == SYS_ARCH_TIMEOUT) { + pthread_mutex_unlock(&(sem->mutex)); + return SYS_ARCH_TIMEOUT; +#ifdef LWIP_UNIX_HURD + } else if(time_needed == SYS_ARCH_INTR) { + pthread_mutex_unlock(&(sem->mutex)); + return 0; +#endif + } + /* pthread_mutex_unlock(&(sem->mutex)); + return time_needed; */ + } else if(cond_wait(&(sem->cond), &(sem->mutex), 0)) { + /* Some error happened or the thread has been awakened but not by lwip */ + pthread_mutex_unlock(&(sem->mutex)); + return 0; + } + } + sem->c--; + pthread_mutex_unlock(&(sem->mutex)); + return (u32_t)time_needed; +} + +void +sys_sem_signal(struct sys_sem **s) +{ + struct sys_sem *sem; + LWIP_ASSERT("invalid sem", (s != NULL) && (*s != NULL)); + sem = *s; + + pthread_mutex_lock(&(sem->mutex)); + sem->c++; + + if (sem->c > 1) { + sem->c = 1; + } + + pthread_cond_broadcast(&(sem->cond)); + pthread_mutex_unlock(&(sem->mutex)); +} + +static void +sys_sem_free_internal(struct sys_sem *sem) +{ + pthread_cond_destroy(&(sem->cond)); + pthread_condattr_destroy(&(sem->condattr)); + pthread_mutex_destroy(&(sem->mutex)); + free(sem); +} + +void +sys_sem_free(struct sys_sem **sem) +{ + if ((sem != NULL) && (*sem != SYS_SEM_NULL)) { + SYS_STATS_DEC(sem.used); + sys_sem_free_internal(*sem); + } +} + +/*-----------------------------------------------------------------------------------*/ +/* Mutex */ +/** Create a new mutex + * @param mutex pointer to the mutex to create + * @return a new mutex */ +err_t +sys_mutex_new(struct sys_mutex **mutex) +{ + struct sys_mutex *mtx; + + mtx = (struct sys_mutex *)malloc(sizeof(struct sys_mutex)); + if (mtx != NULL) { + pthread_mutex_init(&(mtx->mutex), NULL); + *mutex = mtx; + return ERR_OK; + } + else { + return ERR_MEM; + } +} + +/** Lock a mutex + * @param mutex the mutex to lock */ +void +sys_mutex_lock(struct sys_mutex **mutex) +{ + pthread_mutex_lock(&((*mutex)->mutex)); +} + +/** Unlock a mutex + * @param mutex the mutex to unlock */ +void +sys_mutex_unlock(struct sys_mutex **mutex) +{ + pthread_mutex_unlock(&((*mutex)->mutex)); +} + +/** Delete a mutex + * @param mutex the mutex to delete */ +void +sys_mutex_free(struct sys_mutex **mutex) +{ + pthread_mutex_destroy(&((*mutex)->mutex)); + free(*mutex); +} + +#endif /* !NO_SYS */ + +#if LWIP_NETCONN_SEM_PER_THREAD +/*-----------------------------------------------------------------------------------*/ +/* Semaphore per thread located TLS */ + +static void +sys_thread_sem_free(void* data) +{ + sys_sem_t *sem = (sys_sem_t*)(data); + + if (sem) { + sys_sem_free(sem); + free(sem); + } +} + +static sys_sem_t* +sys_thread_sem_alloc(void) +{ + sys_sem_t *sem; + err_t err; + int ret; + + sem = (sys_sem_t*)malloc(sizeof(sys_sem_t*)); + LWIP_ASSERT("failed to allocate memory for TLS semaphore", sem != NULL); + err = sys_sem_new(sem, 0); + LWIP_ASSERT("failed to initialise TLS semaphore", err == ERR_OK); + ret = pthread_setspecific(sys_thread_sem_key, sem); + LWIP_ASSERT("failed to initialise TLS semaphore storage", ret == 0); + return sem; +} + +sys_sem_t* +sys_thread_sem_get(void) +{ + sys_sem_t* sem = (sys_sem_t*)pthread_getspecific(sys_thread_sem_key); + if (!sem) { + sem = sys_thread_sem_alloc(); + } + LWIP_DEBUGF(SYS_DEBUG, ("sys_thread_sem_get s=%p\n", (void*)sem)); + return sem; +} + +void +sys_thread_sem_init(void) +{ + __attribute__((unused)) sys_sem_t* sem = sys_thread_sem_alloc(); + LWIP_DEBUGF(SYS_DEBUG, ("sys_thread_sem created s=%p\n", (void*)sem)); +} + +void +sys_thread_sem_deinit(void) +{ + int ret; + + sys_sem_t *sem = (sys_sem_t *)pthread_getspecific(sys_thread_sem_key); + sys_thread_sem_free(sem); + ret = pthread_setspecific(sys_thread_sem_key, NULL); + LWIP_ASSERT("failed to de-init TLS semaphore storage", ret == 0); +} +#endif /* LWIP_NETCONN_SEM_PER_THREAD */ + +/*-----------------------------------------------------------------------------------*/ +/* Time */ +u32_t +sys_now(void) +{ + struct timespec ts; + u32_t now; + + get_monotonic_time(&ts); + now = (u32_t)(ts.tv_sec * 1000L + ts.tv_nsec / 1000000L); +#ifdef LWIP_FUZZ_SYS_NOW + now += sys_now_offset; +#endif + return now; +} + +u32_t +sys_jiffies(void) +{ + struct timespec ts; + + get_monotonic_time(&ts); + return (u32_t)(ts.tv_sec * 1000000000L + ts.tv_nsec); +} + +/*-----------------------------------------------------------------------------------*/ +/* Init */ + +void +sys_init(void) +{ +#if LWIP_NETCONN_SEM_PER_THREAD + pthread_key_create(&sys_thread_sem_key, sys_thread_sem_free); +#endif +} + +/*-----------------------------------------------------------------------------------*/ +/* Critical section */ +#if SYS_LIGHTWEIGHT_PROT +/** sys_prot_t sys_arch_protect(void) + +This optional function does a "fast" critical region protection and returns +the previous protection level. This function is only called during very short +critical regions. An embedded system which supports ISR-based drivers might +want to implement this function by disabling interrupts. Task-based systems +might want to implement this by using a mutex or disabling tasking. This +function should support recursive calls from the same task or interrupt. In +other words, sys_arch_protect() could be called while already protected. In +that case the return value indicates that it is already protected. + +sys_arch_protect() is only required if your port is supporting an operating +system. +*/ +sys_prot_t +sys_arch_protect(void) +{ + /* Note that for the UNIX port, we are using a lightweight mutex, and our + * own counter (which is locked by the mutex). The return code is not actually + * used. */ + if (lwprot_thread != pthread_self()) + { + /* We are locking the mutex where it has not been locked before * + * or is being locked by another thread */ + pthread_mutex_lock(&lwprot_mutex); + lwprot_thread = pthread_self(); + lwprot_count = 1; + } + else + /* It is already locked by THIS thread */ + lwprot_count++; + return 0; +} + +/** void sys_arch_unprotect(sys_prot_t pval) + +This optional function does a "fast" set of critical region protection to the +value specified by pval. See the documentation for sys_arch_protect() for +more information. This function is only required if your port is supporting +an operating system. +*/ +void +sys_arch_unprotect(sys_prot_t pval) +{ + LWIP_UNUSED_ARG(pval); + if (lwprot_thread == pthread_self()) + { + lwprot_count--; + if (lwprot_count == 0) + { + lwprot_thread = (pthread_t) 0xDEAD; + pthread_mutex_unlock(&lwprot_mutex); + } + } +} +#endif /* SYS_LIGHTWEIGHT_PROT */ + +#if !NO_SYS +/* get keyboard state to terminate the debug app by using select */ +int +lwip_unix_keypressed(void) +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + return select(1, &fds, NULL, NULL, &tv); +} +#endif /* !NO_SYS */ diff --git a/test/apps/lwipopts.h b/test/apps/lwipopts.h new file mode 100644 index 00000000..d37fae16 --- /dev/null +++ b/test/apps/lwipopts.h @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ +#ifndef LWIP_LWIPOPTS_H +#define LWIP_LWIPOPTS_H + +#ifdef LWIP_OPTTEST_FILE +#include "lwipopts_test.h" +#endif /* LWIP_OPTTEST_FILE */ + +#define LWIP_IPV4 1 +#define LWIP_IPV6 0 + +#define NO_SYS 0 +#define LWIP_SOCKET (NO_SYS==0) +#define LWIP_NETCONN (NO_SYS==0) +#define LWIP_NETIF_API (NO_SYS==0) + +#define LWIP_IGMP LWIP_IPV4 +#define LWIP_ICMP LWIP_IPV4 + +#define LWIP_SNMP LWIP_UDP +#define MIB2_STATS LWIP_SNMP +#ifdef LWIP_HAVE_MBEDTLS +#define LWIP_SNMP_V3 (LWIP_SNMP) +#endif + +#define LWIP_DNS LWIP_UDP +#define LWIP_MDNS_RESPONDER LWIP_UDP + +#define LWIP_NUM_NETIF_CLIENT_DATA (LWIP_MDNS_RESPONDER) + +#define LWIP_HAVE_LOOPIF 1 +#define LWIP_NETIF_LOOPBACK 1 +#define LWIP_LOOPBACK_MAX_PBUFS 10 + +#define TCP_LISTEN_BACKLOG 1 + +#define LWIP_COMPAT_SOCKETS 1 +#define LWIP_SO_RCVTIMEO 1 +#define LWIP_SO_RCVBUF 1 + +#define LWIP_TCPIP_CORE_LOCKING 0 + +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_EXT_STATUS_CALLBACK 1 + + +#define LWIP_DBG_TYPES_ON (LWIP_DBG_ON|LWIP_DBG_TRACE|LWIP_DBG_STATE|LWIP_DBG_FRESH|LWIP_DBG_HALT) + + +/* ---------- Memory options ---------- */ +/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which + lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2 + byte alignment -> define MEM_ALIGNMENT to 2. */ +/* MSVC port: intel processors don't need 4-byte alignment, + but are faster that way! */ +#define MEM_ALIGNMENT 4U + +/* MEM_SIZE: the size of the heap memory. If the application will send +a lot of data that needs to be copied, this should be set high. */ +#define MEM_SIZE 16000 + + +/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application + sends a lot of data out of ROM (or other static memory), this + should be set high. */ +#define MEMP_NUM_PBUF 16 +/* MEMP_NUM_RAW_PCB: the number of UDP protocol control blocks. One + per active RAW "connection". */ +#define MEMP_NUM_RAW_PCB 3 +/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One + per active UDP "connection". */ +#define MEMP_NUM_UDP_PCB 4 +/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP + connections. */ +#define MEMP_NUM_TCP_PCB 5 +/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP + connections. */ +#define MEMP_NUM_TCP_PCB_LISTEN 8 +/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP + segments. */ +#define MEMP_NUM_TCP_SEG 16 +/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active + timeouts. */ +#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + 8) + +/* The following four are used only with the sequential API and can be + set to 0 if the application only will use the raw API. */ +/* MEMP_NUM_NETBUF: the number of struct netbufs. */ +#define MEMP_NUM_NETBUF 2 +/* MEMP_NUM_NETCONN: the number of struct netconns. */ +#define MEMP_NUM_NETCONN 10 +/* MEMP_NUM_TCPIP_MSG_*: the number of struct tcpip_msg, which is used + for sequential API communication and incoming packets. Used in + src/api/tcpip.c. */ +#define MEMP_NUM_TCPIP_MSG_API 16 +#define MEMP_NUM_TCPIP_MSG_INPKT 16 + + +/* ---------- Pbuf options ---------- */ +/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */ +#define PBUF_POOL_SIZE 120 + +/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */ +#define PBUF_POOL_BUFSIZE 256 + +/** SYS_LIGHTWEIGHT_PROT + * define SYS_LIGHTWEIGHT_PROT in lwipopts.h if you want inter-task protection + * for certain critical regions during buffer allocation, deallocation and memory + * allocation and deallocation. + */ +#define SYS_LIGHTWEIGHT_PROT (NO_SYS==0) + + +/* ---------- TCP options ---------- */ +#define LWIP_TCP 1 +#define TCP_TTL 255 + +#define LWIP_ALTCP 0 + + +/* Controls if TCP should queue segments that arrive out of + order. Define to 0 if your device is low on memory. */ +#define TCP_QUEUE_OOSEQ 1 + +/* TCP Maximum segment size. */ +#define TCP_MSS 1024 + +/* TCP sender buffer space (bytes). */ +#define TCP_SND_BUF 2048 + +/* TCP sender buffer space (pbufs). This must be at least = 2 * + TCP_SND_BUF/TCP_MSS for things to work. */ +#define TCP_SND_QUEUELEN (4 * TCP_SND_BUF/TCP_MSS) + +/* TCP writable space (bytes). This must be less than or equal + to TCP_SND_BUF. It is the amount of space which must be + available in the tcp snd_buf for select to return writable */ +#define TCP_SNDLOWAT (TCP_SND_BUF/2) + +/* TCP receive window. */ +#define TCP_WND (20 * 1024) + +/* Maximum number of retransmissions of data segments. */ +#define TCP_MAXRTX 12 + +/* Maximum number of retransmissions of SYN segments. */ +#define TCP_SYNMAXRTX 4 + + +/* ---------- ARP options ---------- */ +#define LWIP_ARP 1 +#define ARP_TABLE_SIZE 10 +#define ARP_QUEUEING 1 + + +/* ---------- IP options ---------- */ +/* Define IP_FORWARD to 1 if you wish to have the ability to forward + IP packets across network interfaces. If you are going to run lwIP + on a device with only one network interface, define this to 0. */ +#define IP_FORWARD 0 + +/* IP reassembly and segmentation.These are orthogonal even + * if they both deal with IP fragments */ +#define IP_REASSEMBLY 1 +#define IP_REASS_MAX_PBUFS (10 * ((1500 + PBUF_POOL_BUFSIZE - 1) / PBUF_POOL_BUFSIZE)) +#define MEMP_NUM_REASSDATA IP_REASS_MAX_PBUFS +#define IP_FRAG 1 +#define IPV6_FRAG_COPYHEADER 1 + +/* ---------- ICMP options ---------- */ +#define ICMP_TTL 255 + + +/* ---------- DHCP options ---------- */ +/* Define LWIP_DHCP to 1 if you want DHCP configuration of + interfaces. */ +#define LWIP_DHCP LWIP_UDP + +/* 1 if you want to do an ARP check on the offered address + (recommended). */ +#define DHCP_DOES_ARP_CHECK (LWIP_DHCP) + + +/* ---------- AUTOIP options ------- */ +#define LWIP_AUTOIP (LWIP_DHCP) +#define LWIP_DHCP_AUTOIP_COOP (LWIP_DHCP && LWIP_AUTOIP) + + +/* ---------- UDP options ---------- */ +#define LWIP_UDP 1 +#define LWIP_UDPLITE LWIP_UDP +#define UDP_TTL 255 + + +/* ---------- RAW options ---------- */ +#define LWIP_RAW 1 + + +/* ---------- Statistics options ---------- */ + +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 + +#if LWIP_STATS +#define LINK_STATS 1 +#define IP_STATS 1 +#define ICMP_STATS 1 +#define IGMP_STATS 1 +#define IPFRAG_STATS 1 +#define UDP_STATS 1 +#define TCP_STATS 1 +#define MEM_STATS 1 +#define MEMP_STATS 1 +#define PBUF_STATS 1 +#define SYS_STATS 1 +#endif /* LWIP_STATS */ + +/* ---------- NETBIOS options ---------- */ +#define LWIP_NETBIOS_RESPOND_NAME_QUERY 1 + +/* ---------- PPP options ---------- */ + +#define PPP_SUPPORT 1 /* Set > 0 for PPP */ + +#if PPP_SUPPORT + +#define NUM_PPP 1 /* Max PPP sessions. */ + + +/* Select modules to enable. Ideally these would be set in the makefile but + * we're limited by the command line length so you need to modify the settings + * in this file. + */ +#define PPPOE_SUPPORT 1 +#define PPPOS_SUPPORT 1 + +#define PAP_SUPPORT 1 /* Set > 0 for PAP. */ +#define CHAP_SUPPORT 1 /* Set > 0 for CHAP. */ +#define MSCHAP_SUPPORT 0 /* Set > 0 for MSCHAP */ +#define CBCP_SUPPORT 0 /* Set > 0 for CBCP (NOT FUNCTIONAL!) */ +#define CCP_SUPPORT 0 /* Set > 0 for CCP */ +#define VJ_SUPPORT 1 /* Set > 0 for VJ header compression. */ +#define MD5_SUPPORT 1 /* Set > 0 for MD5 (see also CHAP) */ + +#endif /* PPP_SUPPORT */ + + +/* The following defines must be done even in OPTTEST mode: */ +void sys_check_core_locking(void); +void sys_mark_tcpip_thread(void); + +#define ESP_LWIP 1 +#define LWIP_NETCONN_FULLDUPLEX 1 +#define LWIP_NETCONN_SEM_PER_THREAD 1 + +struct sys_sem; +struct sys_sem **sys_thread_sem_get(void); +void sys_thread_sem_init(void); +void sys_thread_sem_deinit(void); +#define LWIP_NETCONN_THREAD_SEM_GET() sys_thread_sem_get() +#define LWIP_NETCONN_THREAD_SEM_ALLOC() sys_thread_sem_init() +#define LWIP_NETCONN_THREAD_SEM_FREE() sys_thread_sem_deinit() + + +#define ESP_LWIP_IGMP_TIMERS_ONDEMAND 1 +#define ESP_LWIP_MLD6_TIMERS_ONDEMAND 1 +#define ESP_DNS 1 +#define ESP_LWIP_ARP 1 +#define LWIP_AUTOIP_MAX_CONFLICTS 10 +#define LWIP_AUTOIP_RATE_LIMIT_INTERVAL 60 +#define DNS_FALLBACK_SERVER_INDEX (DNS_MAX_SERVERS - 1) + +#endif /* LWIP_LWIPOPTS_H */ diff --git a/test/apps/socket_linger_stress_test.py b/test/apps/socket_linger_stress_test.py new file mode 100644 index 00000000..90b194a1 --- /dev/null +++ b/test/apps/socket_linger_stress_test.py @@ -0,0 +1,7 @@ +import sys +from junit_xml import TestSuite as ts, TestCase as tc + +t=tc("lingering close stress test") +if len(sys.argv) > 1 and sys.argv[1] == "failed": + t.add_failure_info("test got stuck when closing clients socket") +print(ts.to_xml_string([ts("SOCKET SO_LINGER stress test", [t])])) diff --git a/test/apps/test_apps.c b/test/apps/test_apps.c new file mode 100644 index 00000000..3fdcd2b5 --- /dev/null +++ b/test/apps/test_apps.c @@ -0,0 +1,60 @@ +#include "lwip_check.h" + +#include "api/test_sockets.h" + +#include "lwip/init.h" +#include "lwip/tcpip.h" + +Suite* create_suite(const char* name, testfunc *tests, size_t num_tests, SFun setup, SFun teardown) +{ + size_t i; + Suite *s = suite_create(name); + + for(i = 0; i < num_tests; i++) { + TCase *tc_core = tcase_create(name); + if ((setup != NULL) || (teardown != NULL)) { + tcase_add_checked_fixture(tc_core, setup, teardown); + } + tcase_add_named_test(tc_core, tests[i]); + suite_add_tcase(s, tc_core); + } + return s; +} + +void lwip_check_ensure_no_alloc(unsigned int skip) +{ + int i; + unsigned int mask; + for (i = 0, mask = 1; i < MEMP_MAX; i++, mask <<= 1) { + if (!(skip & mask)) { + fail_unless(lwip_stats.memp[i]->used == 0); + } + } +} + +int main(void) +{ + int number_failed; + SRunner *sr; + size_t i; + suite_getter_fn* suites[] = { + sockets_suite, + }; + size_t num = sizeof(suites)/sizeof(void*); + LWIP_ASSERT("No suites defined", num > 0); + + tcpip_init(NULL, NULL); + + sr = srunner_create((suites[0])()); + srunner_set_xml(sr, "lwip_test_apps.xml"); + for(i = 1; i < num; i++) { + srunner_add_suite(sr, ((suite_getter_fn*)suites[i])()); + } + + srunner_set_fork_status(sr, CK_NOFORK); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/apps/test_sockets.c b/test/apps/test_sockets.c new file mode 100644 index 00000000..5a5eefa3 --- /dev/null +++ b/test/apps/test_sockets.c @@ -0,0 +1,249 @@ +#include + +#include "lwip_check.h" + +#include "lwip/mem.h" +#include "lwip/opt.h" +#include "lwip/sockets.h" +#include "lwip/priv/sockets_priv.h" +#include "lwip/priv/tcp_priv.h" +#include "lwip/api.h" + +Suite *sockets_suite(void); + +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; +} + +#if !SO_REUSE +static int +wait_for_pcbs_to_cleanup(void) +{ + struct tcp_pcb *pcb = tcp_active_pcbs; + while (pcb != NULL) { + if (pcb->state == TIME_WAIT || pcb->state == LAST_ACK) { + return -1; + } + pcb = pcb->next; + } + return 0; +} +#endif +/* Setups/teardown functions */ +static void +sockets_setup(void) +{ + fail_unless(test_sockets_get_used_count() == 0); +} + +static void +sockets_teardown(void) +{ + fail_unless(test_sockets_get_used_count() == 0); + /* poll until all memory is released... */ + while (tcp_tw_pcbs) { + tcp_abort(tcp_tw_pcbs); + } + +} + + +struct test_params { + int listener; + size_t tx_buffer_size; + struct linger so_linger; +}; + +static void * +server_thread(void *arg) +{ + struct test_params *params = (struct test_params *)arg; + int srv; + int err; + int ret; + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + char rxbuf[16]; + + /* accept the connection */ + srv = lwip_accept(params->listener, (struct sockaddr *)&source_addr, &addr_len); + /* accepted, so won't need the listener socket anymore, close it */ + ret = lwip_close(params->listener); + fail_unless(ret == 0); + +#if LWIP_SO_LINGER + /* if we linger with timout=0, we might not be able to even accept */ + if (params->so_linger.l_linger == 0 && srv < 0) { + return NULL; + } + fail_unless(srv >= 0); +#endif + + +#if !LWIP_SO_LINGER + /* check that we received exactly the amount of bytes that we sent */ + ret = lwip_recv(srv, rxbuf, params->tx_buffer_size, 0); + err = errno; + fail_unless(ret == (int)params->tx_buffer_size); + /* check that we received exactly 0, i.e. EOF, connection closed cleanly */ + ret = lwip_recv(srv, rxbuf, sizeof(rxbuf), 0); + err = errno; + fail_unless(ret == 0); + /* check that we could receive no longer */ + ret = lwip_recv(srv, rxbuf, sizeof(rxbuf), 0); + err = errno; + fail_unless(ret == -1); + fail_unless(err == ENOTCONN || err == ECONNRESET); +#else + /* try to receive (could be data or EOF if the client lingers on closing) */ + ret = lwip_recv(srv, rxbuf, sizeof(rxbuf), 0); + err = errno; + fail_unless(ret >= 0); + fail_unless(err == 0); + + if (params->so_linger.l_onoff == 1 && params->so_linger.l_linger > 0) { + /* if lingering enabled with a non-zero timeout, let's just close + * (connection could be closed both cleanly and abruptly) */ + ret = lwip_close(srv); + fail_unless(ret == 0); + return NULL; + } + /* otherwise, check that we could receive no longer */ + ret = lwip_recv(srv, rxbuf, sizeof(rxbuf), 0); + err = errno; + if (params->so_linger.l_onoff == 0 || params->so_linger.l_linger > 0) { + /* if lingering disabled or timeout nonzero, we should get a clean exit */ + fail_unless(ret == 0); + } else { + /* linger with timeout=0, expect an abrupt closure */ + fail_unless(ret == -1); + fail_unless(err == ENOTCONN || err == ECONNRESET); + } +#endif + /* close server socket */ + ret = lwip_close(srv); + fail_unless(ret == 0); + + return NULL; +} + +static void +test_socket_close_linger(int l_onoff, int l_linger) +{ + int client; + int ret; + struct sockaddr_in sa_listen; + const u16_t port = 1234; + static const char txbuf[] = "something"; + struct test_params params; + int err; + pthread_t srv_thread; + + /* set test parameters */ + params.so_linger.l_onoff = l_onoff; + params.so_linger.l_linger = l_linger; + params.tx_buffer_size = sizeof(txbuf); + + fail_unless(test_sockets_get_used_count() == 0); + /* set up the listener */ + 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); + params.listener = lwip_socket(AF_INET, SOCK_STREAM, 0); + fail_unless(params.listener >= 0); +#if SO_REUSE + ret = 1; + fail_unless(setsockopt(params.listener, SOL_SOCKET, SO_REUSEADDR, &ret, sizeof(ret)) == 0); +#endif + ret = lwip_bind(params.listener, (struct sockaddr *)&sa_listen, sizeof(sa_listen)); + fail_unless(ret == 0); + ret = lwip_listen(params.listener, 1); + fail_unless(ret == 0); + + /* continue to serve connections in a separate thread*/ + err = pthread_create(&srv_thread, NULL, server_thread, ¶ms); + fail_unless(err == 0); + + /* set up the client */ + client = lwip_socket(AF_INET, SOCK_STREAM, 0); + + /* connect */ + ret = lwip_connect(client, (struct sockaddr *) &sa_listen, sizeof(sa_listen)); + fail_unless(ret == 0); + + /* set socket to enable SO_LINGER option */ + ret = lwip_setsockopt(client, SOL_SOCKET, SO_LINGER, ¶ms.so_linger, sizeof(params.so_linger)); +#if LWIP_SO_LINGER + fail_unless(ret == 0); +#else + /* If not enabled, just expect No Such Option error */ + err = errno; + fail_unless(ret == -1); + fail_unless(err == ENOPROTOOPT); +#endif /* LWIP_SO_LINGER */ + + ret = lwip_send(client, txbuf, sizeof(txbuf), 0); + fail_unless(ret == sizeof(txbuf)); + + /* close from client's side */ + ret = lwip_close(client); +#if !LWIP_SO_LINGER + err = errno; + fail_unless(ret == 0); +#endif + + pthread_join(srv_thread, NULL); +#if !SO_REUSE + while (wait_for_pcbs_to_cleanup() != 0) { + usleep(1000); + } +#endif + +} + +START_TEST(test_sockets_close_state_machine_linger_off) +{ + LWIP_UNUSED_ARG(_i); + test_socket_close_linger(0, 0); +} +END_TEST + +START_TEST(test_sockets_close_state_machine_linger_on) + { + LWIP_UNUSED_ARG(_i); + test_socket_close_linger(1, 1); + } +END_TEST + +START_TEST(test_sockets_close_state_machine_linger_on_timeout_0) +{ + LWIP_UNUSED_ARG(_i); + test_socket_close_linger(1, 0); +} +END_TEST + +/** Create the suite including all tests for this module */ +Suite * +sockets_suite(void) +{ + testfunc tests[] = { + TESTFUNC(test_sockets_close_state_machine_linger_off), + TESTFUNC(test_sockets_close_state_machine_linger_on), + TESTFUNC(test_sockets_close_state_machine_linger_on_timeout_0), + }; + return create_suite("SOCKETS", tests, sizeof(tests)/sizeof(testfunc), sockets_setup, sockets_teardown); +}