diff --git a/.gitmodules b/.gitmodules index 46abd2e..035ddeb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,15 @@ [submodule "make-testsuite/wolfssh"] path = make-testsuite/wolfssh url = https://github.com/wolfSSL/wolfssh.git +[submodule "echo-server/wolfssl"] + path = echo-server/wolfssl + url = https://github.com/wolfSSL/wolfssl.git +[submodule "echo-server/wolfssh"] + path = echo-server/wolfssh + url = https://github.com/wolfSSL/wolfssh.git +[submodule "echo-server/FreeRTOS-Kernel"] + path = echo-server/FreeRTOS-Kernel + url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git +[submodule "echo-server/FreeRTOS-Plus-TCP"] + path = echo-server/FreeRTOS-Plus-TCP + url = https://github.com/FreeRTOS/FreeRTOS-Plus-TCP.git diff --git a/README.md b/README.md index fc5dd3f..8867dd0 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,13 @@ The following examples are available for Espressif devices: * [ESP32-SSH-Server](./Espressif/ESP8266/ESP8266-SSH-Server/README.md) SSH-to-UART. +## echo-server + +A minimal SSH echo server running on FreeRTOS with FreeRTOS-Plus-TCP networking. +Targets PIC32MZ with Microchip Harmony but runnable on Linux using the FreeRTOS +POSIX simulator and a libpcap-based network interface. Uses a Makefile and has a +preconfigured user_settings.h file. + ## make-testsuite This example isn't manufacturer specific, but it has only been tested on diff --git a/echo-server/.gitignore b/echo-server/.gitignore new file mode 100644 index 0000000..a252833 --- /dev/null +++ b/echo-server/.gitignore @@ -0,0 +1,5 @@ +*.o +*.a +obj/ +keys/ +echo-server diff --git a/echo-server/FreeRTOS-Kernel b/echo-server/FreeRTOS-Kernel new file mode 160000 index 0000000..2624889 --- /dev/null +++ b/echo-server/FreeRTOS-Kernel @@ -0,0 +1 @@ +Subproject commit 2624889925fe5be610245dd0f1bc12ecf162a1a1 diff --git a/echo-server/FreeRTOS-Plus-TCP b/echo-server/FreeRTOS-Plus-TCP new file mode 160000 index 0000000..af6b379 --- /dev/null +++ b/echo-server/FreeRTOS-Plus-TCP @@ -0,0 +1 @@ +Subproject commit af6b379e3a580b86ac99f493918c6d18ffcdf264 diff --git a/echo-server/FreeRTOSConfig.h b/echo-server/FreeRTOSConfig.h new file mode 100644 index 0000000..2fee8a3 --- /dev/null +++ b/echo-server/FreeRTOSConfig.h @@ -0,0 +1,102 @@ +/* FreeRTOSConfig.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +/* Scheduler */ +#define configUSE_PREEMPTION 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configUSE_TICKLESS_IDLE 0 +#define configCPU_CLOCK_HZ ( ( unsigned long ) 60000000 ) +#define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) +#define configMAX_PRIORITIES 5 +#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 8192 ) +#define configMAX_TASK_NAME_LEN 16 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configQUEUE_REGISTRY_SIZE 10 +#define configUSE_QUEUE_SETS 0 +#define configUSE_TIME_SLICING 1 +#define configUSE_NEWLIB_REENTRANT 0 +#define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 +#define configUSE_MINI_LIST_ITEM 1 + +/* Memory */ +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 256 * 1024 ) ) +#define configAPPLICATION_ALLOCATED_HEAP 0 + +/* Hook functions */ +#define configUSE_IDLE_HOOK 1 +#define configUSE_TICK_HOOK 0 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#define configUSE_MALLOC_FAILED_HOOK 0 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +/* Stats */ +#define configGENERATE_RUN_TIME_STATS 0 +#define configUSE_TRACE_FACILITY 0 +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +/* Co-routines */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES 1 + +/* Software timers */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE + +/* Logging — used by FreeRTOS-Plus-TCP's FreeRTOS_printf / FreeRTOS_debug_printf */ +#define configPRINTF( X ) printf X + +/* Trap errors during development */ +#define configASSERT( x ) + +/* Optional functions */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_xResumeFromISR 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 0 +#define INCLUDE_xTaskGetIdleTaskHandle 0 +#define INCLUDE_eTaskGetState 0 +#define INCLUDE_xEventGroupSetBitFromISR 1 +#define INCLUDE_xTimerPendFunctionCall 0 +#define INCLUDE_xTaskAbortDelay 0 +#define INCLUDE_xTaskGetHandle 0 +#define INCLUDE_xTaskResumeFromISR 1 + +/* POSIX port */ +#define configPOSIX_STACK_SIZE ( ( unsigned short ) 65536 ) + +#endif /* FREERTOS_CONFIG_H */ diff --git a/echo-server/FreeRTOSIPConfig.h b/echo-server/FreeRTOSIPConfig.h new file mode 100644 index 0000000..58636d7 --- /dev/null +++ b/echo-server/FreeRTOSIPConfig.h @@ -0,0 +1,91 @@ +/* FreeRTOSIPConfig.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +#ifndef FREERTOS_IP_CONFIG_H +#define FREERTOS_IP_CONFIG_H + +/* Use the backward-compatible single-interface FreeRTOS_IPInit() API */ +#define ipconfigIPv4_BACKWARD_COMPATIBLE 1 +#define ipconfigUSE_IPv6 0 + +/* TCP/IP task */ +#define ipconfigIP_TASK_PRIORITY ( configMAX_PRIORITIES - 2 ) +#define ipconfigIP_TASK_STACK_SIZE_WORDS ( configMINIMAL_STACK_SIZE * 4 ) + +/* Protocol support */ +#define ipconfigUSE_TCP 1 +#define ipconfigUSE_DNS 0 +#define ipconfigUSE_DHCP 0 + +/* Network tuning */ +#define ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS 60 +#define ipconfigNETWORK_MTU 1500 +#define ipconfigTCP_MSS ( ipconfigNETWORK_MTU - 40 ) +#define ipconfigTCP_RX_BUFFER_LENGTH ( 16 * 1024 ) +#define ipconfigTCP_TX_BUFFER_LENGTH ( 16 * 1024 ) +#define ipconfigTCP_WIN_SEG_COUNT 64 + +/* Event processing */ +#define ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES 1 +#define ipconfigETHERNET_MINIMUM_PACKET_BYTES 0 +#define ipconfigBUFFER_PADDING 0 +#define ipconfigPACKET_FILLER_SIZE 2 +#define ipconfigEVENT_QUEUE_LENGTH ( ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS + 5 ) + +/* ARP */ +#define ipconfigUSE_ARP_REMOVE_ENTRY 1 +#define ipconfigUSE_ARP_REVERSED_LOOKUP 1 +#define ipconfigARP_CACHE_ENTRIES 6 +#define ipconfigMAX_ARP_RETRANSMISSIONS 5 +#define ipconfigMAX_ARP_AGE 150 + +/* TCP parameters */ +#define ipconfigTCP_HANG_PROTECTION 1 +#define ipconfigTCP_HANG_PROTECTION_TIME 30 +#define ipconfigTCP_KEEP_ALIVE 1 +#define ipconfigTCP_KEEP_ALIVE_INTERVAL 20 + +/* Misc */ +#define ipconfigZERO_COPY_TX_DRIVER 0 +#define ipconfigZERO_COPY_RX_DRIVER 0 +#define ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM 0 +#define ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM 0 +#define ipconfigSUPPORT_OUTGOING_PINGS 1 +#define ipconfigREPLY_TO_INCOMING_PINGS 1 +#define ipconfigSUPPORT_SELECT_FUNCTION 0 +#define ipconfigFILTER_OUT_NON_ETHERNET_II_FRAMES 1 +#define ipconfigBYTE_ORDER pdFREERTOS_LITTLE_ENDIAN +#define ipconfigHAS_DEBUG_PRINTF 0 +#define ipconfigHAS_PRINTF 1 + +/* Linux pcap network interface. + * Override at compile time with -DipconfigNETWORK_INTERFACE_TO_USE=N */ +#ifndef ipconfigNETWORK_INTERFACE_TO_USE + #define ipconfigNETWORK_INTERFACE_TO_USE 2 +#endif + +/* Linux NetworkInterface.c requires these defines */ +#define configNETWORK_INTERFACE_TO_USE ipconfigNETWORK_INTERFACE_TO_USE +#define configMAC_ISR_SIMULATOR_PRIORITY ( configMAX_PRIORITIES - 1 ) +#define configWINDOWS_MAC_INTERRUPT_SIMULATOR_DELAY ( 2 / portTICK_PERIOD_MS ) +#define configNET_MASK0 255 +#define configNET_MASK1 255 +#define configNET_MASK2 255 +#define configNET_MASK3 0 + +#endif /* FREERTOS_IP_CONFIG_H */ diff --git a/echo-server/Makefile b/echo-server/Makefile new file mode 100644 index 0000000..f15b6ff --- /dev/null +++ b/echo-server/Makefile @@ -0,0 +1,184 @@ +MKDIR ?= mkdir + +OBJ = obj + +# wolfSSH +WOLFSSH ?= wolfssh +SSHDIR = $(WOLFSSH)/src +SSHINC = $(WOLFSSH) +OBJSSH = $(OBJ)/$(WOLFSSH) + +# wolfSSL +WOLFSSL ?= wolfssl +CRYPTDIR = $(WOLFSSL)/wolfcrypt/src +CRYPTINC = $(WOLFSSL) +OBJCRYPT = $(OBJ)/$(WOLFSSL) + +# FreeRTOS Kernel +FREERTOS_KERNEL ?= FreeRTOS-Kernel +FREERTOS_PORT = $(FREERTOS_KERNEL)/portable/ThirdParty/GCC/Posix +FREERTOS_HEAP = $(FREERTOS_KERNEL)/portable/MemMang +FREERTOS_UTILS = $(FREERTOS_PORT)/utils +OBJFREERTOS = $(OBJ)/freertos + +# FreeRTOS-Plus-TCP +FREERTOS_TCP ?= FreeRTOS-Plus-TCP +FREERTOS_TCP_SRC = $(FREERTOS_TCP)/source +FREERTOS_TCP_INC = $(FREERTOS_TCP_SRC)/include +FREERTOS_TCP_PORT = $(FREERTOS_TCP_SRC)/portable +FREERTOS_TCP_NI = $(FREERTOS_TCP_PORT)/NetworkInterface/linux +FREERTOS_TCP_BUF = $(FREERTOS_TCP_PORT)/BufferManagement +OBJFREERTOS_TCP = $(OBJ)/freertos_tcp + +CPPFLAGS ?= -I. \ + -I$(SSHINC) -I$(CRYPTINC) \ + -I$(FREERTOS_KERNEL)/include -I$(FREERTOS_PORT) \ + -I$(FREERTOS_TCP_INC) -I$(FREERTOS_TCP_PORT)/Compiler/GCC \ + -DWOLFSSL_USER_SETTINGS + +ifeq ($(BUILD),debug) + DEBUG ?= -O0 -g -DDEBUG_WOLFSSH +endif +CFLAGS := $(DEBUG) $(CFLAGS) + +LDFLAGS ?= -lm -lpthread -lpcap + +.PHONY: clean all + +all: $(OBJ) echo-server keys/server-key-rsa.der + +# Application objects +$(OBJ)/main.o: main.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +$(OBJ)/echo_server.o: echo_server.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +$(OBJ)/freertos_tcp_io.o: freertos_tcp_io.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +APP_OBJS = $(OBJ)/main.o $(OBJ)/echo_server.o $(OBJ)/freertos_tcp_io.o + +# Link +echo-server: $(APP_OBJS) libwolfssh.a libfreertos.a libfreertos_tcp.a + $(CC) $(CFLAGS) -o $@ $(APP_OBJS) libfreertos_tcp.a libfreertos.a libwolfssh.a $(LDFLAGS) + +# --- wolfSSH + wolfSSL static library --- + +libwolfssh.a: $(OBJSSH)/internal.o $(OBJSSH)/log.o $(OBJSSH)/ssh.o \ + $(OBJSSH)/io.o $(OBJSSH)/port.o $(OBJSSH)/keygen.o $(OBJSSH)/wolfterm.o \ + $(OBJCRYPT)/aes.o $(OBJCRYPT)/dh.o $(OBJCRYPT)/integer.o $(OBJCRYPT)/tfm.o \ + $(OBJCRYPT)/sha.o $(OBJCRYPT)/sha256.o $(OBJCRYPT)/sha512.o \ + $(OBJCRYPT)/hash.o $(OBJCRYPT)/ecc.o $(OBJCRYPT)/rsa.o $(OBJCRYPT)/memory.o \ + $(OBJCRYPT)/random.o $(OBJCRYPT)/hmac.o $(OBJCRYPT)/wolfmath.o \ + $(OBJCRYPT)/asn.o $(OBJCRYPT)/coding.o $(OBJCRYPT)/signature.o \ + $(OBJCRYPT)/wc_port.o $(OBJCRYPT)/sp_int.o $(OBJCRYPT)/sp_c64.o \ + $(OBJCRYPT)/sp_c32.o + $(AR) $(ARFLAGS) $@ $^ + +$(OBJSSH)/%.o: $(SSHDIR)/%.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +$(OBJCRYPT)/%.o: $(CRYPTDIR)/%.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +# --- FreeRTOS Kernel static library --- + +FREERTOS_OBJS = \ + $(OBJFREERTOS)/tasks.o \ + $(OBJFREERTOS)/queue.o \ + $(OBJFREERTOS)/list.o \ + $(OBJFREERTOS)/timers.o \ + $(OBJFREERTOS)/event_groups.o \ + $(OBJFREERTOS)/stream_buffer.o \ + $(OBJFREERTOS)/port.o \ + $(OBJFREERTOS)/heap_3.o \ + $(OBJFREERTOS)/wait_for_event.o + +libfreertos.a: $(FREERTOS_OBJS) + $(AR) $(ARFLAGS) $@ $^ + +$(OBJFREERTOS)/tasks.o: $(FREERTOS_KERNEL)/tasks.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/queue.o: $(FREERTOS_KERNEL)/queue.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/list.o: $(FREERTOS_KERNEL)/list.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/timers.o: $(FREERTOS_KERNEL)/timers.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/event_groups.o: $(FREERTOS_KERNEL)/event_groups.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/stream_buffer.o: $(FREERTOS_KERNEL)/stream_buffer.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/port.o: $(FREERTOS_PORT)/port.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/heap_3.o: $(FREERTOS_HEAP)/heap_3.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(OBJFREERTOS)/wait_for_event.o: $(FREERTOS_UTILS)/wait_for_event.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +# --- FreeRTOS-Plus-TCP static library --- + +FREERTOS_TCP_OBJS = \ + $(OBJFREERTOS_TCP)/FreeRTOS_IP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_IP_Utils.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_IP_Timers.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_ARP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_DHCP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_DNS.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Cache.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Callback.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Networking.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Parser.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_ICMP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_IPv4.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_IPv4_Sockets.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_IPv4_Utils.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_Sockets.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_Stream_Buffer.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_IP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_IP_IPv4.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Reception.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_State_Handling.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_State_Handling_IPv4.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Transmission.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Transmission_IPv4.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Utils.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Utils_IPv4.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_TCP_WIN.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_Tiny_TCP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_UDP_IP.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_UDP_IPv4.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_Routing.o \ + $(OBJFREERTOS_TCP)/FreeRTOS_BitConfig.o \ + $(OBJFREERTOS_TCP)/BufferAllocation_2.o \ + $(OBJFREERTOS_TCP)/NetworkInterface.o + +libfreertos_tcp.a: $(FREERTOS_TCP_OBJS) + $(AR) $(ARFLAGS) $@ $^ + +$(OBJFREERTOS_TCP)/%.o: $(FREERTOS_TCP_SRC)/%.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +$(OBJFREERTOS_TCP)/BufferAllocation_2.o: $(FREERTOS_TCP_BUF)/BufferAllocation_2.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +$(OBJFREERTOS_TCP)/NetworkInterface.o: $(FREERTOS_TCP_NI)/NetworkInterface.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +# --- Keys --- + +keys/server-key-rsa.der: + @$(MKDIR) -p keys + @cp $(WOLFSSH)/keys/server-key-rsa.der keys + @cp $(WOLFSSH)/keys/server-key-rsa.pem keys + +# --- Object directories --- + +$(OBJ): + @$(MKDIR) -p $(OBJSSH) $(OBJCRYPT) $(OBJFREERTOS) $(OBJFREERTOS_TCP) + +# --- Clean --- + +clean: + rm -rf echo-server libwolfssh.a libfreertos.a libfreertos_tcp.a $(OBJ) keys diff --git a/echo-server/README.md b/echo-server/README.md new file mode 100644 index 0000000..52f8f0c --- /dev/null +++ b/echo-server/README.md @@ -0,0 +1,209 @@ +# wolfSSH Echo Server on FreeRTOS + +A minimal SSH echo server using wolfSSH, running on the FreeRTOS POSIX/Linux +simulator with FreeRTOS-Plus-TCP networking via libpcap. + +## Dependencies + +* [wolfSSL](https://github.com/wolfSSL/wolfssl) +* [wolfSSH](https://github.com/wolfSSL/wolfssh) +* [FreeRTOS-Kernel](https://github.com/FreeRTOS/FreeRTOS-Kernel) (POSIX port) +* [FreeRTOS-Plus-TCP](https://github.com/FreeRTOS/FreeRTOS-Plus-TCP) (Linux network interface) +* libpcap development headers + +### Installing dependencies + +Fedora: + +``` +sudo dnf install libpcap-devel +``` + +Debian/Ubuntu: + +``` +sudo apt-get install libpcap-dev +``` + +Fetch the library sources (as submodules or cloned into the project directory): + +``` +git submodule update --init +``` + +## Building + +``` +make +``` + +Debug build (enables wolfSSH protocol logging): + +``` +make BUILD=debug +``` + +## Network Setup + +FreeRTOS-Plus-TCP uses libpcap to send and receive raw Ethernet frames, +operating as a separate IP host. On Linux, a **veth pair** provides an isolated +virtual link between the host and the FreeRTOS stack. + +### Create the veth pair + +``` +sudo ip link add veth0 type veth peer name veth1 +sudo ip link set veth0 up +sudo ip link set veth1 up +sudo ip addr add 10.0.0.1/24 dev veth0 +``` + +This creates two linked virtual interfaces: + +* **veth0** -- the host side, with IP `10.0.0.1` +* **veth1** -- the FreeRTOS side, accessed via libpcap + +### Disable checksum offload + +The Linux kernel uses TCP checksum offload by default, which leaves partial +checksums in outgoing packets. Since there is no real NIC on a veth pair, +FreeRTOS-Plus-TCP sees invalid checksums and drops the packets. Disable TX +offload on the host side: + +``` +sudo ethtool -K veth0 tx off +``` + +### Verify the pcap interface number + +FreeRTOS-Plus-TCP opens a pcap device by index number. After creating the veth +pair, check which number `veth1` gets: + +``` +tcpdump --list-interfaces +``` + +The output will look something like: + +``` +1.wlp170s0 [Up, Running, Wireless, Associated] +2.veth1 [Up, Running, Connected] +3.veth0 [Up, Running, Connected] +... +``` + +The default in `FreeRTOSIPConfig.h` is interface 2. If `veth1` has a different +number on your system, either edit the define or override at compile time: + +``` +make CPPFLAGS="-DipconfigNETWORK_INTERFACE_TO_USE=3 $(CPPFLAGS)" +``` + +### Teardown + +To remove the veth pair when done: + +``` +sudo ip link del veth0 +``` + +## Running + +The server needs raw socket access for libpcap: + +``` +sudo ./echo-server +``` + +Once the server prints `Listening on port 22222...`, connect from the host: + +``` +ssh -p 22222 jill@10.0.0.2 +``` + +Password: `upthehill` + +Other test credentials: `jack` / `fetchapail` + +### Control keys + +* **Ctrl+C** -- Disconnect +* **Ctrl+F** -- Trigger SSH key re-exchange + +### Quick setup reference + +All the commands in one block for copy-paste: + +``` +# One-time network setup +sudo ip link add veth0 type veth peer name veth1 +sudo ip link set veth0 up +sudo ip link set veth1 up +sudo ip addr add 10.0.0.1/24 dev veth0 +sudo ethtool -K veth0 tx off + +# Build and run +make +sudo ./echo-server +``` + +## Network Configuration + +The FreeRTOS-Plus-TCP stack uses a static IP configured in `main.c`: + +| Setting | Default | +|------------|---------------| +| IP address | `10.0.0.2` | +| Netmask | `255.255.255.0` | +| Gateway | `10.0.0.1` | +| MAC | `02:00:00:00:00:01` | + +Edit these in `main.c` if your network setup differs. + +`FreeRTOSIPConfig.h` contains the pcap interface index and TCP/IP stack tuning +parameters. + +## Architecture + +* **`main.c`** -- FreeRTOS entry point: initializes FreeRTOS-Plus-TCP, creates + the SSH echo server task, starts the scheduler. Also contains required + FreeRTOS hook functions. +* **`echo_server.c`** -- Platform-agnostic core: wolfSSH initialization, + authentication (password + public key), echo read/send loop. Ported from + `wolfssh/ide/mplabx/wolfssh.c`. +* **`freertos_tcp_io.c`** -- wolfSSH IO callbacks bridging + `FreeRTOS_recv()`/`FreeRTOS_send()` to wolfSSH's IO layer. +* **`user_settings.h`** -- wolfSSL/wolfSSH build configuration. +* **`FreeRTOSConfig.h`** -- FreeRTOS kernel configuration. +* **`FreeRTOSIPConfig.h`** -- FreeRTOS-Plus-TCP stack configuration. + +### Task priorities + +The SSH echo server task must run at a **lower priority** than the +FreeRTOS-Plus-TCP IP task. The defaults are: + +| Task | Priority | Default | +|------------------|-----------------------------|---------| +| MAC ISR simulator| `configMAX_PRIORITIES - 1` | 4 | +| IP task | `configMAX_PRIORITIES - 2` | 3 | +| SSH echo server | `tskIDLE_PRIORITY + 1` | 1 | + +## Embedded Integration (PIC32MZ / Harmony) + +To use on PIC32MZ with Microchip Harmony: + +1. Add `echo_server.c`, `echo_server.h`, `freertos_tcp_io.c`, + `freertos_tcp_io.h` to your MPLABX project +2. In `user_settings.h`, uncomment the Harmony-specific defines +3. Replace `main.c` with your Harmony `app.c`, calling `echoServerInit()`, + `echoServerAccept()`, and `echoServerLoop()` from your task or state machine +4. Adjust `FreeRTOSConfig.h` and `FreeRTOSIPConfig.h` for your hardware + +## user_settings.h + +Key defines controlling the wolfSSL/wolfSSH build: + +* `WOLFSSH_USER_IO` -- Disables default BSD socket IO; custom callbacks in + `freertos_tcp_io.c` are used instead +* `FREERTOS` / `WOLFSSL_FREERTOS` -- Enables FreeRTOS support +* `WOLFSSH_NO_AGENT` -- Strips unused SSH agent support diff --git a/echo-server/echo_server.c b/echo-server/echo_server.c new file mode 100644 index 0000000..38033ac --- /dev/null +++ b/echo-server/echo_server.c @@ -0,0 +1,539 @@ +/* echo_server.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +/* Uses portions of code from wolfssh/ide/mplabx/wolfssh.c and + * wolfssh/examples/echoserver/echoserver.c */ + +#include "echo_server.h" +#include "freertos_tcp_io.h" + +#include +#include +#include +#include + +/* Use embedded test keys */ +#ifndef NO_FILESYSTEM + #define NO_FILESYSTEM + #include + #undef NO_FILESYSTEM +#else + #include +#endif + +#include "FreeRTOS.h" +#include "task.h" + +#include +#include +#include + +/* + * Helper functions ported from wolfssh/ide/mplabx/wolfssh.c + */ + +static const char echoServerBanner[] = "wolfSSH Echo Server\n"; + +static const char samplePasswordBuffer[] = + "jill:upthehill\n" + "jack:fetchapail\n"; + +static const char samplePublicKeyEccBuffer[] = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" + "BBBNkI5JTP6D0lF42tbxX19cE87hztUS6FSDoGvPfiU0CgeNSbI+aFdKIzTP5CQEJSvm25" + "qUzgDtH7oyaQROUnNvk= hansel\n" + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" + "BBBKAtH8cqaDbtJFjtviLobHBmjCtG56DMkP6A4M2H9zX2/YCg1h9bYS7WHd9UQDwXO1Hh" + "IZzRYecXh7SG9P4GhRY= gretel\n"; + +static const char samplePublicKeyRsaBuffer[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9P3ZFowOsONXHD5MwWiCciXytBRZGho" + "MNiisWSgUs5HdHcACuHYPi2W6Z1PBFmBWT9odOrGRjoZXJfDDoPi+j8SSfDGsc/hsCmc3G" + "p2yEhUZUEkDhtOXyqjns1ickC9Gh4u80aSVtwHRnJZh9xPhSq5tLOhId4eP61s+a5pwjTj" + "nEhBaIPUJO2C/M0pFnnbZxKgJlX7t1Doy7h5eXxviymOIvaCZKU+x5OopfzM/wFkey0EPW" + "NmzI5y/+pzU5afsdeEWdiQDIQc80H6Pz8fsoFPvYSG+s4/wz0duu7yeeV1Ypoho65Zr+pE" + "nIf7dO0B8EblgWt+ud+JI8wrAhfE4x hansel\n" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqDwRVTRVk/wjPhoo66+Mztrc31KsxDZ" + "+kAV0139PHQ+wsueNpba6jNn5o6mUTEOrxrz0LMsDJOBM7CmG0983kF4gRIihECpQ0rcjO" + "P6BSfbVTE9mfIK5IsUiZGd8SoE9kSV2pJ2FvZeBQENoAxEFk0zZL9tchPS+OCUGbK4SDjz" + "uNZl/30Mczs73N3MBzi6J1oPo7sFlqzB6ecBjK2Kpjus4Y1rYFphJnUxtKvB0s+hoaadru" + "biE57dK6BrH5iZwVLTQKux31uCJLPhiktI3iLbdlGZEctJkTasfVSsUizwVIyRjhVKmbdI" + "RGwkU38D043AR1h0mUoGCPIKuqcFMf gretel\n"; + + +static INLINE void c32toa(word32 u32, byte* c) +{ + c[0] = (u32 >> 24) & 0xff; + c[1] = (u32 >> 16) & 0xff; + c[2] = (u32 >> 8) & 0xff; + c[3] = u32 & 0xff; +} + + +static PwMap* PwMapNew(PwMapList* list, byte type, const byte* username, + word32 usernameSz, const byte* p, word32 pSz) +{ + PwMap* map; + + map = (PwMap*)malloc(sizeof(PwMap)); + if (map != NULL) { + wc_Sha256 sha; + byte flatSz[4]; + + map->type = type; + if (usernameSz >= sizeof(map->username)) + usernameSz = sizeof(map->username) - 1; + memcpy(map->username, username, usernameSz + 1); + map->username[usernameSz] = 0; + map->usernameSz = usernameSz; + + wc_InitSha256(&sha); + c32toa(pSz, flatSz); + wc_Sha256Update(&sha, flatSz, sizeof(flatSz)); + wc_Sha256Update(&sha, p, pSz); + wc_Sha256Final(&sha, map->p); + + map->next = list->head; + list->head = map; + } + + return map; +} + + +static void PwMapListDelete(PwMapList* list) +{ + if (list != NULL) { + PwMap* head = list->head; + + while (head != NULL) { + PwMap* cur = head; + head = head->next; + memset(cur, 0, sizeof(PwMap)); + free(cur); + } + list->head = NULL; + } +} + + +static int LoadPasswordBuffer(byte* buf, word32 bufSz, PwMapList* list) +{ + char* str = (char*)buf; + char* delimiter; + char* username; + char* password; + + if (list == NULL) + return -1; + + if (buf == NULL || bufSz == 0) + return 0; + + while (*str != 0) { + delimiter = strchr(str, ':'); + if (delimiter == NULL) + break; + username = str; + *delimiter = 0; + password = delimiter + 1; + str = strchr(password, '\n'); + if (str == NULL) + break; + *str = 0; + str++; + if (PwMapNew(list, WOLFSSH_USERAUTH_PASSWORD, + (byte*)username, (word32)strlen(username), + (byte*)password, (word32)strlen(password)) == NULL) { + return -1; + } + } + + return 0; +} + + +static int LoadPublicKeyBuffer(byte* buf, word32 bufSz, PwMapList* list) +{ + char* str = (char*)buf; + char* delimiter; + byte* publicKey64; + word32 publicKey64Sz; + byte* username; + word32 usernameSz; + byte publicKey[300]; + word32 publicKeySz; + + if (list == NULL) + return -1; + + if (buf == NULL || bufSz == 0) + return 0; + + while (*str != 0) { + /* Skip the public key type (e.g., ssh-rsa or ecdsa-sha2-nistp256) */ + delimiter = strchr(str, ' '); + if (delimiter == NULL) + break; + str = delimiter + 1; + delimiter = strchr(str, ' '); + if (delimiter == NULL) + break; + publicKey64 = (byte*)str; + *delimiter = 0; + publicKey64Sz = (word32)(delimiter - str); + str = delimiter + 1; + delimiter = strchr(str, '\n'); + if (delimiter == NULL) + break; + username = (byte*)str; + *delimiter = 0; + usernameSz = (word32)(delimiter - str); + str = delimiter + 1; + publicKeySz = sizeof(publicKey); + + if (Base64_Decode(publicKey64, publicKey64Sz, + publicKey, &publicKeySz) != 0) { + return -1; + } + + if (PwMapNew(list, WOLFSSH_USERAUTH_PUBLICKEY, + username, usernameSz, + publicKey, publicKeySz) == NULL) { + return -1; + } + } + + return 0; +} + + +/* Load embedded DER key from certs_test.h */ +static int load_key(byte isEcc, byte* buf, word32 bufSz) +{ + word32 sz = 0; + + if (isEcc) { + if (sizeof_ecc_key_der_256_ssh > bufSz) { + return 0; + } + WMEMCPY(buf, ecc_key_der_256_ssh, sizeof_ecc_key_der_256_ssh); + sz = sizeof_ecc_key_der_256_ssh; + } + else { + if (sizeof_rsa_key_der_2048_ssh > bufSz) { + return 0; + } + WMEMCPY(buf, (byte*)rsa_key_der_2048_ssh, sizeof_rsa_key_der_2048_ssh); + sz = sizeof_rsa_key_der_2048_ssh; + } + + return sz; +} + + +static byte find_char(const byte* str, const byte* buf, word32 bufSz) +{ + const byte* cur; + + while (bufSz) { + cur = str; + while (*cur != '\0') { + if (*cur == *buf) + return *cur; + cur++; + } + buf++; + bufSz--; + } + + return 0; +} + + +/* + * Authentication callback + */ +static int wsUserAuth(byte authType, + WS_UserAuthData* authData, + void* ctx) +{ + PwMapList* list; + PwMap* map; + byte authHash[WC_SHA256_DIGEST_SIZE]; + + if (ctx == NULL) { + return WOLFSSH_USERAUTH_FAILURE; + } + + if (authType != WOLFSSH_USERAUTH_PASSWORD && + authType != WOLFSSH_USERAUTH_PUBLICKEY) { + return WOLFSSH_USERAUTH_FAILURE; + } + + /* Hash the password or public key with its length */ + { + wc_Sha256 sha; + byte flatSz[4]; + wc_InitSha256(&sha); + if (authType == WOLFSSH_USERAUTH_PASSWORD) { + c32toa(authData->sf.password.passwordSz, flatSz); + wc_Sha256Update(&sha, flatSz, sizeof(flatSz)); + wc_Sha256Update(&sha, + authData->sf.password.password, + authData->sf.password.passwordSz); + } + else if (authType == WOLFSSH_USERAUTH_PUBLICKEY) { + c32toa(authData->sf.publicKey.publicKeySz, flatSz); + wc_Sha256Update(&sha, flatSz, sizeof(flatSz)); + wc_Sha256Update(&sha, + authData->sf.publicKey.publicKey, + authData->sf.publicKey.publicKeySz); + } + wc_Sha256Final(&sha, authHash); + } + + list = (PwMapList*)ctx; + map = list->head; + + while (map != NULL) { + if (authData->usernameSz == map->usernameSz && + memcmp(authData->username, map->username, map->usernameSz) == 0) { + + if (authData->type == map->type) { + if (memcmp(map->p, authHash, WC_SHA256_DIGEST_SIZE) == 0) { + return WOLFSSH_USERAUTH_SUCCESS; + } + else { + return (authType == WOLFSSH_USERAUTH_PASSWORD ? + WOLFSSH_USERAUTH_INVALID_PASSWORD : + WOLFSSH_USERAUTH_INVALID_PUBLICKEY); + } + } + else { + return WOLFSSH_USERAUTH_INVALID_AUTHTYPE; + } + } + map = map->next; + } + + return WOLFSSH_USERAUTH_INVALID_USER; +} + + +/* + * Public API + */ + +int echoServerInit(WOLFSSH_CTX** ctx, PwMapList* pwMapList) +{ + int useEcc = 0; + byte buf[SCRATCH_BUFFER_SZ]; + word32 bufSz; + const char* pubKeyBuf; + + memset(pwMapList, 0, sizeof(PwMapList)); + + if (wolfSSH_Init() != WS_SUCCESS) { + printf("Couldn't initialize wolfSSH.\n"); + return -1; + } + + *ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (*ctx == NULL) { + printf("Couldn't allocate SSH CTX data.\n"); + return -1; + } + + wolfSSH_SetUserAuth(*ctx, wsUserAuth); + wolfSSH_CTX_SetBanner(*ctx, echoServerBanner); + + /* Register FreeRTOS-Plus-TCP IO callbacks */ + freertosIO_SetCallbacks(*ctx); + + /* Load server private key */ + bufSz = load_key(useEcc, buf, SCRATCH_BUFFER_SZ); + if (bufSz == 0) { + printf("Couldn't load key.\n"); + return -1; + } + if (wolfSSH_CTX_UsePrivateKey_buffer(*ctx, buf, bufSz, + WOLFSSH_FORMAT_ASN1) < 0) { + printf("Couldn't use key buffer.\n"); + return -1; + } + + /* Load passwords */ + bufSz = (word32)strlen(samplePasswordBuffer); + memcpy(buf, samplePasswordBuffer, bufSz); + buf[bufSz] = 0; + LoadPasswordBuffer(buf, bufSz, pwMapList); + + /* Load public keys */ + pubKeyBuf = useEcc ? samplePublicKeyEccBuffer : samplePublicKeyRsaBuffer; + bufSz = (word32)strlen(pubKeyBuf); + memcpy(buf, pubKeyBuf, bufSz); + buf[bufSz] = 0; + LoadPublicKeyBuffer(buf, bufSz, pwMapList); + + printf("wolfSSH Echo Server initialized on port %d\n", ECHO_SERVER_PORT); + return 0; +} + + +int echoServerAccept(WOLFSSH_CTX* ctx, WOLFSSH** ssh, + PwMapList* pwMapList, void* clientSocket) +{ + int ret; + int error; + + *ssh = wolfSSH_new(ctx); + if (*ssh == NULL) { + printf("Couldn't allocate SSH session.\n"); + return -1; + } + + wolfSSH_SetUserAuthCtx(*ssh, pwMapList); + + /* Set FreeRTOS-Plus-TCP IO context (socket handle) */ + freertosIO_SetContext(*ssh, clientSocket); + + printf("Performing SSH accept...\n"); + + /* Non-blocking accept loop */ + do { + ret = wolfSSH_accept(*ssh); + error = wolfSSH_get_error(*ssh); + + if (ret == WS_SUCCESS || ret == WS_SFTP_COMPLETE) { + break; + } + + if (error == WS_WANT_READ || error == WS_WANT_WRITE) { + vTaskDelay(pdMS_TO_TICKS(1)); + continue; + } + + printf("SSH accept error: %d (%s)\n", error, + wolfSSH_ErrorToName(error)); + return -1; + } while (1); + + printf("SSH connection accepted.\n"); + return 0; +} + + +int echoServerLoop(WOLFSSH* ssh) +{ + byte* buf = NULL; + byte* tmpBuf; + int bufSz, backlogSz = 0, rxSz, txSz, stop = 0, txSum; + + while (!stop) { + bufSz = ECHO_BUFFER_SZ + backlogSz; + tmpBuf = (byte*)realloc(buf, bufSz); + if (tmpBuf == NULL) { + stop = 1; + break; + } + buf = tmpBuf; + + rxSz = wolfSSH_stream_read(ssh, buf + backlogSz, ECHO_BUFFER_SZ); + + if (rxSz <= 0) { + int error = wolfSSH_get_error(ssh); + + if (error == WS_WANT_READ || error == WS_WANT_WRITE) { + vTaskDelay(pdMS_TO_TICKS(1)); + continue; + } + if (error == WS_EOF) { + printf("Client disconnected (EOF).\n"); + stop = 1; + break; + } + if (error == WS_REKEYING) { + continue; + } + + printf("Stream read error: %d (%s)\n", error, + wolfSSH_ErrorToName(error)); + stop = 1; + break; + } + + backlogSz += rxSz; + txSum = 0; + + while (backlogSz != txSum && !stop) { + txSz = wolfSSH_stream_send(ssh, buf + txSum, backlogSz - txSum); + + if (txSz > 0) { + byte c; + const byte matches[] = { 0x03, 0x06, 0x00 }; + + c = find_char(matches, buf + txSum, txSz); + switch (c) { + case 0x03: /* Ctrl+C */ + printf("Ctrl+C received, disconnecting.\n"); + stop = 1; + break; + case 0x06: /* Ctrl+F */ + if (wolfSSH_TriggerKeyExchange(ssh) != WS_SUCCESS) { + stop = 1; + } + break; + default: + break; + } + txSum += txSz; + } + else if (txSz == WS_REKEYING) { + /* Continue turning the crank during rekey */ + vTaskDelay(pdMS_TO_TICKS(1)); + continue; + } + else { + int error = wolfSSH_get_error(ssh); + if (error == WS_WANT_WRITE) { + vTaskDelay(pdMS_TO_TICKS(1)); + continue; + } + printf("Stream send error: %d\n", error); + stop = 1; + break; + } + } + + if (txSum < backlogSz) + memmove(buf, buf + txSum, backlogSz - txSum); + backlogSz -= txSum; + } + + free(buf); + return 0; +} + + +void echoServerCleanup(WOLFSSH_CTX* ctx, PwMapList* pwMapList) +{ + PwMapListDelete(pwMapList); + wolfSSH_CTX_free(ctx); + wolfSSH_Cleanup(); +} diff --git a/echo-server/echo_server.h b/echo-server/echo_server.h new file mode 100644 index 0000000..019f308 --- /dev/null +++ b/echo-server/echo_server.h @@ -0,0 +1,73 @@ +/* echo_server.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +#ifndef ECHO_SERVER_H +#define ECHO_SERVER_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ECHO_SERVER_PORT + #define ECHO_SERVER_PORT 22222 +#endif +#ifndef ECHO_BUFFER_SZ + #define ECHO_BUFFER_SZ 4096 +#endif +#ifndef SCRATCH_BUFFER_SZ + #define SCRATCH_BUFFER_SZ 1200 +#endif +#ifndef EXAMPLE_HIGHWATER_MARK + #define EXAMPLE_HIGHWATER_MARK 0x3FFF8000 +#endif + +/* Password map entry */ +typedef struct PwMap { + byte type; + byte username[32]; + word32 usernameSz; + byte p[WC_SHA256_DIGEST_SIZE]; + struct PwMap* next; +} PwMap; + +typedef struct PwMapList { + PwMap* head; +} PwMapList; + +/* Initialize wolfSSH context with keys, auth, and IO callbacks */ +int echoServerInit(WOLFSSH_CTX** ctx, PwMapList* pwMapList); + +/* Accept an SSH connection on the given FreeRTOS-Plus-TCP socket */ +int echoServerAccept(WOLFSSH_CTX* ctx, WOLFSSH** ssh, + PwMapList* pwMapList, void* clientSocket); + +/* Run the echo loop for one connected session */ +int echoServerLoop(WOLFSSH* ssh); + +/* Free all resources */ +void echoServerCleanup(WOLFSSH_CTX* ctx, PwMapList* pwMapList); + +#ifdef __cplusplus +} +#endif + +#endif /* ECHO_SERVER_H */ diff --git a/echo-server/freertos_tcp_io.c b/echo-server/freertos_tcp_io.c new file mode 100644 index 0000000..a9ff103 --- /dev/null +++ b/echo-server/freertos_tcp_io.c @@ -0,0 +1,105 @@ +/* freertos_tcp_io.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +/* wolfSSH IO callbacks for FreeRTOS-Plus-TCP. + * Follows the pattern from wolfip/src/port/wolfssh_io.c */ + +#include "freertos_tcp_io.h" + +#include +#include + +#include "FreeRTOS.h" +#include "FreeRTOS_IP.h" +#include "FreeRTOS_Sockets.h" + + +/* Receive callback: FreeRTOS_recv() -> wolfSSH IO return codes */ +static int freertosRecv(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) +{ + Socket_t xSocket; + BaseType_t ret; + + (void)ssh; + + if (ctx == NULL) + return WS_CBIO_ERR_GENERAL; + + xSocket = *(Socket_t*)ctx; + + ret = FreeRTOS_recv(xSocket, buf, (size_t)sz, 0); + + if (ret > 0) + return (int)ret; + if (ret == 0) + return WS_CBIO_ERR_CONN_CLOSE; + if (ret == -pdFREERTOS_ERRNO_EWOULDBLOCK || + ret == -pdFREERTOS_ERRNO_EAGAIN) + return WS_CBIO_ERR_WANT_READ; + if (ret == -pdFREERTOS_ERRNO_ENOTCONN) + return WS_CBIO_ERR_CONN_CLOSE; + + return WS_CBIO_ERR_GENERAL; +} + + +/* Send callback: FreeRTOS_send() -> wolfSSH IO return codes */ +static int freertosSend(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) +{ + Socket_t xSocket; + BaseType_t ret; + + (void)ssh; + + if (ctx == NULL) + return WS_CBIO_ERR_GENERAL; + + xSocket = *(Socket_t*)ctx; + + ret = FreeRTOS_send(xSocket, buf, (size_t)sz, 0); + + if (ret > 0) + return (int)ret; + if (ret == 0) + return WS_CBIO_ERR_CONN_CLOSE; + if (ret == -pdFREERTOS_ERRNO_EWOULDBLOCK || + ret == -pdFREERTOS_ERRNO_EAGAIN) + return WS_CBIO_ERR_WANT_WRITE; + if (ret == -pdFREERTOS_ERRNO_ENOTCONN) + return WS_CBIO_ERR_CONN_CLOSE; + + return WS_CBIO_ERR_GENERAL; +} + + +void freertosIO_SetCallbacks(WOLFSSH_CTX* ctx) +{ + if (ctx != NULL) { + wolfSSH_SetIORecv(ctx, freertosRecv); + wolfSSH_SetIOSend(ctx, freertosSend); + } +} + + +void freertosIO_SetContext(WOLFSSH* ssh, void* socketPtr) +{ + if (ssh != NULL) { + wolfSSH_SetIOReadCtx(ssh, socketPtr); + wolfSSH_SetIOWriteCtx(ssh, socketPtr); + } +} diff --git a/echo-server/freertos_tcp_io.h b/echo-server/freertos_tcp_io.h new file mode 100644 index 0000000..6a6013e --- /dev/null +++ b/echo-server/freertos_tcp_io.h @@ -0,0 +1,38 @@ +/* freertos_tcp_io.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +#ifndef FREERTOS_TCP_IO_H +#define FREERTOS_TCP_IO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Register FreeRTOS-Plus-TCP IO callbacks on a wolfSSH context */ +void freertosIO_SetCallbacks(WOLFSSH_CTX* ctx); + +/* Set the FreeRTOS-Plus-TCP socket as the IO context for a session */ +void freertosIO_SetContext(WOLFSSH* ssh, void* socketPtr); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERTOS_TCP_IO_H */ diff --git a/echo-server/main.c b/echo-server/main.c new file mode 100644 index 0000000..712ad3e --- /dev/null +++ b/echo-server/main.c @@ -0,0 +1,240 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +/* FreeRTOS entry point for the wolfSSH echo server. + * Runs on the FreeRTOS POSIX/Linux simulator with FreeRTOS-Plus-TCP. */ + +#include +#include +#include +#include + +#include "FreeRTOS.h" +#include "task.h" +#include "FreeRTOS_IP.h" +#include "FreeRTOS_Sockets.h" + +#include "echo_server.h" + +/* Network configuration for the FreeRTOS-Plus-TCP stack. + * The stack runs on one end of a veth pair with its own IP address. + * Adjust for your network environment. */ +static const uint8_t ucIPAddress[4] = { 10, 0, 0, 2 }; +static const uint8_t ucNetMask[4] = { 255, 255, 255, 0 }; +static const uint8_t ucGatewayAddress[4] = { 10, 0, 0, 1 }; +static const uint8_t ucDNSServerAddress[4] = { 8, 8, 8, 8 }; +static const uint8_t ucMACAddress[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 }; + + +static void sshEchoServerTask(void* pvParameters) +{ + Socket_t xListenSocket, xClientSocket; + struct freertos_sockaddr xBindAddr, xClientAddr; + socklen_t xClientAddrLen; + TickType_t xRecvTimeout = pdMS_TO_TICKS(1000); + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + PwMapList pwMapList; + + (void)pvParameters; + + /* Wait for the network stack to be ready */ + printf("Waiting for network...\n"); + while (FreeRTOS_IsNetworkUp() == pdFALSE) { + vTaskDelay(pdMS_TO_TICKS(500)); + } + + { + uint32_t ulIPAddress, ulNetMask, ulGateway, ulDNS; + char cBuf[16]; + FreeRTOS_GetAddressConfiguration(&ulIPAddress, &ulNetMask, + &ulGateway, &ulDNS); + FreeRTOS_inet_ntoa(ulIPAddress, cBuf); + printf("Network up. IP Address: %s\n", cBuf); + printf("Connect with: ssh -p %d jill@%s (password: upthehill)\n", + ECHO_SERVER_PORT, cBuf); + } + + /* Initialize wolfSSH */ + if (echoServerInit(&ctx, &pwMapList) != 0) { + printf("Echo server init failed.\n"); + vTaskDelete(NULL); + return; + } + + /* Create TCP listening socket */ + xListenSocket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, + FREERTOS_IPPROTO_TCP); + if (xListenSocket == FREERTOS_INVALID_SOCKET) { + printf("Failed to create listen socket.\n"); + echoServerCleanup(ctx, &pwMapList); + vTaskDelete(NULL); + return; + } + + /* Set receive timeout so accept doesn't block forever */ + FreeRTOS_setsockopt(xListenSocket, 0, FREERTOS_SO_RCVTIMEO, + &xRecvTimeout, sizeof(xRecvTimeout)); + + memset(&xBindAddr, 0, sizeof(xBindAddr)); + xBindAddr.sin_port = FreeRTOS_htons(ECHO_SERVER_PORT); + + xBindAddr.sin_family = FREERTOS_AF_INET; + + if (FreeRTOS_bind(xListenSocket, &xBindAddr, sizeof(xBindAddr)) != 0) { + printf("Failed to bind to port %d.\n", ECHO_SERVER_PORT); + FreeRTOS_closesocket(xListenSocket); + echoServerCleanup(ctx, &pwMapList); + vTaskDelete(NULL); + return; + } + + if (FreeRTOS_listen(xListenSocket, 1) != 0) { + printf("FreeRTOS_listen failed.\n"); + } + printf("Listening on port %d...\n", ECHO_SERVER_PORT); + + /* Accept loop */ + for (;;) { + xClientAddrLen = sizeof(xClientAddr); + xClientSocket = FreeRTOS_accept(xListenSocket, &xClientAddr, + &xClientAddrLen); + + if (xClientSocket != NULL && + xClientSocket != FREERTOS_INVALID_SOCKET) { + + TickType_t xTimeout = pdMS_TO_TICKS(300000); /* 5 minutes */ + + /* Set timeouts on the client socket */ + FreeRTOS_setsockopt(xClientSocket, 0, FREERTOS_SO_RCVTIMEO, + &xTimeout, sizeof(xTimeout)); + FreeRTOS_setsockopt(xClientSocket, 0, FREERTOS_SO_SNDTIMEO, + &xTimeout, sizeof(xTimeout)); + + printf("Client connected.\n"); + + if (echoServerAccept(ctx, &ssh, &pwMapList, + &xClientSocket) == 0) { + echoServerLoop(ssh); + } + + if (ssh != NULL) { + wolfSSH_free(ssh); + ssh = NULL; + } + + FreeRTOS_closesocket(xClientSocket); + printf("Client disconnected. Waiting for next connection...\n"); + } + + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + + +/* + * FreeRTOS and FreeRTOS-Plus-TCP required hooks/callbacks + */ + +void vApplicationIdleHook(void) +{ + usleep(15000); +} + +void vApplicationIPNetworkEventHook_Multi(eIPCallbackEvent_t eNetworkEvent, + struct xNetworkEndPoint* pxEndPoint) +{ + (void)pxEndPoint; + if (eNetworkEvent == eNetworkUp) { + printf("Network interface is up.\n"); + } +} + +/* DHCP hook — accept any address offered by the server */ +eDHCPCallbackAnswer_t xApplicationDHCPHook(eDHCPCallbackPhase_t eDHCPPhase, + uint32_t ulIPAddress) +{ + (void)eDHCPPhase; + (void)ulIPAddress; + + return eDHCPContinue; +} + +/* Called by FreeRTOS-Plus-TCP when a ping reply is received */ +void vApplicationPingReplyHook(ePingReplyStatus_t eStatus, + uint16_t usIdentifier) +{ + (void)eStatus; + (void)usIdentifier; +} + +/* Provide a random number for TCP sequence numbers */ +uint32_t ulApplicationGetNextSequenceNumber(uint32_t ulSourceAddress, + uint16_t usSourcePort, uint32_t ulDestinationAddress, + uint16_t usDestinationPort) +{ + (void)ulSourceAddress; + (void)usSourcePort; + (void)ulDestinationAddress; + (void)usDestinationPort; + + /* Use wolfCrypt RNG for production; simple rand() for this demo */ + return (uint32_t)rand(); +} + +BaseType_t xApplicationGetRandomNumber(uint32_t* pulNumber) +{ + *pulNumber = (uint32_t)rand(); + return pdTRUE; +} + +/* heap_3.c uses malloc/free, so these aren't meaningful, but the TCP + * stack references them for diagnostic prints */ +size_t xPortGetMinimumEverFreeHeapSize(void) +{ + return 0; +} + +size_t xPortGetFreeHeapSize(void) +{ + return 0; +} + + +int main(void) +{ + /* Disable stdout buffering so output shows immediately */ + setvbuf(stdout, NULL, _IONBF, 0); + + printf("Starting wolfSSH Echo Server with FreeRTOS + FreeRTOS-Plus-TCP\n"); + + /* Initialize the FreeRTOS-Plus-TCP stack */ + FreeRTOS_IPInit(ucIPAddress, ucNetMask, ucGatewayAddress, + ucDNSServerAddress, ucMACAddress); + + /* Create the SSH echo server task. + * Priority must be lower than the IP task (configMAX_PRIORITIES - 2). */ + xTaskCreate(sshEchoServerTask, "SSHEcho", + configMINIMAL_STACK_SIZE * 4, NULL, + tskIDLE_PRIORITY + 1, NULL); + + /* Start the FreeRTOS scheduler - does not return */ + vTaskStartScheduler(); + + return 0; +} diff --git a/echo-server/user_settings.h b/echo-server/user_settings.h new file mode 100644 index 0000000..89aaef7 --- /dev/null +++ b/echo-server/user_settings.h @@ -0,0 +1,70 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * wolfSSH is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfSSH. If not, see . + */ + +#ifndef USER_SETTINGS_H +#define USER_SETTINGS_H + +/* Math library: single-precision by default */ +#define WOLFSSL_SP +#define WOLFSSL_HAVE_SP_RSA +#define WOLFSSL_HAVE_SP_DH +#define WOLFSSL_HAVE_SP_ECC + +#if defined(__x86_64__) || defined(__aarch64__) || defined(_M_X64) + #define SP_WORD_SIZE 64 + #define HAVE___UINT128_T +#else + #define SP_WORD_SIZE 32 +#endif + +/* wolfSSL core */ +#define WOLFSSL_KEY_GEN +#define NO_MD5 +#define NO_DSA +#define WOLFCRYPT_ONLY +#define NO_PKCS8 +#define NO_PKCS12 +#define HAVE_ECC +#define TFM_TIMING_RESISTANT +#define ECC_TIMING_RESISTANT +#define WC_RSA_BLINDING +#define HAVE_AESGCM +#define HAVE_AESCCM +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 + +/* FreeRTOS */ +#define FREERTOS +#define WOLFSSL_FREERTOS + +/* wolfSSH */ +#define WOLFSSL_WOLFSSH +#define WOLFSSH_USER_IO +#define DEFAULT_WINDOW_SZ 16384 +#define HAVE_WC_ECC_SET_RNG +#define NO_MAIN_DRIVER +#define WOLFSSH_NO_AGENT + +/* Uncomment for Microchip Harmony on PIC32MZ: + * #define MICROCHIP_MPLAB_HARMONY + * #define NO_FILESYSTEM + * #define SINGLE_THREADED + * #define WOLFSSH_NO_EXIT + */ + +#endif /* USER_SETTINGS_H */ diff --git a/echo-server/wolfssh b/echo-server/wolfssh new file mode 160000 index 0000000..157cb01 --- /dev/null +++ b/echo-server/wolfssh @@ -0,0 +1 @@ +Subproject commit 157cb01f4b6061807c4f72c8c92d8c58ca889b6e diff --git a/echo-server/wolfssl b/echo-server/wolfssl new file mode 160000 index 0000000..b36a9ca --- /dev/null +++ b/echo-server/wolfssl @@ -0,0 +1 @@ +Subproject commit b36a9ca80e8285e80dc0d1a45e057ea60d0fb1a1