pull/1255/merge
John Safranek 2026-09-17 18:44:45 +00:00 committed by GitHub
commit fe9903111a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 234 additions and 20 deletions

97
.github/workflows/espressif.yml vendored 100644
View File

@ -0,0 +1,97 @@
name: wolfSSH Espressif Build Test
on:
schedule:
# Weekly: Mondays at 07:00 UTC
- cron: '0 7 * * 1'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
WOLFSSL_REF: v5.9.1-stable
jobs:
build_examples:
name: ESP-IDF ${{ matrix.idf-ref }}
strategy:
fail-fast: false
matrix:
# release-v5.5 is the current stable line; release-v5.1 is the version
# the example READMEs and VisualGDB projects document.
idf-ref: [ release-v5.5, release-v5.1 ]
runs-on: ubuntu-latest
container:
image: espressif/idf:${{ matrix.idf-ref }}
timeout-minutes: 30
steps:
- name: Checkout wolfSSH
uses: actions/checkout@v6
with:
path: wolfssh
- name: Checkout wolfSSL
uses: actions/checkout@v6
with:
repository: wolfssl/wolfssl
ref: ${{ env.WOLFSSL_REF }}
path: wolfssl
- name: Build the Espressif examples
env:
WOLFSSH_ROOT: ${{ github.workspace }}/wolfssh
WOLFSSL_ROOT: ${{ github.workspace }}/wolfssl
run: |
# The image sets IDF_PATH, but the ESP-IDF tool paths come from
# export.sh, which a job step does not get from the entrypoint.
. "${IDF_PATH}/export.sh"
"${WOLFSSH_ROOT}/ide/Espressif/ESP-IDF/compileAllExamples.sh"
notify_failure:
name: Open issue on scheduled failure
needs: [build_examples]
if: failure() && github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const label = 'espressif-build-failure';
const runUrl = `${context.serverUrl}/${context.repo.owner}/` +
`${context.repo.repo}/actions/runs/${context.runId}`;
const body = [
'The weekly Espressif example build failed.',
'',
`Run: ${runUrl}`,
`Commit: ${context.sha}`,
].join('\n');
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: label,
});
if (existing.data.length > 0) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.data[0].number,
body: body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Weekly Espressif example build failed',
body: body,
labels: [label],
});
}

View File

@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Build every wolfSSH ESP-IDF example project under ./examples/
#
# The examples carry local wolfssh and wolfssl components that compile the
# library sources in place, so both source trees have to be on disk. Their
# CMakeLists find them through the WOLFSSH_ROOT and WOLFSSL_ROOT environment
# variables, which this script fills in when they are not already set.
#
# Run from an ESP-IDF environment:
#
# . /opt/esp/idf/export.sh
# WOLFSSL_ROOT=/path/to/wolfssl ide/Espressif/ESP-IDF/compileAllExamples.sh
#
# WOLFSSH_ROOT defaults to the tree holding this script, WOLFSSL_ROOT to a
# wolfssl checkout beside it. IDF_TARGET selects the chip; default esp32.
if [ -z "${IDF_PATH}" ]; then
echo "ERROR: IDF_PATH is not set. Source the ESP-IDF export.sh first."
exit 1
fi
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
if [ -z "${WOLFSSH_ROOT}" ]; then
WOLFSSH_ROOT=$(cd "${SCRIPT_DIR}/../../.." && pwd)
fi
if [ -z "${WOLFSSL_ROOT}" ]; then
WOLFSSL_ROOT="$(dirname "${WOLFSSH_ROOT}")/wolfssl"
fi
export WOLFSSH_ROOT
export WOLFSSL_ROOT
export IDF_TARGET="${IDF_TARGET:-esp32}"
if [ ! -f "${WOLFSSH_ROOT}/wolfssh/ssh.h" ]; then
echo "ERROR: no wolfSSH source in WOLFSSH_ROOT=${WOLFSSH_ROOT}"
exit 1
fi
if [ ! -f "${WOLFSSL_ROOT}/wolfssl/ssl.h" ]; then
echo "ERROR: no wolfSSL source in WOLFSSL_ROOT=${WOLFSSL_ROOT}"
echo "Clone wolfssl beside wolfssh or set WOLFSSL_ROOT."
exit 1
fi
echo "IDF_PATH = ${IDF_PATH}"
echo "IDF_TARGET = ${IDF_TARGET}"
echo "WOLFSSH_ROOT = ${WOLFSSH_ROOT}"
echo "WOLFSSL_ROOT = ${WOLFSSL_ROOT}"
BUILT=""
FAILED=""
for PROJECT in "${SCRIPT_DIR}"/examples/*/; do
# A project directory is one with a top level CMakeLists.txt.
if [ ! -f "${PROJECT}/CMakeLists.txt" ]; then
continue
fi
NAME=$(basename "${PROJECT}")
echo
echo "--------------------------------------------------------------"
echo "Building ${NAME} for ${IDF_TARGET}"
echo "--------------------------------------------------------------"
if (cd "${PROJECT}" && idf.py fullclean && idf.py build); then
BUILT="${BUILT} ${NAME}"
else
FAILED="${FAILED} ${NAME}"
fi
done
if [ -z "${BUILT}" ] && [ -z "${FAILED}" ]; then
echo "ERROR: no example projects found in ${SCRIPT_DIR}/examples/"
exit 1
fi
echo
echo "Built: ${BUILT:- none}"
if [ -n "${FAILED}" ]; then
echo "Failed:${FAILED}"
exit 1
fi
echo "All wolfSSH Espressif examples built."

View File

@ -510,16 +510,23 @@ static int wolfSSH_FwdDefaultActions(WS_FwdCbAction action, void* vCtx,
ctx->state = FWD_STATE_DIRECT;
}
else if (action == WOLFSSH_FWD_LOCAL_CLEANUP) {
WCLOSESOCKET(ctx->appFd);
if (ctx->hostName) {
WFREE(ctx->hostName, NULL, 0);
ctx->hostName = NULL;
/* Channel id rides in port. Only its holder may tear the slot down. */
if (port == ctx->channelId) {
/* The open can fail after setup, before anything connected. */
if (ctx->appFd != (WS_SOCKET_T)-1) {
WCLOSESOCKET(ctx->appFd);
ctx->appFd = -1;
}
if (ctx->hostName) {
WFREE(ctx->hostName, NULL, 0);
ctx->hostName = NULL;
}
if (ctx->originName) {
WFREE(ctx->originName, NULL, 0);
ctx->originName = NULL;
}
ctx->state = FWD_STATE_INIT;
}
if (ctx->originName) {
WFREE(ctx->originName, NULL, 0);
ctx->originName = NULL;
}
ctx->state = FWD_STATE_INIT;
}
else if (action == WOLFSSH_FWD_REMOTE_SETUP) {
struct sockaddr_in addr;
@ -1150,20 +1157,35 @@ static int ssh_worker(thread_ctx_t* threadCtx)
}
else if (rc == WS_CHANNEL_CLOSED) {
#ifdef WOLFSSH_FWD
if (threadCtx->fwdCbCtx.state == FWD_STATE_CONNECTED &&
lastChannel == threadCtx->fwdCbCtx.channelId) {
/* Read zero-returned. Socket is closed. Go back
to listening. */
if (fwdFd != -1) {
WCLOSESOCKET(fwdFd);
/* wolfSSH_worker() names the channel only for the
* data and EOF statuses; DoChannelClose() recorded
* the id it retired. */
wolfSSH_GetLastRxId(ssh, &lastChannel);
if (lastChannel == threadCtx->fwdCbCtx.channelId) {
if (threadCtx->fwdCbCtx.appFd == (WS_SOCKET_T)-1) {
/* The cleanup handler ran ahead of this and
* closed the socket; only this copy of the
* descriptor is stale. */
fwdFd = -1;
}
if (threadCtx->fwdCbCtx.originName != NULL) {
WFREE(threadCtx->fwdCbCtx.originName,
NULL, 0);
threadCtx->fwdCbCtx.originName = NULL;
else if (threadCtx->fwdCbCtx.state
== FWD_STATE_CONNECTED) {
/* A locally opened forward is armed by no
* LOCAL_SETUP and so draws no cleanup. Its
* teardown is still ours: go back to
* listening. */
if (fwdFd != -1) {
WCLOSESOCKET(fwdFd);
fwdFd = -1;
threadCtx->fwdCbCtx.appFd = -1;
}
if (threadCtx->fwdCbCtx.originName != NULL) {
WFREE(threadCtx->fwdCbCtx.originName,
NULL, 0);
threadCtx->fwdCbCtx.originName = NULL;
}
threadCtx->fwdCbCtx.state = FWD_STATE_LISTEN;
}
threadCtx->fwdCbCtx.state = FWD_STATE_LISTEN;
}
#endif
continue;
@ -1290,6 +1312,7 @@ static int ssh_worker(thread_ctx_t* threadCtx)
to listening. */
WCLOSESOCKET(fwdFd);
fwdFd = -1;
threadCtx->fwdCbCtx.appFd = -1;
if (threadCtx->fwdCbCtx.hostName != NULL) {
WFREE(threadCtx->fwdCbCtx.hostName,
NULL, 0);
@ -1310,6 +1333,8 @@ static int ssh_worker(thread_ctx_t* threadCtx)
/* Connection reset. Socket is closed.
* Go back to listening. */
WCLOSESOCKET(fwdFd);
fwdFd = -1;
threadCtx->fwdCbCtx.appFd = -1;
threadCtx->fwdCbCtx.state = FWD_STATE_LISTEN;
continue;
}
@ -1405,6 +1430,9 @@ static int ssh_worker(thread_ctx_t* threadCtx)
threadCtx->fwdCbCtx.hostPort);
if (fwdFd > 0) {
/* The cleanup handler closes what it finds here, so a
* direct forward has to record its socket too. */
threadCtx->fwdCbCtx.appFd = fwdFd;
threadCtx->fwdCbCtx.state = FWD_STATE_CONNECTED;
}
}